[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:
@@ -5,6 +5,14 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('/home/aldo270717/AndroidStudioProjects/AldoApps_KeystoreUpload.jks')
|
||||
storePassword 'p49Js5ewYPZbkvhj'
|
||||
keyAlias 'upload'
|
||||
keyPassword 'p49Js5ewYPZbkvhj'
|
||||
}
|
||||
}
|
||||
namespace 'com.aldo.apps.familyhelpers'
|
||||
compileSdk 34
|
||||
|
||||
|
||||
@@ -13,6 +13,30 @@
|
||||
}
|
||||
},
|
||||
"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_type": 3
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
android:name=".workers.ShareLocationBackgroundWorker"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
<service android:name=".auto.ShareLocationCarAppService"
|
||||
<service android:name=".auto.AndroidAutoCarAppService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.car.app.CarAppService"/>
|
||||
|
||||
@@ -2,11 +2,12 @@ package com.aldo.apps.familyhelpers;
|
||||
|
||||
import static android.Manifest.permission.CAMERA;
|
||||
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 android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
@@ -15,13 +16,13 @@ import android.widget.Toast;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.aldo.apps.familyhelpers.ui.HelperGroupTile;
|
||||
import com.aldo.apps.familyhelpers.ui.SleepTimerPopup;
|
||||
import com.aldo.apps.familyhelpers.utils.DevicePolicyManagerHelper;
|
||||
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
|
||||
import com.aldo.apps.familyhelpers.workers.LocationHelper;
|
||||
import com.firebase.ui.auth.AuthUI;
|
||||
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
|
||||
@@ -186,6 +187,7 @@ public class HelperGridActivity extends AppCompatActivity {
|
||||
*/
|
||||
private void initFlashlight() {
|
||||
mFlashlightTile = new HelperGroupTile(findViewById(R.id.tile_flashlight)) {
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
@Override
|
||||
public void launchHelper() {
|
||||
if (requestCameraPermission()) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import androidx.car.app.validation.HostValidator;
|
||||
/**
|
||||
* The {@link CarAppService} extension to be called upon connection.
|
||||
*/
|
||||
public class ShareLocationCarAppService extends CarAppService {
|
||||
public class AndroidAutoCarAppService extends CarAppService {
|
||||
|
||||
/**
|
||||
* Tag for debugging purposes.
|
||||
@@ -29,10 +29,10 @@ public class ShareLocationCarAppService extends CarAppService {
|
||||
public Session onCreateSession() {
|
||||
try {
|
||||
Log.d(TAG, "onCreateSession: ");
|
||||
return new ShareLocationSession();
|
||||
return new AndroidAutoSession();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onCreateSession: ", e);
|
||||
return new ShareLocationSession();
|
||||
return new AndroidAutoSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,9 @@ import androidx.car.app.Screen;
|
||||
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.
|
||||
@@ -21,6 +21,6 @@ public class ShareLocationSession extends Session {
|
||||
@Override
|
||||
public Screen onCreateScreen(@NonNull Intent intent) {
|
||||
Log.d(TAG, "onCreateScreen() called with: intent = [" + intent + "]");
|
||||
return new ShareLocationScreen(getCarContext());
|
||||
return new AndroidAutoLandingPage(getCarContext());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.util.Log;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -77,11 +77,6 @@ public final class GlobalConstants {
|
||||
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.
|
||||
|
||||
@@ -71,6 +71,11 @@ public class ShareLocationBackgroundWorker extends Service implements SharedPref
|
||||
*/
|
||||
private boolean mManualHighAccuracy;
|
||||
|
||||
/**
|
||||
* The {@link BatteryStateReceiver} to listen to whether battery is currently charged or not.
|
||||
*/
|
||||
private BatteryStateReceiver mBatteryStateReceiver;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
@@ -78,7 +83,8 @@ public class ShareLocationBackgroundWorker extends Service implements SharedPref
|
||||
final IntentFilter batteryFilter = new IntentFilter();
|
||||
batteryFilter.addAction(ACTION_POWER_CONNECTED);
|
||||
batteryFilter.addAction(ACTION_POWER_DISCONNECTED);
|
||||
registerReceiver(new BatteryStateReceiver(), batteryFilter);
|
||||
mBatteryStateReceiver = new BatteryStateReceiver();
|
||||
registerReceiver(mBatteryStateReceiver, batteryFilter);
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
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);
|
||||
@@ -114,6 +120,7 @@ public class ShareLocationBackgroundWorker extends Service implements SharedPref
|
||||
public void onDestroy() {
|
||||
mLocationHelper.stopLocationUpdates();
|
||||
mHandler.removeCallbacks(mShareLocationRunnable);
|
||||
unregisterReceiver(mBatteryStateReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
||||
8
app/src/main/res/drawable/ic_car_info.xml
Normal file
8
app/src/main/res/drawable/ic_car_info.xml
Normal 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>
|
||||
@@ -72,6 +72,10 @@
|
||||
<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_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>
|
||||
@@ -72,7 +72,11 @@
|
||||
<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_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>
|
||||
@@ -101,6 +101,10 @@
|
||||
<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_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>
|
||||
Reference in New Issue
Block a user