[AA] Refactored Android Auto Logic to be extendable

Refactored the AA logic so it is more easy to be extended
in future. Also changed from a standard Grid to a Tabbed grid
and prepared a UI for the Vehicle Information values
That will be added in the next commit.
This commit is contained in:
Alexander Dörflinger
2025-04-15 13:53:30 +02:00
parent 05151d49fa
commit a74c60a143
20 changed files with 634 additions and 200 deletions

View File

@@ -5,6 +5,14 @@ plugins {
} }
android { android {
signingConfigs {
debug {
storeFile file('/home/aldo270717/AndroidStudioProjects/AldoApps_KeystoreUpload.jks')
storePassword 'p49Js5ewYPZbkvhj'
keyAlias 'upload'
keyPassword 'p49Js5ewYPZbkvhj'
}
}
namespace 'com.aldo.apps.familyhelpers' namespace 'com.aldo.apps.familyhelpers'
compileSdk 34 compileSdk 34

View File

@@ -13,6 +13,30 @@
} }
}, },
"oauth_client": [ "oauth_client": [
{
"client_id": "1057049253579-2p4776d7ifs0calhbp7jmshm9ep894h3.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.aldo.apps.familyhelpers",
"certificate_hash": "c7f86af4a49e2ee9c0b76a272b95f98016f988fa"
}
},
{
"client_id": "1057049253579-8gn720md0fphvav1o8qrhrknrflcjnqt.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.aldo.apps.familyhelpers",
"certificate_hash": "1166e18cf665c3cae0c8ada885a5f0f48d95a9f1"
}
},
{
"client_id": "1057049253579-hsnjd0u6sm06giq53r4fs544jeegr9jm.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.aldo.apps.familyhelpers",
"certificate_hash": "daeaeec1a83c543fc69ac52152df6cf6f736eba3"
}
},
{ {
"client_id": "1057049253579-6de9kv08ne2ti29lpptlb10egfcn5s06.apps.googleusercontent.com", "client_id": "1057049253579-6de9kv08ne2ti29lpptlb10egfcn5s06.apps.googleusercontent.com",
"client_type": 3 "client_type": 3

View File

@@ -83,7 +83,7 @@
android:name=".workers.ShareLocationBackgroundWorker" android:name=".workers.ShareLocationBackgroundWorker"
android:foregroundServiceType="location" /> android:foregroundServiceType="location" />
<service android:name=".auto.ShareLocationCarAppService" <service android:name=".auto.AndroidAutoCarAppService"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="androidx.car.app.CarAppService"/> <action android:name="androidx.car.app.CarAppService"/>

View File

@@ -2,11 +2,12 @@ package com.aldo.apps.familyhelpers;
import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.POST_NOTIFICATIONS; import static android.Manifest.permission.POST_NOTIFICATIONS;
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.CAR_SPEED_PERMISSION; import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_SPEED_PERMISSION;
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SIGN_IN_PROVIDERS; import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SIGN_IN_PROVIDERS;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.widget.TextView; import android.widget.TextView;
@@ -15,13 +16,13 @@ import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.aldo.apps.familyhelpers.ui.HelperGroupTile; import com.aldo.apps.familyhelpers.ui.HelperGroupTile;
import com.aldo.apps.familyhelpers.ui.SleepTimerPopup; import com.aldo.apps.familyhelpers.ui.SleepTimerPopup;
import com.aldo.apps.familyhelpers.utils.DevicePolicyManagerHelper; import com.aldo.apps.familyhelpers.utils.DevicePolicyManagerHelper;
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
import com.aldo.apps.familyhelpers.workers.LocationHelper; import com.aldo.apps.familyhelpers.workers.LocationHelper;
import com.firebase.ui.auth.AuthUI; import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract; import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
@@ -186,6 +187,7 @@ public class HelperGridActivity extends AppCompatActivity {
*/ */
private void initFlashlight() { private void initFlashlight() {
mFlashlightTile = new HelperGroupTile(findViewById(R.id.tile_flashlight)) { mFlashlightTile = new HelperGroupTile(findViewById(R.id.tile_flashlight)) {
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
@Override @Override
public void launchHelper() { public void launchHelper() {
if (requestCameraPermission()) { if (requestCameraPermission()) {

View File

@@ -10,7 +10,7 @@ import androidx.car.app.validation.HostValidator;
/** /**
* The {@link CarAppService} extension to be called upon connection. * The {@link CarAppService} extension to be called upon connection.
*/ */
public class ShareLocationCarAppService extends CarAppService { public class AndroidAutoCarAppService extends CarAppService {
/** /**
* Tag for debugging purposes. * Tag for debugging purposes.
@@ -29,10 +29,10 @@ public class ShareLocationCarAppService extends CarAppService {
public Session onCreateSession() { public Session onCreateSession() {
try { try {
Log.d(TAG, "onCreateSession: "); Log.d(TAG, "onCreateSession: ");
return new ShareLocationSession(); return new AndroidAutoSession();
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "onCreateSession: ", e); Log.e(TAG, "onCreateSession: ", e);
return new ShareLocationSession(); return new AndroidAutoSession();
} }
} }

View File

@@ -0,0 +1,121 @@
package com.aldo.apps.familyhelpers.auto;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.TAB_KEY_CAR_INFO;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.TAB_KEY_LOCATION;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.Screen;
import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.model.Action;
import androidx.car.app.model.TabContents;
import androidx.car.app.model.TabTemplate;
import androidx.car.app.model.Template;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import com.aldo.apps.familyhelpers.auto.screencontent.CarInfoTab;
import com.aldo.apps.familyhelpers.auto.screencontent.IScreenContentChangedListener;
import com.aldo.apps.familyhelpers.auto.screencontent.ShareLocationTab;
/**
* The {@link AndroidAutoLandingPage} to be shown as a starting point for Android Auto.
*/
public class AndroidAutoLandingPage extends Screen
implements TabTemplate.TabCallback, IScreenContentChangedListener {
/**
* Tag for debugging purposes.
*/
private static final String TAG = "AndroidAutoLandingPage";
/**
* Helper instance for the {@link ShareLocationTab}.
*/
private final ShareLocationTab mShareLocationTab;
/**
* Helper instance for the {@link CarInfoTab}.
*/
private final CarInfoTab mCarInfoTab;
/**
* The ID of the currently selected Tab.
*/
private String mCurrentlySelectedTabId = TAB_KEY_LOCATION;
/**
* C'Tor.
*
* @param carContext The {@link CarContext} this was instantiated in.
*/
protected AndroidAutoLandingPage(@NonNull CarContext carContext) {
super(carContext);
Log.d(TAG, "AndroidAutoLandingPage() called with: carContext = [" + carContext + "]");
getLifecycle().addObserver(mLifecycleObserver);
mShareLocationTab = new ShareLocationTab(carContext, this);
mCarInfoTab = new CarInfoTab(carContext, this);
}
/**
* {@link LifecycleObserver} to stop listening to CarInformation updates when AA ends.
*/
private LifecycleObserver mLifecycleObserver = new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_STOP) {
Log.d(TAG, "onStateChanged: Activity stopped, unsubscribe");
}
}
};
@ExperimentalCarApi
@NonNull
@Override
public Template onGetTemplate() {
Log.d(TAG, "onGetTemplate() called");
return new TabTemplate.Builder(this)
.addTab(mShareLocationTab.getTabInformation())
.addTab(mCarInfoTab.getTabInformation())
.setTabContents(getCurrentlySelectedTabContents())
.setHeaderAction(Action.APP_ICON).setActiveTabContentId(mCurrentlySelectedTabId)
.build();
}
/**
* Helper method to get the currently selected tab and the corresponding {@link TabContents}.
*
* @return The {@link TabContents} of the currently selected tab.
*/
private TabContents getCurrentlySelectedTabContents() {
switch (mCurrentlySelectedTabId) {
case TAB_KEY_CAR_INFO:
return new TabContents.Builder(mCarInfoTab.getScreenTemplate()).build();
case TAB_KEY_LOCATION:
default:
return new TabContents.Builder(mShareLocationTab.getScreenTemplate()).build();
}
}
@Override
public void onTabSelected(@NonNull String tabContentId) {
TabTemplate.TabCallback.super.onTabSelected(tabContentId);
mCurrentlySelectedTabId = tabContentId;
invalidate();
}
@Override
public void onScreenContentChanged(final String contentId) {
if (TextUtils.equals(contentId, mCurrentlySelectedTabId)) {
Log.d(TAG, "onScreenContentChanged: Current screen [" + contentId + "] changed");
invalidate();
}
}
}

View File

@@ -8,9 +8,9 @@ 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}. * The {@link Session} to be started by the CarAppService to show the {@link AndroidAutoLandingPage}.
*/ */
public class ShareLocationSession extends Session { public class AndroidAutoSession extends Session {
/** /**
* Tag for debugging purposes. * Tag for debugging purposes.
@@ -21,6 +21,6 @@ public class ShareLocationSession extends Session {
@Override @Override
public Screen onCreateScreen(@NonNull Intent intent) { public Screen onCreateScreen(@NonNull Intent intent) {
Log.d(TAG, "onCreateScreen() called with: intent = [" + intent + "]"); Log.d(TAG, "onCreateScreen() called with: intent = [" + intent + "]");
return new ShareLocationScreen(getCarContext()); return new AndroidAutoLandingPage(getCarContext());
} }
} }

View File

@@ -1,180 +0,0 @@
package com.aldo.apps.familyhelpers.auto;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.GridItem;
import androidx.car.app.model.GridTemplate;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Template;
import androidx.core.graphics.drawable.IconCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import com.aldo.apps.familyhelpers.R;
import com.aldo.apps.familyhelpers.workers.LocationHelper;
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
/**
* {@link Screen} implementation to be shown in the Android Auto environment.
*/
public class ShareLocationScreen extends Screen {
/**
* Tag for debugging purpose.
*/
private static final String TAG = "ShareLocationScreen";
/**
* {@link LocationHelper} instance to observe the current sharing state.
*/
private LocationHelper mLocationHelper;
/**
* {@link VehicleInformationHelper} to read vehicle information in AA use case.
*/
private VehicleInformationHelper mVehicleInformationHelper;
/**
* {@link LifecycleObserver} to stop listening to Vehicle Speed updates when AA ends.
*/
private LifecycleObserver mLifecycleObserver = new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_STOP) {
Log.d(TAG, "onStateChanged: Activity stopped, unsubscribe");
mVehicleInformationHelper.unsubscribeFromSpeedValue();
}
}
};
/**
* C'Tor.
*
* @param carContext The {@link CarContext} this was instantiated in.
*/
protected ShareLocationScreen(@NonNull CarContext carContext) {
super(carContext);
Log.d(TAG, "ShareLocationScreen() called with: carContext = [" + carContext + "]");
mVehicleInformationHelper = new VehicleInformationHelper(carContext);
getLifecycle().addObserver(mLifecycleObserver);
}
@ExperimentalCarApi
@NonNull
@Override
public Template onGetTemplate() {
Log.d(TAG, "onGetTemplate() called");
mLocationHelper = LocationHelper.getInstance(getCarContext());
mVehicleInformationHelper.subscribeToSpeedValues(getCarContext());
final GridItem startItem = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_start))
.setText(getString(R.string.android_auto_share_location_start_desc))
.setImage(getIconForResource(R.drawable.ic_start_sharing), GridItem.IMAGE_TYPE_ICON)
.setOnClickListener(this::startLocationShareService)
.build();
final GridItem stopItem = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_stop))
.setText(getString(R.string.android_auto_share_location_stop_desc))
.setImage(getIconForResource(R.drawable.ic_stop_sharing), GridItem.IMAGE_TYPE_ICON)
.setOnClickListener(this::stopLocationSharingService)
.build();
final GridItem startNavigation = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_navigation_title))
.setText(getString(R.string.android_auto_share_location_naviagation_summary))
.setImage(getIconForResource(R.drawable.ic_navigation), GridItem.IMAGE_TYPE_ICON)
.setOnClickListener(this::startNavigationScreen)
.build();
final ItemList itemList = new ItemList.Builder()
.setNoItemsMessage(getString(R.string.android_auto_share_location_no_items_yet))
.addItem(startItem)
.addItem(stopItem)
.addItem(startNavigation)
.build();
return new GridTemplate.Builder()
.setLoading(false)
.setTitle(getString(R.string.android_auto_share_location_start_desc))
.setSingleList(itemList)
.build();
}
/**
* Helper method to get an Icon from Resources and tint it into the proper color.
*
* @param iconId The DrawableId of the icon.
*
* @return The CarIcon.
*/
private CarIcon getIconForResource(@DrawableRes final int iconId) {
final IconCompat iconCompat = IconCompat.createWithResource(getCarContext(), iconId);
iconCompat.setTint(getCarContext().getColor(R.color.md_theme_primary));
return new CarIcon.Builder(iconCompat).build();
}
/**
* Helper method to start the location sharing by starting the {@link ShareLocationBackgroundWorker}.
*/
private void startLocationShareService() {
Log.d(TAG, "startLocationShareService: ");
if (mLocationHelper != null && mLocationHelper.isCurrentlySharing()) {
CarToast.makeText(getCarContext(), R.string.android_auto_share_location_already_sharing,
CarToast.LENGTH_SHORT).show();
return;
}
// Create an intent to start the ShareLocationBackgroundWorker service
final Intent serviceIntent = new Intent(getCarContext(), ShareLocationBackgroundWorker.class);
// Start the service using the car context
getCarContext().startForegroundService(serviceIntent);
}
/**
* Helper method to stop the location sharing by stopping the {@link ShareLocationBackgroundWorker}.
*/
private void stopLocationSharingService() {
Log.d(TAG, "stopLocationSharingService: ");
if (mLocationHelper != null && !mLocationHelper.isCurrentlySharing()) {
CarToast.makeText(getCarContext(), R.string.android_auto_share_location_not_yet_sharing,
CarToast.LENGTH_SHORT).show();
return;
}
// Create an intent to start the ShareLocationBackgroundWorker service
final Intent serviceIntent = new Intent(getCarContext(), ShareLocationBackgroundWorker.class);
// Start the service using the car context
getCarContext().stopService(serviceIntent);
}
/**
* Helper method to start The Navigation screen.
*/
private void startNavigationScreen() {
CarToast.makeText(getCarContext(), "Not yet implemented", CarToast.LENGTH_LONG).show();
}
/**
* Helper method to extract a string resource.
*
* @param stringId The String resource ID.
*
* @return The unwrapped string.
*/
private String getString(@StringRes final int stringId) {
return getCarContext().getString(stringId);
}
}

View File

@@ -1,6 +1,6 @@
package com.aldo.apps.familyhelpers.auto; package com.aldo.apps.familyhelpers.auto.model;
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.CAR_SPEED_PERMISSION; import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_SPEED_PERMISSION;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.util.Log; import android.util.Log;

View File

@@ -0,0 +1,116 @@
package com.aldo.apps.familyhelpers.auto.screencontent;
import android.util.Log;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import androidx.car.app.CarContext;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.Tab;
import androidx.car.app.model.Template;
import androidx.core.graphics.drawable.IconCompat;
import com.aldo.apps.familyhelpers.R;
import java.lang.ref.WeakReference;
/**
* Base implementation for a TabContent class.
* Will be filled by explicit implementations.
*/
public abstract class AbstractTabContent {
/**
* Tag for debugging purpose.
*/
private static final String TAG = "ShareLocationTab";
/**
* The {@link WeakReference} to the calling {@link CarContext}.
*/
private final WeakReference<CarContext> mContextRef;
/**
* The {@link IScreenContentChangedListener} to be invoked if the page content changed and a
* refresh is needed.
*/
protected final IScreenContentChangedListener mScreenContentChangedListener;
/**
* C'Tor.
*
* @param carContext Calling {@link CarContext}.
* @param listener The {@link IScreenContentChangedListener} to be invoked if page content changed.
*/
protected AbstractTabContent(final CarContext carContext,
final IScreenContentChangedListener listener) {
mContextRef = new WeakReference<>(carContext);
mScreenContentChangedListener = listener;
}
/**
* Returns the {@link CarContext} if available, null otherwise.
*
* @return The {@link CarContext} if available, null otherwise.
*/
public CarContext getCarContext() {
final CarContext carContext = mContextRef.get();
if (carContext == null) {
Log.w(TAG, "getCarContext: CarContext is null, cannot continue.");
return null;
} return carContext;
}
/**
* Returns the {@link Template} to be shown in the screen.
*
* @return The {@link Template} to be shown in the screen.
*/
abstract Template getScreenTemplate();
/**
* Returns the {@link Tab} info for the tab to be shown.
*
* @return The {@link Tab} info for the tab to be shown.
*/
abstract Tab getTabInformation();
/**
* Helper method to refresh the template view.
*/
abstract void refreshTemplate();
/**
* Helper method to extract a string resource.
*
* @param stringId The String resource ID.
*
* @return The unwrapped string.
*/
protected String getString(@StringRes final int stringId) {
final CarContext carContext = getCarContext();
if (carContext == null) {
Log.w(TAG, "getString: CarContext is null, cannot continue.");
return "";
}
return carContext.getString(stringId);
}
/**
* Helper method to get an Icon from Resources and tint it into the proper color.
*
* @param iconId The DrawableId of the icon.
*
* @return The CarIcon.
*/
protected CarIcon getIconForResource(@DrawableRes final int iconId) {
final CarContext carContext = getCarContext();
if (carContext == null) {
Log.w(TAG, "getIconForResource: CarContext is null, cannot continue.");
return CarIcon.ERROR;
}
final IconCompat iconCompat = IconCompat.createWithResource(carContext, iconId);
iconCompat.setTint(carContext.getColor(R.color.md_theme_primary));
return new CarIcon.Builder(iconCompat).build();
}
}

View File

@@ -0,0 +1,83 @@
package com.aldo.apps.familyhelpers.auto.screencontent;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.TAB_KEY_CAR_INFO;
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row;
import androidx.car.app.model.Tab;
import androidx.car.app.model.Template;
import com.aldo.apps.familyhelpers.R;
import com.aldo.apps.familyhelpers.auto.model.VehicleInformationHelper;
public class CarInfoTab extends AbstractTabContent {
/**
* Tag for debugging purpose.
*/
private static final String TAG = "CarInfoTab";
/**
* {@link VehicleInformationHelper} to read vehicle information in AA use case.
*/
private final VehicleInformationHelper mVehicleInformationHelper;
/**
* The {@link Template} for the car info tab.
*/
private Template mCarInfoTab;
/**
* C'Tor.
*
* @param carContext Calling {@link CarContext}.
* @param listener The {@link IScreenContentChangedListener} to be invoked if page content changed.
*/
public CarInfoTab(final CarContext carContext, final IScreenContentChangedListener listener) {
super(carContext, listener);
mVehicleInformationHelper = new VehicleInformationHelper(carContext);
refreshTemplate();
}
@Override
public Template getScreenTemplate() {
return mCarInfoTab;
}
@Override
public Tab getTabInformation() {
return new Tab.Builder()
.setTitle(getString(R.string.android_auto_tab_name_car_info))
.setIcon(getIconForResource(R.drawable.ic_car_info))
.setContentId(TAB_KEY_CAR_INFO)
.build();
}
@Override
public void refreshTemplate() {
final Row vehicleInfo = new Row.Builder()
.setTitle(getString(R.string.android_auto_vehicle_info_title))
.setImage(getIconForResource(R.drawable.ic_car_info), Row.IMAGE_TYPE_ICON)
.setOnClickListener(this::startCarInfoScreen)
.build();
final ItemList carList = new ItemList.Builder()
.setNoItemsMessage(getString(R.string.android_auto_share_location_no_items_yet))
.addItem(vehicleInfo)
.build();
mCarInfoTab = new ListTemplate.Builder()
.setSingleList(carList)
.build();
}
/**
* Helper method to be invoked when a list item is clicked.
*/
private void startCarInfoScreen() {
CarToast.makeText(getCarContext(), "Not yet implemented", CarToast.LENGTH_LONG).show();
}
}

View File

@@ -0,0 +1,15 @@
package com.aldo.apps.familyhelpers.auto.screencontent;
/**
* Listener interface to be invoked if the AndroidAuto Screen content changed.
*/
public interface IScreenContentChangedListener {
/**
* Callback to be invoked if the page needs to be refreshed.
*
* @param contentId The ID of the shown content, used to only refresh the UI if content is
* actually shown
*/
void onScreenContentChanged(String contentId);
}

View File

@@ -0,0 +1,201 @@
package com.aldo.apps.familyhelpers.auto.screencontent;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.TAB_KEY_LOCATION;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.util.Log;
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.model.GridItem;
import androidx.car.app.model.GridTemplate;
import androidx.car.app.model.ItemList;
import androidx.car.app.model.Tab;
import androidx.car.app.model.Template;
import com.aldo.apps.familyhelpers.R;
import com.aldo.apps.familyhelpers.workers.LocationHelper;
import com.aldo.apps.familyhelpers.workers.ShareLocationBackgroundWorker;
/**
* ScreenContent helper class for the ShareLocation Tab.
*/
public class ShareLocationTab extends AbstractTabContent {
/**
* Tag for debugging purpose.
*/
private static final String TAG = "ShareLocationTab";
/**
* {@link LocationHelper} instance to observe the current sharing state.
*/
private final LocationHelper mLocationHelper;
/**
* The {@link Template} for the share location tab.
*/
private Template mShareLocationTab;
/**
* Flag indicating whether currently location sharing is ongoing. Used to react to only updates
* where the state changed.
*/
private boolean mIsCurrentlySharing;
/**
* C'Tor.
* Suppressing checking result warning as not needed in this case.
*
* @param carContext The calling {@link CarContext}.
* @param listener The {@link IScreenContentChangedListener} to be invoked if the screen changed.
*/
@SuppressLint("CheckResult")
public ShareLocationTab(final CarContext carContext, final IScreenContentChangedListener listener) {
super(carContext, listener);
mLocationHelper = LocationHelper.getInstance(carContext);
mLocationHelper.getSharingStateSubject()
.subscribe(this::handleReceivedSharingState, this::handleError);
refreshTemplate();
}
@Override
public Template getScreenTemplate() {
return mShareLocationTab;
}
@Override
public Tab getTabInformation() {
return new Tab.Builder()
.setTitle(getString(R.string.android_auto_tab_name_location))
.setIcon(getIconForResource(R.drawable.ic_location_helper))
.setContentId(TAB_KEY_LOCATION)
.build();
}
/**
* Helper method to create and populate the {@link ItemList} to be shown in the layout.
*
* @return The {@link ItemList} to be shown in the layout.
*/
private ItemList createAndPopulateItemList() {
final GridItem startStopItem;
if (mIsCurrentlySharing) {
startStopItem = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_stop))
.setText(getString(R.string.android_auto_share_location_stop_desc))
.setImage(getIconForResource(R.drawable.ic_stop_sharing), GridItem.IMAGE_TYPE_ICON)
.setOnClickListener(this::stopLocationSharingService)
.build();
} else {
startStopItem = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_start))
.setText(getString(R.string.android_auto_share_location_start_desc))
.setImage(getIconForResource(R.drawable.ic_start_sharing), GridItem.IMAGE_TYPE_ICON)
.setOnClickListener(this::startLocationShareService)
.build();
}
final GridItem startNavigation = new GridItem.Builder()
.setTitle(getString(R.string.android_auto_share_location_navigation_title))
.setText(getString(R.string.android_auto_share_location_navigation_summary))
.setImage(getIconForResource(R.drawable.ic_navigation), GridItem.IMAGE_TYPE_ICON)
.setOnClickListener(this::startNavigationScreen)
.build();
return new ItemList.Builder()
.setNoItemsMessage(getString(R.string.android_auto_share_location_no_items_yet))
.addItem(startStopItem)
.addItem(startNavigation)
.build();
}
/**
* Helper method to start the location sharing by starting the {@link ShareLocationBackgroundWorker}.
*/
private void startLocationShareService() {
final CarContext carContext = getCarContext();
if (carContext == null) {
Log.w(TAG, "startLocationShareService: CarContext is null, cannot continue.");
return;
}
Log.d(TAG, "startLocationShareService: ");
if (mIsCurrentlySharing) {
CarToast.makeText(carContext, R.string.android_auto_share_location_already_sharing,
CarToast.LENGTH_SHORT).show();
return;
}
// Create an intent to start the ShareLocationBackgroundWorker service
final Intent serviceIntent = new Intent(carContext, ShareLocationBackgroundWorker.class);
// Start the service using the car context
carContext.startForegroundService(serviceIntent);
}
/**
* Helper method to stop the location sharing by stopping the {@link ShareLocationBackgroundWorker}.
*/
private void stopLocationSharingService() {
final CarContext carContext = getCarContext();
if (carContext == null) {
Log.w(TAG, "stopLocationSharingService: CarContext is null, cannot continue.");
return;
}
Log.d(TAG, "stopLocationSharingService: ");
if (!mIsCurrentlySharing) {
CarToast.makeText(carContext, R.string.android_auto_share_location_not_yet_sharing,
CarToast.LENGTH_SHORT).show();
return;
}
// Create an intent to start the ShareLocationBackgroundWorker service
final Intent serviceIntent = new Intent(carContext, ShareLocationBackgroundWorker.class);
// Start the service using the car context
carContext.stopService(serviceIntent);
}
/**
* Helper method to start The Navigation screen.
*/
private void startNavigationScreen() {
final CarContext carContext = getCarContext();
if (carContext == null) {
Log.w(TAG, "startLocationShareService: CarContext is null, cannot continue.");
return;
}
CarToast.makeText(carContext, "Not yet implemented", CarToast.LENGTH_LONG).show();
}
@Override
public void refreshTemplate() {
mShareLocationTab = new GridTemplate.Builder()
.setLoading(false)
.setTitle(getString(R.string.android_auto_share_location_start_desc))
.setSingleList(createAndPopulateItemList())
.build();
}
/**
* Handle an updated sharing state.
*
* @param isSharing True if currently sharing, false or null otherwise.
*/
private void handleReceivedSharingState(final Boolean isSharing) {
if (mIsCurrentlySharing == isSharing) {
Log.d(TAG, "handleReceivedSharingState: Sharing state did not change, ignore.");
return;
}
mIsCurrentlySharing = isSharing;
Log.d(TAG, "handleReceivedSharingState: SharingState changed to [" + isSharing + "]");
refreshTemplate();
mScreenContentChangedListener.onScreenContentChanged(TAB_KEY_LOCATION);
}
/**
* Handle a potential error during sharingState subscription.
*
* @param throwable The error that ocured.
*/
private void handleError(final Throwable throwable) {
Log.e(TAG, "handleError: Error retrieving sharing state", throwable);
}
}

View File

@@ -0,0 +1,22 @@
package com.aldo.apps.familyhelpers.auto.utils;
/**
* Constants class for Android Auto.
*/
public class AndroidAutoConstants {
/**
* The key for the Location tab.
*/
public static final String TAB_KEY_LOCATION = "AA_Doerflinger_Location";
/**
* The key for the CarInfo tab.
*/
public static final String TAB_KEY_CAR_INFO = "AA_Doerflinger_CarInfo";
/**
* Name of the permission for the VehicleSpeed.
*/
public static final String CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED";
}

View File

@@ -77,11 +77,6 @@ public final class GlobalConstants {
new AuthUI.IdpConfig.GoogleBuilder().build() new AuthUI.IdpConfig.GoogleBuilder().build()
); );
/**
* Name of the permission for the VehicleSpeed.
*/
public static final String CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED";
/** /**
* Private C'tor to prevent instantiation. * Private C'tor to prevent instantiation.

View File

@@ -71,6 +71,11 @@ public class ShareLocationBackgroundWorker extends Service implements SharedPref
*/ */
private boolean mManualHighAccuracy; private boolean mManualHighAccuracy;
/**
* The {@link BatteryStateReceiver} to listen to whether battery is currently charged or not.
*/
private BatteryStateReceiver mBatteryStateReceiver;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@@ -78,7 +83,8 @@ public class ShareLocationBackgroundWorker extends Service implements SharedPref
final IntentFilter batteryFilter = new IntentFilter(); final IntentFilter batteryFilter = new IntentFilter();
batteryFilter.addAction(ACTION_POWER_CONNECTED); batteryFilter.addAction(ACTION_POWER_CONNECTED);
batteryFilter.addAction(ACTION_POWER_DISCONNECTED); batteryFilter.addAction(ACTION_POWER_DISCONNECTED);
registerReceiver(new BatteryStateReceiver(), batteryFilter); mBatteryStateReceiver = new BatteryStateReceiver();
registerReceiver(mBatteryStateReceiver, batteryFilter);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
mSelectedFrequency = preferences.getInt(getString(R.string.pref_key_share_location_freq), DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS); mSelectedFrequency = preferences.getInt(getString(R.string.pref_key_share_location_freq), DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS);
mAutoPriority = preferences.getBoolean(getString(R.string.pref_key_share_location_auto_acc), true); mAutoPriority = preferences.getBoolean(getString(R.string.pref_key_share_location_auto_acc), true);
@@ -114,6 +120,7 @@ public class ShareLocationBackgroundWorker extends Service implements SharedPref
public void onDestroy() { public void onDestroy() {
mLocationHelper.stopLocationUpdates(); mLocationHelper.stopLocationUpdates();
mHandler.removeCallbacks(mShareLocationRunnable); mHandler.removeCallbacks(mShareLocationRunnable);
unregisterReceiver(mBatteryStateReceiver);
super.onDestroy(); super.onDestroy();
} }

View File

@@ -0,0 +1,8 @@
<vector android:height="200dp" android:viewportHeight="512"
android:viewportWidth="512" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000000" android:pathData="M147.4,277.97c-24.67,0 -44.67,19.98 -44.67,44.67c0.02,24.67 19.98,44.64 44.67,44.67c24.69,-0.03 44.66,-20 44.69,-44.67c0.02,-12.33 -5.02,-23.55 -13.09,-31.61C170.95,282.97 159.7,277.95 147.4,277.97zM147.4,346.58c-13.23,-0.03 -23.92,-10.73 -23.95,-23.94c0.03,-13.27 10.72,-23.92 23.95,-23.95c13.25,0 23.95,10.69 23.95,23.95C171.34,335.84 160.64,346.55 147.4,346.58z"/>
<path android:fillColor="#000000" android:pathData="M388.78,277.97c-24.69,0 -44.7,19.97 -44.7,44.67c0.02,24.67 20,44.64 44.7,44.67c24.67,-0.03 44.64,-20 44.66,-44.67C433.43,297.95 413.43,277.97 388.78,277.97zM388.78,346.58c-13.23,-0.03 -23.95,-10.75 -23.97,-23.94c0.02,-13.25 10.73,-23.92 23.97,-23.95c13.23,0.03 23.92,10.69 23.94,23.95C412.7,335.84 401.99,346.55 388.78,346.58z"/>
<path android:fillColor="#000000" android:pathData="M147.4,313.84c-4.84,0 -8.78,3.94 -8.78,8.8c0,4.84 3.94,8.8 8.78,8.8s8.78,-3.95 8.78,-8.8C156.18,317.78 152.24,313.84 147.4,313.84z"/>
<path android:fillColor="#000000" android:pathData="M388.76,313.84c-4.84,0 -8.78,3.94 -8.78,8.8c0,4.84 3.94,8.8 8.78,8.8c4.83,0 8.77,-3.95 8.77,-8.8C397.53,317.78 393.59,313.84 388.76,313.84z"/>
<path android:fillColor="#000000" android:pathData="M508.12,223.53c-3.13,-3.72 -7.77,-5.91 -12.66,-5.91h-70.98l-35.95,-62.56c-3.7,-6.41 -10.52,-10.38 -17.92,-10.38H195.01c-6.36,0 -12.36,2.94 -16.28,7.94l-44.63,57.06c-3.92,5.02 -9.94,7.94 -16.28,7.94H16.54c-4.86,0 -9.45,2.16 -12.61,5.84c-3.13,3.69 -4.5,8.58 -3.72,13.36l17.17,66.88c2.33,9.17 10.59,15.56 20.06,15.55l55.28,-0.13c1.84,-28.69 25.52,-51.53 54.67,-51.53c14.73,0 28.55,5.73 38.94,16.13c9.5,9.47 14.84,21.88 15.73,35.17l132.06,-0.34c2.14,-28.41 25.69,-50.95 54.64,-50.95c28.86,0 52.33,22.41 54.58,50.72l41.11,-0.09c10.55,-0.02 19.36,-7.97 20.52,-18.44l6.77,-62.75C512.6,232.22 511.28,227.28 508.12,223.53zM283.03,221.02H154.18l41.84,-53.52h87V221.02zM310.21,221.02V167.5h59.16l30.77,53.52H310.21z"/>
</vector>

View File

@@ -72,6 +72,10 @@
<string name="android_auto_share_location_already_sharing">Du teilst deinen Standort schon</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> <string name="android_auto_share_location_not_yet_sharing">Du teilst deinen Standort noch nicht</string>
<string name="android_auto_share_location_navigation_title">Navigation</string> <string name="android_auto_share_location_navigation_title">Navigation</string>
<string name="android_auto_share_location_naviagation_summary">Teilen und navigieren</string> <string name="android_auto_share_location_navigation_summary">Teilen und navigieren</string>
<string name="android_auto_vehicle_info_title">Fahrzeug</string>
<string name="android_auto_vehicle_info_summary">Informationenb über das Fahrzeug</string>
<string name="android_auto_tab_name_location">Standort</string>
<string name="android_auto_tab_name_car_info">Fahrzeug</string>
</resources> </resources>

View File

@@ -72,7 +72,11 @@
<string name="android_auto_share_location_already_sharing">You already share your location</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> <string name="android_auto_share_location_not_yet_sharing">You are not yet sharing your location</string>
<string name="android_auto_share_location_navigation_title">Navigate</string> <string name="android_auto_share_location_navigation_title">Navigate</string>
<string name="android_auto_share_location_naviagation_summary">Share and navigate</string> <string name="android_auto_share_location_navigation_summary">Share and navigate</string>
<string name="android_auto_vehicle_info_title">Car Info</string>
<string name="android_auto_vehicle_info_summary">Information about the car</string>
<string name="android_auto_tab_name_location">Location</string>
<string name="android_auto_tab_name_car_info">CarInfo</string>
</resources> </resources>

View File

@@ -101,6 +101,10 @@
<string name="android_auto_share_location_already_sharing">You already share your location</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> <string name="android_auto_share_location_not_yet_sharing">You are not yet sharing your location</string>
<string name="android_auto_share_location_navigation_title">Navigate</string> <string name="android_auto_share_location_navigation_title">Navigate</string>
<string name="android_auto_share_location_naviagation_summary">Share and navigate</string> <string name="android_auto_share_location_navigation_summary">Share and navigate</string>
<string name="android_auto_vehicle_info_title">Car Info</string>
<string name="android_auto_vehicle_info_summary">Information about the car</string>
<string name="android_auto_tab_name_location">Location</string>
<string name="android_auto_tab_name_car_info">CarInfo</string>
</resources> </resources>