Loading app/src/main/java/net/fabiszewski/ulogger/AutoNamePreference.java +0 −2 Original line number Diff line number Diff line Loading @@ -19,9 +19,7 @@ import androidx.preference.PreferenceManager; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.TimeZone; Loading app/src/main/java/net/fabiszewski/ulogger/LocationHelper.java +6 −14 Original line number Diff line number Diff line Loading @@ -9,10 +9,8 @@ package net.fabiszewski.ulogger; import android.Manifest; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; Loading @@ -25,7 +23,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceManager; import java.util.ArrayList; Loading @@ -52,10 +49,13 @@ class LocationHelper { private long maxTimeMillis; private final List<String> userProviders = new ArrayList<>(); private final PermissionHelper permissionHelper; private LocationHelper(@NonNull Context context) { this.context = context.getApplicationContext(); locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); permissionHelper = new PermissionHelper(context); updatePreferences(); } Loading Loading @@ -101,10 +101,7 @@ class LocationHelper { * @return True if permission granted, false otherwise */ boolean canAccessLocation() { boolean ret = (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) || (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED); if (Logger.DEBUG) { Log.d(TAG, "[canAccessLocation: " + ret + "]"); } return ret; return permissionHelper.hasForegroundLocationPermission(); } /** Loading @@ -114,12 +111,7 @@ class LocationHelper { */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean canAccessBackgroundLocation() { boolean ret = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ret = (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED); } if (Logger.DEBUG) { Log.d(TAG, "[canAccessBackgroundLocation: " + ret + "]"); } return ret; return permissionHelper.hasBackgroundLocationPermission(); } /** Loading Loading @@ -315,7 +307,7 @@ class LocationHelper { /** * Fix GPS week count rollover bug if needed * https://galileognss.eu/gps-week-number-rollover-april-6-2019/ * <a href="https://galileognss.eu/gps-week-number-rollover-april-6-2019/">https://galileognss.eu/gps-week-number-rollover-april-6-2019/</a> * @param location Location */ static void handleRolloverBug(@NonNull Location location) { Loading app/src/main/java/net/fabiszewski/ulogger/MainFragment.java +20 −35 Original line number Diff line number Diff line Loading @@ -12,7 +12,6 @@ package net.fabiszewski.ulogger; import static net.fabiszewski.ulogger.Alert.showAlert; import static net.fabiszewski.ulogger.Alert.showConfirm; import android.Manifest; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; Loading @@ -22,7 +21,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.util.Log; Loading @@ -36,8 +34,6 @@ import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; Loading @@ -48,16 +44,13 @@ import androidx.fragment.app.FragmentTransaction; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; @SuppressWarnings("WeakerAccess") public class MainFragment extends Fragment { public class MainFragment extends Fragment implements PermissionHelper.PermissionRequester { private final String TAG = MainFragment.class.getSimpleName(); Loading Loading @@ -85,7 +78,11 @@ public class MainFragment extends Fragment { private OnFragmentInteractionListener mListener; final PermissionHelper permissionHelper; public MainFragment() { permissionHelper = new PermissionHelper(this, this); } static MainFragment newInstance() { Loading Loading @@ -602,6 +599,20 @@ public class MainFragment extends Fragment { } } @Override public void onPermissionGranted(@Nullable String requestCode) { if (Logger.DEBUG) { Log.d(TAG, "[LocationPermission: granted]"); } Context context = getContext(); if (context != null) { startLogger(context); } } @Override public void onPermissionDenied(@Nullable String requestCode) { if (Logger.DEBUG) { Log.d(TAG, "[LocationPermission: denied]"); } } /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated Loading Loading @@ -712,36 +723,10 @@ public class MainFragment extends Fragment { case LoggerService.BROADCAST_LOCATION_PERMISSION_DENIED: showToast(getString(R.string.location_permission_denied)); setLocLed(LED_RED); List<String> permissions = new ArrayList<>(); permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // On Android 12+ coarse location permission must be also requested permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); } requestLocationPermission.launch(permissions.toArray(new String[0])); permissionHelper.requestFineLocationPermission(); break; } } }; /** * Request location permission, on granted start logger service */ final ActivityResultLauncher<String[]> requestLocationPermission = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), results -> { if (Logger.DEBUG) { Log.d(TAG, "[requestLocationPermission: " + results.entrySet() + "]"); } boolean isGranted = false; for (Map.Entry<String, Boolean> result : results.entrySet()) { if (result.getValue()) { isGranted = true; } } if (isGranted) { if (Logger.DEBUG) { Log.d(TAG, "[LocationPermission: granted]"); } Context context = getContext(); if (context != null) { startLogger(context); } } }); } app/src/main/java/net/fabiszewski/ulogger/PermissionHelper.java +151 −54 Original line number Diff line number Diff line 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 android.Manifest; import android.annotation.SuppressLint; Loading @@ -15,6 +13,7 @@ import android.util.Log; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; Loading @@ -28,18 +27,35 @@ import java.util.Map; public class PermissionHelper { public interface PermissionRequester { void onPermissionGranted(String requestCode); void onPermissionDenied(String requestCode); void onPermissionGranted(@Nullable String requestCode); void onPermissionDenied(@Nullable String requestCode); } private static final String TAG = PermissionHelper.class.getSimpleName(); @Nullable private String requestCode; final ActivityResultLauncher<String[]> resultLauncher; final Fragment fragment; @Nullable private ActivityResultLauncher<String[]> resultLauncher; @Nullable private Fragment fragment; @Nullable private Context context; boolean isLocationStageTwoNeeded = false; public PermissionHelper(Fragment fragment, PermissionRequester requester) { /** * Constructor for simple usage, without requesting permissions * @param context Context */ public PermissionHelper(@NonNull Context context) { this.context = context; } /** * Constructor for extended usage with requesting permissions * @param fragment Fragment * @param requester Permission requester */ public PermissionHelper(@NonNull Fragment fragment, @NonNull PermissionRequester requester) { this.fragment = fragment; resultLauncher = fragment.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), results -> { if (Logger.DEBUG) { Log.d(TAG, "[requestPermission: " + results.entrySet() + "]"); } Loading @@ -47,6 +63,7 @@ public class PermissionHelper { for (Map.Entry<String, Boolean> result : results.entrySet()) { if (result.getValue()) { isGranted = true; break; } } boolean isStageTwoNeeded = isLocationStageTwoNeeded; Loading @@ -68,16 +85,38 @@ public class PermissionHelper { }); } public void requestWritePermission(String requestCode) { @Nullable private Context getContext() { return fragment != null ? fragment.getContext() : context; } /** * @param requestCode Request code will be returned with callback */ public void requestWriteExternalStoragePermission(@Nullable String requestCode) { requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, requestCode); } public void requestWriteExternalStoragePermission() { requestWriteExternalStoragePermission(null); } /** * @param requestCode Request code will be returned with callback */ @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) public void requestNotificationsPermission(String requestCode) { public void requestNotificationsPermission(@Nullable String requestCode) { requestPermission(Manifest.permission.POST_NOTIFICATIONS, requestCode); } public void requestFineLocationPermission(String requestCode) { @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) public void requestNotificationsPermission() { requestNotificationsPermission(null); } /** * @param requestCode Request code will be returned with callback */ public void requestFineLocationPermission(@Nullable String requestCode) { List<String> permissions = new ArrayList<>(); permissions.add(android.Manifest.permission.ACCESS_FINE_LOCATION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { Loading @@ -87,16 +126,33 @@ public class PermissionHelper { requestPermissions(permissions, requestCode); } public void requestCoarseLocationPermission(String requestCode) { public void requestFineLocationPermission() { requestFineLocationPermission(null); } /** * @param requestCode Request code will be returned with callback */ public void requestCoarseLocationPermission(@Nullable String requestCode) { requestPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, requestCode); } public void requestCoarseLocationPermission() { requestCoarseLocationPermission(null); } /** * @param requestCode Request code will be returned with callback */ @RequiresApi(api = Build.VERSION_CODES.R) public void requestBackgroundLocationPermission(String requestCode) { public void requestBackgroundLocationPermission(@Nullable String requestCode) { if (fragment == null || fragment.getActivity() == null || resultLauncher == null) { if (Logger.DEBUG) { Log.d(TAG, "[requestBackgroundLocationPermission: missing fragment context]"); } return; } List<String> permissions = new ArrayList<>(); // Background location permission can only be granted when forward location is permitted if (hasForwardLocationPermission()) { if (hasForegroundLocationPermission()) { permissions.add(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION); } else { if (Logger.DEBUG) { Log.d(TAG, "[forward location permission denied]"); } Loading @@ -109,52 +165,101 @@ public class PermissionHelper { if (permissions.contains(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) && ActivityCompat.shouldShowRequestPermissionRationale(fragment.requireActivity(), android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { final CharSequence label = getBackgroundPermissionOptionLabel(fragment.requireContext()); showBackgroundLocationPermissionRationale(permissions, requestCode); } else { requestPermissions(permissions, requestCode); } } /** * Show permission rationale dialog, on accept request permission * * @param permissions Requested permissions * @param requestCode Request code which will be returned with callback */ @RequiresApi(api = Build.VERSION_CODES.R) private void showBackgroundLocationPermissionRationale(List<String> permissions, @Nullable String requestCode) { final Context ctx = getContext(); if (ctx == null) { if (Logger.DEBUG) { Log.d(TAG, "[requestBackgroundLocationPermission: missing context]"); } return; } final CharSequence label = getBackgroundPermissionOptionLabel(ctx); Alert.showConfirm( fragment.requireContext(), fragment.getString(R.string.background_location_required), fragment.getString(R.string.background_location_rationale, label), ctx, ctx.getString(R.string.background_location_required), ctx.getString(R.string.background_location_rationale, label), (dialog, which) -> { dialog.dismiss(); requestPermissions(permissions, requestCode); } ); } else { requestPermissions(permissions, requestCode); } @RequiresApi(api = Build.VERSION_CODES.R) public void requestBackgroundLocationPermission() { requestBackgroundLocationPermission(null); } public void requestPermission(String permission, String requestCode) { /** * @param permission Requested permission * @param requestCode Request code which will be returned with callback */ public void requestPermission(@NonNull String permission, @Nullable String requestCode) { List<String> permissions = new ArrayList<>(); permissions.add(permission); requestPermissions(permissions, requestCode); } public void requestPermissions(List<String> permissions, String requestCode) { /** * @param permissions Requested permissions * @param requestCode Request code which will be returned with callback */ public void requestPermissions(@NonNull List<String> permissions, @Nullable String requestCode) { if (fragment != null && resultLauncher != null) { this.requestCode = requestCode; resultLauncher.launch(permissions.toArray(new String[0])); } else { if (Logger.DEBUG) { Log.d(TAG, "[requestPermissions: missing fragment context]"); } } } public boolean hasPermission(String permission) { Context context = fragment.getContext(); if (context == null) { if (Logger.DEBUG) { Log.d(TAG, "[hasPermission: null context]"); } /** * Check if user granted given permission. * * @param permission Requested permission * @return True if has requested permission */ public boolean hasPermission(@NonNull String permission) { final Context ctx = getContext(); if (ctx == null) { if (Logger.DEBUG) { Log.d(TAG, "[hasPermission: missing context]"); } return false; } return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; boolean ret = ContextCompat.checkSelfPermission(ctx, permission) == PackageManager.PERMISSION_GRANTED; if (Logger.DEBUG) { Log.d(TAG, "[has " + permission + " permission: " + ret + "]"); } return ret; } /** * Check if user granted permission to access location. * Check if user granted permission to write external storage. * * @return True if permission granted, false otherwise */ boolean hasForwardLocationPermission() { boolean ret = (ActivityCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) || (ActivityCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED); if (Logger.DEBUG) { Log.d(TAG, "[hasForwardLocationPermission: " + ret + "]"); } return ret; boolean hasWriteExternalStoragePermission() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); } return true; } /** * Check if user granted permission to access location (coarse or fine). * * @return True if permission granted, false otherwise */ boolean hasForegroundLocationPermission() { return hasPermission(Manifest.permission.ACCESS_FINE_LOCATION) || hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION); } /** Loading @@ -165,39 +270,31 @@ public class PermissionHelper { boolean hasBackgroundLocationPermission() { boolean ret = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ret = (ActivityCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED); ret = hasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION); } if (Logger.DEBUG) { Log.d(TAG, "[hasBackgroundLocationPermission: " + ret + "]"); } return ret; } public static boolean isLocationPermission(@NonNull String permission) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ACCESS_BACKGROUND_LOCATION.equals(permission)) { return true; } return ACCESS_COARSE_LOCATION.equals(permission) || ACCESS_FINE_LOCATION.equals(permission); } /** * Wrapper for getBackgroundPermissionOptionLabel() method * Will return translated label only when context string was also translated * @param context Context * @param ctx Context * @return Localized label */ @SuppressLint("AppBundleLocaleChanges") @RequiresApi(api = Build.VERSION_CODES.R) private CharSequence getBackgroundPermissionOptionLabel(Context context) { CharSequence label = context.getPackageManager().getBackgroundPermissionOptionLabel(); private CharSequence getBackgroundPermissionOptionLabel(@NonNull Context ctx) { CharSequence label = ctx.getPackageManager().getBackgroundPermissionOptionLabel(); CharSequence defaultLabel = "Allow all the time"; if (Locale.getDefault().getLanguage().equals("en")) { return label.length() > 0 ? label : defaultLabel; } CharSequence translated = context.getString(R.string.background_location_rationale); Configuration config = new Configuration(context.getResources().getConfiguration()); CharSequence translated = ctx.getString(R.string.background_location_rationale); Configuration config = new Configuration(ctx.getResources().getConfiguration()); config.setLocale(Locale.ENGLISH); CharSequence defaultText = context.createConfigurationContext(config).getText(R.string.background_location_rationale); CharSequence defaultText = ctx.createConfigurationContext(config).getText(R.string.background_location_rationale); return translated.equals(defaultText) ? defaultLabel : label; } Loading app/src/main/java/net/fabiszewski/ulogger/SelfCheckFragment.java +5 −5 Original line number Diff line number Diff line Loading @@ -266,22 +266,22 @@ public class SelfCheckFragment extends Fragment implements PermissionHelper.Perm if (isChecked) { switch (permission) { case ACCESS_COARSE_LOCATION: permissionHelper.requestFineLocationPermission(null); permissionHelper.requestCoarseLocationPermission(); break; case ACCESS_FINE_LOCATION: permissionHelper.requestCoarseLocationPermission(null); permissionHelper.requestFineLocationPermission(); break; case ACCESS_BACKGROUND_LOCATION: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { permissionHelper.requestBackgroundLocationPermission(null); permissionHelper.requestBackgroundLocationPermission(); } break; case WRITE_EXTERNAL_STORAGE: permissionHelper.requestWritePermission(null); permissionHelper.requestWriteExternalStoragePermission(); break; case POST_NOTIFICATIONS: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { permissionHelper.requestNotificationsPermission(null); permissionHelper.requestNotificationsPermission(); } break; } Loading Loading
app/src/main/java/net/fabiszewski/ulogger/AutoNamePreference.java +0 −2 Original line number Diff line number Diff line Loading @@ -19,9 +19,7 @@ import androidx.preference.PreferenceManager; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.TimeZone; Loading
app/src/main/java/net/fabiszewski/ulogger/LocationHelper.java +6 −14 Original line number Diff line number Diff line Loading @@ -9,10 +9,8 @@ package net.fabiszewski.ulogger; import android.Manifest; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; Loading @@ -25,7 +23,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceManager; import java.util.ArrayList; Loading @@ -52,10 +49,13 @@ class LocationHelper { private long maxTimeMillis; private final List<String> userProviders = new ArrayList<>(); private final PermissionHelper permissionHelper; private LocationHelper(@NonNull Context context) { this.context = context.getApplicationContext(); locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); permissionHelper = new PermissionHelper(context); updatePreferences(); } Loading Loading @@ -101,10 +101,7 @@ class LocationHelper { * @return True if permission granted, false otherwise */ boolean canAccessLocation() { boolean ret = (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) || (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED); if (Logger.DEBUG) { Log.d(TAG, "[canAccessLocation: " + ret + "]"); } return ret; return permissionHelper.hasForegroundLocationPermission(); } /** Loading @@ -114,12 +111,7 @@ class LocationHelper { */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean canAccessBackgroundLocation() { boolean ret = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ret = (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED); } if (Logger.DEBUG) { Log.d(TAG, "[canAccessBackgroundLocation: " + ret + "]"); } return ret; return permissionHelper.hasBackgroundLocationPermission(); } /** Loading Loading @@ -315,7 +307,7 @@ class LocationHelper { /** * Fix GPS week count rollover bug if needed * https://galileognss.eu/gps-week-number-rollover-april-6-2019/ * <a href="https://galileognss.eu/gps-week-number-rollover-april-6-2019/">https://galileognss.eu/gps-week-number-rollover-april-6-2019/</a> * @param location Location */ static void handleRolloverBug(@NonNull Location location) { Loading
app/src/main/java/net/fabiszewski/ulogger/MainFragment.java +20 −35 Original line number Diff line number Diff line Loading @@ -12,7 +12,6 @@ package net.fabiszewski.ulogger; import static net.fabiszewski.ulogger.Alert.showAlert; import static net.fabiszewski.ulogger.Alert.showConfirm; import android.Manifest; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; Loading @@ -22,7 +21,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.util.Log; Loading @@ -36,8 +34,6 @@ import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; Loading @@ -48,16 +44,13 @@ import androidx.fragment.app.FragmentTransaction; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; @SuppressWarnings("WeakerAccess") public class MainFragment extends Fragment { public class MainFragment extends Fragment implements PermissionHelper.PermissionRequester { private final String TAG = MainFragment.class.getSimpleName(); Loading Loading @@ -85,7 +78,11 @@ public class MainFragment extends Fragment { private OnFragmentInteractionListener mListener; final PermissionHelper permissionHelper; public MainFragment() { permissionHelper = new PermissionHelper(this, this); } static MainFragment newInstance() { Loading Loading @@ -602,6 +599,20 @@ public class MainFragment extends Fragment { } } @Override public void onPermissionGranted(@Nullable String requestCode) { if (Logger.DEBUG) { Log.d(TAG, "[LocationPermission: granted]"); } Context context = getContext(); if (context != null) { startLogger(context); } } @Override public void onPermissionDenied(@Nullable String requestCode) { if (Logger.DEBUG) { Log.d(TAG, "[LocationPermission: denied]"); } } /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated Loading Loading @@ -712,36 +723,10 @@ public class MainFragment extends Fragment { case LoggerService.BROADCAST_LOCATION_PERMISSION_DENIED: showToast(getString(R.string.location_permission_denied)); setLocLed(LED_RED); List<String> permissions = new ArrayList<>(); permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // On Android 12+ coarse location permission must be also requested permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); } requestLocationPermission.launch(permissions.toArray(new String[0])); permissionHelper.requestFineLocationPermission(); break; } } }; /** * Request location permission, on granted start logger service */ final ActivityResultLauncher<String[]> requestLocationPermission = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), results -> { if (Logger.DEBUG) { Log.d(TAG, "[requestLocationPermission: " + results.entrySet() + "]"); } boolean isGranted = false; for (Map.Entry<String, Boolean> result : results.entrySet()) { if (result.getValue()) { isGranted = true; } } if (isGranted) { if (Logger.DEBUG) { Log.d(TAG, "[LocationPermission: granted]"); } Context context = getContext(); if (context != null) { startLogger(context); } } }); }
app/src/main/java/net/fabiszewski/ulogger/PermissionHelper.java +151 −54 Original line number Diff line number Diff line 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 android.Manifest; import android.annotation.SuppressLint; Loading @@ -15,6 +13,7 @@ import android.util.Log; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; Loading @@ -28,18 +27,35 @@ import java.util.Map; public class PermissionHelper { public interface PermissionRequester { void onPermissionGranted(String requestCode); void onPermissionDenied(String requestCode); void onPermissionGranted(@Nullable String requestCode); void onPermissionDenied(@Nullable String requestCode); } private static final String TAG = PermissionHelper.class.getSimpleName(); @Nullable private String requestCode; final ActivityResultLauncher<String[]> resultLauncher; final Fragment fragment; @Nullable private ActivityResultLauncher<String[]> resultLauncher; @Nullable private Fragment fragment; @Nullable private Context context; boolean isLocationStageTwoNeeded = false; public PermissionHelper(Fragment fragment, PermissionRequester requester) { /** * Constructor for simple usage, without requesting permissions * @param context Context */ public PermissionHelper(@NonNull Context context) { this.context = context; } /** * Constructor for extended usage with requesting permissions * @param fragment Fragment * @param requester Permission requester */ public PermissionHelper(@NonNull Fragment fragment, @NonNull PermissionRequester requester) { this.fragment = fragment; resultLauncher = fragment.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), results -> { if (Logger.DEBUG) { Log.d(TAG, "[requestPermission: " + results.entrySet() + "]"); } Loading @@ -47,6 +63,7 @@ public class PermissionHelper { for (Map.Entry<String, Boolean> result : results.entrySet()) { if (result.getValue()) { isGranted = true; break; } } boolean isStageTwoNeeded = isLocationStageTwoNeeded; Loading @@ -68,16 +85,38 @@ public class PermissionHelper { }); } public void requestWritePermission(String requestCode) { @Nullable private Context getContext() { return fragment != null ? fragment.getContext() : context; } /** * @param requestCode Request code will be returned with callback */ public void requestWriteExternalStoragePermission(@Nullable String requestCode) { requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, requestCode); } public void requestWriteExternalStoragePermission() { requestWriteExternalStoragePermission(null); } /** * @param requestCode Request code will be returned with callback */ @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) public void requestNotificationsPermission(String requestCode) { public void requestNotificationsPermission(@Nullable String requestCode) { requestPermission(Manifest.permission.POST_NOTIFICATIONS, requestCode); } public void requestFineLocationPermission(String requestCode) { @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) public void requestNotificationsPermission() { requestNotificationsPermission(null); } /** * @param requestCode Request code will be returned with callback */ public void requestFineLocationPermission(@Nullable String requestCode) { List<String> permissions = new ArrayList<>(); permissions.add(android.Manifest.permission.ACCESS_FINE_LOCATION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { Loading @@ -87,16 +126,33 @@ public class PermissionHelper { requestPermissions(permissions, requestCode); } public void requestCoarseLocationPermission(String requestCode) { public void requestFineLocationPermission() { requestFineLocationPermission(null); } /** * @param requestCode Request code will be returned with callback */ public void requestCoarseLocationPermission(@Nullable String requestCode) { requestPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, requestCode); } public void requestCoarseLocationPermission() { requestCoarseLocationPermission(null); } /** * @param requestCode Request code will be returned with callback */ @RequiresApi(api = Build.VERSION_CODES.R) public void requestBackgroundLocationPermission(String requestCode) { public void requestBackgroundLocationPermission(@Nullable String requestCode) { if (fragment == null || fragment.getActivity() == null || resultLauncher == null) { if (Logger.DEBUG) { Log.d(TAG, "[requestBackgroundLocationPermission: missing fragment context]"); } return; } List<String> permissions = new ArrayList<>(); // Background location permission can only be granted when forward location is permitted if (hasForwardLocationPermission()) { if (hasForegroundLocationPermission()) { permissions.add(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION); } else { if (Logger.DEBUG) { Log.d(TAG, "[forward location permission denied]"); } Loading @@ -109,52 +165,101 @@ public class PermissionHelper { if (permissions.contains(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) && ActivityCompat.shouldShowRequestPermissionRationale(fragment.requireActivity(), android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { final CharSequence label = getBackgroundPermissionOptionLabel(fragment.requireContext()); showBackgroundLocationPermissionRationale(permissions, requestCode); } else { requestPermissions(permissions, requestCode); } } /** * Show permission rationale dialog, on accept request permission * * @param permissions Requested permissions * @param requestCode Request code which will be returned with callback */ @RequiresApi(api = Build.VERSION_CODES.R) private void showBackgroundLocationPermissionRationale(List<String> permissions, @Nullable String requestCode) { final Context ctx = getContext(); if (ctx == null) { if (Logger.DEBUG) { Log.d(TAG, "[requestBackgroundLocationPermission: missing context]"); } return; } final CharSequence label = getBackgroundPermissionOptionLabel(ctx); Alert.showConfirm( fragment.requireContext(), fragment.getString(R.string.background_location_required), fragment.getString(R.string.background_location_rationale, label), ctx, ctx.getString(R.string.background_location_required), ctx.getString(R.string.background_location_rationale, label), (dialog, which) -> { dialog.dismiss(); requestPermissions(permissions, requestCode); } ); } else { requestPermissions(permissions, requestCode); } @RequiresApi(api = Build.VERSION_CODES.R) public void requestBackgroundLocationPermission() { requestBackgroundLocationPermission(null); } public void requestPermission(String permission, String requestCode) { /** * @param permission Requested permission * @param requestCode Request code which will be returned with callback */ public void requestPermission(@NonNull String permission, @Nullable String requestCode) { List<String> permissions = new ArrayList<>(); permissions.add(permission); requestPermissions(permissions, requestCode); } public void requestPermissions(List<String> permissions, String requestCode) { /** * @param permissions Requested permissions * @param requestCode Request code which will be returned with callback */ public void requestPermissions(@NonNull List<String> permissions, @Nullable String requestCode) { if (fragment != null && resultLauncher != null) { this.requestCode = requestCode; resultLauncher.launch(permissions.toArray(new String[0])); } else { if (Logger.DEBUG) { Log.d(TAG, "[requestPermissions: missing fragment context]"); } } } public boolean hasPermission(String permission) { Context context = fragment.getContext(); if (context == null) { if (Logger.DEBUG) { Log.d(TAG, "[hasPermission: null context]"); } /** * Check if user granted given permission. * * @param permission Requested permission * @return True if has requested permission */ public boolean hasPermission(@NonNull String permission) { final Context ctx = getContext(); if (ctx == null) { if (Logger.DEBUG) { Log.d(TAG, "[hasPermission: missing context]"); } return false; } return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; boolean ret = ContextCompat.checkSelfPermission(ctx, permission) == PackageManager.PERMISSION_GRANTED; if (Logger.DEBUG) { Log.d(TAG, "[has " + permission + " permission: " + ret + "]"); } return ret; } /** * Check if user granted permission to access location. * Check if user granted permission to write external storage. * * @return True if permission granted, false otherwise */ boolean hasForwardLocationPermission() { boolean ret = (ActivityCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) || (ActivityCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED); if (Logger.DEBUG) { Log.d(TAG, "[hasForwardLocationPermission: " + ret + "]"); } return ret; boolean hasWriteExternalStoragePermission() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); } return true; } /** * Check if user granted permission to access location (coarse or fine). * * @return True if permission granted, false otherwise */ boolean hasForegroundLocationPermission() { return hasPermission(Manifest.permission.ACCESS_FINE_LOCATION) || hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION); } /** Loading @@ -165,39 +270,31 @@ public class PermissionHelper { boolean hasBackgroundLocationPermission() { boolean ret = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ret = (ActivityCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED); ret = hasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION); } if (Logger.DEBUG) { Log.d(TAG, "[hasBackgroundLocationPermission: " + ret + "]"); } return ret; } public static boolean isLocationPermission(@NonNull String permission) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ACCESS_BACKGROUND_LOCATION.equals(permission)) { return true; } return ACCESS_COARSE_LOCATION.equals(permission) || ACCESS_FINE_LOCATION.equals(permission); } /** * Wrapper for getBackgroundPermissionOptionLabel() method * Will return translated label only when context string was also translated * @param context Context * @param ctx Context * @return Localized label */ @SuppressLint("AppBundleLocaleChanges") @RequiresApi(api = Build.VERSION_CODES.R) private CharSequence getBackgroundPermissionOptionLabel(Context context) { CharSequence label = context.getPackageManager().getBackgroundPermissionOptionLabel(); private CharSequence getBackgroundPermissionOptionLabel(@NonNull Context ctx) { CharSequence label = ctx.getPackageManager().getBackgroundPermissionOptionLabel(); CharSequence defaultLabel = "Allow all the time"; if (Locale.getDefault().getLanguage().equals("en")) { return label.length() > 0 ? label : defaultLabel; } CharSequence translated = context.getString(R.string.background_location_rationale); Configuration config = new Configuration(context.getResources().getConfiguration()); CharSequence translated = ctx.getString(R.string.background_location_rationale); Configuration config = new Configuration(ctx.getResources().getConfiguration()); config.setLocale(Locale.ENGLISH); CharSequence defaultText = context.createConfigurationContext(config).getText(R.string.background_location_rationale); CharSequence defaultText = ctx.createConfigurationContext(config).getText(R.string.background_location_rationale); return translated.equals(defaultText) ? defaultLabel : label; } Loading
app/src/main/java/net/fabiszewski/ulogger/SelfCheckFragment.java +5 −5 Original line number Diff line number Diff line Loading @@ -266,22 +266,22 @@ public class SelfCheckFragment extends Fragment implements PermissionHelper.Perm if (isChecked) { switch (permission) { case ACCESS_COARSE_LOCATION: permissionHelper.requestFineLocationPermission(null); permissionHelper.requestCoarseLocationPermission(); break; case ACCESS_FINE_LOCATION: permissionHelper.requestCoarseLocationPermission(null); permissionHelper.requestFineLocationPermission(); break; case ACCESS_BACKGROUND_LOCATION: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { permissionHelper.requestBackgroundLocationPermission(null); permissionHelper.requestBackgroundLocationPermission(); } break; case WRITE_EXTERNAL_STORAGE: permissionHelper.requestWritePermission(null); permissionHelper.requestWriteExternalStoragePermission(); break; case POST_NOTIFICATIONS: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { permissionHelper.requestNotificationsPermission(null); permissionHelper.requestNotificationsPermission(); } break; } Loading