From 19ab52216bc1011bd8d1c6cf6b39970712699f06 Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Mon, 21 Nov 2022 19:48:34 +0100 Subject: [PATCH 1/6] [android] Add Android Auto support Signed-off-by: Andrew Shkrob --- android/AndroidManifest.xml | 18 ++ android/build.gradle | 3 + android/gradle.properties | 2 +- android/res/values/car_hosts.xml | 11 ++ android/res/xml/automotive_app_desc.xml | 4 + .../car/NavigationCarAppService.java | 46 +++++ .../app/organicmaps/car/NavigationScreen.java | 84 +++++++++ .../organicmaps/car/NavigationSession.java | 22 +++ .../app/organicmaps/car/SurfaceRenderer.java | 175 ++++++++++++++++++ 9 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 android/res/values/car_hosts.xml create mode 100644 android/res/xml/automotive_app_desc.xml create mode 100644 android/src/app/organicmaps/car/NavigationCarAppService.java create mode 100644 android/src/app/organicmaps/car/NavigationScreen.java create mode 100644 android/src/app/organicmaps/car/NavigationSession.java create mode 100644 android/src/app/organicmaps/car/SurfaceRenderer.java diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 9167327a9c..4c6095089b 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -38,6 +38,8 @@ //--> + + @@ -723,6 +725,14 @@ android:name="app.organicmaps.background.OsmUploadService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"/> + + + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index 96c989afd2..8d3066b70c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -84,6 +84,7 @@ dependencies { implementation 'androidx.annotation:annotation:1.5.0' implementation 'androidx.appcompat:appcompat:1.7.0-alpha01' + implementation 'androidx.car.app:app:1.3.0-beta01' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.fragment:fragment:1.5.4' // Lifecycle is added as a workaround for duplicate classes error caused by some outdated dependency: @@ -97,6 +98,8 @@ dependencies { implementation 'androidx.work:work-runtime:2.7.1' implementation 'com.google.android.material:material:1.8.0-alpha02' implementation 'com.google.code.gson:gson:2.10' + // Fix for app/organicmaps/util/FileUploadWorker.java:14: error: cannot access ListenableFuture + implementation 'com.google.guava:guava:29.0-android' implementation 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar' implementation 'com.github.devnullorthrow:MPAndroidChart:3.2.0-alpha' implementation 'net.jcip:jcip-annotations:1.0' diff --git a/android/gradle.properties b/android/gradle.properties index c9cf2e5f8f..5608450632 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ -propMinSdkVersion=21 +propMinSdkVersion=23 propTargetSdkVersion=33 propCompileSdkVersion=33 propBuildToolsVersion=33.0.0 diff --git a/android/res/values/car_hosts.xml b/android/res/values/car_hosts.xml new file mode 100644 index 0000000000..cc47a82af6 --- /dev/null +++ b/android/res/values/car_hosts.xml @@ -0,0 +1,11 @@ + + + + fdb00c43dbde8b51cb312aa81d3b5fa17713adb94b28f598d77f8eb89daceedf,com.google.android.projection.gearhead + 70811a3eacfd2e83e18da9bfede52df16ce91f2e69a44d21f18ab66991130771,com.google.android.projection.gearhead + 1975b2f17177bc89a5dff31f9e64a6cae281a53dc1d1d59b1d147fe1c82afa00,com.google.android.projection.gearhead + c241ffbc8e287c4e9a4ad19632ba1b1351ad361d5177b7d7b29859bd2b7fc631,com.google.android.apps.automotive.templates.host + dd66deaf312d8daec7adbe85a218ecc8c64f3b152f9b5998d5b29300c2623f61,com.google.android.apps.automotive.templates.host + 50e603d333c6049a37bd751375d08f3bd0abebd33facd30bd17b64b89658b421,com.google.android.apps.automotive.templates.host + + diff --git a/android/res/xml/automotive_app_desc.xml b/android/res/xml/automotive_app_desc.xml new file mode 100644 index 0000000000..83b683397c --- /dev/null +++ b/android/res/xml/automotive_app_desc.xml @@ -0,0 +1,4 @@ + + + + diff --git a/android/src/app/organicmaps/car/NavigationCarAppService.java b/android/src/app/organicmaps/car/NavigationCarAppService.java new file mode 100644 index 0000000000..a78529f294 --- /dev/null +++ b/android/src/app/organicmaps/car/NavigationCarAppService.java @@ -0,0 +1,46 @@ +package app.organicmaps.car; + +import android.content.pm.ApplicationInfo; + +import androidx.annotation.NonNull; +import androidx.car.app.CarAppService; +import androidx.car.app.Session; +import androidx.car.app.validation.HostValidator; + +import app.organicmaps.MwmApplication; +import app.organicmaps.R; + +import java.io.IOException; + +public final class NavigationCarAppService extends CarAppService +{ + @NonNull + @Override + public HostValidator createHostValidator() + { + if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) + { + return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR; + } + else + { + return new HostValidator.Builder(getApplicationContext()) + .addAllowedHosts(R.array.hosts_allowlist) + .build(); + } + } + + @NonNull + @Override + public Session onCreateSession() + { + try + { + MwmApplication.from(getApplicationContext()).init(); + } catch (IOException e) + { + e.printStackTrace(); + } + return new NavigationSession(); + } +} diff --git a/android/src/app/organicmaps/car/NavigationScreen.java b/android/src/app/organicmaps/car/NavigationScreen.java new file mode 100644 index 0000000000..012c1b4eef --- /dev/null +++ b/android/src/app/organicmaps/car/NavigationScreen.java @@ -0,0 +1,84 @@ +package app.organicmaps.car; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.CarToast; +import androidx.car.app.Screen; +import androidx.car.app.model.Action; +import androidx.car.app.model.ActionStrip; +import androidx.car.app.model.CarIcon; +import androidx.car.app.model.Template; +import androidx.car.app.navigation.model.NavigationTemplate; +import androidx.core.graphics.drawable.IconCompat; + +import app.organicmaps.R; +import app.organicmaps.location.LocationHelper; +import app.organicmaps.util.LocationUtils; + +public class NavigationScreen extends Screen +{ + private static final String TAG = NavigationScreen.class.getSimpleName(); + + @NonNull + private final SurfaceRenderer mSurfaceRenderer; + + protected NavigationScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) + { + super(carContext); + mSurfaceRenderer = surfaceRenderer; + } + + @NonNull + @Override + public Template onGetTemplate() + { + Log.d(TAG, "onGetTemplate"); + NavigationTemplate.Builder builder = new NavigationTemplate.Builder(); + ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder(); + actionStripBuilder.addAction(new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_settings)).build()) + .setOnClickListener(this::settings) + .build()); + + Action panAction = new Action.Builder(Action.PAN).build(); + Action location = new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_not_follow)).build()) + .setOnClickListener(this::location) + .build(); + Action zoomIn = new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_plus)).build()) + .setOnClickListener(this::zoomIn) + .build(); + Action zoomOut = new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_minus)).build()) + .setOnClickListener(this::zoomOut) + .build(); + + ActionStrip mapActionStrip = new ActionStrip.Builder().addAction(location) + .addAction(zoomIn) + .addAction(zoomOut) + .addAction(panAction) + .build(); + builder.setMapActionStrip(mapActionStrip); + builder.setActionStrip(actionStripBuilder.build()); + return builder.build(); + } + + private void location() + { + CarToast.makeText(getCarContext(), "Location", CarToast.LENGTH_LONG).show(); + } + + private void zoomOut() + { + mSurfaceRenderer.onZoomOut(); + } + + private void zoomIn() + { + mSurfaceRenderer.onZoomIn(); + } + + private void settings() + { + CarToast.makeText(getCarContext(), "Settings", CarToast.LENGTH_LONG).show(); + } +} diff --git a/android/src/app/organicmaps/car/NavigationSession.java b/android/src/app/organicmaps/car/NavigationSession.java new file mode 100644 index 0000000000..85402fd110 --- /dev/null +++ b/android/src/app/organicmaps/car/NavigationSession.java @@ -0,0 +1,22 @@ +package app.organicmaps.car; + +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.car.app.Screen; +import androidx.car.app.Session; + +public final class NavigationSession extends Session +{ + @Nullable + private SurfaceRenderer mNavigationSurface; + + @NonNull + @Override + public Screen onCreateScreen(@NonNull Intent intent) + { + mNavigationSurface = new SurfaceRenderer(getCarContext(), getLifecycle()); + return new NavigationScreen(getCarContext(), mNavigationSurface); + } +} diff --git a/android/src/app/organicmaps/car/SurfaceRenderer.java b/android/src/app/organicmaps/car/SurfaceRenderer.java new file mode 100644 index 0000000000..9b4692a21c --- /dev/null +++ b/android/src/app/organicmaps/car/SurfaceRenderer.java @@ -0,0 +1,175 @@ +package app.organicmaps.car; + +import android.graphics.Rect; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.car.app.AppManager; +import androidx.car.app.CarContext; +import androidx.car.app.CarToast; +import androidx.car.app.SurfaceCallback; +import androidx.car.app.SurfaceContainer; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; + +import app.organicmaps.Map; +import app.organicmaps.R; + +public class SurfaceRenderer implements DefaultLifecycleObserver, SurfaceCallback +{ + private static final String TAG = SurfaceRenderer.class.getSimpleName(); + + private final CarContext mCarContext; + private final Map mMap; + + @Nullable + private Rect mVisibleArea; + @Nullable + private Rect mStableArea; + + public SurfaceRenderer(@NonNull CarContext carContext, @NonNull Lifecycle lifecycle) + { + Log.d(TAG, "SurfaceRenderer()"); + mCarContext = carContext; + mMap = new Map(); + lifecycle.addObserver(this); + } + + @Override + public void onSurfaceAvailable(@NonNull SurfaceContainer surfaceContainer) + { + Log.d(TAG, "Surface available " + surfaceContainer); + mMap.onSurfaceCreated( + mCarContext, + surfaceContainer.getSurface(), + new Rect(0, 0, surfaceContainer.getWidth(), surfaceContainer.getHeight()), + surfaceContainer.getDpi() + ); + } + + @Override + public void onVisibleAreaChanged(@NonNull Rect visibleArea) + { + Log.d(TAG, "Visible area changed. stableArea: " + mStableArea + " visibleArea:" + visibleArea); + mVisibleArea = visibleArea; + } + + @Override + public void onStableAreaChanged(@NonNull Rect stableArea) + { + Log.d(TAG, "Stable area changed. stableArea: " + mStableArea + " visibleArea:" + mVisibleArea); + mStableArea = stableArea; + } + + @Override + public void onSurfaceDestroyed(@NonNull SurfaceContainer surfaceContainer) + { + Log.d(TAG, "Surface destroyed"); + mMap.onSurfaceDestroyed(false, true); + } + + @Override + public void onCreate(@NonNull LifecycleOwner owner) + { + Log.d(TAG, "onCreate"); + mCarContext.getCarService(AppManager.class).setSurfaceCallback(this); + + boolean launchByDeepLink = false; + mMap.onCreate(launchByDeepLink); + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) + { + Log.d(TAG, "onStart"); + mMap.onStart(); + mMap.setCallbackUnsupported(this::reportUnsupported); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) + { + Log.d(TAG, "onStop"); + mMap.onStop(); + mMap.setCallbackUnsupported(null); + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) + { + Log.d(TAG, "onPause"); + mMap.onPause(mCarContext); + } + + @Override + public void onResume(@NonNull LifecycleOwner owner) + { + Log.d(TAG, "onResume"); + mMap.onResume(); + } + + @Override + public void onScroll(float distanceX, float distanceY) + { + Log.d(TAG, "onScroll: distanceX: " + distanceX + ", distanceY: " + distanceY); + mMap.onScroll(distanceX, distanceY); + } + + @Override + public void onFling(float velocityX, float velocityY) + { + Log.d(TAG, "onFling: velocityX: " + velocityX + ", velocityY: " + velocityY); + } + + public void onZoomIn() + { + Map.zoomIn(); + } + + public void onZoomOut() + { + Map.zoomOut(); + } + + @Override + public void onScale(float focusX, float focusY, float scaleFactor) + { + Log.d(TAG, "onScale: focusX: " + focusX + ", focusY: " + focusY + ", scaleFactor: " + scaleFactor); + float x = focusX; + float y = focusY; + + Rect visibleArea = mVisibleArea; + if (visibleArea != null) + { + // If a focal point value is negative, use the center point of the visible area. + if (x < 0) + { + x = visibleArea.centerX(); + } + if (y < 0) + { + y = visibleArea.centerY(); + } + } + + boolean animated = Float.compare(scaleFactor, 2f) == 0; + + Map.onScale(scaleFactor, x, y, animated); + } + + @Override + public void onClick(float x, float y) + { + Log.d(TAG, "onClick: x: " + x + ", y: " + y); + Map.onTouch(x, y); + } + + private void reportUnsupported() + { + String message = mCarContext.getString(R.string.unsupported_phone); + Log.e(TAG, mCarContext.getString(R.string.unsupported_phone)); + CarToast.makeText(mCarContext, message, CarToast.LENGTH_LONG).show(); + } +} -- 2.45.3 From ba62a208c4ce34736a2efaf5340b12b431551e7d Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Sun, 27 Nov 2022 01:41:51 +0100 Subject: [PATCH 2/6] [android-auto] Add Screens Add Settings, Bookmarks, Categories, Search screens. Signed-off-by: Andrew Shkrob --- android/res/drawable/ic_check_box.xml | 5 + android/res/drawable/ic_check_box_checked.xml | 5 + .../car/NavigationCarAppService.java | 10 -- .../app/organicmaps/car/NavigationScreen.java | 84 ----------- .../organicmaps/car/NavigationSession.java | 112 +++++++++++++- .../src/app/organicmaps/car/OMController.java | 40 +++++ .../src/app/organicmaps/car/UiHelpers.java | 80 ++++++++++ .../car/screens/BookmarksScreen.java | 140 ++++++++++++++++++ .../car/screens/CategoriesScreen.java | 93 ++++++++++++ .../organicmaps/car/screens/ErrorScreen.java | 32 ++++ .../organicmaps/car/screens/MapScreen.java | 46 ++++++ .../car/screens/NavigationScreen.java | 103 +++++++++++++ .../organicmaps/car/screens/SearchScreen.java | 61 ++++++++ .../settings/DrivingOptionsScreen.java | 54 +++++++ .../car/screens/settings/HelpScreen.java | 73 +++++++++ .../car/screens/settings/SettingsScreen.java | 77 ++++++++++ 16 files changed, 915 insertions(+), 100 deletions(-) create mode 100644 android/res/drawable/ic_check_box.xml create mode 100644 android/res/drawable/ic_check_box_checked.xml delete mode 100644 android/src/app/organicmaps/car/NavigationScreen.java create mode 100644 android/src/app/organicmaps/car/OMController.java create mode 100644 android/src/app/organicmaps/car/UiHelpers.java create mode 100644 android/src/app/organicmaps/car/screens/BookmarksScreen.java create mode 100644 android/src/app/organicmaps/car/screens/CategoriesScreen.java create mode 100644 android/src/app/organicmaps/car/screens/ErrorScreen.java create mode 100644 android/src/app/organicmaps/car/screens/MapScreen.java create mode 100644 android/src/app/organicmaps/car/screens/NavigationScreen.java create mode 100644 android/src/app/organicmaps/car/screens/SearchScreen.java create mode 100644 android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java create mode 100644 android/src/app/organicmaps/car/screens/settings/HelpScreen.java create mode 100644 android/src/app/organicmaps/car/screens/settings/SettingsScreen.java diff --git a/android/res/drawable/ic_check_box.xml b/android/res/drawable/ic_check_box.xml new file mode 100644 index 0000000000..50101a198b --- /dev/null +++ b/android/res/drawable/ic_check_box.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/res/drawable/ic_check_box_checked.xml b/android/res/drawable/ic_check_box_checked.xml new file mode 100644 index 0000000000..5c0babf540 --- /dev/null +++ b/android/res/drawable/ic_check_box_checked.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/src/app/organicmaps/car/NavigationCarAppService.java b/android/src/app/organicmaps/car/NavigationCarAppService.java index a78529f294..4e4c2593b6 100644 --- a/android/src/app/organicmaps/car/NavigationCarAppService.java +++ b/android/src/app/organicmaps/car/NavigationCarAppService.java @@ -7,11 +7,8 @@ import androidx.car.app.CarAppService; import androidx.car.app.Session; import androidx.car.app.validation.HostValidator; -import app.organicmaps.MwmApplication; import app.organicmaps.R; -import java.io.IOException; - public final class NavigationCarAppService extends CarAppService { @NonNull @@ -34,13 +31,6 @@ public final class NavigationCarAppService extends CarAppService @Override public Session onCreateSession() { - try - { - MwmApplication.from(getApplicationContext()).init(); - } catch (IOException e) - { - e.printStackTrace(); - } return new NavigationSession(); } } diff --git a/android/src/app/organicmaps/car/NavigationScreen.java b/android/src/app/organicmaps/car/NavigationScreen.java deleted file mode 100644 index 012c1b4eef..0000000000 --- a/android/src/app/organicmaps/car/NavigationScreen.java +++ /dev/null @@ -1,84 +0,0 @@ -package app.organicmaps.car; - -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.car.app.CarContext; -import androidx.car.app.CarToast; -import androidx.car.app.Screen; -import androidx.car.app.model.Action; -import androidx.car.app.model.ActionStrip; -import androidx.car.app.model.CarIcon; -import androidx.car.app.model.Template; -import androidx.car.app.navigation.model.NavigationTemplate; -import androidx.core.graphics.drawable.IconCompat; - -import app.organicmaps.R; -import app.organicmaps.location.LocationHelper; -import app.organicmaps.util.LocationUtils; - -public class NavigationScreen extends Screen -{ - private static final String TAG = NavigationScreen.class.getSimpleName(); - - @NonNull - private final SurfaceRenderer mSurfaceRenderer; - - protected NavigationScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) - { - super(carContext); - mSurfaceRenderer = surfaceRenderer; - } - - @NonNull - @Override - public Template onGetTemplate() - { - Log.d(TAG, "onGetTemplate"); - NavigationTemplate.Builder builder = new NavigationTemplate.Builder(); - ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder(); - actionStripBuilder.addAction(new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_settings)).build()) - .setOnClickListener(this::settings) - .build()); - - Action panAction = new Action.Builder(Action.PAN).build(); - Action location = new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_not_follow)).build()) - .setOnClickListener(this::location) - .build(); - Action zoomIn = new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_plus)).build()) - .setOnClickListener(this::zoomIn) - .build(); - Action zoomOut = new Action.Builder().setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_minus)).build()) - .setOnClickListener(this::zoomOut) - .build(); - - ActionStrip mapActionStrip = new ActionStrip.Builder().addAction(location) - .addAction(zoomIn) - .addAction(zoomOut) - .addAction(panAction) - .build(); - builder.setMapActionStrip(mapActionStrip); - builder.setActionStrip(actionStripBuilder.build()); - return builder.build(); - } - - private void location() - { - CarToast.makeText(getCarContext(), "Location", CarToast.LENGTH_LONG).show(); - } - - private void zoomOut() - { - mSurfaceRenderer.onZoomOut(); - } - - private void zoomIn() - { - mSurfaceRenderer.onZoomIn(); - } - - private void settings() - { - CarToast.makeText(getCarContext(), "Settings", CarToast.LENGTH_LONG).show(); - } -} diff --git a/android/src/app/organicmaps/car/NavigationSession.java b/android/src/app/organicmaps/car/NavigationSession.java index 85402fd110..1cbeffbcb3 100644 --- a/android/src/app/organicmaps/car/NavigationSession.java +++ b/android/src/app/organicmaps/car/NavigationSession.java @@ -1,22 +1,122 @@ package app.organicmaps.car; import android.content.Intent; +import android.util.Log; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import androidx.car.app.CarToast; import androidx.car.app.Screen; +import androidx.car.app.ScreenManager; import androidx.car.app.Session; +import androidx.car.app.model.Action; +import androidx.car.app.model.ActionStrip; +import androidx.car.app.model.CarIcon; +import androidx.car.app.navigation.model.MapController; +import androidx.core.graphics.drawable.IconCompat; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; -public final class NavigationSession extends Session +import app.organicmaps.MwmApplication; +import app.organicmaps.R; +import app.organicmaps.car.screens.ErrorScreen; +import app.organicmaps.car.screens.NavigationScreen; +import app.organicmaps.car.screens.settings.SettingsScreen; + +import java.io.IOException; + +public final class NavigationSession extends Session implements DefaultLifecycleObserver { - @Nullable - private SurfaceRenderer mNavigationSurface; + private static final String TAG = NavigationSession.class.getSimpleName(); + + + private OMController mMapController; + + boolean mInitFailed = false; + + public NavigationSession() + { + getLifecycle().addObserver(this); + } @NonNull @Override public Screen onCreateScreen(@NonNull Intent intent) { - mNavigationSurface = new SurfaceRenderer(getCarContext(), getLifecycle()); - return new NavigationScreen(getCarContext(), mNavigationSurface); + Log.d(TAG, "onCreateScreen()"); + if (mInitFailed) + return new ErrorScreen(getCarContext()); + + createMapController(); + return new NavigationScreen(getCarContext(), mMapController); + } + + @Override + public void onCreate(@NonNull LifecycleOwner owner) + { + Log.d(TAG, "onCreate()"); + init(); + } + + @Override + public void onResume(@NonNull LifecycleOwner owner) + { + Log.d(TAG, "onResume()"); + init(); + } + + private void init() + { + mInitFailed = false; + MwmApplication app = MwmApplication.from(getCarContext()); + try + { + app.init(); + } catch (IOException e) + { + mInitFailed = true; + Log.e(TAG, "Failed to initialize the app."); + } + } + + private void createMapController() + { + final CarIcon iconPlus = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_plus)).build(); + final CarIcon iconMinus = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_minus)).build(); + final CarIcon iconLocation = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_not_follow)).build(); + final CarIcon iconSettings = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_settings)).build(); + + SurfaceRenderer surfaceRenderer = new SurfaceRenderer(getCarContext(), getLifecycle()); + + Action panAction = new Action.Builder(Action.PAN).build(); + Action location = new Action.Builder().setIcon(iconLocation).setOnClickListener(this::location).build(); + Action zoomIn = new Action.Builder().setIcon(iconPlus).setOnClickListener(this::zoomIn).build(); + Action zoomOut = new Action.Builder().setIcon(iconMinus).setOnClickListener(this::zoomOut).build(); + ActionStrip mapActionStrip = new ActionStrip.Builder().addAction(location).addAction(zoomIn).addAction(zoomOut).addAction(panAction).build(); + MapController mapController = new MapController.Builder().setMapActionStrip(mapActionStrip).build(); + + Action settings = new Action.Builder().setIcon(iconSettings).setOnClickListener(this::openSettings).build(); + ActionStrip actionStrip = new ActionStrip.Builder().addAction(settings).build(); + + mMapController = new OMController(surfaceRenderer, mapController, actionStrip); + } + + private void location() + { + CarToast.makeText(getCarContext(), "Location", CarToast.LENGTH_LONG).show(); + } + + private void zoomOut() + { + mMapController.getSurfaceRenderer().onZoomOut(); + } + + private void zoomIn() + { + mMapController.getSurfaceRenderer().onZoomIn(); + } + + private void openSettings() + { + getCarContext().getCarService(ScreenManager.class).push(new SettingsScreen(getCarContext(), mMapController)); } } diff --git a/android/src/app/organicmaps/car/OMController.java b/android/src/app/organicmaps/car/OMController.java new file mode 100644 index 0000000000..61b05f646d --- /dev/null +++ b/android/src/app/organicmaps/car/OMController.java @@ -0,0 +1,40 @@ +package app.organicmaps.car; + +import androidx.annotation.NonNull; +import androidx.car.app.model.ActionStrip; +import androidx.car.app.navigation.model.MapController; + +public class OMController +{ + @NonNull + private final SurfaceRenderer mSurfaceRenderer; + @NonNull + private final MapController mMapController; + @NonNull + private final ActionStrip mActionStrip; + + public OMController(@NonNull SurfaceRenderer surfaceRenderer, @NonNull MapController mapController, @NonNull ActionStrip actionStrip) + { + mSurfaceRenderer = surfaceRenderer; + mMapController = mapController; + mActionStrip = actionStrip; + } + + @NonNull + public SurfaceRenderer getSurfaceRenderer() + { + return mSurfaceRenderer; + } + + @NonNull + public MapController getMapController() + { + return mMapController; + } + + @NonNull + public ActionStrip getActionStrip() + { + return mActionStrip; + } +} diff --git a/android/src/app/organicmaps/car/UiHelpers.java b/android/src/app/organicmaps/car/UiHelpers.java new file mode 100644 index 0000000000..89497899c3 --- /dev/null +++ b/android/src/app/organicmaps/car/UiHelpers.java @@ -0,0 +1,80 @@ +package app.organicmaps.car; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.car.app.CarContext; +import androidx.car.app.ScreenManager; +import androidx.car.app.model.CarIcon; +import androidx.car.app.model.Row; +import androidx.core.graphics.drawable.IconCompat; + +import app.organicmaps.R; +import app.organicmaps.routing.RoutingOptions; +import app.organicmaps.settings.RoadType; + +public final class UiHelpers +{ + public interface PrefsGetter + { + boolean get(); + } + + public interface PrefsSetter + { + void set(boolean newValue); + } + + @Nullable + private static CarIcon mCheckboxIcon; + @Nullable + private static CarIcon mCheckboxSelectedIcon; + + @NonNull + public static Row createSharedPrefsCheckbox( + @NonNull CarContext context, @StringRes int titleRes, PrefsGetter getter, PrefsSetter setter) + { + if (mCheckboxIcon == null || mCheckboxSelectedIcon == null) + initCheckboxIcons(context); + Row.Builder builder = new Row.Builder(); + builder.setTitle(context.getString(titleRes)); + builder.setOnClickListener(() -> { + setter.set(!getter.get()); + context.getCarService(ScreenManager.class).getTop().invalidate(); + }); + if (getter.get()) + builder.setImage(mCheckboxSelectedIcon); + else + builder.setImage(mCheckboxIcon); + + return builder.build(); + } + + @NonNull + public static Row createDrivingOptionCheckbox( + @NonNull CarContext context, RoadType roadType, @StringRes int titleRes) + { + if (mCheckboxIcon == null || mCheckboxSelectedIcon == null) + initCheckboxIcons(context); + Row.Builder builder = new Row.Builder(); + builder.setTitle(context.getString(titleRes)); + builder.setOnClickListener(() -> { + if (RoutingOptions.hasOption(roadType)) + RoutingOptions.removeOption(roadType); + else + RoutingOptions.addOption(roadType); + context.getCarService(ScreenManager.class).getTop().invalidate(); + }); + if (RoutingOptions.hasOption(roadType)) + builder.setImage(mCheckboxSelectedIcon); + else + builder.setImage(mCheckboxIcon); + return builder.build(); + } + + private static void initCheckboxIcons(@NonNull CarContext context) + { + mCheckboxIcon = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_check_box)).build(); + mCheckboxSelectedIcon = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_check_box_checked)).build(); + } +} diff --git a/android/src/app/organicmaps/car/screens/BookmarksScreen.java b/android/src/app/organicmaps/car/screens/BookmarksScreen.java new file mode 100644 index 0000000000..4e9b36ea4f --- /dev/null +++ b/android/src/app/organicmaps/car/screens/BookmarksScreen.java @@ -0,0 +1,140 @@ +package app.organicmaps.car.screens; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.car.app.CarContext; +import androidx.car.app.constraints.ConstraintManager; +import androidx.car.app.model.Action; +import androidx.car.app.model.CarIcon; +import androidx.car.app.model.Header; +import androidx.car.app.model.ItemList; +import androidx.car.app.model.Row; +import androidx.car.app.model.Template; +import androidx.car.app.navigation.model.MapTemplate; +import androidx.core.graphics.drawable.IconCompat; + +import app.organicmaps.R; +import app.organicmaps.bookmarks.data.BookmarkCategory; +import app.organicmaps.bookmarks.data.BookmarkInfo; +import app.organicmaps.bookmarks.data.BookmarkManager; +import app.organicmaps.car.OMController; +import app.organicmaps.util.Graphics; + +import java.util.ArrayList; +import java.util.List; + +public class BookmarksScreen extends MapScreen +{ + private final int MAX_CATEGORIES_SIZE; + + @Nullable + private BookmarkCategory mBookmarkCategory; + + public BookmarksScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + { + super(carContext, mapController); + MAX_CATEGORIES_SIZE = getCarContext().getCarService(ConstraintManager.class).getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); + } + + private BookmarksScreen(@NonNull CarContext carContext, @NonNull OMController mapController, @NonNull BookmarkCategory bookmarkCategory) + { + this(carContext, mapController); + mBookmarkCategory = bookmarkCategory; + } + + @NonNull + @Override + public Template onGetTemplate() + { + + MapTemplate.Builder builder = new MapTemplate.Builder(); + builder.setHeader(createHeader()); + builder.setMapController(getMapController()); + builder.setActionStrip(getActionStrip()); + if (mBookmarkCategory == null) + builder.setItemList(createBookmarkCategoriesList()); + else + builder.setItemList(createBookmarksList()); + return builder.build(); + } + + @NonNull + private Header createHeader() + { + Header.Builder builder = new Header.Builder(); + builder.setStartHeaderAction(Action.BACK); + if (mBookmarkCategory == null) + builder.setTitle(getCarContext().getString(R.string.bookmarks)); + else + builder.setTitle(mBookmarkCategory.getName()); + return builder.build(); + } + + @NonNull + private ItemList createBookmarkCategoriesList() + { + final List bookmarkCategories = getBookmarks(); + final int categoriesSize = Math.min(bookmarkCategories.size(), MAX_CATEGORIES_SIZE); + + ItemList.Builder builder = new ItemList.Builder(); + for (int i = 0; i < categoriesSize; ++i) + { + final BookmarkCategory bookmarkCategory = bookmarkCategories.get(i); + + Row.Builder itemBuilder = new Row.Builder(); + itemBuilder.setTitle(bookmarkCategory.getName()); + itemBuilder.addText(bookmarkCategory.getDescription()); + itemBuilder.setOnClickListener(() -> getScreenManager().push(new BookmarksScreen(getCarContext(), getOMController(), bookmarkCategory))); + itemBuilder.setBrowsable(true); + builder.addItem(itemBuilder.build()); + } + return builder.build(); + } + + @NonNull + private ItemList createBookmarksList() + { + final long bookmarkCategoryId = mBookmarkCategory.getId(); + final int bookmarkCategoriesSize = Math.min(mBookmarkCategory.getBookmarksCount(), MAX_CATEGORIES_SIZE); + + ItemList.Builder builder = new ItemList.Builder(); + for (int i = 0; i < bookmarkCategoriesSize; ++i) + { + final long bookmarkId = BookmarkManager.INSTANCE.getBookmarkIdByPosition(bookmarkCategoryId, i); + final BookmarkInfo bookmarkInfo = new BookmarkInfo(bookmarkCategoryId, bookmarkId); + + Row.Builder itemBuilder = new Row.Builder(); + itemBuilder.setTitle(bookmarkInfo.getName()); + if (!bookmarkInfo.getAddress().isEmpty()) + itemBuilder.addText(bookmarkInfo.getAddress()); + if (!bookmarkInfo.getFeatureType().isEmpty()) + itemBuilder.addText(bookmarkInfo.getFeatureType()); + final Drawable icon = Graphics.drawCircleAndImage(bookmarkInfo.getIcon().argb(), + R.dimen.track_circle_size, + bookmarkInfo.getIcon().getResId(), + R.dimen.bookmark_icon_size, + getCarContext()); + itemBuilder.setImage(new CarIcon.Builder(IconCompat.createWithBitmap(Graphics.drawableToBitmap(icon))).build()); + builder.addItem(itemBuilder.build()); + } + return builder.build(); + } + + @NonNull + private static List getBookmarks() + { + List bookmarkCategories = new ArrayList<>(BookmarkManager.INSTANCE.getCategories()); + + List toRemove = new ArrayList<>(); + for (BookmarkCategory bookmarkCategory : bookmarkCategories) + { + if (bookmarkCategory.getBookmarksCount() == 0) + toRemove.add(bookmarkCategory); + } + bookmarkCategories.removeAll(toRemove); + + return bookmarkCategories; + } +} diff --git a/android/src/app/organicmaps/car/screens/CategoriesScreen.java b/android/src/app/organicmaps/car/screens/CategoriesScreen.java new file mode 100644 index 0000000000..4bd74ced23 --- /dev/null +++ b/android/src/app/organicmaps/car/screens/CategoriesScreen.java @@ -0,0 +1,93 @@ +package app.organicmaps.car.screens; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.car.app.CarContext; +import androidx.car.app.constraints.ConstraintManager; +import androidx.car.app.model.Action; +import androidx.car.app.model.CarIcon; +import androidx.car.app.model.Header; +import androidx.car.app.model.ItemList; +import androidx.car.app.model.Row; +import androidx.car.app.model.Template; +import androidx.car.app.navigation.model.MapTemplate; +import androidx.core.graphics.drawable.IconCompat; + +import app.organicmaps.R; +import app.organicmaps.car.OMController; + +import java.util.Arrays; +import java.util.List; + +public class CategoriesScreen extends MapScreen +{ + private static class CategoryData + { + @StringRes + public final int nameResId; + + @DrawableRes + public final int iconResId; + + public CategoryData(int nameResId, int iconResId) + { + this.nameResId = nameResId; + this.iconResId = iconResId; + } + } + + private static final List CATEGORIES = Arrays.asList( + new CategoryData(R.string.fuel, R.drawable.ic_category_fuel), + new CategoryData(R.string.parking, R.drawable.ic_category_parking), + new CategoryData(R.string.eat, R.drawable.ic_category_eat), + new CategoryData(R.string.food, R.drawable.ic_category_food), + new CategoryData(R.string.hotel, R.drawable.ic_category_hotel), + new CategoryData(R.string.toilet, R.drawable.ic_category_toilet), + new CategoryData(R.string.rv, R.drawable.ic_category_rv) + ); + + private final int MAX_CATEGORIES_SIZE; + + public CategoriesScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + { + super(carContext, mapController); + MAX_CATEGORIES_SIZE = getCarContext().getCarService(ConstraintManager.class).getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); + } + + @NonNull + @Override + public Template onGetTemplate() + { + MapTemplate.Builder builder = new MapTemplate.Builder(); + builder.setHeader(createHeader()); + builder.setMapController(getMapController()); + builder.setActionStrip(getActionStrip()); + builder.setItemList(createCategoriesList()); + return builder.build(); + } + + @NonNull + private Header createHeader() + { + Header.Builder builder = new Header.Builder(); + builder.setStartHeaderAction(Action.BACK); + builder.setTitle(getCarContext().getString(R.string.categories)); + return builder.build(); + } + + @NonNull + private ItemList createCategoriesList() + { + ItemList.Builder builder = new ItemList.Builder(); + int categoriesSize = Math.min(CATEGORIES.size(), MAX_CATEGORIES_SIZE); + for (int i = 0; i < categoriesSize; ++i) + { + Row.Builder itemBuilder = new Row.Builder(); + itemBuilder.setTitle(getCarContext().getString(CATEGORIES.get(i).nameResId)); + itemBuilder.setImage(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), CATEGORIES.get(i).iconResId)).build()); + builder.addItem(itemBuilder.build()); + } + return builder.build(); + } +} diff --git a/android/src/app/organicmaps/car/screens/ErrorScreen.java b/android/src/app/organicmaps/car/screens/ErrorScreen.java new file mode 100644 index 0000000000..bcdaf8525f --- /dev/null +++ b/android/src/app/organicmaps/car/screens/ErrorScreen.java @@ -0,0 +1,32 @@ +package app.organicmaps.car.screens; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.Screen; +import androidx.car.app.model.LongMessageTemplate; +import androidx.car.app.model.Template; + +import app.organicmaps.R; + +public class ErrorScreen extends Screen +{ + private static final String TAG = ErrorScreen.class.getSimpleName(); + + public ErrorScreen(@NonNull CarContext carContext) + { + super(carContext); + } + + @NonNull + @Override + public Template onGetTemplate() + { + Log.d(TAG, "onGetTemplate"); + LongMessageTemplate.Builder builder = new LongMessageTemplate.Builder(getCarContext().getString(R.string.dialog_error_storage_message)); + builder.setTitle(getCarContext().getString(R.string.dialog_error_storage_title)); + + return builder.build(); + } +} diff --git a/android/src/app/organicmaps/car/screens/MapScreen.java b/android/src/app/organicmaps/car/screens/MapScreen.java new file mode 100644 index 0000000000..5876ee897c --- /dev/null +++ b/android/src/app/organicmaps/car/screens/MapScreen.java @@ -0,0 +1,46 @@ +package app.organicmaps.car.screens; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.Screen; +import androidx.car.app.model.ActionStrip; +import androidx.car.app.navigation.model.MapController; + +import app.organicmaps.car.OMController; +import app.organicmaps.car.SurfaceRenderer; + +public abstract class MapScreen extends Screen +{ + @NonNull + private final OMController mMapController; + + public MapScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + { + super(carContext); + mMapController = mapController; + } + + @NonNull + public OMController getOMController() + { + return mMapController; + } + + @NonNull + public SurfaceRenderer getSurfaceRenderer() + { + return mMapController.getSurfaceRenderer(); + } + + @NonNull + public MapController getMapController() + { + return mMapController.getMapController(); + } + + @NonNull + public ActionStrip getActionStrip() + { + return mMapController.getActionStrip(); + } +} diff --git a/android/src/app/organicmaps/car/screens/NavigationScreen.java b/android/src/app/organicmaps/car/screens/NavigationScreen.java new file mode 100644 index 0000000000..36e4225268 --- /dev/null +++ b/android/src/app/organicmaps/car/screens/NavigationScreen.java @@ -0,0 +1,103 @@ +package app.organicmaps.car.screens; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.model.Action; +import androidx.car.app.model.CarIcon; +import androidx.car.app.model.Header; +import androidx.car.app.model.Item; +import androidx.car.app.model.ItemList; +import androidx.car.app.model.Row; +import androidx.car.app.model.Template; +import androidx.car.app.navigation.model.MapTemplate; +import androidx.core.graphics.drawable.IconCompat; + +import app.organicmaps.R; +import app.organicmaps.car.OMController; + +public class NavigationScreen extends MapScreen +{ + public NavigationScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + { + super(carContext, mapController); + } + + @NonNull + @Override + public Template onGetTemplate() + { + MapTemplate.Builder builder = new MapTemplate.Builder(); + builder.setHeader(createHeader()); + builder.setMapController(getMapController()); + builder.setActionStrip(getActionStrip()); + builder.setItemList(createList()); + return builder.build(); + } + + @NonNull + private Header createHeader() + { + Header.Builder builder = new Header.Builder(); + builder.setStartHeaderAction(new Action.Builder(Action.APP_ICON).build()); + builder.setTitle(getCarContext().getString(R.string.app_name)); + return builder.build(); + } + + @NonNull + private ItemList createList() + { + ItemList.Builder builder = new ItemList.Builder(); + builder.addItem(createSearchItem()); + builder.addItem(createCategoriesItem()); + builder.addItem(createBookmarksItem()); + return builder.build(); + } + + @NonNull + private Item createSearchItem() + { + final CarIcon iconSearch = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_search)).build(); + + Row.Builder builder = new Row.Builder(); + builder.setTitle(getCarContext().getString(R.string.search)); + builder.setImage(iconSearch); + builder.setBrowsable(true); + builder.setOnClickListener(this::openSearch); + return builder.build(); + } + + @NonNull + private Item createCategoriesItem() + { + Row.Builder builder = new Row.Builder(); + builder.setTitle(getCarContext().getString(R.string.categories)); + builder.setBrowsable(true); + builder.setOnClickListener(this::openCategories); + return builder.build(); + } + + @NonNull + private Item createBookmarksItem() + { + Row.Builder builder = new Row.Builder(); + builder.setTitle(getCarContext().getString(R.string.bookmarks)); + builder.setBrowsable(true); + builder.setOnClickListener(this::openBookmarks); + return builder.build(); + } + + private void openSearch() + { + getScreenManager().push(new SearchScreen(getCarContext())); + } + + private void openCategories() + { + getScreenManager().push(new CategoriesScreen(getCarContext(), getOMController())); + } + + private void openBookmarks() + { + getScreenManager().push(new BookmarksScreen(getCarContext(), getOMController())); + } +} diff --git a/android/src/app/organicmaps/car/screens/SearchScreen.java b/android/src/app/organicmaps/car/screens/SearchScreen.java new file mode 100644 index 0000000000..d461d5cf30 --- /dev/null +++ b/android/src/app/organicmaps/car/screens/SearchScreen.java @@ -0,0 +1,61 @@ +package app.organicmaps.car.screens; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.Screen; +import androidx.car.app.constraints.ConstraintManager; +import androidx.car.app.model.Action; +import androidx.car.app.model.CarIcon; +import androidx.car.app.model.ItemList; +import androidx.car.app.model.Row; +import androidx.car.app.model.SearchTemplate; +import androidx.car.app.model.Template; +import androidx.core.graphics.drawable.IconCompat; + +import app.organicmaps.R; +import app.organicmaps.search.SearchRecents; + +public class SearchScreen extends Screen implements SearchTemplate.SearchCallback +{ + private final int MAX_RESULTS_SIZE; + private ItemList mResults; + private String mSearchText = ""; + + public SearchScreen(@NonNull CarContext carContext) + { + super(carContext); + MAX_RESULTS_SIZE = getCarContext().getCarService(ConstraintManager.class).getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); + } + + @NonNull + @Override + public Template onGetTemplate() + { + SearchTemplate.Builder builder = new SearchTemplate.Builder(this); + builder.setHeaderAction(Action.BACK); + builder.setShowKeyboardByDefault(false); + if (mSearchText.isEmpty() || mResults == null) + loadRecents(); + builder.setItemList(mResults); + builder.setInitialSearchText(mSearchText); + return builder.build(); + } + + private void loadRecents() + { + final CarIcon iconRecent = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_search_recent)).build(); + + ItemList.Builder builder = new ItemList.Builder(); + builder.setNoItemsMessage(getCarContext().getString(R.string.search_history_text)); + SearchRecents.refresh(); + int recentsSize = Math.min(SearchRecents.getSize(), MAX_RESULTS_SIZE); + for (int i = 0; i < recentsSize; ++i) + { + Row.Builder itemBuilder = new Row.Builder(); + itemBuilder.setTitle(SearchRecents.get(i)); + itemBuilder.setImage(iconRecent); + builder.addItem(itemBuilder.build()); + } + mResults = builder.build(); + } +} diff --git a/android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java b/android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java new file mode 100644 index 0000000000..dc35a0b886 --- /dev/null +++ b/android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java @@ -0,0 +1,54 @@ +package app.organicmaps.car.screens.settings; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.model.Action; +import androidx.car.app.model.Header; +import androidx.car.app.model.ItemList; +import androidx.car.app.model.Template; +import androidx.car.app.navigation.model.MapTemplate; + +import app.organicmaps.R; +import app.organicmaps.car.OMController; +import app.organicmaps.car.UiHelpers; +import app.organicmaps.car.screens.MapScreen; +import app.organicmaps.settings.RoadType; + +public class DrivingOptionsScreen extends MapScreen +{ + public DrivingOptionsScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + { + super(carContext, mapController); + } + + @NonNull + @Override + public Template onGetTemplate() + { + MapTemplate.Builder builder = new MapTemplate.Builder(); + builder.setHeader(createHeader()); + builder.setMapController(getMapController()); + builder.setItemList(createDrivingOptionsList()); + return builder.build(); + } + + @NonNull + private Header createHeader() + { + Header.Builder builder = new Header.Builder(); + builder.setStartHeaderAction(Action.BACK); + builder.setTitle(getCarContext().getString(R.string.driving_options_subheader)); + return builder.build(); + } + + @NonNull + private ItemList createDrivingOptionsList() + { + ItemList.Builder builder = new ItemList.Builder(); + builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Toll, R.string.avoid_tolls)); + builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Dirty, R.string.avoid_unpaved)); + builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Ferry, R.string.avoid_ferry)); + builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Motorway, R.string.avoid_motorways)); + return builder.build(); + } +} diff --git a/android/src/app/organicmaps/car/screens/settings/HelpScreen.java b/android/src/app/organicmaps/car/screens/settings/HelpScreen.java new file mode 100644 index 0000000000..6a02678e04 --- /dev/null +++ b/android/src/app/organicmaps/car/screens/settings/HelpScreen.java @@ -0,0 +1,73 @@ +package app.organicmaps.car.screens.settings; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.model.Action; +import androidx.car.app.model.Header; +import androidx.car.app.model.Item; +import androidx.car.app.model.ItemList; +import androidx.car.app.model.Row; +import androidx.car.app.model.Template; +import androidx.car.app.navigation.model.MapTemplate; + +import app.organicmaps.BuildConfig; +import app.organicmaps.Framework; +import app.organicmaps.R; +import app.organicmaps.car.OMController; +import app.organicmaps.car.screens.MapScreen; +import app.organicmaps.util.DateUtils; + +public class HelpScreen extends MapScreen +{ + public HelpScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + { + super(carContext, mapController); + } + + @NonNull + @Override + public Template onGetTemplate() + { + MapTemplate.Builder builder = new MapTemplate.Builder(); + builder.setHeader(createHeader()); + builder.setMapController(getMapController()); + builder.setItemList(createSettingsList()); + return builder.build(); + } + + @NonNull + private Header createHeader() + { + Header.Builder builder = new Header.Builder(); + builder.setStartHeaderAction(Action.BACK); + builder.setTitle(getCarContext().getString(R.string.help)); + return builder.build(); + } + + @NonNull + private ItemList createSettingsList() + { + ItemList.Builder builder = new ItemList.Builder(); + builder.addItem(createVersionInfo()); + builder.addItem(createDataVersionInfo()); + return builder.build(); + } + + @NonNull + private Item createVersionInfo() + { + return new Row.Builder() + .setTitle(getCarContext().getString(R.string.app_name)) + .addText(BuildConfig.VERSION_NAME) + .build(); + } + + @NonNull + private Item createDataVersionInfo() + { + return new Row.Builder() + .setTitle(getCarContext().getString(R.string.data_version, "")) + .addText(DateUtils.getLocalDate(Framework.nativeGetDataVersion())) + .build(); + } +} diff --git a/android/src/app/organicmaps/car/screens/settings/SettingsScreen.java b/android/src/app/organicmaps/car/screens/settings/SettingsScreen.java new file mode 100644 index 0000000000..7fad3c8d39 --- /dev/null +++ b/android/src/app/organicmaps/car/screens/settings/SettingsScreen.java @@ -0,0 +1,77 @@ +package app.organicmaps.car.screens.settings; + +import androidx.annotation.NonNull; +import androidx.car.app.CarContext; +import androidx.car.app.model.Action; +import androidx.car.app.model.Header; +import androidx.car.app.model.Item; +import androidx.car.app.model.ItemList; +import androidx.car.app.model.Row; +import androidx.car.app.model.Template; +import androidx.car.app.navigation.model.MapTemplate; + +import app.organicmaps.R; +import app.organicmaps.car.OMController; +import app.organicmaps.car.UiHelpers; +import app.organicmaps.car.screens.MapScreen; +import app.organicmaps.util.Config; + +public class SettingsScreen extends MapScreen +{ + + public SettingsScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + { + super(carContext, mapController); + } + + @NonNull + @Override + public Template onGetTemplate() + { + MapTemplate.Builder builder = new MapTemplate.Builder(); + builder.setHeader(createHeader()); + builder.setMapController(getMapController()); + builder.setItemList(createSettingsList()); + return builder.build(); + } + + @NonNull + private Header createHeader() + { + Header.Builder builder = new Header.Builder(); + builder.setStartHeaderAction(Action.BACK); + builder.setTitle(getCarContext().getString(R.string.settings)); + return builder.build(); + } + + @NonNull + private ItemList createSettingsList() + { + ItemList.Builder builder = new ItemList.Builder(); + builder.addItem(createRoutingOptionsItem()); + builder.addItem(UiHelpers.createSharedPrefsCheckbox(getCarContext(), R.string.big_font, Config::isLargeFontsSize, Config::setLargeFontsSize)); + builder.addItem(UiHelpers.createSharedPrefsCheckbox(getCarContext(), R.string.transliteration_title, Config::isTransliteration, Config::setTransliteration)); + builder.addItem(createHelpItem()); + return builder.build(); + } + + @NonNull + private Item createRoutingOptionsItem() + { + Row.Builder builder = new Row.Builder(); + builder.setTitle(getCarContext().getString(R.string.driving_options_title)); + builder.setOnClickListener(() -> getScreenManager().push(new DrivingOptionsScreen(getCarContext(), getOMController()))); + builder.setBrowsable(true); + return builder.build(); + } + + @NonNull + private Item createHelpItem() + { + Row.Builder builder = new Row.Builder(); + builder.setTitle(getCarContext().getString(R.string.help)); + builder.setOnClickListener(() -> getScreenManager().push(new HelpScreen(getCarContext(), getOMController()))); + builder.setBrowsable(true); + return builder.build(); + } +} -- 2.45.3 From e0cb3e4bdf61b9af8627de1f764a4722deaef930 Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Thu, 8 Dec 2022 20:51:24 +0100 Subject: [PATCH 3/6] [android-auto] Update car.app library to 1.3.0-rc01 version Signed-off-by: Andrew Shkrob --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 8d3066b70c..8f23e41966 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -84,7 +84,7 @@ dependencies { implementation 'androidx.annotation:annotation:1.5.0' implementation 'androidx.appcompat:appcompat:1.7.0-alpha01' - implementation 'androidx.car.app:app:1.3.0-beta01' + implementation 'androidx.car.app:app:1.3.0-rc01' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.fragment:fragment:1.5.4' // Lifecycle is added as a workaround for duplicate classes error caused by some outdated dependency: -- 2.45.3 From 90662aed813e88201717d2406449996d58f100b3 Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Sun, 11 Dec 2022 14:43:52 +0100 Subject: [PATCH 4/6] [android-auto] Nit fixes Signed-off-by: Andrew Shkrob --- android/AndroidManifest.xml | 2 +- .../app/organicmaps/car/NavigationSession.java | 1 - .../src/app/organicmaps/car/SurfaceRenderer.java | 7 ++----- android/src/app/organicmaps/car/UiHelpers.java | 15 +++++---------- .../organicmaps/car/screens/BookmarksScreen.java | 14 ++++---------- .../app/organicmaps/car/screens/SearchScreen.java | 3 ++- 6 files changed, 14 insertions(+), 28 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 4c6095089b..b3b955f59c 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -726,7 +726,7 @@ android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"/> diff --git a/android/src/app/organicmaps/car/NavigationSession.java b/android/src/app/organicmaps/car/NavigationSession.java index 1cbeffbcb3..8a9625c365 100644 --- a/android/src/app/organicmaps/car/NavigationSession.java +++ b/android/src/app/organicmaps/car/NavigationSession.java @@ -28,7 +28,6 @@ public final class NavigationSession extends Session implements DefaultLifecycle { private static final String TAG = NavigationSession.class.getSimpleName(); - private OMController mMapController; boolean mInitFailed = false; diff --git a/android/src/app/organicmaps/car/SurfaceRenderer.java b/android/src/app/organicmaps/car/SurfaceRenderer.java index 9b4692a21c..7db089925e 100644 --- a/android/src/app/organicmaps/car/SurfaceRenderer.java +++ b/android/src/app/organicmaps/car/SurfaceRenderer.java @@ -76,6 +76,7 @@ public class SurfaceRenderer implements DefaultLifecycleObserver, SurfaceCallbac Log.d(TAG, "onCreate"); mCarContext.getCarService(AppManager.class).setSurfaceCallback(this); + // TODO: Properly process deep links from other apps on AA. boolean launchByDeepLink = false; mMap.onCreate(launchByDeepLink); } @@ -145,16 +146,12 @@ public class SurfaceRenderer implements DefaultLifecycleObserver, SurfaceCallbac { // If a focal point value is negative, use the center point of the visible area. if (x < 0) - { x = visibleArea.centerX(); - } if (y < 0) - { y = visibleArea.centerY(); - } } - boolean animated = Float.compare(scaleFactor, 2f) == 0; + final boolean animated = Float.compare(scaleFactor, 2f) == 0; Map.onScale(scaleFactor, x, y, animated); } diff --git a/android/src/app/organicmaps/car/UiHelpers.java b/android/src/app/organicmaps/car/UiHelpers.java index 89497899c3..a85d09ee6c 100644 --- a/android/src/app/organicmaps/car/UiHelpers.java +++ b/android/src/app/organicmaps/car/UiHelpers.java @@ -34,19 +34,17 @@ public final class UiHelpers public static Row createSharedPrefsCheckbox( @NonNull CarContext context, @StringRes int titleRes, PrefsGetter getter, PrefsSetter setter) { + final boolean getterValue = getter.get(); + if (mCheckboxIcon == null || mCheckboxSelectedIcon == null) initCheckboxIcons(context); Row.Builder builder = new Row.Builder(); builder.setTitle(context.getString(titleRes)); builder.setOnClickListener(() -> { - setter.set(!getter.get()); + setter.set(!getterValue); context.getCarService(ScreenManager.class).getTop().invalidate(); }); - if (getter.get()) - builder.setImage(mCheckboxSelectedIcon); - else - builder.setImage(mCheckboxIcon); - + builder.setImage(getterValue ? mCheckboxSelectedIcon : mCheckboxIcon); return builder.build(); } @@ -65,10 +63,7 @@ public final class UiHelpers RoutingOptions.addOption(roadType); context.getCarService(ScreenManager.class).getTop().invalidate(); }); - if (RoutingOptions.hasOption(roadType)) - builder.setImage(mCheckboxSelectedIcon); - else - builder.setImage(mCheckboxIcon); + builder.setImage(RoutingOptions.hasOption(roadType) ? mCheckboxSelectedIcon : mCheckboxIcon); return builder.build(); } diff --git a/android/src/app/organicmaps/car/screens/BookmarksScreen.java b/android/src/app/organicmaps/car/screens/BookmarksScreen.java index 4e9b36ea4f..349f41b3ad 100644 --- a/android/src/app/organicmaps/car/screens/BookmarksScreen.java +++ b/android/src/app/organicmaps/car/screens/BookmarksScreen.java @@ -35,7 +35,8 @@ public class BookmarksScreen extends MapScreen public BookmarksScreen(@NonNull CarContext carContext, @NonNull OMController mapController) { super(carContext, mapController); - MAX_CATEGORIES_SIZE = getCarContext().getCarService(ConstraintManager.class).getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); + final ConstraintManager constraintManager = getCarContext().getCarService(ConstraintManager.class); + MAX_CATEGORIES_SIZE = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); } private BookmarksScreen(@NonNull CarContext carContext, @NonNull OMController mapController, @NonNull BookmarkCategory bookmarkCategory) @@ -48,15 +49,11 @@ public class BookmarksScreen extends MapScreen @Override public Template onGetTemplate() { - MapTemplate.Builder builder = new MapTemplate.Builder(); builder.setHeader(createHeader()); builder.setMapController(getMapController()); builder.setActionStrip(getActionStrip()); - if (mBookmarkCategory == null) - builder.setItemList(createBookmarkCategoriesList()); - else - builder.setItemList(createBookmarksList()); + builder.setItemList(mBookmarkCategory == null ? createBookmarkCategoriesList() : createBookmarksList()); return builder.build(); } @@ -65,10 +62,7 @@ public class BookmarksScreen extends MapScreen { Header.Builder builder = new Header.Builder(); builder.setStartHeaderAction(Action.BACK); - if (mBookmarkCategory == null) - builder.setTitle(getCarContext().getString(R.string.bookmarks)); - else - builder.setTitle(mBookmarkCategory.getName()); + builder.setTitle(mBookmarkCategory == null ? getCarContext().getString(R.string.bookmarks) : mBookmarkCategory.getName()); return builder.build(); } diff --git a/android/src/app/organicmaps/car/screens/SearchScreen.java b/android/src/app/organicmaps/car/screens/SearchScreen.java index d461d5cf30..f6464aa869 100644 --- a/android/src/app/organicmaps/car/screens/SearchScreen.java +++ b/android/src/app/organicmaps/car/screens/SearchScreen.java @@ -24,7 +24,8 @@ public class SearchScreen extends Screen implements SearchTemplate.SearchCallbac public SearchScreen(@NonNull CarContext carContext) { super(carContext); - MAX_RESULTS_SIZE = getCarContext().getCarService(ConstraintManager.class).getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); + final ConstraintManager constraintManager = getCarContext().getCarService(ConstraintManager.class); + MAX_RESULTS_SIZE = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); } @NonNull -- 2.45.3 From 05a1411862c926fd4a75f2d25a877d01221b7717 Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Sun, 11 Dec 2022 15:48:04 +0100 Subject: [PATCH 5/6] [android-auto] Remove `OMController` and move checkbox creator functions to the Screen classes Signed-off-by: Andrew Shkrob --- .../organicmaps/car/NavigationSession.java | 60 +------------ .../src/app/organicmaps/car/OMController.java | 40 --------- .../src/app/organicmaps/car/UiHelpers.java | 84 +++++++------------ .../car/screens/BookmarksScreen.java | 23 ++--- .../car/screens/CategoriesScreen.java | 14 ++-- .../organicmaps/car/screens/MapScreen.java | 29 +------ .../car/screens/NavigationScreen.java | 15 ++-- .../settings/DrivingOptionsScreen.java | 44 ++++++++-- .../car/screens/settings/HelpScreen.java | 9 +- .../car/screens/settings/SettingsScreen.java | 52 ++++++++++-- 10 files changed, 148 insertions(+), 222 deletions(-) delete mode 100644 android/src/app/organicmaps/car/OMController.java diff --git a/android/src/app/organicmaps/car/NavigationSession.java b/android/src/app/organicmaps/car/NavigationSession.java index 8a9625c365..e500aa5f16 100644 --- a/android/src/app/organicmaps/car/NavigationSession.java +++ b/android/src/app/organicmaps/car/NavigationSession.java @@ -4,23 +4,14 @@ import android.content.Intent; import android.util.Log; import androidx.annotation.NonNull; -import androidx.car.app.CarToast; import androidx.car.app.Screen; -import androidx.car.app.ScreenManager; import androidx.car.app.Session; -import androidx.car.app.model.Action; -import androidx.car.app.model.ActionStrip; -import androidx.car.app.model.CarIcon; -import androidx.car.app.navigation.model.MapController; -import androidx.core.graphics.drawable.IconCompat; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import app.organicmaps.MwmApplication; -import app.organicmaps.R; import app.organicmaps.car.screens.ErrorScreen; import app.organicmaps.car.screens.NavigationScreen; -import app.organicmaps.car.screens.settings.SettingsScreen; import java.io.IOException; @@ -28,13 +19,13 @@ public final class NavigationSession extends Session implements DefaultLifecycle { private static final String TAG = NavigationSession.class.getSimpleName(); - private OMController mMapController; - - boolean mInitFailed = false; + private final SurfaceRenderer mSurfaceRenderer; + private boolean mInitFailed = false; public NavigationSession() { getLifecycle().addObserver(this); + mSurfaceRenderer = new SurfaceRenderer(getCarContext(), getLifecycle()); } @NonNull @@ -45,8 +36,7 @@ public final class NavigationSession extends Session implements DefaultLifecycle if (mInitFailed) return new ErrorScreen(getCarContext()); - createMapController(); - return new NavigationScreen(getCarContext(), mMapController); + return new NavigationScreen(getCarContext(), mSurfaceRenderer); } @Override @@ -76,46 +66,4 @@ public final class NavigationSession extends Session implements DefaultLifecycle Log.e(TAG, "Failed to initialize the app."); } } - - private void createMapController() - { - final CarIcon iconPlus = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_plus)).build(); - final CarIcon iconMinus = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_minus)).build(); - final CarIcon iconLocation = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_not_follow)).build(); - final CarIcon iconSettings = new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_settings)).build(); - - SurfaceRenderer surfaceRenderer = new SurfaceRenderer(getCarContext(), getLifecycle()); - - Action panAction = new Action.Builder(Action.PAN).build(); - Action location = new Action.Builder().setIcon(iconLocation).setOnClickListener(this::location).build(); - Action zoomIn = new Action.Builder().setIcon(iconPlus).setOnClickListener(this::zoomIn).build(); - Action zoomOut = new Action.Builder().setIcon(iconMinus).setOnClickListener(this::zoomOut).build(); - ActionStrip mapActionStrip = new ActionStrip.Builder().addAction(location).addAction(zoomIn).addAction(zoomOut).addAction(panAction).build(); - MapController mapController = new MapController.Builder().setMapActionStrip(mapActionStrip).build(); - - Action settings = new Action.Builder().setIcon(iconSettings).setOnClickListener(this::openSettings).build(); - ActionStrip actionStrip = new ActionStrip.Builder().addAction(settings).build(); - - mMapController = new OMController(surfaceRenderer, mapController, actionStrip); - } - - private void location() - { - CarToast.makeText(getCarContext(), "Location", CarToast.LENGTH_LONG).show(); - } - - private void zoomOut() - { - mMapController.getSurfaceRenderer().onZoomOut(); - } - - private void zoomIn() - { - mMapController.getSurfaceRenderer().onZoomIn(); - } - - private void openSettings() - { - getCarContext().getCarService(ScreenManager.class).push(new SettingsScreen(getCarContext(), mMapController)); - } } diff --git a/android/src/app/organicmaps/car/OMController.java b/android/src/app/organicmaps/car/OMController.java deleted file mode 100644 index 61b05f646d..0000000000 --- a/android/src/app/organicmaps/car/OMController.java +++ /dev/null @@ -1,40 +0,0 @@ -package app.organicmaps.car; - -import androidx.annotation.NonNull; -import androidx.car.app.model.ActionStrip; -import androidx.car.app.navigation.model.MapController; - -public class OMController -{ - @NonNull - private final SurfaceRenderer mSurfaceRenderer; - @NonNull - private final MapController mMapController; - @NonNull - private final ActionStrip mActionStrip; - - public OMController(@NonNull SurfaceRenderer surfaceRenderer, @NonNull MapController mapController, @NonNull ActionStrip actionStrip) - { - mSurfaceRenderer = surfaceRenderer; - mMapController = mapController; - mActionStrip = actionStrip; - } - - @NonNull - public SurfaceRenderer getSurfaceRenderer() - { - return mSurfaceRenderer; - } - - @NonNull - public MapController getMapController() - { - return mMapController; - } - - @NonNull - public ActionStrip getActionStrip() - { - return mActionStrip; - } -} diff --git a/android/src/app/organicmaps/car/UiHelpers.java b/android/src/app/organicmaps/car/UiHelpers.java index a85d09ee6c..c7980b89c6 100644 --- a/android/src/app/organicmaps/car/UiHelpers.java +++ b/android/src/app/organicmaps/car/UiHelpers.java @@ -1,75 +1,47 @@ package app.organicmaps.car; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; import androidx.car.app.CarContext; +import androidx.car.app.CarToast; import androidx.car.app.ScreenManager; +import androidx.car.app.model.Action; +import androidx.car.app.model.ActionStrip; import androidx.car.app.model.CarIcon; -import androidx.car.app.model.Row; +import androidx.car.app.navigation.model.MapController; import androidx.core.graphics.drawable.IconCompat; import app.organicmaps.R; -import app.organicmaps.routing.RoutingOptions; -import app.organicmaps.settings.RoadType; +import app.organicmaps.car.screens.settings.SettingsScreen; public final class UiHelpers { - public interface PrefsGetter + public static ActionStrip createSettingsActionStrip(@NonNull CarContext context, @NonNull SurfaceRenderer surfaceRenderer) { - boolean get(); + final CarIcon iconSettings = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_settings)).build(); + final Action settings = new Action.Builder().setIcon(iconSettings).setOnClickListener( + () -> context.getCarService(ScreenManager.class).push(new SettingsScreen(context, surfaceRenderer)) + ).build(); + return new ActionStrip.Builder().addAction(settings).build(); } - public interface PrefsSetter + public static MapController createMapController(@NonNull CarContext context, @NonNull SurfaceRenderer surfaceRenderer) { - void set(boolean newValue); - } + final CarIcon iconPlus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_plus)).build(); + final CarIcon iconMinus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_minus)).build(); + final CarIcon iconLocation = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_not_follow)).build(); - @Nullable - private static CarIcon mCheckboxIcon; - @Nullable - private static CarIcon mCheckboxSelectedIcon; - - @NonNull - public static Row createSharedPrefsCheckbox( - @NonNull CarContext context, @StringRes int titleRes, PrefsGetter getter, PrefsSetter setter) - { - final boolean getterValue = getter.get(); - - if (mCheckboxIcon == null || mCheckboxSelectedIcon == null) - initCheckboxIcons(context); - Row.Builder builder = new Row.Builder(); - builder.setTitle(context.getString(titleRes)); - builder.setOnClickListener(() -> { - setter.set(!getterValue); - context.getCarService(ScreenManager.class).getTop().invalidate(); - }); - builder.setImage(getterValue ? mCheckboxSelectedIcon : mCheckboxIcon); - return builder.build(); - } - - @NonNull - public static Row createDrivingOptionCheckbox( - @NonNull CarContext context, RoadType roadType, @StringRes int titleRes) - { - if (mCheckboxIcon == null || mCheckboxSelectedIcon == null) - initCheckboxIcons(context); - Row.Builder builder = new Row.Builder(); - builder.setTitle(context.getString(titleRes)); - builder.setOnClickListener(() -> { - if (RoutingOptions.hasOption(roadType)) - RoutingOptions.removeOption(roadType); - else - RoutingOptions.addOption(roadType); - context.getCarService(ScreenManager.class).getTop().invalidate(); - }); - builder.setImage(RoutingOptions.hasOption(roadType) ? mCheckboxSelectedIcon : mCheckboxIcon); - return builder.build(); - } - - private static void initCheckboxIcons(@NonNull CarContext context) - { - mCheckboxIcon = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_check_box)).build(); - mCheckboxSelectedIcon = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_check_box_checked)).build(); + final Action panAction = new Action.Builder(Action.PAN).build(); + final Action location = new Action.Builder().setIcon(iconLocation).setOnClickListener( + () -> CarToast.makeText(context, "Location", CarToast.LENGTH_LONG).show() + ).build(); + final Action zoomIn = new Action.Builder().setIcon(iconPlus).setOnClickListener(surfaceRenderer::onZoomIn).build(); + final Action zoomOut = new Action.Builder().setIcon(iconMinus).setOnClickListener(surfaceRenderer::onZoomOut).build(); + final ActionStrip mapActionStrip = new ActionStrip.Builder() + .addAction(location) + .addAction(zoomIn) + .addAction(zoomOut) + .addAction(panAction) + .build(); + return new MapController.Builder().setMapActionStrip(mapActionStrip).build(); } } diff --git a/android/src/app/organicmaps/car/screens/BookmarksScreen.java b/android/src/app/organicmaps/car/screens/BookmarksScreen.java index 349f41b3ad..42cb9dee3b 100644 --- a/android/src/app/organicmaps/car/screens/BookmarksScreen.java +++ b/android/src/app/organicmaps/car/screens/BookmarksScreen.java @@ -19,7 +19,8 @@ import app.organicmaps.R; import app.organicmaps.bookmarks.data.BookmarkCategory; import app.organicmaps.bookmarks.data.BookmarkInfo; import app.organicmaps.bookmarks.data.BookmarkManager; -import app.organicmaps.car.OMController; +import app.organicmaps.car.SurfaceRenderer; +import app.organicmaps.car.UiHelpers; import app.organicmaps.util.Graphics; import java.util.ArrayList; @@ -32,16 +33,16 @@ public class BookmarksScreen extends MapScreen @Nullable private BookmarkCategory mBookmarkCategory; - public BookmarksScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + public BookmarksScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) { - super(carContext, mapController); + super(carContext, surfaceRenderer); final ConstraintManager constraintManager = getCarContext().getCarService(ConstraintManager.class); MAX_CATEGORIES_SIZE = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); } - private BookmarksScreen(@NonNull CarContext carContext, @NonNull OMController mapController, @NonNull BookmarkCategory bookmarkCategory) + private BookmarksScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer, @NonNull BookmarkCategory bookmarkCategory) { - this(carContext, mapController); + this(carContext, surfaceRenderer); mBookmarkCategory = bookmarkCategory; } @@ -51,8 +52,8 @@ public class BookmarksScreen extends MapScreen { MapTemplate.Builder builder = new MapTemplate.Builder(); builder.setHeader(createHeader()); - builder.setMapController(getMapController()); - builder.setActionStrip(getActionStrip()); + builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer())); + builder.setActionStrip(UiHelpers.createSettingsActionStrip(getCarContext(), getSurfaceRenderer())); builder.setItemList(mBookmarkCategory == null ? createBookmarkCategoriesList() : createBookmarksList()); return builder.build(); } @@ -80,7 +81,7 @@ public class BookmarksScreen extends MapScreen Row.Builder itemBuilder = new Row.Builder(); itemBuilder.setTitle(bookmarkCategory.getName()); itemBuilder.addText(bookmarkCategory.getDescription()); - itemBuilder.setOnClickListener(() -> getScreenManager().push(new BookmarksScreen(getCarContext(), getOMController(), bookmarkCategory))); + itemBuilder.setOnClickListener(() -> getScreenManager().push(new BookmarksScreen(getCarContext(), getSurfaceRenderer(), bookmarkCategory))); itemBuilder.setBrowsable(true); builder.addItem(itemBuilder.build()); } @@ -99,7 +100,7 @@ public class BookmarksScreen extends MapScreen final long bookmarkId = BookmarkManager.INSTANCE.getBookmarkIdByPosition(bookmarkCategoryId, i); final BookmarkInfo bookmarkInfo = new BookmarkInfo(bookmarkCategoryId, bookmarkId); - Row.Builder itemBuilder = new Row.Builder(); + final Row.Builder itemBuilder = new Row.Builder(); itemBuilder.setTitle(bookmarkInfo.getName()); if (!bookmarkInfo.getAddress().isEmpty()) itemBuilder.addText(bookmarkInfo.getAddress()); @@ -119,9 +120,9 @@ public class BookmarksScreen extends MapScreen @NonNull private static List getBookmarks() { - List bookmarkCategories = new ArrayList<>(BookmarkManager.INSTANCE.getCategories()); + final List bookmarkCategories = new ArrayList<>(BookmarkManager.INSTANCE.getCategories()); - List toRemove = new ArrayList<>(); + final List toRemove = new ArrayList<>(); for (BookmarkCategory bookmarkCategory : bookmarkCategories) { if (bookmarkCategory.getBookmarksCount() == 0) diff --git a/android/src/app/organicmaps/car/screens/CategoriesScreen.java b/android/src/app/organicmaps/car/screens/CategoriesScreen.java index 4bd74ced23..4595273821 100644 --- a/android/src/app/organicmaps/car/screens/CategoriesScreen.java +++ b/android/src/app/organicmaps/car/screens/CategoriesScreen.java @@ -15,7 +15,8 @@ import androidx.car.app.navigation.model.MapTemplate; import androidx.core.graphics.drawable.IconCompat; import app.organicmaps.R; -import app.organicmaps.car.OMController; +import app.organicmaps.car.SurfaceRenderer; +import app.organicmaps.car.UiHelpers; import java.util.Arrays; import java.util.List; @@ -49,10 +50,11 @@ public class CategoriesScreen extends MapScreen private final int MAX_CATEGORIES_SIZE; - public CategoriesScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + public CategoriesScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) { - super(carContext, mapController); - MAX_CATEGORIES_SIZE = getCarContext().getCarService(ConstraintManager.class).getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); + super(carContext, surfaceRenderer); + final ConstraintManager constraintManager = getCarContext().getCarService(ConstraintManager.class); + MAX_CATEGORIES_SIZE = constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST); } @NonNull @@ -61,8 +63,8 @@ public class CategoriesScreen extends MapScreen { MapTemplate.Builder builder = new MapTemplate.Builder(); builder.setHeader(createHeader()); - builder.setMapController(getMapController()); - builder.setActionStrip(getActionStrip()); + builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer())); + builder.setActionStrip(UiHelpers.createSettingsActionStrip(getCarContext(), getSurfaceRenderer())); builder.setItemList(createCategoriesList()); return builder.build(); } diff --git a/android/src/app/organicmaps/car/screens/MapScreen.java b/android/src/app/organicmaps/car/screens/MapScreen.java index 5876ee897c..38ab3de45d 100644 --- a/android/src/app/organicmaps/car/screens/MapScreen.java +++ b/android/src/app/organicmaps/car/screens/MapScreen.java @@ -3,44 +3,23 @@ package app.organicmaps.car.screens; import androidx.annotation.NonNull; import androidx.car.app.CarContext; import androidx.car.app.Screen; -import androidx.car.app.model.ActionStrip; -import androidx.car.app.navigation.model.MapController; -import app.organicmaps.car.OMController; import app.organicmaps.car.SurfaceRenderer; public abstract class MapScreen extends Screen { @NonNull - private final OMController mMapController; + private final SurfaceRenderer mSurfaceRenderer; - public MapScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + public MapScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) { super(carContext); - mMapController = mapController; - } - - @NonNull - public OMController getOMController() - { - return mMapController; + mSurfaceRenderer = surfaceRenderer; } @NonNull public SurfaceRenderer getSurfaceRenderer() { - return mMapController.getSurfaceRenderer(); - } - - @NonNull - public MapController getMapController() - { - return mMapController.getMapController(); - } - - @NonNull - public ActionStrip getActionStrip() - { - return mMapController.getActionStrip(); + return mSurfaceRenderer; } } diff --git a/android/src/app/organicmaps/car/screens/NavigationScreen.java b/android/src/app/organicmaps/car/screens/NavigationScreen.java index 36e4225268..477113f8d0 100644 --- a/android/src/app/organicmaps/car/screens/NavigationScreen.java +++ b/android/src/app/organicmaps/car/screens/NavigationScreen.java @@ -13,13 +13,14 @@ import androidx.car.app.navigation.model.MapTemplate; import androidx.core.graphics.drawable.IconCompat; import app.organicmaps.R; -import app.organicmaps.car.OMController; +import app.organicmaps.car.SurfaceRenderer; +import app.organicmaps.car.UiHelpers; public class NavigationScreen extends MapScreen { - public NavigationScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + public NavigationScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) { - super(carContext, mapController); + super(carContext, surfaceRenderer); } @NonNull @@ -28,8 +29,8 @@ public class NavigationScreen extends MapScreen { MapTemplate.Builder builder = new MapTemplate.Builder(); builder.setHeader(createHeader()); - builder.setMapController(getMapController()); - builder.setActionStrip(getActionStrip()); + builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer())); + builder.setActionStrip(UiHelpers.createSettingsActionStrip(getCarContext(), getSurfaceRenderer())); builder.setItemList(createList()); return builder.build(); } @@ -93,11 +94,11 @@ public class NavigationScreen extends MapScreen private void openCategories() { - getScreenManager().push(new CategoriesScreen(getCarContext(), getOMController())); + getScreenManager().push(new CategoriesScreen(getCarContext(), getSurfaceRenderer())); } private void openBookmarks() { - getScreenManager().push(new BookmarksScreen(getCarContext(), getOMController())); + getScreenManager().push(new BookmarksScreen(getCarContext(), getSurfaceRenderer())); } } diff --git a/android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java b/android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java index dc35a0b886..3142500653 100644 --- a/android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java +++ b/android/src/app/organicmaps/car/screens/settings/DrivingOptionsScreen.java @@ -1,24 +1,36 @@ package app.organicmaps.car.screens.settings; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.car.app.CarContext; import androidx.car.app.model.Action; +import androidx.car.app.model.CarIcon; import androidx.car.app.model.Header; import androidx.car.app.model.ItemList; +import androidx.car.app.model.Row; import androidx.car.app.model.Template; import androidx.car.app.navigation.model.MapTemplate; +import androidx.core.graphics.drawable.IconCompat; import app.organicmaps.R; -import app.organicmaps.car.OMController; +import app.organicmaps.car.SurfaceRenderer; import app.organicmaps.car.UiHelpers; import app.organicmaps.car.screens.MapScreen; +import app.organicmaps.routing.RoutingOptions; import app.organicmaps.settings.RoadType; public class DrivingOptionsScreen extends MapScreen { - public DrivingOptionsScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + @NonNull + private final CarIcon mCheckboxIcon; + @NonNull + private final CarIcon mCheckboxSelectedIcon; + + public DrivingOptionsScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) { - super(carContext, mapController); + super(carContext, surfaceRenderer); + mCheckboxIcon = new CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_check_box)).build(); + mCheckboxSelectedIcon = new CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_check_box_checked)).build(); } @NonNull @@ -27,7 +39,7 @@ public class DrivingOptionsScreen extends MapScreen { MapTemplate.Builder builder = new MapTemplate.Builder(); builder.setHeader(createHeader()); - builder.setMapController(getMapController()); + builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer())); builder.setItemList(createDrivingOptionsList()); return builder.build(); } @@ -45,10 +57,26 @@ public class DrivingOptionsScreen extends MapScreen private ItemList createDrivingOptionsList() { ItemList.Builder builder = new ItemList.Builder(); - builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Toll, R.string.avoid_tolls)); - builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Dirty, R.string.avoid_unpaved)); - builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Ferry, R.string.avoid_ferry)); - builder.addItem(UiHelpers.createDrivingOptionCheckbox(getCarContext(), RoadType.Motorway, R.string.avoid_motorways)); + builder.addItem(createDrivingOptionCheckbox(RoadType.Toll, R.string.avoid_tolls)); + builder.addItem(createDrivingOptionCheckbox(RoadType.Dirty, R.string.avoid_unpaved)); + builder.addItem(createDrivingOptionCheckbox(RoadType.Ferry, R.string.avoid_ferry)); + builder.addItem(createDrivingOptionCheckbox(RoadType.Motorway, R.string.avoid_motorways)); + return builder.build(); + } + + @NonNull + private Row createDrivingOptionCheckbox(RoadType roadType, @StringRes int titleRes) + { + Row.Builder builder = new Row.Builder(); + builder.setTitle(getCarContext().getString(titleRes)); + builder.setOnClickListener(() -> { + if (RoutingOptions.hasOption(roadType)) + RoutingOptions.removeOption(roadType); + else + RoutingOptions.addOption(roadType); + DrivingOptionsScreen.this.invalidate(); + }); + builder.setImage(RoutingOptions.hasOption(roadType) ? mCheckboxSelectedIcon : mCheckboxIcon); return builder.build(); } } diff --git a/android/src/app/organicmaps/car/screens/settings/HelpScreen.java b/android/src/app/organicmaps/car/screens/settings/HelpScreen.java index 6a02678e04..aa1ffab2f9 100644 --- a/android/src/app/organicmaps/car/screens/settings/HelpScreen.java +++ b/android/src/app/organicmaps/car/screens/settings/HelpScreen.java @@ -13,15 +13,16 @@ import androidx.car.app.navigation.model.MapTemplate; import app.organicmaps.BuildConfig; import app.organicmaps.Framework; import app.organicmaps.R; -import app.organicmaps.car.OMController; +import app.organicmaps.car.SurfaceRenderer; +import app.organicmaps.car.UiHelpers; import app.organicmaps.car.screens.MapScreen; import app.organicmaps.util.DateUtils; public class HelpScreen extends MapScreen { - public HelpScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + public HelpScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) { - super(carContext, mapController); + super(carContext, surfaceRenderer); } @NonNull @@ -30,7 +31,7 @@ public class HelpScreen extends MapScreen { MapTemplate.Builder builder = new MapTemplate.Builder(); builder.setHeader(createHeader()); - builder.setMapController(getMapController()); + builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer())); builder.setItemList(createSettingsList()); return builder.build(); } diff --git a/android/src/app/organicmaps/car/screens/settings/SettingsScreen.java b/android/src/app/organicmaps/car/screens/settings/SettingsScreen.java index 7fad3c8d39..58acd4942f 100644 --- a/android/src/app/organicmaps/car/screens/settings/SettingsScreen.java +++ b/android/src/app/organicmaps/car/screens/settings/SettingsScreen.java @@ -1,27 +1,46 @@ package app.organicmaps.car.screens.settings; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.car.app.CarContext; import androidx.car.app.model.Action; +import androidx.car.app.model.CarIcon; import androidx.car.app.model.Header; import androidx.car.app.model.Item; import androidx.car.app.model.ItemList; import androidx.car.app.model.Row; import androidx.car.app.model.Template; import androidx.car.app.navigation.model.MapTemplate; +import androidx.core.graphics.drawable.IconCompat; import app.organicmaps.R; -import app.organicmaps.car.OMController; +import app.organicmaps.car.SurfaceRenderer; import app.organicmaps.car.UiHelpers; import app.organicmaps.car.screens.MapScreen; import app.organicmaps.util.Config; public class SettingsScreen extends MapScreen { - - public SettingsScreen(@NonNull CarContext carContext, @NonNull OMController mapController) + private interface PrefsGetter { - super(carContext, mapController); + boolean get(); + } + + private interface PrefsSetter + { + void set(boolean newValue); + } + + @NonNull + private final CarIcon mCheckboxIcon; + @NonNull + private final CarIcon mCheckboxSelectedIcon; + + public SettingsScreen(@NonNull CarContext carContext, @NonNull SurfaceRenderer surfaceRenderer) + { + super(carContext, surfaceRenderer); + mCheckboxIcon = new CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_check_box)).build(); + mCheckboxSelectedIcon = new CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_check_box_checked)).build(); } @NonNull @@ -30,7 +49,7 @@ public class SettingsScreen extends MapScreen { MapTemplate.Builder builder = new MapTemplate.Builder(); builder.setHeader(createHeader()); - builder.setMapController(getMapController()); + builder.setMapController(UiHelpers.createMapController(getCarContext(), getSurfaceRenderer())); builder.setItemList(createSettingsList()); return builder.build(); } @@ -49,8 +68,8 @@ public class SettingsScreen extends MapScreen { ItemList.Builder builder = new ItemList.Builder(); builder.addItem(createRoutingOptionsItem()); - builder.addItem(UiHelpers.createSharedPrefsCheckbox(getCarContext(), R.string.big_font, Config::isLargeFontsSize, Config::setLargeFontsSize)); - builder.addItem(UiHelpers.createSharedPrefsCheckbox(getCarContext(), R.string.transliteration_title, Config::isTransliteration, Config::setTransliteration)); + builder.addItem(createSharedPrefsCheckbox(R.string.big_font, Config::isLargeFontsSize, Config::setLargeFontsSize)); + builder.addItem(createSharedPrefsCheckbox(R.string.transliteration_title, Config::isTransliteration, Config::setTransliteration)); builder.addItem(createHelpItem()); return builder.build(); } @@ -60,7 +79,7 @@ public class SettingsScreen extends MapScreen { Row.Builder builder = new Row.Builder(); builder.setTitle(getCarContext().getString(R.string.driving_options_title)); - builder.setOnClickListener(() -> getScreenManager().push(new DrivingOptionsScreen(getCarContext(), getOMController()))); + builder.setOnClickListener(() -> getScreenManager().push(new DrivingOptionsScreen(getCarContext(), getSurfaceRenderer()))); builder.setBrowsable(true); return builder.build(); } @@ -70,8 +89,23 @@ public class SettingsScreen extends MapScreen { Row.Builder builder = new Row.Builder(); builder.setTitle(getCarContext().getString(R.string.help)); - builder.setOnClickListener(() -> getScreenManager().push(new HelpScreen(getCarContext(), getOMController()))); + builder.setOnClickListener(() -> getScreenManager().push(new HelpScreen(getCarContext(), getSurfaceRenderer()))); builder.setBrowsable(true); return builder.build(); } + + @NonNull + private Row createSharedPrefsCheckbox(@StringRes int titleRes, PrefsGetter getter, PrefsSetter setter) + { + final boolean getterValue = getter.get(); + + Row.Builder builder = new Row.Builder(); + builder.setTitle(getCarContext().getString(titleRes)); + builder.setOnClickListener(() -> { + setter.set(!getterValue); + SettingsScreen.this.invalidate(); + }); + builder.setImage(getterValue ? mCheckboxSelectedIcon : mCheckboxIcon); + return builder.build(); + } } -- 2.45.3 From 8fe646042249220e565cad1ecd9457f6efd2fe06 Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Sun, 11 Dec 2022 16:58:37 +0100 Subject: [PATCH 6/6] [android-auto] Remove car_hosts.xml and refactor createHostValidator function Signed-off-by: Andrew Shkrob --- android/res/values/car_hosts.xml | 11 ----------- .../car/NavigationCarAppService.java | 18 ++++++------------ 2 files changed, 6 insertions(+), 23 deletions(-) delete mode 100644 android/res/values/car_hosts.xml diff --git a/android/res/values/car_hosts.xml b/android/res/values/car_hosts.xml deleted file mode 100644 index cc47a82af6..0000000000 --- a/android/res/values/car_hosts.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - fdb00c43dbde8b51cb312aa81d3b5fa17713adb94b28f598d77f8eb89daceedf,com.google.android.projection.gearhead - 70811a3eacfd2e83e18da9bfede52df16ce91f2e69a44d21f18ab66991130771,com.google.android.projection.gearhead - 1975b2f17177bc89a5dff31f9e64a6cae281a53dc1d1d59b1d147fe1c82afa00,com.google.android.projection.gearhead - c241ffbc8e287c4e9a4ad19632ba1b1351ad361d5177b7d7b29859bd2b7fc631,com.google.android.apps.automotive.templates.host - dd66deaf312d8daec7adbe85a218ecc8c64f3b152f9b5998d5b29300c2623f61,com.google.android.apps.automotive.templates.host - 50e603d333c6049a37bd751375d08f3bd0abebd33facd30bd17b64b89658b421,com.google.android.apps.automotive.templates.host - - diff --git a/android/src/app/organicmaps/car/NavigationCarAppService.java b/android/src/app/organicmaps/car/NavigationCarAppService.java index 4e4c2593b6..3bcadafb78 100644 --- a/android/src/app/organicmaps/car/NavigationCarAppService.java +++ b/android/src/app/organicmaps/car/NavigationCarAppService.java @@ -1,13 +1,11 @@ package app.organicmaps.car; -import android.content.pm.ApplicationInfo; - import androidx.annotation.NonNull; import androidx.car.app.CarAppService; import androidx.car.app.Session; import androidx.car.app.validation.HostValidator; -import app.organicmaps.R; +import app.organicmaps.BuildConfig; public final class NavigationCarAppService extends CarAppService { @@ -15,16 +13,12 @@ public final class NavigationCarAppService extends CarAppService @Override public HostValidator createHostValidator() { - if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) - { + if (BuildConfig.DEBUG) return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR; - } - else - { - return new HostValidator.Builder(getApplicationContext()) - .addAllowedHosts(R.array.hosts_allowlist) - .build(); - } + + return new HostValidator.Builder(getApplicationContext()) + .addAllowedHosts(androidx.car.app.R.array.hosts_allowlist_sample) + .build(); } @NonNull -- 2.45.3