forked from organicmaps/organicmaps
[android] Remove UI from LocationHelper
AlertDialogs are moved from LocationHelper back to parent MwmActivity. Initial idea of attaching LocationHelper to different activities failed and MwmActivity was the only one user of LocationHelper.attach(). Location permission checks are extracted from LocationHelper.start() to callers of LocationHelper.start() to avoid unnecessary inverted callbacks. LocationHelper.start() now has @RequiresPermission compile-time annotation to validate that required location permissions are checked before the call. LocationUtils.isLocationGranted() is replaced with direct calls to ActitivyCompat.checkSelfPermission() because Android Studio and Android Lint are not smart enough to detect it for @RequiresPermission check. Remove harmful Listeners<> abstraction from LocationHelper. Make all things explicit. Listeners should be removed when the upper levels want to remove them, not sometime later in the future. See #4240 Needed for #573 (#4611) Signed-off-by: Roman Tsisyk <roman@tsisyk.com>
This commit is contained in:
parent
b1235fc407
commit
4300c6f70b
8 changed files with 388 additions and 336 deletions
|
@ -1,5 +1,7 @@
|
|||
package app.organicmaps.location;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static app.organicmaps.util.concurrency.UiThread.runLater;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
@ -8,6 +10,7 @@ import android.location.Location;
|
|||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresPermission;
|
||||
|
||||
import com.google.android.gms.common.api.ApiException;
|
||||
import com.google.android.gms.common.api.ResolvableApiException;
|
||||
|
@ -65,9 +68,8 @@ class GoogleFusedLocationProvider extends BaseLocationProvider
|
|||
mContext = context;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
// A permission is checked externally
|
||||
@Override
|
||||
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
|
||||
public void start(long interval)
|
||||
{
|
||||
Logger.d(TAG);
|
||||
|
|
|
@ -2,6 +2,8 @@ package app.organicmaps;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
|
@ -14,12 +16,17 @@ import android.view.View;
|
|||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.IntentSenderRequest;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
@ -29,6 +36,7 @@ import androidx.fragment.app.FragmentTransaction;
|
|||
import androidx.lifecycle.ViewModelProvider;
|
||||
import app.organicmaps.Framework.PlacePageActivationListener;
|
||||
import app.organicmaps.api.Const;
|
||||
import app.organicmaps.background.AppBackgroundTracker;
|
||||
import app.organicmaps.background.Notifier;
|
||||
import app.organicmaps.base.BaseMwmFragmentActivity;
|
||||
import app.organicmaps.base.CustomNavigateUpListener;
|
||||
|
@ -81,6 +89,7 @@ import app.organicmaps.settings.UnitLocale;
|
|||
import app.organicmaps.sound.TtsPlayer;
|
||||
import app.organicmaps.util.Config;
|
||||
import app.organicmaps.util.Counters;
|
||||
import app.organicmaps.util.LocationUtils;
|
||||
import app.organicmaps.util.SharingUtils;
|
||||
import app.organicmaps.util.ThemeSwitcher;
|
||||
import app.organicmaps.util.ThemeUtils;
|
||||
|
@ -99,6 +108,11 @@ import java.util.ArrayList;
|
|||
import java.util.Objects;
|
||||
import java.util.Stack;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static app.organicmaps.location.LocationState.LOCATION_TAG;
|
||||
|
||||
public class MwmActivity extends BaseMwmFragmentActivity
|
||||
implements PlacePageActivationListener,
|
||||
View.OnTouchListener,
|
||||
|
@ -114,7 +128,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
NoConnectionListener,
|
||||
MenuBottomSheetFragment.MenuBottomSheetInterfaceWithHeader,
|
||||
PlacePageController.PlacePageRouteSettingsListener,
|
||||
MapButtonsController.MapButtonClickListener
|
||||
MapButtonsController.MapButtonClickListener,
|
||||
AppBackgroundTracker.OnTransitionListener
|
||||
{
|
||||
private static final String TAG = MwmActivity.class.getSimpleName();
|
||||
|
||||
|
@ -129,12 +144,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
EditorHostFragment.class.getName(),
|
||||
ReportFragment.class.getName() };
|
||||
|
||||
public static final int REQ_CODE_ERROR_DRIVING_OPTIONS_DIALOG = 5;
|
||||
public static final int REQ_CODE_DRIVING_OPTIONS = 6;
|
||||
private static final int REQ_CODE_ISOLINES_ERROR = 8;
|
||||
|
||||
public static final String ERROR_DRIVING_OPTIONS_DIALOG_TAG = "error_driving_options_dialog_tag";
|
||||
private static final String ISOLINES_ERROR_DIALOG_TAG = "isolines_dialog_tag";
|
||||
|
||||
private static final String MAIN_MENU_ID = "MAIN_MENU_BOTTOM_SHEET";
|
||||
private static final String LAYERS_MENU_ID = "LAYERS_MENU_BOTTOM_SHEET";
|
||||
|
@ -186,6 +196,17 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
@Nullable
|
||||
private WindowInsetsCompat mCurrentWindowInsets;
|
||||
|
||||
@Nullable
|
||||
private Dialog mLocationErrorDialog;
|
||||
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
@NonNull
|
||||
private ActivityResultLauncher<String[]> mLocationPermissionRequest;
|
||||
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
@NonNull
|
||||
private ActivityResultLauncher<IntentSenderRequest> mLocationResolutionRequest;
|
||||
|
||||
public interface LeftAnimationTrackListener
|
||||
{
|
||||
void onTrackStarted(boolean collapsed);
|
||||
|
@ -353,6 +374,9 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
protected void onSafeCreate(@Nullable Bundle savedInstanceState)
|
||||
{
|
||||
super.onSafeCreate(savedInstanceState);
|
||||
|
||||
MwmApplication.backgroundTracker(this).addListener(this);
|
||||
|
||||
mIsTabletLayout = getResources().getBoolean(R.bool.tabletLayout);
|
||||
|
||||
if (!mIsTabletLayout)
|
||||
|
@ -377,6 +401,12 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
initViews(isLaunchByDeepLink);
|
||||
updateViewsInsets();
|
||||
|
||||
// Note: You must call registerForActivityResult() before the fragment or activity is created.
|
||||
mLocationPermissionRequest = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
this::onLocationPermissionsResult);
|
||||
mLocationResolutionRequest = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
|
||||
this::onLocationResolutionResult);
|
||||
|
||||
boolean isConsumed = savedInstanceState == null && processIntent(getIntent());
|
||||
boolean isFirstLaunch = Counters.isFirstLaunch(this);
|
||||
// If the map activity is launched by any incoming intent (deeplink, update maps event, etc)
|
||||
|
@ -485,7 +515,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
startActivity(new Intent(MwmActivity.this, FeatureCategoryActivity.class));
|
||||
else
|
||||
{
|
||||
new AlertDialog.Builder(this, R.style.MwmTheme_AlertDialog)
|
||||
new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.message_invalid_feature_position)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
|
@ -621,8 +651,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
break;
|
||||
case myPosition:
|
||||
LocationState.nativeSwitchToNextMode();
|
||||
if (!LocationHelper.INSTANCE.isActive())
|
||||
LocationHelper.INSTANCE.start();
|
||||
startLocation();
|
||||
break;
|
||||
case toggleMapLayer:
|
||||
toggleMapLayerBottomSheet();
|
||||
|
@ -770,8 +799,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
public void startLocationToPoint(final @Nullable MapObject endPoint)
|
||||
{
|
||||
closeFloatingPanels();
|
||||
if (!LocationHelper.INSTANCE.isActive())
|
||||
LocationHelper.INSTANCE.start();
|
||||
startLocation();
|
||||
|
||||
MapObject startPoint = LocationHelper.INSTANCE.getMyPosition();
|
||||
RoutingController.get().prepare(startPoint, endPoint);
|
||||
|
@ -967,6 +995,17 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
refreshLightStatusBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransit(boolean foreground)
|
||||
{
|
||||
Logger.d(TAG, "foreground = " + foreground);
|
||||
|
||||
if (foreground)
|
||||
resumeLocationInForeground();
|
||||
else
|
||||
pauseLocationInBackground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recreate()
|
||||
{
|
||||
|
@ -991,7 +1030,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
if (mOnmapDownloader != null)
|
||||
mOnmapDownloader.onPause();
|
||||
mNavigationController.onActivityPaused(this);
|
||||
LocationHelper.INSTANCE.closeLocationDialog();
|
||||
pauseLocationInBackground();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
|
@ -1003,7 +1042,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
BookmarkManager.INSTANCE.addLoadingListener(this);
|
||||
RoutingController.get().attach(this);
|
||||
IsolinesManager.from(getApplicationContext()).attach(this::onIsolinesStateChanged);
|
||||
LocationHelper.INSTANCE.attach(this);
|
||||
LocationState.nativeSetListener(this);
|
||||
LocationHelper.INSTANCE.addListener(this);
|
||||
onMyPositionModeChanged(LocationState.nativeGetMode());
|
||||
|
@ -1020,7 +1058,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
BookmarkManager.INSTANCE.removeLoadingListener(this);
|
||||
LocationHelper.INSTANCE.removeListener(this);
|
||||
LocationState.nativeRemoveListener();
|
||||
LocationHelper.INSTANCE.detach();
|
||||
RoutingController.get().detach();
|
||||
IsolinesManager.from(getApplicationContext()).detach();
|
||||
mSearchController.detach();
|
||||
|
@ -1033,6 +1070,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
{
|
||||
super.onSafeDestroy();
|
||||
mNavigationController.destroy();
|
||||
mLocationPermissionRequest.unregister();
|
||||
mLocationPermissionRequest = null;
|
||||
mLocationResolutionRequest.unregister();
|
||||
mLocationResolutionRequest = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1612,16 +1653,33 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
@Override
|
||||
public void onMyPositionModeChanged(int newMode)
|
||||
{
|
||||
Logger.d(TAG, "location newMode = " + newMode);
|
||||
Logger.d(LOCATION_TAG, "newMode = " + newMode);
|
||||
mMapButtonsViewModel.setMyPositionMode(newMode);
|
||||
RoutingController controller = RoutingController.get();
|
||||
if (controller.isPlanning())
|
||||
showAddStartOrFinishFrame(controller, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss location error dialog from the screen, if any.
|
||||
*/
|
||||
private void dismissLocationErrorDialog()
|
||||
{
|
||||
if (mLocationErrorDialog != null && mLocationErrorDialog.isShowing())
|
||||
mLocationErrorDialog.dismiss();
|
||||
mLocationErrorDialog = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when location is updated.
|
||||
* @param location new location
|
||||
*/
|
||||
@Override
|
||||
@UiThread
|
||||
public void onLocationUpdated(@NonNull Location location)
|
||||
{
|
||||
dismissLocationErrorDialog();
|
||||
|
||||
final RoutingController routing = RoutingController.get();
|
||||
if (!routing.isNavigating())
|
||||
return;
|
||||
|
@ -1642,13 +1700,256 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when compass data is updated.
|
||||
* @param north offset from the north
|
||||
*/
|
||||
@Override
|
||||
@UiThread
|
||||
public void onCompassUpdated(double north)
|
||||
{
|
||||
Map.onCompassUpdated(north, false);
|
||||
mNavigationController.updateNorth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start location services when the user presses a button or starts routing.
|
||||
*/
|
||||
private void startLocation()
|
||||
{
|
||||
Logger.d(LOCATION_TAG);
|
||||
|
||||
if (ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED)
|
||||
{
|
||||
Logger.i(LOCATION_TAG, "Permission ACCESS_FINE_LOCATION is granted");
|
||||
LocationHelper.INSTANCE.start();
|
||||
return;
|
||||
}
|
||||
|
||||
// Always try to optimistically request FINE permission when the user presses a button or starts routing.
|
||||
// Android will suppress annoying dialogs and skip directly to onLocationPermissionsResult().
|
||||
Logger.i(LOCATION_TAG, "Requesting ACCESS_FINE_LOCATION permission");
|
||||
dismissLocationErrorDialog();
|
||||
mLocationPermissionRequest.launch(new String[]{
|
||||
ACCESS_COARSE_LOCATION,
|
||||
ACCESS_FINE_LOCATION
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume location services when entering the foreground.
|
||||
*/
|
||||
private void resumeLocationInForeground()
|
||||
{
|
||||
LocationHelper.INSTANCE.setRotation(getWindowManager().getDefaultDisplay().getRotation());
|
||||
LocationState.nativeSetLocationPendingTimeoutListener(this::onLocationPendingTimeout);
|
||||
|
||||
if (LocationState.nativeGetMode() == LocationState.NOT_FOLLOW_NO_POSITION)
|
||||
{
|
||||
Logger.i(LOCATION_TAG, "Location updates are stopped by the user manually.");
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
LocationHelper.INSTANCE.stop();
|
||||
}
|
||||
else if (ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED)
|
||||
{
|
||||
Logger.i(LOCATION_TAG, "Permission ACCESS_FINE_LOCATION is granted");
|
||||
LocationHelper.INSTANCE.start();
|
||||
}
|
||||
else if (ActivityCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED)
|
||||
{
|
||||
Logger.i(LOCATION_TAG, "Permission ACCESS_COARSE_LOCATION is granted");
|
||||
LocationHelper.INSTANCE.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.w(LOCATION_TAG, "Permissions ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION are not granted");
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_DENIED);
|
||||
LocationHelper.INSTANCE.stop();
|
||||
|
||||
Logger.i(LOCATION_TAG, "Requesting ACCESS_FINE_LOCATION + ACCESS_FINE_LOCATION permissions");
|
||||
dismissLocationErrorDialog();
|
||||
mLocationPermissionRequest.launch(new String[]{
|
||||
ACCESS_COARSE_LOCATION,
|
||||
ACCESS_FINE_LOCATION
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause location services when entering the background.
|
||||
*/
|
||||
private void pauseLocationInBackground()
|
||||
{
|
||||
dismissLocationErrorDialog();
|
||||
LocationState.nativeRemoveLocationPendingTimeoutListener();
|
||||
|
||||
if (!LocationHelper.INSTANCE.isActive())
|
||||
return;
|
||||
|
||||
Logger.i(LOCATION_TAG);
|
||||
LocationHelper.INSTANCE.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the result of the system location dialog.
|
||||
* @param permissions permissions granted or refused.
|
||||
*/
|
||||
@UiThread
|
||||
private void onLocationPermissionsResult(java.util.Map<String, Boolean> permissions)
|
||||
{
|
||||
// Print permissions that have been granted or refused.
|
||||
for (java.util.Map.Entry<String, Boolean> entry : permissions.entrySet())
|
||||
{
|
||||
final String permission = entry.getKey().substring(entry.getKey().lastIndexOf('.') + 1);
|
||||
if (entry.getValue())
|
||||
Logger.i(LOCATION_TAG, "Permission " + permission + " has been granted");
|
||||
else
|
||||
Logger.w(LOCATION_TAG, "Permission " + permission + " has been refused");
|
||||
}
|
||||
|
||||
// Sic: Android Studio requires explicit calls to checkSelfPermission() for @RequiresPermission in start().
|
||||
if (ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED ||
|
||||
ActivityCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED)
|
||||
{
|
||||
LocationHelper.INSTANCE.start();
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.w(LOCATION_TAG, "Permissions ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION have been refused");
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_DENIED);
|
||||
LocationHelper.INSTANCE.stop();
|
||||
|
||||
if (mLocationErrorDialog != null && mLocationErrorDialog.isShowing())
|
||||
{
|
||||
Logger.w(LOCATION_TAG, "Don't show 'location denied' error dialog because another dialog is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
mLocationErrorDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.enable_location_services)
|
||||
.setMessage(R.string.location_is_disabled_long_text)
|
||||
.setOnDismissListener(dialog -> mLocationErrorDialog = null)
|
||||
.setNegativeButton(R.string.close, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by GoogleFusedLocationProvider to request to GPS and/or Wi-Fi.
|
||||
* @param pendingIntent an intent to launch.
|
||||
*/
|
||||
@Override
|
||||
@UiThread
|
||||
public void onLocationResolutionRequired(@NonNull PendingIntent pendingIntent)
|
||||
{
|
||||
Logger.d(LOCATION_TAG);
|
||||
|
||||
// Cancel our dialog in favor of system dialog.
|
||||
dismissLocationErrorDialog();
|
||||
|
||||
// Launch system permission resolution dialog.
|
||||
Logger.i(LOCATION_TAG, "Starting location resolution dialog");
|
||||
IntentSenderRequest intentSenderRequest = new IntentSenderRequest.Builder(pendingIntent.getIntentSender()).build();
|
||||
mLocationResolutionRequest.launch(intentSenderRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered by onLocationResolutionRequired().
|
||||
* @param result invocation result.
|
||||
*/
|
||||
@UiThread
|
||||
private void onLocationResolutionResult(@NonNull ActivityResult result)
|
||||
{
|
||||
final int resultCode = result.getResultCode();
|
||||
Logger.d(LOCATION_TAG, "resultCode = " + resultCode);
|
||||
|
||||
if (resultCode != Activity.RESULT_OK)
|
||||
{
|
||||
Logger.w(LOCATION_TAG, "Location resolution has been refused");
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
LocationHelper.INSTANCE.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(LOCATION_TAG, "Location resolution has been granted");
|
||||
LocationHelper.INSTANCE.restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by AndroidNativeLocationProvider when no suitable location methods are available.
|
||||
*/
|
||||
@Override
|
||||
@UiThread
|
||||
public void onLocationDisabled()
|
||||
{
|
||||
Logger.d(LOCATION_TAG, "settings = " + LocationUtils.areLocationServicesTurnedOn(this));
|
||||
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
LocationHelper.INSTANCE.stop();
|
||||
|
||||
if (mLocationErrorDialog != null && mLocationErrorDialog.isShowing())
|
||||
{
|
||||
Logger.d(LOCATION_TAG, "Don't show 'location disabled' error dialog because another dialog is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.enable_location_services)
|
||||
.setMessage(R.string.location_is_disabled_long_text)
|
||||
.setOnDismissListener(dialog -> mLocationErrorDialog = null)
|
||||
.setNegativeButton(R.string.close, null);
|
||||
final Intent intent = Utils.makeSystemLocationSettingIntent(this);
|
||||
if (intent != null)
|
||||
{
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
builder.setPositiveButton(R.string.connection_settings, (dialog, which) -> startActivity(intent));
|
||||
}
|
||||
mLocationErrorDialog = builder.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the core when location updates were not received after the 30 second deadline.
|
||||
*/
|
||||
@UiThread
|
||||
private void onLocationPendingTimeout()
|
||||
{
|
||||
// Sic: the callback can be called after the activity is destroyed because of being queued.
|
||||
if (isDestroyed())
|
||||
{
|
||||
Logger.w(LOCATION_TAG, "Ignore late callback from core because activity is already destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.d(LOCATION_TAG, "services = " + LocationUtils.areLocationServicesTurnedOn(this));
|
||||
|
||||
//
|
||||
// For all cases below we don't stop location provider until user explicitly clicks "Stop" in the dialog.
|
||||
//
|
||||
|
||||
if (mLocationErrorDialog != null && mLocationErrorDialog.isShowing())
|
||||
{
|
||||
Logger.d(LOCATION_TAG, "Don't show 'location timeout' error dialog because another dialog is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
mLocationErrorDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.current_location_unknown_title)
|
||||
.setMessage(R.string.current_location_unknown_message)
|
||||
.setOnDismissListener(dialog -> mLocationErrorDialog = null)
|
||||
.setNegativeButton(R.string.current_location_unknown_stop_button, (dialog, which) ->
|
||||
{
|
||||
Logger.w(LOCATION_TAG, "Disabled by user");
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
LocationHelper.INSTANCE.stop();
|
||||
})
|
||||
.setPositiveButton(R.string.current_location_unknown_continue_button, (dialog, which) ->
|
||||
{
|
||||
// Do nothing - provider will continue to search location.
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUseMyPositionAsStart()
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package app.organicmaps.location;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static app.organicmaps.util.concurrency.UiThread.runLater;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -9,6 +11,7 @@ import android.os.Bundle;
|
|||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresPermission;
|
||||
import androidx.core.location.LocationListenerCompat;
|
||||
import androidx.core.location.LocationManagerCompat;
|
||||
import androidx.core.location.LocationRequestCompat;
|
||||
|
@ -72,9 +75,9 @@ class AndroidNativeProvider extends BaseLocationProvider
|
|||
throw new IllegalStateException("Can't get LOCATION_SERVICE");
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
// A permission is checked externally
|
||||
@Override
|
||||
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
|
||||
public void start(long interval)
|
||||
{
|
||||
Logger.d(TAG);
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package app.organicmaps.location;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresPermission;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
abstract class BaseLocationProvider
|
||||
|
@ -28,6 +32,7 @@ abstract class BaseLocationProvider
|
|||
mListener = listener;
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
|
||||
protected abstract void start(long interval);
|
||||
protected abstract void stop();
|
||||
}
|
||||
|
|
|
@ -2,40 +2,33 @@ package app.organicmaps.location;
|
|||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.IntentSenderRequest;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresPermission;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import app.organicmaps.Framework;
|
||||
import app.organicmaps.MwmApplication;
|
||||
import app.organicmaps.R;
|
||||
import app.organicmaps.background.AppBackgroundTracker;
|
||||
import app.organicmaps.base.Initializable;
|
||||
import app.organicmaps.bookmarks.data.FeatureId;
|
||||
import app.organicmaps.bookmarks.data.MapObject;
|
||||
import app.organicmaps.routing.RoutingController;
|
||||
import app.organicmaps.util.Config;
|
||||
import app.organicmaps.util.Listeners;
|
||||
import app.organicmaps.util.LocationUtils;
|
||||
import app.organicmaps.util.NetworkPolicy;
|
||||
import app.organicmaps.util.Utils;
|
||||
import app.organicmaps.util.log.Logger;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public enum LocationHelper implements Initializable<Context>, AppBackgroundTracker.OnTransitionListener, BaseLocationProvider.Listener
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public enum LocationHelper implements Initializable<Context>, BaseLocationProvider.Listener
|
||||
{
|
||||
INSTANCE;
|
||||
|
||||
|
@ -54,9 +47,9 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
@NonNull
|
||||
private Context mContext;
|
||||
|
||||
private static final String TAG = LocationHelper.class.getSimpleName();
|
||||
private static final String TAG = LocationState.LOCATION_TAG;
|
||||
@NonNull
|
||||
private final Listeners<LocationListener> mListeners = new Listeners<>();
|
||||
private final Set<LocationListener> mListeners = new LinkedHashSet<>();
|
||||
@Nullable
|
||||
private Location mSavedLocation;
|
||||
private double mSavedNorth = Double.NaN;
|
||||
|
@ -67,17 +60,11 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
@NonNull
|
||||
private BaseLocationProvider mLocationProvider;
|
||||
@Nullable
|
||||
private AppCompatActivity mActivity;
|
||||
private long mInterval;
|
||||
private boolean mInFirstRun;
|
||||
private boolean mActive;
|
||||
@Nullable
|
||||
private Dialog mErrorDialog;
|
||||
@Nullable
|
||||
private ActivityResultLauncher<String[]> mPermissionRequest;
|
||||
@Nullable
|
||||
private ActivityResultLauncher<IntentSenderRequest> mResolutionRequest;
|
||||
|
||||
private int mRotation = 0;
|
||||
|
||||
@Override
|
||||
public void initialize(@NonNull Context context)
|
||||
|
@ -85,7 +72,6 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
mContext = context;
|
||||
mSensorHelper = new SensorHelper(context);
|
||||
mLocationProvider = LocationProviderFactory.getProvider(mContext, this);
|
||||
MwmApplication.backgroundTracker(context).addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -131,54 +117,17 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
return mActive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransit(boolean foreground)
|
||||
public void setRotation(int rotation)
|
||||
{
|
||||
Logger.d(TAG, "foreground = " + foreground + " mode = " + LocationState.nativeGetMode());
|
||||
|
||||
if (foreground)
|
||||
{
|
||||
if (isActive())
|
||||
return;
|
||||
|
||||
if (LocationState.nativeGetMode() == LocationState.NOT_FOLLOW_NO_POSITION)
|
||||
{
|
||||
Logger.d(TAG, "Location updates are stopped by the user manually, so skip provider start"
|
||||
+ " until the user starts it manually.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.d(TAG, "Starting in foreground");
|
||||
start();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
Logger.d(TAG, "Stopping in background");
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeLocationDialog()
|
||||
{
|
||||
if (mErrorDialog != null && mErrorDialog.isShowing())
|
||||
mErrorDialog.dismiss();
|
||||
mErrorDialog = null;
|
||||
Logger.i(TAG, "rotation = " + rotation);
|
||||
mRotation = rotation;
|
||||
}
|
||||
|
||||
void notifyCompassUpdated(double north)
|
||||
{
|
||||
mSavedNorth = north;
|
||||
if (mActivity != null)
|
||||
{
|
||||
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
mSavedNorth = LocationUtils.correctCompassAngle(rotation, mSavedNorth);
|
||||
}
|
||||
mSavedNorth = LocationUtils.correctCompassAngle(mRotation, north);
|
||||
for (LocationListener listener : mListeners)
|
||||
listener.onCompassUpdated(mSavedNorth);
|
||||
mListeners.finishIterate();
|
||||
}
|
||||
|
||||
private void notifyLocationUpdated()
|
||||
|
@ -186,11 +135,8 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
if (mSavedLocation == null)
|
||||
throw new IllegalStateException("No saved location");
|
||||
|
||||
closeLocationDialog();
|
||||
|
||||
for (LocationListener listener : mListeners)
|
||||
listener.onLocationUpdated(mSavedLocation);
|
||||
mListeners.finishIterate();
|
||||
|
||||
// If we are still in the first run mode, i.e. user is staying on the first run screens,
|
||||
// not on the map, we mustn't post location update to the core. Only this preserving allows us
|
||||
|
@ -213,11 +159,14 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
@Override
|
||||
public void onLocationChanged(@NonNull Location location)
|
||||
{
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
Logger.d(TAG, "provider = " + mLocationProvider.getClass().getSimpleName() + " location = " + location);
|
||||
|
||||
if (!isActive())
|
||||
{
|
||||
Logger.w(TAG, "Provider is not active");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocationUtils.isAccuracySatisfied(location))
|
||||
{
|
||||
Logger.w(TAG, "Unsatisfied accuracy for location = " + location);
|
||||
|
@ -242,26 +191,16 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
@UiThread
|
||||
public void onLocationResolutionRequired(@NonNull PendingIntent pendingIntent)
|
||||
{
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
Logger.d(TAG);
|
||||
|
||||
if (mResolutionRequest == null)
|
||||
if (!isActive())
|
||||
{
|
||||
Logger.d(TAG, "Can't resolve location permissions because UI is not attached");
|
||||
stop();
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
Logger.w(TAG, "Provider is not active");
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel our dialog in favor of system dialog.
|
||||
closeLocationDialog();
|
||||
|
||||
// Launch system permission resolution dialog.
|
||||
IntentSenderRequest intentSenderRequest = new IntentSenderRequest.Builder(pendingIntent.getIntentSender())
|
||||
.build();
|
||||
mResolutionRequest.launch(intentSenderRequest);
|
||||
for (LocationListener listener : mListeners)
|
||||
listener.onLocationResolutionRequired(pendingIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -280,114 +219,13 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
public void onLocationDisabled()
|
||||
{
|
||||
Logger.d(TAG, "provider = " + mLocationProvider.getClass().getSimpleName() +
|
||||
" permissions = " + LocationUtils.isLocationGranted(mContext) +
|
||||
" settings = " + LocationUtils.areLocationServicesTurnedOn(mContext));
|
||||
|
||||
stop();
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
|
||||
if (mActivity == null)
|
||||
{
|
||||
Logger.d(TAG, "Don't show 'location disabled' error dialog because Activity is not attached");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mErrorDialog != null && mErrorDialog.isShowing())
|
||||
{
|
||||
Logger.d(TAG, "Don't show 'location disabled' error dialog because another dialog is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(mActivity, R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.enable_location_services)
|
||||
.setMessage(R.string.location_is_disabled_long_text)
|
||||
.setOnDismissListener(dialog -> mErrorDialog = null)
|
||||
.setNegativeButton(R.string.close, null);
|
||||
final Intent intent = Utils.makeSystemLocationSettingIntent(mActivity);
|
||||
if (intent != null)
|
||||
{
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
builder.setPositiveButton(R.string.connection_settings, (dialog, which) -> mActivity.startActivity(intent));
|
||||
}
|
||||
mErrorDialog = builder.show();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onLocationDenied()
|
||||
{
|
||||
Logger.d(TAG, "provider = " + mLocationProvider.getClass().getSimpleName() +
|
||||
" permissions = " + LocationUtils.isLocationGranted(mContext) +
|
||||
" settings = " + LocationUtils.areLocationServicesTurnedOn(mContext));
|
||||
|
||||
stop();
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_DENIED);
|
||||
|
||||
if (mActivity == null)
|
||||
{
|
||||
Logger.w(TAG, "Don't show 'location denied' error dialog because Activity is not attached");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mErrorDialog != null && mErrorDialog.isShowing())
|
||||
{
|
||||
Logger.w(TAG, "Don't show 'location denied' error dialog because another dialog is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
mErrorDialog = new MaterialAlertDialogBuilder(mActivity, R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.enable_location_services)
|
||||
.setMessage(R.string.location_is_disabled_long_text)
|
||||
.setOnDismissListener(dialog -> mErrorDialog = null)
|
||||
.setNegativeButton(R.string.close, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onLocationPendingTimeout()
|
||||
{
|
||||
Logger.d(TAG, " permissions = " + LocationUtils.isLocationGranted(mContext) +
|
||||
" settings = " + LocationUtils.areLocationServicesTurnedOn(mContext));
|
||||
|
||||
//
|
||||
// For all cases below we don't stop location provider until user explicitly clicks "Stop" in the dialog.
|
||||
//
|
||||
|
||||
if (!isActive())
|
||||
{
|
||||
Logger.d(TAG, "Don't show 'location timeout' error dialog because provider is already stopped");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mActivity == null)
|
||||
{
|
||||
Logger.d(TAG, "Don't show 'location timeout' error dialog because Activity is not attached");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mErrorDialog != null && mErrorDialog.isShowing())
|
||||
{
|
||||
Logger.d(TAG, "Don't show 'location timeout' error dialog because another dialog is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
final AppCompatActivity activity = mActivity;
|
||||
mErrorDialog = new MaterialAlertDialogBuilder(activity, R.style.MwmTheme_AlertDialog)
|
||||
.setTitle(R.string.current_location_unknown_title)
|
||||
.setMessage(R.string.current_location_unknown_message)
|
||||
.setOnDismissListener(dialog -> mErrorDialog = null)
|
||||
.setNegativeButton(R.string.current_location_unknown_stop_button, (dialog, which) ->
|
||||
{
|
||||
Logger.w(TAG, "Disabled by user");
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
stop();
|
||||
})
|
||||
.setPositiveButton(R.string.current_location_unknown_continue_button, (dialog, which) ->
|
||||
{
|
||||
// Do nothing - provider will continue to search location.
|
||||
})
|
||||
.show();
|
||||
for (LocationListener listener : mListeners)
|
||||
listener.onLocationDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -398,24 +236,24 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
@UiThread
|
||||
public void addListener(@NonNull LocationListener listener)
|
||||
{
|
||||
Logger.d(TAG, "listener: " + listener + " count was: " + mListeners.getSize());
|
||||
Logger.d(TAG, "listener: " + listener + " count was: " + mListeners.size());
|
||||
|
||||
mListeners.register(listener);
|
||||
mListeners.add(listener);
|
||||
if (mSavedLocation != null)
|
||||
listener.onLocationUpdated(mSavedLocation);
|
||||
if (!Double.isNaN(mSavedNorth))
|
||||
listener.onCompassUpdated(mSavedNorth);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
/**
|
||||
* Removes given location listener.
|
||||
* @param listener listener to unregister.
|
||||
*/
|
||||
@UiThread
|
||||
public void removeListener(@NonNull LocationListener listener)
|
||||
{
|
||||
Logger.d(TAG, "listener: " + listener + " count was: " + mListeners.getSize());
|
||||
mListeners.unregister(listener);
|
||||
Logger.d(TAG, "listener: " + listener + " count was: " + mListeners.size());
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
private void calcLocationUpdatesInterval()
|
||||
|
@ -481,40 +319,30 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
{
|
||||
Logger.d(TAG);
|
||||
stop();
|
||||
if (ContextCompat.checkSelfPermission(mContext, ACCESS_COARSE_LOCATION) != PERMISSION_GRANTED &&
|
||||
ContextCompat.checkSelfPermission(mContext, ACCESS_FINE_LOCATION) != PERMISSION_GRANTED)
|
||||
{
|
||||
Logger.w(TAG, "Location is not restarted in foreground because of missing permissions");
|
||||
return;
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts polling location updates.
|
||||
*/
|
||||
@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
|
||||
public void start()
|
||||
{
|
||||
Logger.d(TAG);
|
||||
|
||||
if (isActive())
|
||||
throw new IllegalStateException("Already started");
|
||||
|
||||
if (!LocationUtils.isLocationGranted(mContext))
|
||||
{
|
||||
Logger.w(TAG, "Dynamic permissions ACCESS_COARSE_LOCATION and/or ACCESS_FINE_LOCATION are not granted");
|
||||
Logger.d(TAG, "error mode = " + LocationState.nativeGetMode());
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_DENIED);
|
||||
|
||||
if (mPermissionRequest == null)
|
||||
{
|
||||
Logger.w(TAG, "Don't request location permissions because Activity is not attached");
|
||||
return;
|
||||
}
|
||||
mPermissionRequest.launch(new String[]{
|
||||
ACCESS_COARSE_LOCATION,
|
||||
ACCESS_FINE_LOCATION
|
||||
});
|
||||
Logger.d(TAG, "Already started");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(TAG);
|
||||
checkForAgpsUpdates();
|
||||
|
||||
LocationState.nativeSetLocationPendingTimeoutListener(this::onLocationPendingTimeout);
|
||||
mSensorHelper.start();
|
||||
final long oldInterval = mInterval;
|
||||
calcLocationUpdatesInterval();
|
||||
|
@ -529,17 +357,15 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
*/
|
||||
public void stop()
|
||||
{
|
||||
Logger.d(TAG);
|
||||
|
||||
if (!isActive())
|
||||
{
|
||||
Logger.w(TAG, "Already stopped");
|
||||
Logger.d(TAG, "Already stopped");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(TAG);
|
||||
mLocationProvider.stop();
|
||||
mSensorHelper.stop();
|
||||
LocationState.nativeRemoveLocationPendingTimeoutListener();
|
||||
mActive = false;
|
||||
}
|
||||
|
||||
|
@ -563,83 +389,6 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
manager.sendExtraCommand(LocationManager.GPS_PROVIDER, "force_time_injection", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach UI to helper.
|
||||
*/
|
||||
@UiThread
|
||||
public void attach(@NonNull AppCompatActivity activity)
|
||||
{
|
||||
Logger.d(TAG, "activity = " + activity);
|
||||
|
||||
if (mActivity != null)
|
||||
{
|
||||
Logger.e(TAG, "Another Activity = " + mActivity + " is already attached");
|
||||
detach();
|
||||
}
|
||||
|
||||
mActivity = activity;
|
||||
|
||||
mPermissionRequest = mActivity.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
result -> onRequestPermissionsResult());
|
||||
mResolutionRequest = mActivity.registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
|
||||
result -> onLocationResolutionResult(result.getResultCode()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach UI from helper.
|
||||
*/
|
||||
@UiThread
|
||||
public void detach()
|
||||
{
|
||||
Logger.d(TAG, "activity = " + mActivity);
|
||||
|
||||
if (mActivity == null)
|
||||
{
|
||||
Logger.e(TAG, "Activity is not attached");
|
||||
return;
|
||||
}
|
||||
|
||||
assert mPermissionRequest != null;
|
||||
mPermissionRequest.unregister();
|
||||
mPermissionRequest = null;
|
||||
assert mResolutionRequest != null;
|
||||
mResolutionRequest.unregister();
|
||||
mResolutionRequest = null;
|
||||
mActivity = null;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onRequestPermissionsResult()
|
||||
{
|
||||
Logger.d(TAG);
|
||||
|
||||
if (LocationUtils.isLocationGranted(mContext))
|
||||
{
|
||||
Logger.i(TAG, "Permissions have been granted");
|
||||
if (!isActive())
|
||||
start();
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.w(TAG, "Permissions have not been granted");
|
||||
onLocationDenied();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onLocationResolutionResult(int resultCode)
|
||||
{
|
||||
if (resultCode != Activity.RESULT_OK)
|
||||
{
|
||||
Logger.w(TAG, "Resolution has not been granted");
|
||||
stop();
|
||||
LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Resolution has been granted");
|
||||
restart();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public boolean isInFirstRun()
|
||||
{
|
||||
|
@ -669,11 +418,7 @@ public enum LocationHelper implements Initializable<Context>, AppBackgroundTrack
|
|||
notifyLocationUpdated();
|
||||
Logger.d(TAG, "Current location is available, so play the nice zoom animation");
|
||||
Framework.nativeRunFirstLaunchAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart location service to show alert dialog if any location error.
|
||||
restart();
|
||||
}
|
||||
|
||||
public double getSavedNorth()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package app.organicmaps.location;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -12,4 +13,14 @@ public interface LocationListener
|
|||
{
|
||||
// No op.
|
||||
}
|
||||
|
||||
default void onLocationDisabled()
|
||||
{
|
||||
// No op.
|
||||
}
|
||||
|
||||
default void onLocationResolutionRequired(@NonNull PendingIntent pendingIntent)
|
||||
{
|
||||
// No op.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ public final class LocationState
|
|||
void onMyPositionModeChanged(int newMode);
|
||||
}
|
||||
|
||||
interface PendingTimeoutListener
|
||||
public interface PendingTimeoutListener
|
||||
{
|
||||
void onLocationPendingTimeout();
|
||||
}
|
||||
|
@ -45,10 +45,10 @@ public final class LocationState
|
|||
public static native void nativeSetListener(@NonNull ModeChangeListener listener);
|
||||
public static native void nativeRemoveListener();
|
||||
|
||||
static native void nativeSetLocationPendingTimeoutListener(@NonNull PendingTimeoutListener listener);
|
||||
static native void nativeRemoveLocationPendingTimeoutListener();
|
||||
public static native void nativeSetLocationPendingTimeoutListener(@NonNull PendingTimeoutListener listener);
|
||||
public static native void nativeRemoveLocationPendingTimeoutListener();
|
||||
|
||||
static native void nativeOnLocationError(int errorCode);
|
||||
public static native void nativeOnLocationError(int errorCode);
|
||||
|
||||
static native void nativeLocationUpdated(long time, double lat, double lon, float accuracy,
|
||||
double altitude, float speed, float bearing);
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package app.organicmaps.util;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Build;
|
||||
|
@ -12,7 +8,6 @@ import android.provider.Settings;
|
|||
import android.view.Surface;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class LocationUtils
|
||||
{
|
||||
|
@ -116,14 +111,4 @@ public class LocationUtils
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFineLocationGranted(@NonNull Context context)
|
||||
{
|
||||
return ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public static boolean isLocationGranted(@NonNull Context context)
|
||||
{
|
||||
return isFineLocationGranted(context) || ContextCompat.checkSelfPermission(context, ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue