From 65e69c1b96cca0ef7f0be519c5b90f0f7d9dd87a Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Sun, 29 Dec 2024 15:51:20 +0100 Subject: [PATCH] [android-auto] Tell user to enable location services Signed-off-by: Andrew Shkrob --- .../java/app/organicmaps/MwmActivity.java | 7 --- .../app/organicmaps/car/CarAppSession.java | 17 ++++++ .../organicmaps/car/screens/ErrorScreen.java | 43 +++++++++++++- .../car/util/CarSensorsManager.java | 57 ++++++++++++++++++- .../location/LocationListener.java | 7 +++ .../app/organicmaps/util/LocationUtils.java | 21 +------ 6 files changed, 124 insertions(+), 28 deletions(-) diff --git a/android/app/src/main/java/app/organicmaps/MwmActivity.java b/android/app/src/main/java/app/organicmaps/MwmActivity.java index a7d4bb8c0e..7ac7d5118e 100644 --- a/android/app/src/main/java/app/organicmaps/MwmActivity.java +++ b/android/app/src/main/java/app/organicmaps/MwmActivity.java @@ -1983,10 +1983,6 @@ public class MwmActivity extends BaseMwmFragmentActivity Logger.w(POWER_MANAGEMENT_TAG, "Power Save mode wasn't disabled on the device"); } - /** - * 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) @@ -2028,9 +2024,6 @@ public class MwmActivity extends BaseMwmFragmentActivity } } - /** - * Called by AndroidNativeLocationProvider when no suitable location methods are available. - */ @Override @UiThread public void onLocationDisabled() diff --git a/android/app/src/main/java/app/organicmaps/car/CarAppSession.java b/android/app/src/main/java/app/organicmaps/car/CarAppSession.java index fd20c22a7e..695f052138 100644 --- a/android/app/src/main/java/app/organicmaps/car/CarAppSession.java +++ b/android/app/src/main/java/app/organicmaps/car/CarAppSession.java @@ -1,10 +1,13 @@ package app.organicmaps.car; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + import android.content.Intent; import android.content.res.Configuration; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; import androidx.car.app.Screen; import androidx.car.app.ScreenManager; import androidx.car.app.Session; @@ -31,6 +34,7 @@ import app.organicmaps.car.util.ThemeUtils; import app.organicmaps.display.DisplayChangedListener; import app.organicmaps.display.DisplayManager; import app.organicmaps.display.DisplayType; +import app.organicmaps.location.LocationHelper; import app.organicmaps.location.LocationState; import app.organicmaps.routing.RoutingController; import app.organicmaps.util.Config; @@ -197,12 +201,25 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse return screensStack.get(screensStack.size() - 1); } + @RequiresPermission(ACCESS_FINE_LOCATION) @Override public void onMyPositionModeChanged(int newMode) { + final LocationHelper locationHelper = LocationHelper.from(getCarContext()); + // Check if location was disabled by the user. + if (LocationState.getMode() == LocationState.NOT_FOLLOW_NO_POSITION) + { + Logger.i(TAG, "Location updates are stopped by the user manually."); + if (locationHelper.isActive()) + locationHelper.stop(); + } + final Screen screen = mScreenManager.getTop(); if (screen instanceof BaseMapScreen) screen.invalidate(); + + if (LocationState.getMode() != LocationState.NOT_FOLLOW_NO_POSITION) + locationHelper.restartWithNewMode(); } @Override diff --git a/android/app/src/main/java/app/organicmaps/car/screens/ErrorScreen.java b/android/app/src/main/java/app/organicmaps/car/screens/ErrorScreen.java index b6f9f22cf9..fd04a491c2 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/ErrorScreen.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/ErrorScreen.java @@ -12,9 +12,18 @@ import androidx.car.app.model.Template; import app.organicmaps.R; import app.organicmaps.car.screens.base.BaseScreen; import app.organicmaps.car.util.Colors; +import app.organicmaps.util.concurrency.ThreadPool; +import app.organicmaps.util.concurrency.UiThread; + +import java.util.concurrent.ExecutorService; public class ErrorScreen extends BaseScreen { + public interface FinishPrecondition + { + boolean canFinish(); + } + @StringRes private final int mTitle; @StringRes @@ -30,6 +39,11 @@ public class ErrorScreen extends BaseScreen @Nullable private final Runnable mNegativeButtonCallback; + @Nullable + private final FinishPrecondition mFinishPrecondition; + @Nullable + private ExecutorService mExecutorService; + private ErrorScreen(@NonNull Builder builder) { super(builder.mCarContext); @@ -39,6 +53,7 @@ public class ErrorScreen extends BaseScreen mPositiveButtonCallback = builder.mPositiveButtonCallback; mNegativeButtonText = builder.mNegativeButtonText; mNegativeButtonCallback = builder.mNegativeButtonCallback; + mFinishPrecondition = builder.mFinishPrecondition; } @NonNull @@ -67,7 +82,11 @@ public class ErrorScreen extends BaseScreen .setOnClickListener(this::onNegativeButton).build() ); } - + if (mFinishPrecondition != null && mExecutorService == null) + { + mExecutorService = ThreadPool.getWorker(); + mExecutorService.execute(this::checkFinishPrecondition); + } return builder.build(); } @@ -85,6 +104,17 @@ public class ErrorScreen extends BaseScreen finish(); } + private void checkFinishPrecondition() + { + assert mFinishPrecondition != null; + assert mExecutorService != null; + + if (mFinishPrecondition.canFinish()) + UiThread.runLater(this::finish); + else + mExecutorService.execute(this::checkFinishPrecondition); + } + public static class Builder { @NonNull @@ -105,6 +135,9 @@ public class ErrorScreen extends BaseScreen @Nullable private Runnable mNegativeButtonCallback; + @Nullable + private FinishPrecondition mFinishPrecondition; + public Builder(@NonNull CarContext carContext) { mCarContext = carContext; @@ -136,8 +169,16 @@ public class ErrorScreen extends BaseScreen return this; } + public Builder setFinishPrecondition(@NonNull FinishPrecondition precondition) + { + mFinishPrecondition = precondition; + return this; + } + public ErrorScreen build() { + if (mFinishPrecondition != null && (mPositiveButtonCallback != null || mNegativeButtonCallback != null)) + throw new IllegalStateException("Finish precondition is not compatible with positive or negative button callbacks"); return new ErrorScreen(this); } } diff --git a/android/app/src/main/java/app/organicmaps/car/util/CarSensorsManager.java b/android/app/src/main/java/app/organicmaps/car/util/CarSensorsManager.java index b8c9a68c37..230a4f3b94 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/CarSensorsManager.java +++ b/android/app/src/main/java/app/organicmaps/car/util/CarSensorsManager.java @@ -2,11 +2,13 @@ package app.organicmaps.car.util; 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.car.app.CarContext; +import androidx.car.app.ScreenManager; import androidx.car.app.hardware.CarHardwareManager; import androidx.car.app.hardware.common.CarValue; import androidx.car.app.hardware.info.CarHardwareLocation; @@ -15,8 +17,13 @@ import androidx.car.app.hardware.info.Compass; import androidx.core.content.ContextCompat; import app.organicmaps.Map; +import app.organicmaps.R; +import app.organicmaps.car.screens.ErrorScreen; import app.organicmaps.location.LocationHelper; +import app.organicmaps.location.LocationListener; +import app.organicmaps.location.LocationState; import app.organicmaps.location.SensorHelper; +import app.organicmaps.util.LocationUtils; import app.organicmaps.util.log.Logger; import java.util.List; @@ -30,6 +37,29 @@ public class CarSensorsManager private final CarContext mCarContext; @NonNull private final CarSensors mCarSensors; + @NonNull + private final LocationHelper mLocationHelper; + + private final LocationListener mLocationListener = new LocationListener() + { + @Override + public void onLocationUpdated(@NonNull Location location) + { + // No op. + } + + @Override + public void onLocationResolutionRequired(@NonNull PendingIntent pendingIntent) + { + onLocationError(); + } + + @Override + public void onLocationDisabled() + { + onLocationError(); + } + }; private boolean mIsCarCompassUsed = true; private boolean mIsCarLocationUsed = true; @@ -38,6 +68,7 @@ public class CarSensorsManager { mCarContext = context; mCarSensors = mCarContext.getCarService(CarHardwareManager.class).getCarSensors(); + mLocationHelper = LocationHelper.from(mCarContext); } @RequiresPermission(ACCESS_FINE_LOCATION) @@ -50,8 +81,9 @@ public class CarSensorsManager else SensorHelper.from(mCarContext).addListener(this::onCompassUpdated); - if (!LocationHelper.from(mCarContext).isActive()) - LocationHelper.from(mCarContext).start(); + if (!mLocationHelper.isActive()) + mLocationHelper.start(); + mLocationHelper.addListener(mLocationListener); if (mIsCarLocationUsed) mCarSensors.addCarHardwareLocationListener(CarSensors.UPDATE_RATE_FASTEST, executor, this::onCarLocationDataAvailable); @@ -66,6 +98,8 @@ public class CarSensorsManager if (mIsCarLocationUsed) mCarSensors.removeCarHardwareLocationListener(this::onCarLocationDataAvailable); + + mLocationHelper.removeListener(mLocationListener); } private void onCarCompassDataAvailable(@NonNull final Compass compass) @@ -115,4 +149,23 @@ public class CarSensorsManager mCarSensors.removeCompassListener(this::onCarCompassDataAvailable); SensorHelper.from(mCarContext).addListener(this::onCompassUpdated); } + + private void onLocationError() + { + Logger.d(TAG); + LocationState.nativeOnLocationError(LocationState.ERROR_GPS_OFF); + final ErrorScreen errorScreen = new ErrorScreen.Builder(mCarContext) + .setTitle(R.string.enable_location_services) + .setErrorMessage(R.string.location_is_disabled_long_text) + .setFinishPrecondition(() -> { + if (LocationUtils.areLocationServicesTurnedOn(mCarContext)) + { + if (LocationState.getMode() == LocationState.NOT_FOLLOW_NO_POSITION) + LocationState.nativeSwitchToNextMode(); + return true; + } + return false; + }).build(); + mCarContext.getCarService(ScreenManager.class).push(errorScreen); + } } diff --git a/android/app/src/main/java/app/organicmaps/location/LocationListener.java b/android/app/src/main/java/app/organicmaps/location/LocationListener.java index b05ab8ffb9..5542a3e6e0 100644 --- a/android/app/src/main/java/app/organicmaps/location/LocationListener.java +++ b/android/app/src/main/java/app/organicmaps/location/LocationListener.java @@ -14,11 +14,18 @@ public interface LocationListener // No op. } + /** + * Called by AndroidNativeLocationProvider when no suitable location methods are available. + */ default void onLocationDisabled() { // No op. } + /** + * Called by GoogleFusedLocationProvider to request to GPS and/or Wi-Fi. + * @param pendingIntent an intent to launch. + */ default void onLocationResolutionRequired(@NonNull PendingIntent pendingIntent) { // No op. diff --git a/android/app/src/main/java/app/organicmaps/util/LocationUtils.java b/android/app/src/main/java/app/organicmaps/util/LocationUtils.java index 0631893b7e..fd56a7dbf8 100644 --- a/android/app/src/main/java/app/organicmaps/util/LocationUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/LocationUtils.java @@ -7,12 +7,11 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.content.Context; import android.location.Location; import android.location.LocationManager; -import android.os.Build; -import android.provider.Settings; import android.view.Surface; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; +import androidx.core.location.LocationManagerCompat; public class LocationUtils { @@ -95,22 +94,8 @@ public class LocationUtils public static boolean areLocationServicesTurnedOn(@NonNull Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) - { - final LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - return lm.isLocationEnabled(); - } - - try - { - return Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE) - != Settings.Secure.LOCATION_MODE_OFF; - } - catch (Settings.SettingNotFoundException e) - { - e.printStackTrace(); - return false; - } + final LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + return LocationManagerCompat.isLocationEnabled(lm); } public static boolean checkFineLocationPermission(@NonNull Context context) -- 2.45.3