diff --git a/app/build.gradle b/app/build.gradle index e0323fd..94bd6b8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/google-services.json b/app/google-services.json index 3d1bff3..ebfcab8 100644 --- a/app/google-services.json +++ b/app/google-services.json @@ -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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 295628d..16d15d1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -83,7 +83,7 @@ android:name=".workers.ShareLocationBackgroundWorker" android:foregroundServiceType="location" /> - diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java b/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java index 1b917c9..a50a439 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java @@ -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()) { diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationCarAppService.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoCarAppService.java similarity index 84% rename from app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationCarAppService.java rename to app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoCarAppService.java index ef0690e..9512453 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationCarAppService.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoCarAppService.java @@ -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(); } } diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoLandingPage.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoLandingPage.java new file mode 100644 index 0000000..3872760 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoLandingPage.java @@ -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(); + } + } +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationSession.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoSession.java similarity index 79% rename from app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationSession.java rename to app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoSession.java index f1d4af6..309a9d0 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationSession.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/AndroidAutoSession.java @@ -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()); } } diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationScreen.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationScreen.java deleted file mode 100644 index 5f5e4c7..0000000 --- a/app/src/main/java/com/aldo/apps/familyhelpers/auto/ShareLocationScreen.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/VehicleInformationHelper.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/VehicleInformationHelper.java similarity index 94% rename from app/src/main/java/com/aldo/apps/familyhelpers/auto/VehicleInformationHelper.java rename to app/src/main/java/com/aldo/apps/familyhelpers/auto/model/VehicleInformationHelper.java index 8fded6f..ef3f13e 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/auto/VehicleInformationHelper.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/VehicleInformationHelper.java @@ -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; diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/AbstractTabContent.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/AbstractTabContent.java new file mode 100644 index 0000000..a7e95fa --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/AbstractTabContent.java @@ -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 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(); + } +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/CarInfoTab.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/CarInfoTab.java new file mode 100644 index 0000000..fdf951a --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/CarInfoTab.java @@ -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(); + } +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/IScreenContentChangedListener.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/IScreenContentChangedListener.java new file mode 100644 index 0000000..8cf1403 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/IScreenContentChangedListener.java @@ -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); +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/ShareLocationTab.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/ShareLocationTab.java new file mode 100644 index 0000000..c475e7b --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/screencontent/ShareLocationTab.java @@ -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); + } +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/utils/AndroidAutoConstants.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/utils/AndroidAutoConstants.java new file mode 100644 index 0000000..d996c7d --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/utils/AndroidAutoConstants.java @@ -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"; +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java b/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java index 820d7f1..0175c90 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java @@ -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. diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/workers/ShareLocationBackgroundWorker.java b/app/src/main/java/com/aldo/apps/familyhelpers/workers/ShareLocationBackgroundWorker.java index 58e4986..0d16363 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/workers/ShareLocationBackgroundWorker.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/workers/ShareLocationBackgroundWorker.java @@ -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(); } diff --git a/app/src/main/res/drawable/ic_car_info.xml b/app/src/main/res/drawable/ic_car_info.xml new file mode 100644 index 0000000..40169e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_car_info.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 36e69e6..86e7e35 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -72,6 +72,10 @@ Du teilst deinen Standort schon Du teilst deinen Standort noch nicht Navigation - Teilen und navigieren + Teilen und navigieren + Fahrzeug + Informationenb über das Fahrzeug + Standort + Fahrzeug \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 156a860..a5de2ae 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -72,7 +72,11 @@ You already share your location You are not yet sharing your location Navigate - Share and navigate + Share and navigate + Car Info + Information about the car + Location + CarInfo \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0366348..0d62cc5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,6 +101,10 @@ You already share your location You are not yet sharing your location Navigate - Share and navigate + Share and navigate + Car Info + Information about the car + Location + CarInfo \ No newline at end of file