Commit f2412830 authored by Bartek Fabiszewski's avatar Bartek Fabiszewski
Browse files

Add self check view

parent be6fc77a
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;

import java.util.concurrent.ExecutorService;
@@ -141,6 +142,13 @@ public class MainActivity extends AppCompatActivity
            Intent intent = new Intent(this, SettingsActivity.class);
            settingsLauncher.launch(intent);
            return true;
        } else if (id == R.id.menu_self_check) {
            SelfCheckFragment fragment = new SelfCheckFragment();
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.fragment_placeholder, fragment);
            transaction.addToBackStack(null);
            transaction.commit();
            return true;
        } else if (id == R.id.menu_about) {
            showAbout();
            return true;
+234 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2019 Bartek Fabiszewski
 * http://www.fabiszewski.net
 *
 * This file is part of μlogger-android.
 * Licensed under GPL, either version 3, or any later.
 * See <http://www.gnu.org/licenses/>
 */

package net.fabiszewski.ulogger;

import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;

import org.json.JSONException;

import java.io.IOException;

public class SelfCheckFragment extends Fragment {

    private static final String TAG = SelfCheckFragment.class.getSimpleName();

    private SwitchCompat preciseLocationSwitch;
    private TextView approximateLocationLabel;
    private SwitchCompat approximateLocationSwitch;
    private TextView preciseLocationLabel;
    private View backgroundLocationLayout;
    private TextView backgroundLocationLabel;
    private SwitchCompat backgroundLocationSwitch;
    private View storageLayout;
    private TextView storageLabel;
    private SwitchCompat storageSwitch;
    private View notificationsLayout;
    private TextView notificationsLabel;
    private SwitchCompat notificationsSwitch;

    private SwitchCompat locationGpsSwitch;
    private SwitchCompat locationNetSwitch;
    private SwitchCompat serverConfiguredSwitch;
    private SwitchCompat serverReachableSwitch;
    private TextView serverReachableDetails;
    private TextView validAccountDetails;
    private SwitchCompat validAccountSwitch;
    private View batteryUsageLayout;
    private SwitchCompat batteryUsageSwitch;


    public SelfCheckFragment() { }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (Logger.DEBUG) { Log.d(TAG, "[onCreateView]"); }
        View layout = inflater.inflate(R.layout.fragment_self_check, container, false);
        approximateLocationSwitch = layout.findViewById(R.id.permissionApproximateLocationResult);
        approximateLocationLabel = layout.findViewById(R.id.permissionApproximateLocationLabel);
        preciseLocationSwitch = layout.findViewById(R.id.permissionPreciseLocationResult);
        preciseLocationLabel = layout.findViewById(R.id.permissionPreciseLocationLabel);
        backgroundLocationLayout = layout.findViewById(R.id.permissionBackgroundLocationSelfCheck);
        backgroundLocationLabel = layout.findViewById(R.id.permissionBackgroundLocationLabel);
        backgroundLocationSwitch = layout.findViewById(R.id.permissionBackgroundLocationResult);
        storageLayout = layout.findViewById(R.id.permissionStorageSelfCheck);
        storageLabel = layout.findViewById(R.id.permissionStorageLabel);
        storageSwitch = layout.findViewById(R.id.permissionStorageResult);
        notificationsLayout = layout.findViewById(R.id.permissionNotificationsSelfCheck);
        notificationsLabel = layout.findViewById(R.id.permissionNotificationsLabel);
        notificationsSwitch = layout.findViewById(R.id.permissionNotificationsResult);
        locationGpsSwitch = layout.findViewById(R.id.providerGpsResult);
        locationNetSwitch = layout.findViewById(R.id.providerNetResult);
        serverConfiguredSwitch = layout.findViewById(R.id.serverConfiguredResult);
        serverReachableSwitch = layout.findViewById(R.id.serverReachableResult);
        serverReachableDetails = layout.findViewById(R.id.serverReachableDetails);
        validAccountDetails = layout.findViewById(R.id.validAccountDetails);
        validAccountSwitch = layout.findViewById(R.id.validAccountResult);
        batteryUsageLayout = layout.findViewById(R.id.batteryUnrestrictedSelfCheck);
        batteryUsageSwitch = layout.findViewById(R.id.batteryUnrestrictedUsageResult);

        selfCheck();

        return layout;
    }

    private void selfCheck() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            checkPermissions();
        }

        checkProviders();

        checkServer();
    }

    private void checkServer() {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
        String host = prefs.getString(SettingsActivity.KEY_HOST, "").replaceAll("/+$", "");

        serverConfiguredSwitch.setChecked(!host.isEmpty());
        serverReachableSwitch.setChecked(false);
        validAccountSwitch.setChecked(false);
        if (!host.isEmpty()) {
            Handler handler = new Handler(Looper.getMainLooper());

            new Thread(() -> {
                WebHelper webHelper = new WebHelper(requireContext());
                boolean isReachable = false;
                String details = null;
                try {
                    isReachable = webHelper.isReachable();
                } catch (IOException e) {
                    details = e.getLocalizedMessage();
                }
                boolean finalIsReachable = isReachable;
                String finalDetails = details;
                handler.post(() -> {
                    if (finalDetails != null) {
                        serverReachableDetails.setText(finalDetails);
                    }
                    serverReachableSwitch.setChecked(finalIsReachable);
                });
                if (isReachable) {
                    boolean isValidAccount = false;
                    try {
                        webHelper.checkAuthorization();
                        isValidAccount = true;
                    } catch (IOException | WebAuthException | JSONException e) {
                        details = e.getLocalizedMessage();
                    }
                    boolean finalIsValidAccount = isValidAccount;
                    String finalAccountDetails = details;
                    handler.post(() -> {
                        if (finalAccountDetails != null) {
                            validAccountDetails.setText(finalAccountDetails);
                        }
                        validAccountSwitch.setChecked(finalIsValidAccount);
                    });
                }
            }).start();

        }
    }

    private void checkProviders() {
        LocationManager locationManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE);
        locationGpsSwitch.setChecked(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER));
        locationNetSwitch.setChecked(locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER));
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void checkPermissions() {
        setItem(null, approximateLocationLabel, approximateLocationSwitch, ACCESS_COARSE_LOCATION);
        setItem(null, preciseLocationLabel, preciseLocationSwitch, ACCESS_FINE_LOCATION);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            setItem(backgroundLocationLayout, backgroundLocationLabel, backgroundLocationSwitch, ACCESS_BACKGROUND_LOCATION);
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            setItem(storageLayout, storageLabel, storageSwitch, WRITE_EXTERNAL_STORAGE);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            setItem(notificationsLayout, notificationsLabel, notificationsSwitch, POST_NOTIFICATIONS);
        }
        checkBatteryUsage();
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void setItem(@Nullable View layout, @NonNull TextView label, @NonNull SwitchCompat switchCompat, @NonNull String permission) {
        if (layout != null) {
            layout.setVisibility(View.VISIBLE);
        }
        switchCompat.setChecked(isAllowed(permission));
        label.setText(getPermissionLabel(permission));
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private boolean isAllowed(@NonNull String permission) {
        boolean isAllowed =  ActivityCompat.checkSelfPermission(requireContext(), permission) == PackageManager.PERMISSION_GRANTED;
        if (Logger.DEBUG) { Log.d(TAG, "[isAllowed " + permission + ": " + isAllowed + " ]"); }
        return isAllowed;
    }

    private CharSequence getPermissionLabel(@NonNull String permission) {
        try {
            PackageManager pm = requireContext().getPackageManager();
            PermissionInfo info = pm.getPermissionInfo(permission, 0);
            CharSequence label = info.loadLabel(pm);
            return new StringBuilder(label.length())
                    .appendCodePoint(Character.toTitleCase(Character.codePointAt(label, 0)))
                    .append(label, Character.offsetByCodePoints(label, 0, 1), label.length());
        } catch (PackageManager.NameNotFoundException e) {
            if (Logger.DEBUG) { Log.d(TAG, "[getPermissionLabel not found:" + e + "]"); }
        }
        return permission;
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void checkBatteryUsage() {
        batteryUsageLayout.setVisibility(View.VISIBLE);
        PowerManager pm = (PowerManager) requireContext().getSystemService(Context.POWER_SERVICE);
        boolean isIgnoringOptimizations = pm.isIgnoringBatteryOptimizations(requireContext().getPackageName());
        batteryUsageSwitch.setChecked(isIgnoringOptimizations);
        if (Logger.DEBUG) { Log.d(TAG, "[isIgnoringOptimizations: " + isIgnoringOptimizations + " ]"); }
    }


}
+75 −0
Original line number Diff line number Diff line
@@ -12,6 +12,9 @@ package net.fabiszewski.ulogger;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.util.Base64;
@@ -459,6 +462,78 @@ class WebHelper {
        isAuthorized = false;
    }

    void checkAuthorization() throws JSONException, IOException, WebAuthException {
        boolean wasAuthorized = isAuthorized;
        try {
            authorize();
            if (!wasAuthorized) {
                deauthorize();
            }
        } catch (IOException | WebAuthException | JSONException e) {
            if (Logger.DEBUG) { Log.d(TAG, "[isValidAccount exception: " + e + "]"); }
            throw e;
        }
    }

    /**
     * Ping server without authorization
     * @throws IOException Exception on timeout or server internal error
     */
    boolean isReachable() throws IOException {
        if (!isNetworkAvailable()) {
            return false;
        }
        HttpURLConnection connection = null;
        try {
            URL url = new URL(host + "/" + CLIENT_SCRIPT);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("HEAD");
            connection.setRequestProperty("User-Agent", userAgent);
            connection.setConnectTimeout(SOCKET_TIMEOUT);
            connection.setReadTimeout(SOCKET_TIMEOUT);
            int responseCode = connection.getResponseCode();
            if (Logger.DEBUG) { Log.d(TAG, "[isReachable " + host + ": " + responseCode + "]"); }
            if (responseCode / 100 == 5) {
                throw new IOException(context.getString(R.string.e_http_code, responseCode));
            }
            return true;
        } catch (IOException e) {
            if (Logger.DEBUG) { Log.d(TAG, "[isReachable exception: " + e + "]"); }
            throw e;
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private boolean isNetworkAvailable() {
        boolean isAvailable = false;

        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivityManager != null) {

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
                NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
                if (capabilities != null) {
                    isAvailable = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
                            capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
                }
            } else {
                isAvailable = isNetworkAvailableApi21(connectivityManager);
            }

        }
        if (Logger.DEBUG) { Log.d(TAG, "[isNetworkAvailable " + isAvailable + "]"); }
        return isAvailable;
    }

    @SuppressWarnings({"deprecation", "RedundantSuppression"})
    private boolean isNetworkAvailableApi21(ConnectivityManager connectivityManager) {
        NetworkInfo info = connectivityManager.getActiveNetworkInfo();
        return info != null && info.isAvailable() && info.isConnected();
    }

    /**
     * Get settings from shared preferences
     * @param context Context
+5 −0
Original line number Diff line number Diff line
<vector android:height="24dp" android:tint="#FFFFFF"
    android:viewportHeight="24" android:viewportWidth="24"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>
+493 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading