From 0e1b01c6536fc5c8591ec672032e082e8f152042 Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Mon, 25 Dec 2023 20:54:48 +0100 Subject: [PATCH] [android-auto] Use compass and location data from car Signed-off-by: Andrew Shkrob --- android/app/build.gradle | 1 + .../app/organicmaps/car/CarAppSession.java | 24 ++-- .../car/util/CarSensorsManager.java | 115 ++++++++++++++++++ 3 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 android/app/src/main/java/app/organicmaps/car/util/CarSensorsManager.java diff --git a/android/app/build.gradle b/android/app/build.gradle index 1eb7ac7a52..eeeca1abc7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -97,6 +97,7 @@ dependencies { implementation 'androidx.annotation:annotation:1.7.1' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.car.app:app:1.4.0-rc02' + implementation 'androidx.car.app:app-projected:1.4.0-rc2' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.fragment:fragment:1.6.2' implementation 'androidx.preference:preference:1.2.1' 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 ebb63ceb92..deaf94a3e3 100644 --- a/android/app/src/main/java/app/organicmaps/car/CarAppSession.java +++ b/android/app/src/main/java/app/organicmaps/car/CarAppSession.java @@ -13,7 +13,6 @@ import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import app.organicmaps.Framework; -import app.organicmaps.Map; import app.organicmaps.MwmApplication; import app.organicmaps.R; import app.organicmaps.bookmarks.data.MapObject; @@ -26,16 +25,14 @@ import app.organicmaps.car.screens.RequestPermissionsScreen; import app.organicmaps.car.screens.base.BaseMapScreen; import app.organicmaps.car.screens.download.DownloadMapsScreenBuilder; import app.organicmaps.car.screens.download.DownloaderHelpers; +import app.organicmaps.car.util.CarSensorsManager; import app.organicmaps.car.util.CurrentCountryChangedListener; import app.organicmaps.car.util.IntentUtils; 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.location.SensorHelper; -import app.organicmaps.location.SensorListener; import app.organicmaps.routing.RoutingController; import app.organicmaps.util.Config; import app.organicmaps.util.LocationUtils; @@ -47,7 +44,7 @@ import java.util.ArrayList; import java.util.List; public final class CarAppSession extends Session implements DefaultLifecycleObserver, - SensorListener, LocationState.ModeChangeListener, DisplayChangedListener, Framework.PlacePageActivationListener + LocationState.ModeChangeListener, DisplayChangedListener, Framework.PlacePageActivationListener { private static final String TAG = CarAppSession.class.getSimpleName(); @@ -58,6 +55,8 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse @NonNull private final ScreenManager mScreenManager; @NonNull + private final CarSensorsManager mSensorsManager; + @NonNull private final CurrentCountryChangedListener mCurrentCountryChangedListener; @SuppressWarnings("NotNullFieldNotInitialized") @NonNull @@ -70,6 +69,7 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse mSessionInfo = sessionInfo; mSurfaceRenderer = new SurfaceRenderer(getCarContext(), getLifecycle()); mScreenManager = getCarContext().getCarService(ScreenManager.class); + mSensorsManager = new CarSensorsManager(getCarContext()); mCurrentCountryChangedListener = new CurrentCountryChangedListener(); } @@ -127,9 +127,8 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse Framework.nativePlacePageActivationListener(this); mCurrentCountryChangedListener.onStart(getCarContext()); } - SensorHelper.from(getCarContext()).addListener(this); - if (LocationUtils.checkFineLocationPermission(getCarContext()) && !LocationHelper.from(getCarContext()).isActive()) - LocationHelper.from(getCarContext()).start(); + if (LocationUtils.checkFineLocationPermission(getCarContext())) + mSensorsManager.onStart(); if (mDisplayManager.isCarDisplayUsed()) { @@ -142,7 +141,7 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse public void onStop(@NonNull LifecycleOwner owner) { Logger.d(TAG); - SensorHelper.from(getCarContext()).removeListener(this); + mSensorsManager.onStop(); if (mDisplayManager.isCarDisplayUsed()) { LocationState.nativeRemoveListener(); @@ -183,7 +182,7 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse screensStack.add(new DownloadMapsScreenBuilder(getCarContext()).setDownloaderType(DownloadMapsScreenBuilder.DownloaderType.FirstLaunch).build()); if (!LocationUtils.checkFineLocationPermission(getCarContext())) - screensStack.add(new RequestPermissionsScreen(getCarContext(), () -> LocationHelper.from(getCarContext()).start())); + screensStack.add(new RequestPermissionsScreen(getCarContext(), mSensorsManager::onStart)); if (mDisplayManager.isDeviceDisplayUsed()) { @@ -206,11 +205,6 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse screen.invalidate(); } - public void onCompassUpdated(double north) - { - Map.onCompassUpdated(north, true); - } - @Override public void onDisplayChangedToDevice(@NonNull Runnable onTaskFinishedCallback) { 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 new file mode 100644 index 0000000000..a3f7055b0a --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/car/util/CarSensorsManager.java @@ -0,0 +1,115 @@ +package app.organicmaps.car.util; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + +import android.location.Location; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresPermission; +import androidx.car.app.CarContext; +import androidx.car.app.hardware.CarHardwareManager; +import androidx.car.app.hardware.common.CarValue; +import androidx.car.app.hardware.info.CarHardwareLocation; +import androidx.car.app.hardware.info.CarSensors; +import androidx.car.app.hardware.info.Compass; +import androidx.core.content.ContextCompat; + +import app.organicmaps.Map; +import app.organicmaps.location.LocationHelper; +import app.organicmaps.location.SensorHelper; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +public class CarSensorsManager +{ + @NonNull + private final CarContext mCarContext; + @SuppressWarnings("NotNullFieldNotInitialized") + @NonNull + private CarSensors mCarSensors; + + private boolean mIsCarCompassUsed = true; + private boolean mIsCarLocationUsed = true; + + public CarSensorsManager(@NonNull final CarContext context) + { + mCarContext = context; + } + + @RequiresPermission(ACCESS_FINE_LOCATION) + public void onStart() + { + final Executor executor = ContextCompat.getMainExecutor(mCarContext); + mCarSensors = mCarContext.getCarService(CarHardwareManager.class).getCarSensors(); + + if (mIsCarCompassUsed) + mCarSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, executor, this::onCarCompassDataAvailable); + else + SensorHelper.from(mCarContext).addListener(this::onCompassUpdated); + + if (!LocationHelper.from(mCarContext).isActive()) + LocationHelper.from(mCarContext).start(); + + if (mIsCarLocationUsed) + mCarSensors.addCarHardwareLocationListener(CarSensors.UPDATE_RATE_FASTEST, executor, this::onCarLocationDataAvailable); + } + + public void onStop() + { + if (mIsCarCompassUsed) + mCarSensors.removeCompassListener(this::onCarCompassDataAvailable); + else + SensorHelper.from(mCarContext).removeListener(this::onCompassUpdated); + + if (mIsCarLocationUsed) + mCarSensors.removeCarHardwareLocationListener(this::onCarLocationDataAvailable); + } + + private void onCarCompassDataAvailable(@NonNull final Compass compass) + { + final CarValue> data = compass.getOrientations(); + if (data.getStatus() == CarValue.STATUS_UNIMPLEMENTED) + onCarCompassUnsupported(); + else if (data.getStatus() == CarValue.STATUS_SUCCESS) + { + final List orientations = compass.getOrientations().getValue(); + if (orientations == null) + return; + final float azimuth = orientations.get(0); + Map.onCompassUpdated(Math.toRadians(azimuth), true); + } + } + + private void onCompassUpdated(double north) + { + Map.onCompassUpdated(north, true); + } + + private void onCarLocationDataAvailable(@NonNull final CarHardwareLocation hardwareLocation) + { + final CarValue location = hardwareLocation.getLocation(); + if (location.getStatus() == CarValue.STATUS_UNIMPLEMENTED) + onCarLocationUnsupported(); + else if (location.getStatus() == CarValue.STATUS_SUCCESS) + { + final Location loc = location.getValue(); + if (loc != null) + LocationHelper.from(mCarContext).onLocationChanged(loc); + } + } + + private void onCarLocationUnsupported() + { + mIsCarLocationUsed = false; + mCarSensors.removeCarHardwareLocationListener(this::onCarLocationDataAvailable); + } + + private void onCarCompassUnsupported() + { + mIsCarCompassUsed = false; + mCarSensors.removeCompassListener(this::onCarCompassDataAvailable); + SensorHelper.from(mCarContext).addListener(this::onCompassUpdated); + } +}