[AA] Working GridView for Android Auto

With this commit we have the first functional version
of the AndroidAuto integration.
This commit is contained in:
Alexander Dörflinger
2025-04-09 12:57:06 +02:00
parent fdb2273af1
commit 93cae8d160
9 changed files with 111 additions and 81 deletions

View File

@@ -15,6 +15,12 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-sdk android:minSdkVersion="32"
android:targetSdkVersion="34" />
<uses-feature
android:name="android.software.car.templates_host"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -31,6 +37,9 @@
android:value="AIzaSyB7C4QCJEBvS7mFa_DeIZdzqe2hddtl-vk" /> android:value="AIzaSyB7C4QCJEBvS7mFa_DeIZdzqe2hddtl-vk" />
<meta-data android:name="com.google.android.gms.car.application" <meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/> android:resource="@xml/automotive_app_desc"/>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1" />
<activity <activity
android:name=".FlashlightActivity" android:name=".FlashlightActivity"
@@ -51,7 +60,6 @@
tools:ignore="LockedOrientationActivity"> tools:ignore="LockedOrientationActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
@@ -71,9 +79,8 @@
<service android:name=".auto.ShareLocationCarAppService" <service android:name=".auto.ShareLocationCarAppService"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="androidx.car.app.action.SERVICE" />
<action android:name="androidx.car.app.CarAppService"/> <action android:name="androidx.car.app.CarAppService"/>
<category android:name="androidx.car.app.category.MESSAGING"/> <category android:name="androidx.intent.category.IOT" />
</intent-filter> </intent-filter>
</service> </service>

View File

@@ -476,6 +476,10 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
@Override @Override
public void onMapReady(@NonNull final GoogleMap googleMap) { public void onMapReady(@NonNull final GoogleMap googleMap) {
if (googleMap == null) {
Log.w(TAG, "onMapReady: Map null, cannot continue");
return;
}
mGmap = googleMap; mGmap = googleMap;
if (mIsNightMode) { if (mIsNightMode) {
mGmap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_style_night)); mGmap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_style_night));

View File

@@ -7,8 +7,16 @@ import androidx.car.app.CarAppService;
import androidx.car.app.Session; import androidx.car.app.Session;
import androidx.car.app.validation.HostValidator; import androidx.car.app.validation.HostValidator;
/**
* The {@link CarAppService} extension to be called upon connection.
*/
public class ShareLocationCarAppService extends CarAppService { public class ShareLocationCarAppService extends CarAppService {
/**
* Tag for debugging purposes.
*/
private static final String TAG = "ShareLocationCarAppServ"; private static final String TAG = "ShareLocationCarAppServ";
@NonNull @NonNull
@Override @Override
public HostValidator createHostValidator() { public HostValidator createHostValidator() {
@@ -19,7 +27,13 @@ public class ShareLocationCarAppService extends CarAppService {
@NonNull @NonNull
@Override @Override
public Session onCreateSession() { public Session onCreateSession() {
try {
Log.d(TAG, "onCreateSession: "); Log.d(TAG, "onCreateSession: ");
return new ShareLocationSession(); return new ShareLocationSession();
} catch (Exception e) {
Log.e(TAG, "onCreateSession: ", e);
return new ShareLocationSession();
} }
} }
}

View File

@@ -1,10 +1,5 @@
package com.aldo.apps.familyhelpers.auto; package com.aldo.apps.familyhelpers.auto;
import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT;
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SHARE_LOCATION_CANCEL_ACTION;
import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
@@ -17,136 +12,127 @@ import androidx.car.app.Screen;
import androidx.car.app.annotations.ExperimentalCarApi; import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.model.CarIcon; import androidx.car.app.model.CarIcon;
import androidx.car.app.model.GridItem; import androidx.car.app.model.GridItem;
import androidx.car.app.model.GridTemplate;
import androidx.car.app.model.ItemList; import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Template; import androidx.car.app.model.Template;
import androidx.car.app.notification.CarAppExtender;
import androidx.car.app.notification.CarNotificationManager;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.graphics.drawable.IconCompat; import androidx.core.graphics.drawable.IconCompat;
import com.aldo.apps.familyhelpers.R; import com.aldo.apps.familyhelpers.R;
import com.aldo.apps.familyhelpers.workers.LocationHelper; import com.aldo.apps.familyhelpers.workers.LocationHelper;
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker; import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
/**
* {@link Screen} implementation to be shown in the Android Auto environment.
*/
public class ShareLocationScreen extends Screen { public class ShareLocationScreen extends Screen {
private static final String AA_NOTIFICATION_CHANNEL = "ShareLocation_AA"; /**
* Tag for debugging purpose.
private static final int AA_NOTIFICATION_ID = 1; */
private static final String TAG = "ShareLocationScreen"; private static final String TAG = "ShareLocationScreen";
/**
* {@link LocationHelper} instance to observe the current sharing state.
*/
private LocationHelper mLocationHelper; private LocationHelper mLocationHelper;
// Constructor for ShareLocationScreen /**
* C'Tor.
*
* @param carContext The {@link CarContext} this was instantiated in.
*/
protected ShareLocationScreen(@NonNull CarContext carContext) { protected ShareLocationScreen(@NonNull CarContext carContext) {
super(carContext); super(carContext);
Log.d(TAG, "ShareLocationScreen() called with: carContext = [" + carContext + "]"); Log.d(TAG, "ShareLocationScreen() called with: carContext = [" + carContext + "]");
mLocationHelper = LocationHelper.getInstance(getCarContext());
} }
@ExperimentalCarApi @ExperimentalCarApi
@NonNull @NonNull
@Override @Override
public Template onGetTemplate() { public Template onGetTemplate() {
Log.d(TAG, "onGetTemplate() called"); Log.d(TAG, "onGetTemplate() called");
mLocationHelper = LocationHelper.getInstance(getCarContext());
final GridItem startItem = new GridItem.Builder() final GridItem startItem = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_start)) .setTitle(getString(R.string.android_auto_share_location_start))
.setText(getString(R.string.android_auto_share_location_start_desc)) .setText(getString(R.string.android_auto_share_location_start_desc))
.setImage(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_start_sharing)) .setImage(getIconForResource(R.drawable.ic_start_sharing), GridItem.IMAGE_TYPE_ICON)
.build(), GridItem.IMAGE_TYPE_LARGE)
.setOnClickListener(this::startLocationShareService) .setOnClickListener(this::startLocationShareService)
.build(); .build();
final GridItem stopItem = new GridItem.Builder() final GridItem stopItem = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_stop)) .setTitle(getString(R.string.android_auto_share_location_stop))
.setText(getString(R.string.android_auto_share_location_stop_desc)) .setText(getString(R.string.android_auto_share_location_stop_desc))
.setImage(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_stop_sharing)) .setImage(getIconForResource(R.drawable.ic_stop_sharing), GridItem.IMAGE_TYPE_ICON)
.build(), GridItem.IMAGE_TYPE_LARGE)
.setOnClickListener(this::stopLocationSharingService) .setOnClickListener(this::stopLocationSharingService)
.build(); .build();
final ItemList itemList = new ItemList.Builder() final ItemList itemList = new ItemList.Builder()
.setNoItemsMessage("No Items yet, something went wrong I guess") .setNoItemsMessage(getString(R.string.android_auto_share_location_no_items_yet))
.addItem(startItem) .addItem(startItem)
.addItem(stopItem) .addItem(stopItem)
.build(); .build();
mLocationHelper.getSharingStateSubject().subscribe(this::createNotification, this::handleError); return new GridTemplate.Builder()
return new ListTemplate.Builder()
.setLoading(false) .setLoading(false)
.setTitle("Share Location") .setTitle(getString(R.string.android_auto_share_location_start_desc))
.setSingleList(itemList) .setSingleList(itemList)
.build(); .build();
} }
private void createNotification(final Boolean isSharing) { /**
Log.d(TAG, "createNotification() called with: isSharing = [" + isSharing + "]"); * Helper method to get an Icon from Resources and tint it into the proper color.
CarNotificationManager notificationManager = CarNotificationManager.from(getCarContext()); *
notificationManager.createNotificationChannel( * @param iconId The DrawableId of the icon.
new NotificationChannelCompat.Builder(AA_NOTIFICATION_CHANNEL, IMPORTANCE_DEFAULT) *
.build()); * @return The CarIcon.
final PendingIntent serviceIntent; */
@DrawableRes final int iconId; private CarIcon getIconForResource(@DrawableRes final int iconId) {
final String title; final IconCompat iconCompat = IconCompat.createWithResource(getCarContext(), iconId);
if (isSharing) { iconCompat.setTint(getCarContext().getColor(R.color.md_theme_primary));
// Create an intent to start the ShareLocationBackgroundWorker service return new CarIcon.Builder(iconCompat).build();
final Intent stopService = new Intent(getCarContext(), ShareLocationBackgroundWorker.class);
stopService.setAction(SHARE_LOCATION_CANCEL_ACTION);
serviceIntent = PendingIntent.getService(getCarContext(), 0, stopService, PendingIntent.FLAG_IMMUTABLE);
title = getString(R.string.android_auto_share_location_stop);
iconId = R.drawable.ic_stop_sharing;
} else {
// Create an intent to start the ShareLocationBackgroundWorker service
final Intent startServiceIntent = new Intent(getCarContext(), ShareLocationBackgroundWorker.class);
serviceIntent = PendingIntent.getService(getCarContext(), 0, startServiceIntent, PendingIntent.FLAG_IMMUTABLE);
title = getString(R.string.android_auto_share_location_start);
iconId = R.drawable.ic_start_sharing;
} }
final NotificationCompat.Builder carNotification = new NotificationCompat.Builder(getCarContext(), AA_NOTIFICATION_CHANNEL) /**
.setContentTitle("Share Location") * Helper method to start the location sharing by starting the {@link ShareLocationBackgroundWorker}.
.addAction(iconId, title, serviceIntent) */
.extend(new CarAppExtender.Builder()
.build());
notificationManager.notify(AA_NOTIFICATION_ID, carNotification);
}
private void startLocationShareService() { private void startLocationShareService() {
Log.d(TAG, "startLocationShareService: "); Log.d(TAG, "startLocationShareService: ");
if (mLocationHelper != null && mLocationHelper.isCurrentlySharing()) { if (mLocationHelper != null && mLocationHelper.isCurrentlySharing()) {
CarToast.makeText(getCarContext(), "You already are sharing", CarToast.LENGTH_SHORT).show(); CarToast.makeText(getCarContext(), R.string.android_auto_share_location_already_sharing,
CarToast.LENGTH_SHORT).show();
return; return;
} }
// Create an intent to start the ShareLocationBackgroundWorker service // Create an intent to start the ShareLocationBackgroundWorker service
final Intent startServiceIntent = new Intent(getCarContext(), ShareLocationBackgroundWorker.class); final Intent serviceIntent = new Intent(getCarContext(), ShareLocationBackgroundWorker.class);
// Start the service using the car context // Start the service using the car context
getCarContext().startForegroundService(startServiceIntent); getCarContext().startForegroundService(serviceIntent);
} }
/**
* Helper method to stop the location sharing by stopping the {@link ShareLocationBackgroundWorker}.
*/
private void stopLocationSharingService() { private void stopLocationSharingService() {
Log.d(TAG, "stopLocationSharingService: "); Log.d(TAG, "stopLocationSharingService: ");
if (mLocationHelper != null && !mLocationHelper.isCurrentlySharing()) { if (mLocationHelper != null && !mLocationHelper.isCurrentlySharing()) {
CarToast.makeText(getCarContext(), "You are not sharing", CarToast.LENGTH_SHORT).show(); CarToast.makeText(getCarContext(), R.string.android_auto_share_location_not_yet_sharing,
CarToast.LENGTH_SHORT).show();
return; return;
} }
// Create an intent to start the ShareLocationBackgroundWorker service // Create an intent to start the ShareLocationBackgroundWorker service
final Intent startServiceIntent = new Intent(getCarContext(), ShareLocationBackgroundWorker.class); final Intent serviceIntent = new Intent(getCarContext(), ShareLocationBackgroundWorker.class);
// Start the service using the car context // Start the service using the car context
getCarContext().stopService(startServiceIntent); getCarContext().stopService(serviceIntent);
} }
/**
* Helper method to extract a string resource.
*
* @param stringId The String resource ID.
*
* @return The unwrapped string.
*/
private String getString(@StringRes final int stringId) { private String getString(@StringRes final int stringId) {
return getCarContext().getString(stringId); return getCarContext().getString(stringId);
} }
private void handleError(final Throwable error) {
Log.e(TAG, "handleErrot: ", error);
}
} }

View File

@@ -7,7 +7,14 @@ import androidx.annotation.NonNull;
import androidx.car.app.Screen; import androidx.car.app.Screen;
import androidx.car.app.Session; import androidx.car.app.Session;
/**
* The {@link Session} to be started by the CarAppService to show the {@link ShareLocationScreen}.
*/
public class ShareLocationSession extends Session { public class ShareLocationSession extends Session {
/**
* Tag for debugging purposes.
*/
private static final String TAG = "ShareLocationSession"; private static final String TAG = "ShareLocationSession";
@NonNull @NonNull

View File

@@ -63,9 +63,13 @@
<string name="android_auto_actions_start_sharing">Ja, fang an zu teilen</string> <string name="android_auto_actions_start_sharing">Ja, fang an zu teilen</string>
<string name="android_auto_actions_stop_sharing">Ja, hör auf zu teilen</string> <string name="android_auto_actions_stop_sharing">Ja, hör auf zu teilen</string>
<string name="android_auto_share_location_start">Start</string> <string name="android_auto_share_location_start">Start</string>
<string name="android_auto_share_location_start_desc">Anfangen den Standort zu teilen</string> <string name="android_auto_share_location_start_desc">Standrot teilen</string>
<string name="android_auto_share_location_stop">Stop</string> <string name="android_auto_share_location_stop">Stop</string>
<string name="android_auto_share_location_stop_desc">Aufhören den Standort zu teilen</string> <string name="android_auto_share_location_stop_desc">Nicht mehr teilen</string>
<string name="pref_on_screen_flashlight_custom_color_summary">Wähle eine eigene Farbe für deine Bildschirm-Taschenlampe</string> <string name="pref_on_screen_flashlight_custom_color_summary">Wähle eine eigene Farbe für deine Bildschirm-Taschenlampe</string>
<string name="flashlight_on_screen_custom_color">Eigene Farbe</string> <string name="flashlight_on_screen_custom_color">Eigene Farbe</string>
<string name="android_auto_share_location_no_items_yet">Noch keine Einträge, seltsam...</string>
<string name="android_auto_share_location_already_sharing">Du teilst deinen Standort schon</string>
<string name="android_auto_share_location_not_yet_sharing">Du teilst deinen Standort noch nicht</string>
</resources> </resources>

View File

@@ -62,10 +62,15 @@
<string name="android_auto_message_not_sharing">Android Auto start has been detected, do you wanna share your location?</string> <string name="android_auto_message_not_sharing">Android Auto start has been detected, do you wanna share your location?</string>
<string name="android_auto_actions_start_sharing">Yes, start to share</string> <string name="android_auto_actions_start_sharing">Yes, start to share</string>
<string name="android_auto_actions_stop_sharing">Yes, stop sharing</string> <string name="android_auto_actions_stop_sharing">Yes, stop sharing</string>
<string name="android_auto_share_location_start">Start</string> <string name="android_auto_share_location_start_desc">Share Location</string>
<string name="android_auto_share_location_start_desc">Start to share location</string>
<string name="android_auto_share_location_stop">Stop</string> <string name="android_auto_share_location_stop">Stop</string>
<string name="android_auto_share_location_stop_desc">Stop to share the location</string> <string name="android_auto_share_location_stop_desc">Stop Sharing</string>
<string name="android_auto_share_location_start">Start</string>
<string name="pref_on_screen_flashlight_custom_color_summary">Choose an own color to be displayed in the on-screen flashlight.</string> <string name="pref_on_screen_flashlight_custom_color_summary">Choose an own color to be displayed in the on-screen flashlight.</string>
<string name="flashlight_on_screen_custom_color">Custom Color</string> <string name="flashlight_on_screen_custom_color">Custom Color</string>
<string name="android_auto_share_location_no_items_yet">No Items yet, something went wrong</string>
<string name="android_auto_share_location_already_sharing">You already share your location</string>
<string name="android_auto_share_location_not_yet_sharing">You are not yet sharing your location</string>
</resources> </resources>

View File

@@ -94,8 +94,11 @@
<string name="android_auto_actions_start_sharing">Yes, start to share</string> <string name="android_auto_actions_start_sharing">Yes, start to share</string>
<string name="android_auto_actions_stop_sharing">Yes, stop sharing</string> <string name="android_auto_actions_stop_sharing">Yes, stop sharing</string>
<string name="android_auto_share_location_start">Start</string> <string name="android_auto_share_location_start">Start</string>
<string name="android_auto_share_location_start_desc">Start to share location</string> <string name="android_auto_share_location_start_desc">Share Location</string>
<string name="android_auto_share_location_stop">Stop</string> <string name="android_auto_share_location_stop">Stop</string>
<string name="android_auto_share_location_stop_desc">Stop to share the location</string> <string name="android_auto_share_location_stop_desc">Stop Sharing</string>
<string name="android_auto_share_location_no_items_yet">No Items yet, something went wrong</string>
<string name="android_auto_share_location_already_sharing">You already share your location</string>
<string name="android_auto_share_location_not_yet_sharing">You are not yet sharing your location</string>
</resources> </resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<automotiveApp> <automotiveApp>
<uses name="notification" /> <uses name="template"/>
</automotiveApp> </automotiveApp>