diff --git a/app/build.gradle b/app/build.gradle
index 94bd6b8..ae04c4e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,14 +5,6 @@ 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
@@ -29,9 +21,9 @@ android {
applicationId "com.aldo.apps.familyhelpers"
minSdk 32
targetSdk 34
- versionCode 8
- versionName "0.2.3"
android.buildFeatures.buildConfig true
+ versionCode 12
+ versionName "0.2.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -40,6 +32,7 @@ android {
signingConfig signingConfigs.myCustomKeystore
buildConfigField "String", "GOOGLE_MAPS_API_KEY", "\"${googleMapsApiKeyRelease}\""
manifestPlaceholders.google_maps_api_key = googleMapsApiKeyRelease
+ minifyEnabled false
}
release {
signingConfig signingConfigs.myCustomKeystore
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 16d15d1..1f07624 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,8 @@
+
+
@@ -46,7 +48,7 @@
android:resource="@xml/automotive_app_desc"/>
+ android:value="3" />
mContextRef;
+
+ /**
+ * The {@link AbstractCarInfo} implementation for the {@link Speed} value.
+ */
+ private final AbstractCarInfo mSpeedValue = new CarInfoSpeed();
+
+ /**
+ * The {@link AbstractCarInfo} implementation for the {@link TollCard} value.
+ */
+ private final AbstractCarInfo mTollCardValue = new CarInfoTollCard();
+
+ /**
+ * The {@link AbstractCarInfo} implementation for the {@link EnergyLevel} value.
+ */
+ private final AbstractCarInfo mEnergyLevel = new CarInfoEnergyLevel();
+
+ /**
+ * The {@link AbstractCarInfo} implementation for the {@link EvStatus} value.
+ */
+ private final AbstractCarInfo mEvStatus = new CarInfoEvStatus();
+
+ /**
+ * The {@link AbstractCarInfo} implementation for the {@link Mileage} value.
+ */
+ private final AbstractCarInfo mMileage = new CarInfoMileage();
+
+ /**
+ * The {@link AbstractCarInfo} implementation for the {@link Model} value.
+ */
+ private final AbstractCarInfo mModel = new CarInfoModel();
+
+ /**
+ * The {@link AbstractCarInfo} implementation for the {@link EnergyProfile} value.
+ */
+ private final AbstractCarInfo mEnergyProfile = new CarInfoEnergyProfile();
+
+ private final CarInfoCallbackRepository mCallbacks;
+
+ /**
+ * The {@link OnCarDataAvailableListener} to be invoked when the {@link Speed} value changed
+ */
+ private final OnCarDataAvailableListener mSpeedListener = this::handleReceivedSpeed;
+
+ /**
+ * The {@link OnCarDataAvailableListener} to be invoked when the {@link EnergyProfile} value changed
+ */
+ private final OnCarDataAvailableListener mEnergyProfileListener = this::handleEnergyProfile;
+
+ /**
+ * The {@link OnCarDataAvailableListener} to be invoked when the {@link Model} value changed
+ */
+ private final OnCarDataAvailableListener mModelListener = this::handleModel;
+
+ /**
+ * The {@link OnCarDataAvailableListener} to be invoked when the {@link EnergyLevel} value changed
+ */
+ private final OnCarDataAvailableListener mEnergyLevelListener = this::handleReceivedEnergyLevel;
+
+ /**
+ * The {@link OnCarDataAvailableListener} to be invoked when the {@link EvStatus} value changed
+ */
+ private final OnCarDataAvailableListener mEvStatusListener = this::handleReceivedEvStatus;
+
+ /**
+ * The {@link OnCarDataAvailableListener} to be invoked when the {@link Mileage} value changed
+ */
+ private final OnCarDataAvailableListener mMileageListener = this::handleReceivedMileage;
+
+ /**
+ * The {@link OnCarDataAvailableListener} to be invoked when the {@link TollCard} value changed
+ */
+ private final OnCarDataAvailableListener mTollCardListener = this::handleReceivedTollInformation;
+
/**
* C'Tor.
*
* @param carContext The {@link CarContext} from which this was instantiated.
*/
- public VehicleInformationHelper(final CarContext carContext) {
+ public VehicleInformationHelper(final CarContext carContext, final CarInfoCallbackRepository callbacks) {
+ mContextRef = new WeakReference<>(carContext);
+ mCallbacks = callbacks;
if (carContext.checkCallingOrSelfPermission(CAR_SPEED_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "VehicleInformationHelper: Nothing to do, permission not granted");
return;
@@ -47,36 +163,199 @@ public class VehicleInformationHelper {
}
/**
- * Subscribe to the Vehicle Speed and handle it.
- *
- * @param carContext The {@link CarContext} from which this was called.
+ * Helper method to subscribe to all available {@link CarInfo} values.
+ * @param carContext
*/
- public void subscribeToSpeedValues(final CarContext carContext) {
- if (carContext.checkCallingOrSelfPermission(CAR_SPEED_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
- mCarInfo = carContext.getCarService(CarHardwareManager.class).getCarInfo();
- if (mCarInfo == null) {
- Log.e(TAG, "subscribeToSpeedValues: No car info available");
- return;
- }
- mCarInfo.addSpeedListener(carContext.getMainExecutor(), this::handleReceivedSpeed);
- } else {
- Log.w(TAG, "subscribeToSpeedValues: No Speed permission granted, cannot subscribe");
+ @OptIn(markerClass = ExperimentalCarApi.class)
+ public void subscribeToAllCarInfo(final CarContext carContext) {
+ mCarInfo = carContext.getCarService(CarHardwareManager.class).getCarInfo();
+ if (mCarInfo == null) {
+ Log.d(TAG, "subscribeToAllCarInfo: No CarInfo available, return...");
+ return;
}
+ mCarInfo.addTollListener(carContext.getMainExecutor(), mTollCardListener);
+ if (carContext.checkCallingOrSelfPermission(CAR_SPEED_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
+ mCarInfo.addSpeedListener(carContext.getMainExecutor(), mSpeedListener);
+ } else {
+ Log.d(TAG, "subscribeToAllCarInfo: No speed updates as permission not granted");
+ }
+ if (carContext.checkCallingOrSelfPermission(CAR_FUEL_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
+ mCarInfo.addEnergyLevelListener(carContext.getMainExecutor(), mEnergyLevelListener);
+ } else {
+ Log.d(TAG, "subscribeToAllCarInfo: No fuel updates as permission is not granted.");
+ }
+ mCarInfo.addEvStatusListener(carContext.getMainExecutor(), mEvStatusListener);
+ if (carContext.checkCallingOrSelfPermission(CAR_MILEAGE_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
+ mCarInfo.addMileageListener(carContext.getMainExecutor(), mMileageListener);
+ } else {
+ Log.d(TAG, "subscribeToAllCarInfo: No mileage updates as permission is not granted");
+ }
+ mCarInfo.fetchModel(carContext.getMainExecutor(), mModelListener);
+ mCarInfo.fetchEnergyProfile(carContext.getMainExecutor(), mEnergyProfileListener);
}
/**
- * When AA ends, unsubscribe from the speed value again.
+ * Helper method to unsubscribe from all {@link CarInfo} values, when activity stops.
*/
- public void unsubscribeFromSpeedValue() {
- mCarInfo.removeSpeedListener(this::handleReceivedSpeed);
+ @ExperimentalCarApi
+ public void unsubscribeFromAllCarInfo() {
+ Log.d(TAG, "unsubscribeFromAllCarInfo: Unsubscribing");
+ mCarInfo.removeTollListener(mTollCardListener);
+ mCarInfo.removeSpeedListener(mSpeedListener);
+ mCarInfo.removeEnergyLevelListener(mEnergyLevelListener);
+ mCarInfo.removeMileageListener(mMileageListener);
+ mCarInfo.removeEvStatusListener(mEvStatusListener);
}
/**
- * Handle the received speed value by publishing it to the {@link LocationHelper}.
+ * Handle the received {@link Speed} value and publish it to the {@link LocationHelper}.
*
- * @param data The {@link Speed} data.
+ * @param speed The {@link Speed} data.
*/
- private void handleReceivedSpeed(@NonNull final Speed data) {
- mLocationHelper.updateVehicleSpeed(data.getDisplaySpeedMetersPerSecond());
+ private void handleReceivedSpeed(@NonNull final Speed speed) {
+ Log.d(TAG, "handleReceivedSpeed() called with: speed = [" + speed + "]");
+ final CarContext carContext = mContextRef.get();
+ if (carContext == null) {
+ Log.w(TAG, "handleReceivedSpeed: CarContext died, cannot continue.");
+ return;
+ }
+ if (mSpeedValue.hasValueChanged(speed, DISPLAY_SPEED)) {
+ mCallbacks.onMessageChanged(DISPLAY_SPEED, mSpeedValue.getMessageForValue(carContext, speed, DISPLAY_SPEED));
+ mLocationHelper.updateVehicleSpeed(speed.getDisplaySpeedMetersPerSecond());
+ }
+ if (mSpeedValue.hasValueChanged(speed, RAW_SPEED)) {
+ mCallbacks.onMessageChanged(RAW_SPEED, mSpeedValue.getMessageForValue(carContext, speed, RAW_SPEED));
+ }
+ mSpeedValue.setValue(speed);
+ }
+
+ /**
+ * Handle the received {@link TollCard} value.
+ *
+ * @param tollCard The {@link TollCard} data.
+ */
+ private void handleReceivedTollInformation(@NonNull final TollCard tollCard) {
+ Log.d(TAG, "handleReceivedTollInformation() called with: tollCard = [" + tollCard + "]");
+ final CarContext carContext = mContextRef.get();
+ if (carContext == null) {
+ Log.w(TAG, "handleReceivedTollInformation: CarContext died, cannot continue.");
+ return;
+ }
+ if (mTollCardValue.hasValueChanged(tollCard, TOLL_CARD)) {
+ mCallbacks.onMessageChanged(TOLL_CARD, mTollCardValue.getMessageForValue(carContext, tollCard, TOLL_CARD));
+ }
+ mTollCardValue.setValue(tollCard);
+ }
+
+ /**
+ * Handle the received {@link EnergyLevel} value.
+ *
+ * @param energyLevel The {@link EnergyLevel} data.
+ */
+ @OptIn(markerClass = ExperimentalCarApi.class)
+ private void handleReceivedEnergyLevel(@NonNull final EnergyLevel energyLevel) {
+ Log.d(TAG, "handleReceivedEnergyLevel() called with: energyLevel = [" + energyLevel + "]");
+ final CarContext carContext = mContextRef.get();
+ if (carContext == null) {
+ Log.w(TAG, "handleReceivedEnergyLevel: CarContext died, cannot continue.");
+ return;
+ }
+ if (mEnergyLevel.hasValueChanged(energyLevel, BATTERY_PERCENTAGE)) {
+ mCallbacks.onMessageChanged(BATTERY_PERCENTAGE, mEnergyLevel.getMessageForValue(carContext, energyLevel, BATTERY_PERCENTAGE));
+ }
+ if (mEnergyLevel.hasValueChanged(energyLevel, FUEL_PERCENTAGE)) {
+ mCallbacks.onMessageChanged(FUEL_PERCENTAGE, mEnergyLevel.getMessageForValue(carContext, energyLevel, FUEL_PERCENTAGE));
+ }
+ if (mEnergyLevel.hasValueChanged(energyLevel, LOW_ENERGY)) {
+ mCallbacks.onMessageChanged(LOW_ENERGY, mEnergyLevel.getMessageForValue(carContext, energyLevel, LOW_ENERGY));
+ }
+ if (mEnergyLevel.hasValueChanged(energyLevel, RANGE_REMAINING)) {
+ mCallbacks.onMessageChanged(RANGE_REMAINING, mEnergyLevel.getMessageForValue(carContext, energyLevel, RANGE_REMAINING));
+ }
+ mEnergyLevel.setValue(energyLevel);
+ }
+
+ /**
+ * Handle the received {@link EvStatus} value.
+ *
+ * @param evStatus The {@link EvStatus} data.
+ */
+ private void handleReceivedEvStatus(@NonNull final EvStatus evStatus) {
+ Log.d(TAG, "handleReceivedEvStatus() called with: evStatus = [" + evStatus + "]");
+ final CarContext carContext = mContextRef.get();
+ if (carContext == null) {
+ Log.w(TAG, "handleReceivedEvStatus: CarContext died, cannot continue.");
+ return;
+ }
+ if (mEvStatus.hasValueChanged(evStatus, EV_CONNECTOR_OPEN)) {
+ mCallbacks.onMessageChanged(EV_CONNECTOR_OPEN, mEvStatus.getMessageForValue(carContext, evStatus, EV_CONNECTOR_OPEN));
+ }
+ if (mEvStatus.hasValueChanged(evStatus, EV_CONNECTOR_STATE)) {
+ mCallbacks.onMessageChanged(EV_CONNECTOR_STATE, mEvStatus.getMessageForValue(carContext, evStatus, EV_CONNECTOR_STATE));
+ }
+ mEvStatus.setValue(evStatus);
+ }
+
+ /**
+ * Handle the received {@link Mileage} value.
+ *
+ * @param mileage The {@link Mileage} data.
+ */
+ private void handleReceivedMileage(@NonNull Mileage mileage) {
+ Log.d(TAG, "handleReceivedMileage() called with: mileage = [" + mileage + "]");
+ final CarContext carContext = mContextRef.get();
+ if (carContext == null) {
+ Log.w(TAG, "handleReceivedMileage: CarContext died, cannot continue.");
+ return;
+ }
+ if (mMileage.hasValueChanged(mileage, MILEAGE)) {
+ mCallbacks.onMessageChanged(MILEAGE, mMileage.getMessageForValue(carContext, mileage, MILEAGE));
+ }
+ mMileage.setValue(mileage);
+ }
+
+ /**
+ * Handle the received {@link Model} value.
+ *
+ * @param model The {@link Model} data.
+ */
+ private void handleModel(@NonNull Model model) {
+ Log.d(TAG, "handleModel() called with: model = [" + model + "]");
+ final CarContext carContext = mContextRef.get();
+ if (carContext == null) {
+ Log.w(TAG, "handleModel: CarContext died, cannot continue.");
+ return;
+ }
+ if (mModel.hasValueChanged(model, MANUFACTURER)) {
+ mCallbacks.onMessageChanged(MANUFACTURER, mModel.getMessageForValue(carContext, model, MANUFACTURER));
+ }
+ if (mModel.hasValueChanged(model, NAME)) {
+ mCallbacks.onMessageChanged(NAME, mModel.getMessageForValue(carContext, model, NAME));
+ }
+ if (mModel.hasValueChanged(model, YEAR)) {
+ mCallbacks.onMessageChanged(YEAR, mModel.getMessageForValue(carContext, model, YEAR));
+ }
+ mModel.setValue(model);
+ }
+
+ /**
+ * Handle the received {@link EnergyProfile} value.
+ *
+ * @param energyProfile The {@link EnergyProfile} data.
+ */
+ private void handleEnergyProfile(@NonNull final EnergyProfile energyProfile) {
+ Log.d(TAG, "handleEnergyProfile() called with: energyProfile = [" + energyProfile + "]");
+ final CarContext carContext = mContextRef.get();
+ if (carContext == null) {
+ Log.w(TAG, "handleEnergyProfile: CarContext died, cannot continue.");
+ return;
+ }
+ if (mEnergyProfile.hasValueChanged(energyProfile, CONNECTOR_TYPE)) {
+ mCallbacks.onMessageChanged(CONNECTOR_TYPE, mEnergyProfile.getMessageForValue(carContext, energyProfile, CONNECTOR_TYPE));
+ }
+ if (mEnergyProfile.hasValueChanged(energyProfile, FUEL_TYPE)) {
+ mCallbacks.onMessageChanged(FUEL_TYPE, mEnergyProfile.getMessageForValue(carContext, energyProfile, FUEL_TYPE));
+ }
+ mEnergyProfile.setValue(energyProfile);
}
}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/AbstractCarInfo.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/AbstractCarInfo.java
new file mode 100644
index 0000000..3424358
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/AbstractCarInfo.java
@@ -0,0 +1,194 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.hardware.common.CarValue;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.model.CarPropertyType;
+
+/**
+ * Abstract implementation of the CarInfo value holder to prevent boilerplate code.
+ * @param
+ */
+public abstract class AbstractCarInfo {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "AbstractCarInfo";
+
+ /**
+ * The received {@link T} value.
+ */
+ private T mValue;
+
+ /**
+ * Standard empty C'tor.
+ */
+ protected AbstractCarInfo() {
+ // Do nothing.
+ }
+
+ /**
+ * Sets the actual value as received from the sensor.
+ *
+ * @param value The {@link T} value.
+ */
+ public void setValue(final T value) {
+ mValue = value;
+ }
+
+ /**
+ * Returns the to be displayed {@link String} for the {@link T} value.
+ *
+ * @param carContext The requesting {@link CarContext}.
+ * @param value The to be handled {@link T} value.
+ * @param carInfo The {@link SupportedCarInfo} to identify which message to generate.
+ *
+ * @return The string to be displayed to the user.
+ */
+ public String getMessageForValue(final CarContext carContext,
+ final T value,
+ final SupportedCarInfo carInfo) {
+ if (carContext == null) {
+ Log.w(TAG, "getCarManufacturerMessage: Unavailable context, return empty");
+ return "";
+ }
+ if (value == null) {
+ return carContext.getString(R.string.android_auto_car_info_not_received);
+ }
+ switch (getPropertyStatus(value, carInfo)) {
+ case CarValue.STATUS_SUCCESS:
+ return createMessage(carContext, value, carInfo);
+ case CarValue.STATUS_UNIMPLEMENTED:
+ return carContext.getString(R.string.android_auto_car_info_unimplemented);
+ case CarValue.STATUS_UNAVAILABLE:
+ return carContext.getString(R.string.android_auto_car_info_unavailable);
+ case CarValue.STATUS_UNKNOWN:
+ default:
+ return carContext.getString(R.string.android_auto_car_info_unknown);
+ }
+ }
+
+ /**
+ * Helper method to check whether the message has changed.
+ *
+ * @param carContext The calling {@link CarContext}.
+ * @param newData The received {@link T} data.
+ * @param carInfo The {@link SupportedCarInfo} to identify which message to generate.
+ *
+ * @return true if data has changed, false otherwise.
+ */
+ public boolean hasMessageChanged(final CarContext carContext,
+ final T newData,
+ final SupportedCarInfo carInfo) {
+ return TextUtils.equals(getMessageForValue(carContext, newData, carInfo),
+ getMessageForValue(carContext, mValue, carInfo));
+ }
+
+ /**
+ * Helper method to check whether a specific value has changed (not only the message).
+ *
+ * @param value The received {@link T} data.
+ * @param carInfo The {@link SupportedCarInfo} to identify which value to check.
+ *
+ * @return true if value has changed, false otherwise.
+ */
+ public boolean hasValueChanged(final T value, final SupportedCarInfo carInfo) {
+ final CarPropertyType propType = carInfo.getPropType();
+ switch (propType) {
+ case FLOAT:
+ return getFloatValue(value, carInfo) == getFloatValue(mValue, carInfo);
+ case INT:
+ return getIntValue(value, carInfo) == getIntValue(mValue, carInfo);
+ case STRING:
+ return TextUtils.equals(getStringValue(value, carInfo), getStringValue(mValue, carInfo));
+ case BOOL:
+ return getBooleanValue(value, carInfo) == getBooleanValue(mValue, carInfo);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Helper method to create the actual message to be shown for the {@link SupportedCarInfo} of
+ * {@link T} in case of {@link CarValue#STATUS_SUCCESS}.
+ *
+ * @param carContext The calling {@link CarContext}.
+ * @param newData The received {@link T} data.
+ * @param carInfo The {@link SupportedCarInfo} to identify which message to generate.
+ *
+ * @return The generated String message.
+ */
+ /*package-private*/ abstract String createMessage(final CarContext carContext,
+ final T newData,
+ final SupportedCarInfo carInfo);
+
+ /**
+ * Helper method to the {@link CarValue} status of the specified {@link SupportedCarInfo} of
+ * {@link T}.
+ *
+ * @param newData The received {@link T} data.
+ * @param carInfo The {@link SupportedCarInfo} to identify which status to check.
+ *
+ * @return Either of {@link CarValue#STATUS_SUCCESS}, {@link CarValue#STATUS_UNAVAILABLE},
+ * {@link CarValue#STATUS_UNIMPLEMENTED} or {@link CarValue#STATUS_UNKNOWN}.
+ */
+ /*package-private*/ abstract int getPropertyStatus(final T newData,
+ final SupportedCarInfo carInfo);
+
+ /**
+ * Helper method to get an integer value of the specified {@link SupportedCarInfo} of
+ * {@link T}.
+ *
+ * @param value The received {@link T} data.
+ * @param propType The {@link SupportedCarInfo} to identify which status to check.
+ *
+ * @return The unwrapped integer value.
+ */
+ /*package-private*/int getIntValue(final T value, final SupportedCarInfo propType) {
+ return -1;
+ }
+
+ /**
+ * Helper method to get a float value of the specified {@link SupportedCarInfo} of
+ * {@link T}.
+ *
+ * @param value The received {@link T} data.
+ * @param propType The {@link SupportedCarInfo} to identify which status to check.
+ *
+ * @return The unwrapped float value.
+ */
+ /*package-private*/float getFloatValue(final T value, final SupportedCarInfo propType) {
+ return -1;
+ }
+
+ /**
+ * Helper method to get a String value of the specified {@link SupportedCarInfo} of
+ * {@link T}.
+ *
+ * @param value The received {@link T} data.
+ * @param propType The {@link SupportedCarInfo} to identify which status to check.
+ *
+ * @return The unwrapped String value.
+ */
+ /*package-private*/ String getStringValue(final T value, final SupportedCarInfo propType) {
+ return "";
+ }
+
+ /**
+ * Helper method to get a boolean value of the specified {@link SupportedCarInfo} of
+ * {@link T}.
+ *
+ * @param value The received {@link T} data.
+ * @param propType The {@link SupportedCarInfo} to identify which status to check.
+ *
+ * @return The unwrapped boolean value.
+ */
+ /*package-private*/ boolean getBooleanValue(final T value, final SupportedCarInfo propType) {
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEnergyLevel.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEnergyLevel.java
new file mode 100644
index 0000000..ec4a3a5
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEnergyLevel.java
@@ -0,0 +1,90 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
+
+import android.annotation.SuppressLint;
+import android.util.Log;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.hardware.info.EnergyLevel;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants;
+
+/**
+ * Helper class to encapsulate the operations needed for the {@link EnergyLevel}.
+ */
+public class CarInfoEnergyLevel extends AbstractCarInfo{
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "CarInfoEnergyLevel";
+
+ @SuppressLint("StringFormatInvalid")
+ @Override
+ String createMessage(final CarContext carContext, final EnergyLevel newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case BATTERY_PERCENTAGE:
+ return String.format(carContext.getString(R.string.android_auto_energy_level_battery_percentage_base),
+ getFloatValue(newData, SupportedCarInfo.BATTERY_PERCENTAGE));
+ case FUEL_PERCENTAGE:
+ return String.format(carContext.getString(R.string.android_auto_energy_level_fuel_percentage_base),
+ getFloatValue(newData, SupportedCarInfo.FUEL_PERCENTAGE));
+ case LOW_ENERGY:
+ if (getBooleanValue(newData, SupportedCarInfo.LOW_ENERGY)) {
+ return carContext.getString(R.string.android_auto_energy_level_low_energy_true);
+ }
+ return carContext.getString(R.string.android_auto_energy_level_low_energy_false);
+ case RANGE_REMAINING:
+ final int displayUnit = AndroidAutoConstants.getDisplayDistanceUnit(
+ newData.getDistanceDisplayUnit());
+ return String.format(carContext.getString(R.string.android_auto_energy_level_range_remaining_base),
+ AndroidAutoConstants.getTransformedDistanceValue(
+ getFloatValue(newData, SupportedCarInfo.RANGE_REMAINING), displayUnit),
+ AndroidAutoConstants.getDisplayUnitString(carContext, displayUnit));
+ }
+ Log.d(TAG, "createMessage: Unavailable property [" + carInfo + "]");
+ return null;
+ }
+
+ @Override
+ int getPropertyStatus(final EnergyLevel newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case BATTERY_PERCENTAGE:
+ return newData.getBatteryPercent().getStatus();
+ case FUEL_PERCENTAGE:
+ return newData.getFuelPercent().getStatus();
+ case LOW_ENERGY:
+ return newData.getEnergyIsLow().getStatus();
+ case RANGE_REMAINING:
+ return newData.getRangeRemainingMeters().getStatus();
+ }
+ Log.d(TAG, "getPropertyStatus: Unavailable property [" + carInfo + "]");
+ return STATUS_UNKNOWN;
+ }
+
+ @Override
+ float getFloatValue(final EnergyLevel value, final SupportedCarInfo propType) {
+ switch (propType) {
+ case BATTERY_PERCENTAGE:
+ return AndroidAutoConstants.unwrapFloatValue(value.getBatteryPercent());
+ case FUEL_PERCENTAGE:
+ return AndroidAutoConstants.unwrapFloatValue(value.getFuelPercent());
+ case YEAR:case RANGE_REMAINING:
+ return AndroidAutoConstants.unwrapFloatValue(value.getRangeRemainingMeters());
+ }
+ Log.w(TAG, "getFloatValue: Unavailable property [" + propType + "]");
+ return super.getFloatValue(value, propType);
+ }
+
+ @Override
+ boolean getBooleanValue(final EnergyLevel value, final SupportedCarInfo propType) {
+ switch (propType) {
+ case LOW_ENERGY:
+ return AndroidAutoConstants.unwrapBooleanValue(value.getEnergyIsLow());
+ }
+ Log.w(TAG, "getBooleanValue: Unavailable property [" + propType + "]");
+ return super.getBooleanValue(value, propType);
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEnergyProfile.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEnergyProfile.java
new file mode 100644
index 0000000..2f75c3f
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEnergyProfile.java
@@ -0,0 +1,67 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
+
+import android.util.Log;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.hardware.info.EnergyProfile;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants;
+
+import java.util.List;
+
+/**
+ * Helper class to encapsulate the operations needed for the {@link EnergyProfile}.
+ */
+public class CarInfoEnergyProfile extends AbstractCarInfo {
+
+ /**
+ * Tag for debugging profile
+ */
+ private static final String TAG = "CarInfoEnergyProfile";
+
+ @Override
+ String createMessage(final CarContext carContext, final EnergyProfile newData, final SupportedCarInfo carInfo) {
+ final StringBuilder stringBuilder = new StringBuilder();
+ switch (carInfo) {
+ case FUEL_TYPE:
+ final List fuelTypes = newData.getFuelTypes().getValue();
+ if (fuelTypes == null || fuelTypes.isEmpty()) {
+ return carContext.getString(R.string.android_auto_car_info_unavailable);
+ }
+ for (final int fuelType : fuelTypes) {
+ stringBuilder.append(AndroidAutoConstants.getFuelTypeString(carContext, fuelType));
+ stringBuilder.append(", ");
+ }
+ return String.format(carContext.getString(R.string.android_auto_energy_profile_fuel_type_base),
+ stringBuilder.toString());
+ case CONNECTOR_TYPE:
+ final List connectorTypes = newData.getEvConnectorTypes().getValue();
+ if (connectorTypes == null || connectorTypes.isEmpty()) {
+ return carContext.getString(R.string.android_auto_car_info_unavailable);
+ }
+ for (final int connectorType : connectorTypes) {
+ stringBuilder.append(AndroidAutoConstants.getEvConnectorTypeString(carContext, connectorType));
+ stringBuilder.append(", ");
+ }
+ return String.format(carContext.getString(R.string.android_auto_energy_profile_connector_type_base),
+ stringBuilder.toString());
+ }
+ Log.d(TAG, "createMessage: Unavailable property [" + carInfo + "]");
+ return null;
+ }
+
+ @Override
+ int getPropertyStatus(final EnergyProfile newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case FUEL_TYPE:
+ return newData.getFuelTypes().getStatus();
+ case CONNECTOR_TYPE:
+ return newData.getEvConnectorTypes().getStatus();
+ }
+ Log.d(TAG, "getPropertyStatus: Unavailable property [" + carInfo + "]");
+ return STATUS_UNKNOWN;
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEvStatus.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEvStatus.java
new file mode 100644
index 0000000..a5fcbdb
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoEvStatus.java
@@ -0,0 +1,69 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
+
+import android.util.Log;
+
+import androidx.annotation.OptIn;
+import androidx.car.app.CarContext;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.hardware.info.EvStatus;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants;
+
+/**
+ * Helper class to encapsulate the operations needed for the {@link EvStatus}.
+ */
+public class CarInfoEvStatus extends AbstractCarInfo {
+
+ /**
+ * Tag fo debugging purpose.
+ */
+ private static final String TAG = "CarInfoEvStatus";
+
+ @Override
+ String createMessage(final CarContext carContext, final EvStatus newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case EV_CONNECTOR_STATE:
+ if(getBooleanValue(newData, SupportedCarInfo.EV_CONNECTOR_STATE)) {
+ return carContext.getString(R.string.android_auto_ev_status_charge_port_connected_true);
+ }
+ return carContext.getString(R.string.android_auto_ev_status_charge_port_connected_false);
+ case EV_CONNECTOR_OPEN:
+ if(getBooleanValue(newData, SupportedCarInfo.EV_CONNECTOR_OPEN)) {
+ return carContext.getString(R.string.android_auto_ev_status_charge_port_connected_true);
+ }
+ return carContext.getString(R.string.android_auto_ev_status_charge_port_connected_false);
+ }
+ Log.d(TAG, "createMessage: Unavailable property [" + carInfo + "]");
+ return null;
+ }
+
+ @OptIn(markerClass = ExperimentalCarApi.class)
+ @Override
+ int getPropertyStatus(final EvStatus newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case EV_CONNECTOR_STATE:
+ return newData.getEvChargePortConnected().getStatus();
+ case EV_CONNECTOR_OPEN:
+ return newData.getEvChargePortOpen().getStatus();
+ }
+ Log.d(TAG, "getPropertyStatus: Unavailable property [" + carInfo + "]");
+ return STATUS_UNKNOWN;
+ }
+
+ @OptIn(markerClass = ExperimentalCarApi.class)
+ @Override
+ boolean getBooleanValue(final EvStatus value, final SupportedCarInfo propType) {
+ switch (propType) {
+ case EV_CONNECTOR_STATE:
+ return AndroidAutoConstants.unwrapBooleanValue(value.getEvChargePortConnected());
+ case EV_CONNECTOR_OPEN:
+ return AndroidAutoConstants.unwrapBooleanValue(value.getEvChargePortOpen());
+ }
+ Log.w(TAG, "getBooleanValue: Unavailable property [" + propType + "]");
+ return super.getBooleanValue(value, propType);
+ }
+
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoMileage.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoMileage.java
new file mode 100644
index 0000000..3df3b4f
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoMileage.java
@@ -0,0 +1,54 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
+
+import android.util.Log;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.hardware.info.Mileage;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants;
+
+/**
+ * Helper class to encapsulate the operations needed for the {@link Mileage}.
+ */
+public class CarInfoMileage extends AbstractCarInfo {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "CarInfoMileage";
+
+ @Override
+ String createMessage(final CarContext carContext, final Mileage newData, final SupportedCarInfo carInfo) {
+ if (carInfo == SupportedCarInfo.MILEAGE) {
+ final int targetUnit = AndroidAutoConstants.getDisplayDistanceUnit(
+ newData.getDistanceDisplayUnit());
+ return String.format(carContext.getString(R.string.android_auto_ev_mileage_odometer_base),
+ AndroidAutoConstants.getTransformedDistanceValue(
+ getFloatValue(newData, SupportedCarInfo.MILEAGE), targetUnit),
+ AndroidAutoConstants.getDisplayUnitString(carContext, targetUnit));
+ }
+ Log.d(TAG, "createMessage: Unavailable property [" + carInfo + "]");
+ return null;
+ }
+
+ @Override
+ int getPropertyStatus(final Mileage newData, final SupportedCarInfo carInfo) {
+ if (carInfo == SupportedCarInfo.MILEAGE) {
+ newData.getOdometerMeters().getStatus();
+ }
+ Log.d(TAG, "getPropertyStatus: Unavailable property [" + carInfo + "]");
+ return STATUS_UNKNOWN;
+ }
+
+ @Override
+ float getFloatValue(final Mileage value, final SupportedCarInfo propType) {
+ if (propType == SupportedCarInfo.MILEAGE) {
+ return AndroidAutoConstants.unwrapFloatValue(value.getOdometerMeters());
+ }
+ Log.w(TAG, "getFloatValue: Unavailable property [" + propType + "]");
+ return super.getFloatValue(value, propType);
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoModel.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoModel.java
new file mode 100644
index 0000000..7e17a79
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoModel.java
@@ -0,0 +1,82 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
+
+import android.util.Log;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.hardware.info.Model;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants;
+
+/**
+ * Helper class to encapsulate the operations needed for the {@link Model}.
+ */
+public class CarInfoModel extends AbstractCarInfo {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "CarInfoModel";
+
+ /**
+ * Empty C'Tor.
+ */
+ public CarInfoModel() {
+ // Do nothing.
+ }
+
+ @Override
+ String createMessage(final CarContext carContext,final Model newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case MANUFACTURER:
+ return String.format(carContext.getString(R.string.android_auto_model_manufacturer_base),
+ getStringValue(newData, carInfo));
+ case NAME:
+ return String.format(carContext.getString(R.string.android_auto_model_name_base),
+ getStringValue(newData, carInfo));
+ case YEAR:
+ return String.format(carContext.getString(R.string.android_auto_model_year_base),
+ getIntValue(newData, carInfo));
+ }
+ Log.d(TAG, "createMessage: Unavailable property [" + carInfo + "]");
+ return null;
+ }
+
+ @Override
+ int getPropertyStatus(final Model newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case MANUFACTURER:
+ return newData.getManufacturer().getStatus();
+ case NAME:
+ return newData.getName().getStatus();
+ case YEAR:
+ return newData.getYear().getStatus();
+ }
+ Log.d(TAG, "getPropertyStatus: Unavailable property [" + carInfo + "]");
+ return STATUS_UNKNOWN;
+ }
+
+ @Override
+ int getIntValue(final Model value, final SupportedCarInfo propType) {
+ switch (propType) {
+ case YEAR:
+ return AndroidAutoConstants.unwrapIntValue(value.getYear());
+ }
+ Log.w(TAG, "getIntValue: Unavailable property [" + propType + "]");
+ return super.getIntValue(value, propType);
+ }
+
+ @Override
+ String getStringValue(final Model value, final SupportedCarInfo propType) {
+ switch (propType) {
+ case MANUFACTURER:
+ return value.getManufacturer().getValue();
+ case NAME:
+ return value.getName().getValue();
+ }
+ Log.w(TAG, "getStringValue: Unavailable property [" + propType + "]");
+ return super.getStringValue(value, propType);
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoSpeed.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoSpeed.java
new file mode 100644
index 0000000..cbc64a1
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoSpeed.java
@@ -0,0 +1,66 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
+
+import android.util.Log;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.hardware.info.Speed;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants;
+
+/**
+ * Helper class to encapsulate the operations needed for the {@link Speed}.
+ */
+public class CarInfoSpeed extends AbstractCarInfo {
+
+ /**
+ * Tag for debugging purposes.
+ */
+ private static final String TAG = "CarInfoSpeed";
+
+ @Override
+ String createMessage(final CarContext carContext, final Speed newData, final SupportedCarInfo carInfo) {
+ final int targetUnit = AndroidAutoConstants.getDisplaySpeedUnit(
+ newData.getSpeedDisplayUnit());
+ switch (carInfo) {
+ case RAW_SPEED:
+ return String.format(carContext.getString(R.string.android_auto_raw_speed_base_string),
+ AndroidAutoConstants.getTransformedSpeedValue(
+ getFloatValue(newData, SupportedCarInfo.RAW_SPEED), targetUnit),
+ AndroidAutoConstants.getDisplaySpeedUnitString(carContext, targetUnit));
+ case DISPLAY_SPEED:
+ return String.format(carContext.getString(R.string.android_auto_display_speed_base_string),
+ AndroidAutoConstants.getTransformedSpeedValue(
+ getFloatValue(newData, SupportedCarInfo.DISPLAY_SPEED), targetUnit),
+ AndroidAutoConstants.getDisplaySpeedUnitString(carContext, targetUnit));
+ }
+ Log.d(TAG, "createMessage: Unavailable property [" + carInfo + "]");
+ return null;
+ }
+
+ @Override
+ int getPropertyStatus(final Speed newData, final SupportedCarInfo carInfo) {
+ switch (carInfo) {
+ case RAW_SPEED:
+ newData.getRawSpeedMetersPerSecond().getStatus();
+ case DISPLAY_SPEED:
+ newData.getDisplaySpeedMetersPerSecond().getStatus();
+ }
+ Log.d(TAG, "getPropertyStatus: Unavailable property [" + carInfo + "]");
+ return STATUS_UNKNOWN;
+ }
+
+ @Override
+ float getFloatValue(final Speed value, final SupportedCarInfo propType) {
+ switch (propType) {
+ case RAW_SPEED:
+ return AndroidAutoConstants.unwrapFloatValue(value.getRawSpeedMetersPerSecond());
+ case DISPLAY_SPEED:
+ return AndroidAutoConstants.unwrapFloatValue(value.getDisplaySpeedMetersPerSecond());
+ }
+ Log.w(TAG, "getFloatValue: Unavailable property [" + propType + "]");
+ return super.getFloatValue(value, propType);
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoTollCard.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoTollCard.java
new file mode 100644
index 0000000..09726ac
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/CarInfoTollCard.java
@@ -0,0 +1,77 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import static androidx.car.app.hardware.common.CarValue.STATUS_UNKNOWN;
+import static androidx.car.app.hardware.info.TollCard.TOLLCARD_STATE_INVALID;
+import static androidx.car.app.hardware.info.TollCard.TOLLCARD_STATE_NOT_INSERTED;
+import static androidx.car.app.hardware.info.TollCard.TOLLCARD_STATE_UNKNOWN;
+import static androidx.car.app.hardware.info.TollCard.TOLLCARD_STATE_VALID;
+
+import android.util.Log;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.hardware.info.TollCard;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants;
+
+/**
+ * Helper class to encapsulate the operations needed for the {@link TollCard}.
+ */
+public class CarInfoTollCard extends AbstractCarInfo {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "CarInfoTollCard";
+
+ @Override
+ String createMessage(final CarContext carContext, final TollCard newData, final SupportedCarInfo carInfo) {
+ if (carInfo == SupportedCarInfo.TOLL_CARD) {
+ return String.format(carContext.getString(R.string.android_auto_toll_card_state_base),
+ getTollCardStateString(carContext, getIntValue(newData, SupportedCarInfo.TOLL_CARD)));
+ }
+ Log.d(TAG, "createMessage: Unavailable property [" + carInfo + "]");
+ return null;
+ }
+
+ @Override
+ int getPropertyStatus(final TollCard newData, final SupportedCarInfo carInfo) {
+ if (carInfo == SupportedCarInfo.TOLL_CARD) {
+ return newData.getCardState().getStatus();
+ }
+ Log.d(TAG, "getPropertyStatus: Unavailable property [" + carInfo + "]");
+ return STATUS_UNKNOWN;
+ }
+
+ @Override
+ int getIntValue(final TollCard value, final SupportedCarInfo propType) {
+ if (propType == SupportedCarInfo.TOLL_CARD) {
+ return AndroidAutoConstants.unwrapIntValue(value.getCardState());
+ }
+ Log.w(TAG, "getIntValue: Unavailable property [" + propType + "]");
+ return super.getIntValue(value, propType);
+ }
+
+ /**
+ * Helper method to unbox the displayUnit.
+ *
+ * @param carContext The requesting {@link CarContext}.
+ * @param tollCardState The {@link TollCard} state representation.
+ *
+ * @return The {@link String} representing the {@link TollCard} state, defaults to
+ * {@link TollCard#TOLLCARD_STATE_UNKNOWN}.
+ */
+ private String getTollCardStateString(final CarContext carContext, final int tollCardState) {
+ switch (tollCardState) {
+ case TOLLCARD_STATE_INVALID:
+ return carContext.getString(R.string.android_auto_toll_card_state_invalid);
+ case TOLLCARD_STATE_VALID:
+ return carContext.getString(R.string.android_auto_toll_card_state_valid);
+ case TOLLCARD_STATE_NOT_INSERTED:
+ return carContext.getString(R.string.android_auto_toll_card_state_not_inserted);
+ case TOLLCARD_STATE_UNKNOWN:
+ default:
+ return carContext.getString(R.string.android_auto_toll_card_state_unknown);
+ }
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/SupportedCarInfo.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/SupportedCarInfo.java
new file mode 100644
index 0000000..5419817
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/model/carinfo/SupportedCarInfo.java
@@ -0,0 +1,122 @@
+package com.aldo.apps.familyhelpers.auto.model.carinfo;
+
+import com.aldo.apps.familyhelpers.auto.model.CarPropertyType;
+
+/**
+ * Enum class representing the potentially available {@link androidx.car.app.hardware.info.CarInfo}s.
+ */
+public enum SupportedCarInfo {
+ /**
+ * The raw speed as reported by the car.
+ */
+ RAW_SPEED(0, CarPropertyType.FLOAT),
+
+ /**
+ * The display speed as reported by the car.
+ */
+ DISPLAY_SPEED(1, CarPropertyType.FLOAT),
+
+ /**
+ * The toll card status as reported by the car.
+ */
+ TOLL_CARD(2, CarPropertyType.INT),
+
+ /**
+ * The current charge percentage of the battery.
+ */
+ BATTERY_PERCENTAGE(3, CarPropertyType.FLOAT),
+
+ /**
+ * The current fuel percentage.
+ */
+ FUEL_PERCENTAGE(4, CarPropertyType.FLOAT),
+
+ /**
+ * The remaining range.
+ */
+ RANGE_REMAINING(5, CarPropertyType.FLOAT),
+
+ /**
+ * The flag indicating whether the Car is on lowEnergy or not.
+ */
+ LOW_ENERGY(6, CarPropertyType.BOOL),
+
+ /**
+ * The flag indicating whether the EV connector port is open or not.
+ */
+ EV_CONNECTOR_OPEN(7, CarPropertyType.BOOL),
+
+ /**
+ * The flag indicating whether the EV connector port is connected or not.
+ */
+ EV_CONNECTOR_STATE(8, CarPropertyType.BOOL),
+
+ /**
+ * The flag indicating whether the EV connector port is open or not.
+ */
+ MILEAGE(9, CarPropertyType.FLOAT),
+
+ /**
+ * The name of the cars manufacturer.
+ */
+ MANUFACTURER(10, CarPropertyType.STRING),
+
+ /**
+ * The name of the car model.
+ */
+ NAME(11, CarPropertyType.STRING),
+
+ /**
+ * The construction year.
+ */
+ YEAR(12, CarPropertyType.INT),
+
+ /**
+ * The list of supported fuel types.
+ */
+ FUEL_TYPE(13, CarPropertyType.INT),
+
+ /**
+ * The list of supported ev connector ports.
+ */
+ CONNECTOR_TYPE(14, CarPropertyType.INT);
+
+ /**
+ * The index of the item in the UI.
+ */
+ private final int mIndex;
+
+ /**
+ * The {@link CarPropertyType} to determine the value to be retrieved.
+ */
+ private final CarPropertyType mPropType;
+
+ /**
+ * C'Tor.
+ *
+ * @param index The index of the item in the UI.
+ * @param propType The {@link CarPropertyType} to determine the value to be retrieved.
+ */
+ SupportedCarInfo(final int index, final CarPropertyType propType) {
+ mIndex = index;
+ mPropType = propType;
+ }
+
+ /**
+ * Returns the index of the item in the UI.
+ *
+ * @return The index of the item in the UI.
+ */
+ public int getIndex() {
+ return mIndex;
+ }
+
+ /**
+ * The {@link CarPropertyType} to determine the value to be retrieved.
+ *
+ * @return The {@link CarPropertyType} to determine the value to be retrieved.
+ */
+ public CarPropertyType getPropType() {
+ return mPropType;
+ }
+}
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
index a7e95fa..fd39a54 100644
--- 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
@@ -43,7 +43,7 @@ public abstract class AbstractTabContent {
* @param listener The {@link IScreenContentChangedListener} to be invoked if page content changed.
*/
protected AbstractTabContent(final CarContext carContext,
- final IScreenContentChangedListener listener) {
+ final IScreenContentChangedListener listener) {
mContextRef = new WeakReference<>(carContext);
mScreenContentChangedListener = listener;
}
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
index fdf951a..3a5de57 100644
--- 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
@@ -2,17 +2,29 @@ package com.aldo.apps.familyhelpers.auto.screencontent;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.TAB_KEY_CAR_INFO;
+import android.util.Log;
+
import androidx.car.app.CarContext;
-import androidx.car.app.CarToast;
+import androidx.car.app.annotations.ExperimentalCarApi;
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 androidx.car.app.model.Toggle;
import com.aldo.apps.familyhelpers.R;
-import com.aldo.apps.familyhelpers.auto.model.VehicleInformationHelper;
+import com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo;
+import com.aldo.apps.familyhelpers.auto.utils.CarInfoCallbackRepository;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+
+/**
+ * The {@link Tab} to be shown for the CarInfo.
+ */
public class CarInfoTab extends AbstractTabContent {
/**
@@ -20,25 +32,52 @@ public class CarInfoTab extends AbstractTabContent {
*/
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;
+ /**
+ * {@link CarInfoCallbackRepository} holding all registered callbacks.
+ */
+ private final CarInfoCallbackRepository mCallbacks;
+
+ /**
+ * The {@link IScreenContentChangedListener} to be invoked if the page content changed.
+ */
+ private IScreenContentChangedListener mContentChangeListener;
+
+ /**
+ * The {@link Map} holding a mapping of {@link SupportedCarInfo} and the {@link Disposable}
+ * subscription in order to cancel ongoing subscriptions.
+ */
+ private final Map mSubscriptionMap = new HashMap<>();
+
+ /**
+ * The {@link Map} holding the last received message for each {@link SupportedCarInfo} to be
+ * read from.
+ */
+ private final Map mMessageMap = new HashMap<>();
+
+ /**
+ * The currently selected {@link SupportedCarInfo} item.
+ */
+ private SupportedCarInfo mCurrentlySelectedItem = SupportedCarInfo.RAW_SPEED;
+
/**
* C'Tor.
*
* @param carContext Calling {@link CarContext}.
* @param listener The {@link IScreenContentChangedListener} to be invoked if page content changed.
+ * @param callbacks The {@link CarInfoCallbackRepository} holding all registered callbacks.
*/
- public CarInfoTab(final CarContext carContext, final IScreenContentChangedListener listener) {
+ @ExperimentalCarApi
+ public CarInfoTab(final CarContext carContext,
+ final IScreenContentChangedListener listener,
+ final CarInfoCallbackRepository callbacks) {
super(carContext, listener);
- mVehicleInformationHelper = new VehicleInformationHelper(carContext);
+ mCallbacks = callbacks;
+ mContentChangeListener = listener;
refreshTemplate();
}
@@ -57,16 +96,133 @@ public class CarInfoTab extends AbstractTabContent {
}
@Override
+ @ExperimentalCarApi
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)
+ final Row rawSpeed = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_raw_speed_title))
+ .setImage(getIconForResource(R.drawable.ic_speed), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.RAW_SPEED, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.RAW_SPEED))
.build();
+ final Row displaySpeed = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_display_speed_title))
+ .setImage(getIconForResource(R.drawable.ic_speed), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.DISPLAY_SPEED, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.DISPLAY_SPEED))
+ .build();
+
+ final Row tollCard = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_toll_card_title))
+ .setImage(getIconForResource(R.drawable.ic_toll_card), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.TOLL_CARD, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.TOLL_CARD))
+ .build();
+
+ final Row batPerc = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_energy_level_battery_percentage_title))
+ .setImage(getIconForResource(R.drawable.ic_battery_percentage), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.BATTERY_PERCENTAGE, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.BATTERY_PERCENTAGE))
+ .build();
+
+ final Row fuelPerc = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_energy_level_fuel_percentage_title))
+ .setImage(getIconForResource(R.drawable.ic_fuel_percentage), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.FUEL_PERCENTAGE, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.FUEL_PERCENTAGE))
+ .build();
+
+ final Row lowEnergy = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_energy_level_low_energy_title))
+ .setImage(getIconForResource(R.drawable.ic_low_energy), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.LOW_ENERGY, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.LOW_ENERGY))
+ .build();
+
+ final Row rangeRemaining = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_energy_level_range_remaining_title))
+ .setImage(getIconForResource(R.drawable.ic_range_remaining), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.RANGE_REMAINING, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.RANGE_REMAINING))
+ .build();
+
+ final Row evConnectorOpen = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_ev_status_charge_port_open_title))
+ .setImage(getIconForResource(R.drawable.ic_ev_connector), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.EV_CONNECTOR_OPEN, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.EV_CONNECTOR_OPEN))
+ .build();
+
+ final Row evConnectorState = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_ev_status_charge_port_connected_title))
+ .setImage(getIconForResource(R.drawable.ic_ev_connector), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.EV_CONNECTOR_STATE, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.EV_CONNECTOR_STATE))
+ .build();
+
+ final Row mileage = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_mileage_title))
+ .setImage(getIconForResource(R.drawable.ic_mileage), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.MILEAGE, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.MILEAGE))
+ .build();
+
+ final Row manufacturer = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_model_manufacturer_title))
+ .setImage(getIconForResource(R.drawable.ic_car_info), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.MANUFACTURER, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.MANUFACTURER))
+ .build();
+
+ final Row name = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_model_name_title))
+ .setImage(getIconForResource(R.drawable.ic_car_info), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.NAME, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.NAME))
+ .build();
+
+ final Row year = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_model_year_title))
+ .setImage(getIconForResource(R.drawable.ic_car_info), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.YEAR, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.YEAR))
+ .build();
+
+ final Row fuelType = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_energy_profile_fuel_type_title))
+ .setImage(getIconForResource(R.drawable.ic_fuel_percentage), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.FUEL_TYPE, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.FUEL_TYPE))
+ .build();
+
+ final Row connectorType = new Row.Builder()
+ .setTitle(getString(R.string.android_auto_energy_profile_connector_type_title))
+ .setImage(getIconForResource(R.drawable.ic_ev_connector), Row.IMAGE_TYPE_ICON)
+ .addText(mMessageMap.getOrDefault(SupportedCarInfo.CONNECTOR_TYPE, ""))
+ .setToggle(createAndAssigneToggle(SupportedCarInfo.CONNECTOR_TYPE))
+ .build();
+
+ Log.d(TAG, "refreshTemplate: selected Item = [" + mCurrentlySelectedItem + "]");
+
final ItemList carList = new ItemList.Builder()
.setNoItemsMessage(getString(R.string.android_auto_share_location_no_items_yet))
- .addItem(vehicleInfo)
+ .addItem(rawSpeed)
+ .addItem(displaySpeed)
+ .addItem(tollCard)
+ .addItem(batPerc)
+ .addItem(fuelPerc)
+ .addItem(rangeRemaining)
+ .addItem(lowEnergy)
+ .addItem(evConnectorOpen)
+ .addItem(evConnectorState)
+ .addItem(mileage)
+ .addItem(manufacturer)
+ .addItem(name)
+ .addItem(year)
+ .addItem(fuelType)
+ .addItem(connectorType)
+ .setSelectedIndex(mCurrentlySelectedItem.getIndex())
.build();
mCarInfoTab = new ListTemplate.Builder()
@@ -75,9 +231,74 @@ public class CarInfoTab extends AbstractTabContent {
}
/**
- * Helper method to be invoked when a list item is clicked.
+ * Helper method to create and fill a {@link Toggle} to be shown in the UI.
+ *
+ * @param supportedCarInfo The {@link SupportedCarInfo} to be assigned to the topic.
+ *
+ * @return The created {@link Toggle}.
*/
- private void startCarInfoScreen() {
- CarToast.makeText(getCarContext(), "Not yet implemented", CarToast.LENGTH_LONG).show();
+ private Toggle createAndAssigneToggle(final SupportedCarInfo supportedCarInfo) {
+ return new Toggle.Builder(isChecked -> handleCheckedChanged(isChecked, supportedCarInfo))
+ .setChecked(mSubscriptionMap.containsKey(supportedCarInfo))
+ .build();
+ }
+
+ /**
+ * Helper method to be invoked when the checked state of a toggle changes.
+ *
+ * @param isChecked True if checked, false otherwise.
+ * @param supportedCarInfo The assigned {@link SupportedCarInfo}.
+ */
+ private void handleCheckedChanged(final boolean isChecked, final SupportedCarInfo supportedCarInfo) {
+ mCurrentlySelectedItem = supportedCarInfo;
+ if (isChecked) {
+ if (mSubscriptionMap.containsKey(supportedCarInfo)) {
+ Log.d(TAG, "handleCheckedChanged: Already subscribed, no need to update");
+ return;
+ }
+ Log.d(TAG, "handleCheckedChanged: Subscribing to [" + supportedCarInfo + "]");
+ mSubscriptionMap.put(supportedCarInfo, mCallbacks.getObservableForTopic(supportedCarInfo)
+ .subscribe(message -> handleSubscriptionEvent(supportedCarInfo, message),
+ throwable -> handleSubscriptionError(supportedCarInfo, throwable)));
+ } else {
+ if (mSubscriptionMap.containsKey(supportedCarInfo)) {
+ Log.d(TAG, "handleCheckedChanged: Unsubscribing from [" + supportedCarInfo + "]");
+ final Disposable disposable = mSubscriptionMap.get(supportedCarInfo);
+ if (disposable != null && !disposable.isDisposed()) {
+ disposable.dispose();
+ mSubscriptionMap.remove(supportedCarInfo);
+ }
+ } else {
+ Log.d(TAG, "handleCheckedChanged: Not yet subscribed, no need to update.");
+ }
+ }
+ }
+
+ /**
+ * Helper method to be invoked whenever data is received from the CarService.
+ *
+ * @param supportedCarInfo The {@link SupportedCarInfo} in focus.
+ * @param newMessage The new message received.
+ */
+ private void handleSubscriptionEvent(final SupportedCarInfo supportedCarInfo, final String newMessage) {
+ Log.d(TAG, "handleSubscriptionEvent() called with: supportedCarInfo = [" + supportedCarInfo
+ + "], newMessage = [" + newMessage + "]");
+ mMessageMap.put(supportedCarInfo, newMessage);
+ mContentChangeListener.onScreenContentChanged(TAB_KEY_CAR_INFO);
+ }
+
+ /**
+ * Helper method to be invoked if the subscription failed to a specific topic.
+ *
+ * @param supportedCarInfo The {@link SupportedCarInfo}.
+ * @param throwable The {@link Throwable} that occurred.
+ */
+ private void handleSubscriptionError(final SupportedCarInfo supportedCarInfo, final Throwable throwable) {
+ if (mSubscriptionMap.containsKey(supportedCarInfo)) {
+ mSubscriptionMap.get(supportedCarInfo).dispose();
+ mSubscriptionMap.remove(supportedCarInfo);
+ }
+ Log.e(TAG, "handleSubscriptionError()supportedCarInfo = [" + supportedCarInfo
+ + "], throwable = [" + 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
index d996c7d..2240404 100644
--- 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
@@ -1,5 +1,39 @@
package com.aldo.apps.familyhelpers.auto.utils;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_CHADEMO;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_COMBO_1;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_COMBO_2;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_GBT;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_GBT_DC;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_J1772;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_MENNEKES;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_OTHER;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_SCAME;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_TESLA_HPWC;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_TESLA_ROADSTER;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_TESLA_SUPERCHARGER;
+import static androidx.car.app.hardware.info.EnergyProfile.EVCONNECTOR_TYPE_UNKNOWN;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_BIODIESEL;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_CNG;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_DIESEL_1;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_DIESEL_2;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_E85;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_ELECTRIC;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_HYDROGEN;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_LEADED;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_LNG;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_LPG;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_OTHER;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_UNKNOWN;
+import static androidx.car.app.hardware.info.EnergyProfile.FUEL_TYPE_UNLEADED;
+
+import androidx.car.app.CarContext;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.hardware.common.CarUnit;
+import androidx.car.app.hardware.common.CarValue;
+
+import com.aldo.apps.familyhelpers.R;
+
/**
* Constants class for Android Auto.
*/
@@ -19,4 +53,360 @@ public class AndroidAutoConstants {
* Name of the permission for the VehicleSpeed.
*/
public static final String CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED";
+
+ /**
+ * Name of the permission for the VehicleFuel state.
+ */
+ public static final String CAR_FUEL_PERMISSION = "com.google.android.gms.permission.CAR_FUEL";
+
+ /**
+ * Name of the permission for the VehicleMileage state.
+ */
+ public static final String CAR_MILEAGE_PERMISSION = "com.google.android.gms.permission.CAR_MILEAGE";
+
+ /**
+ * Modifier to transform received meter per second values to kilometer per hour.
+ */
+ public static final float MPS_TP_KMH_MODIFICATOR = 3.6f;
+
+ /**
+ * Modifier to transform received meter per second values to miles per hour.
+ */
+ public static final float MPS_TP_MPH_MODIFICATOR = 2.23694f;
+
+ /**
+ * Modifier to transform received meters into kilometers.
+ */
+ public static final float METERS_TO_KILOMETERS_MODIFICATOR = 0.001f;
+
+ /**
+ * Modifier to transform received meters into miles.
+ */
+ public static final float METERS_TO_MILES_MODIFICATOR = 0.00062137119223734f;
+
+ /**
+ * Modifier to transform received liters into US gallons.
+ */
+ public static final float LITERS_TO_US_GALLON_MODIFICATOR = 0.26417205235815f;
+
+ /**
+ * Modifier to transform received liters into imperial gallons.
+ */
+ public static final float LITERS_TO_IMPERIAL_GALLON_MODIFIER = 0.219969f;
+
+ /**
+ * Modifier to transform received liters into milliliters.
+ */
+ public static final float LITERS_TO_MILLILITERS_MODIFIER = 0.0001f;
+
+
+ /**
+ * Transforms the received MeterPerSecond value into the respective value of the given display
+ * unit.
+
+ * @param baseValue The received value.
+ * @param targetUnit The unit to which to transform.
+ *
+ * @return The transformed value.
+ */
+ public static float getTransformedSpeedValue(final float baseValue, final int targetUnit) {
+ switch (targetUnit) {
+ case CarUnit.METERS_PER_SEC:
+ return baseValue;
+ case CarUnit.MILES_PER_HOUR:
+ return baseValue * MPS_TP_MPH_MODIFICATOR;
+ case CarUnit.KILOMETERS_PER_HOUR:
+ default:
+ return baseValue * MPS_TP_KMH_MODIFICATOR;
+ }
+ }
+
+ /**
+ * Helper method to unbox the displayUnit.
+ *
+ * @param displayUnitObj The {@link CarValue} representing the unit.
+ *
+ * @return The {@link CarUnit} of the displaySpeed, defaults to {@link CarUnit#METERS_PER_SEC}.
+ */
+ public static int getDisplaySpeedUnit(final CarValue displayUnitObj) {
+ if (displayUnitObj == null || displayUnitObj.getStatus() != CarValue.STATUS_SUCCESS) {
+ return CarUnit.METERS_PER_SEC;
+ }
+ return displayUnitObj.getValue() != null ? displayUnitObj.getValue() : CarUnit.METERS_PER_SEC;
+ }
+
+ /**
+ * Helper method to get the proper text representation of the displaySpeedValue.
+ *
+ * @param carContext The calling {@link CarContext}
+ * @param unit The int representation of the target unit.
+ *
+ * @return The string representation of the provided {@link CarUnit}, defaults to
+ * {@link CarUnit#METERS_PER_SEC}.
+ */
+ public static String getDisplaySpeedUnitString(final CarContext carContext, final int unit) {
+ switch (unit) {
+ case CarUnit.KILOMETERS_PER_HOUR:
+ return carContext.getString(R.string.android_auto_speed_unit_kmh);
+ case CarUnit.MILES_PER_HOUR:
+ return carContext.getString(R.string.android_auto_speed_unit_mph);
+ case CarUnit.METERS_PER_SEC:
+ default:
+ return carContext.getString(R.string.android_auto_speed_unit_mps);
+ }
+ }
+
+ /**
+ * Transforms the received Meter value into the respective value of the given display
+ * unit.
+
+ * @param baseValue The received value.
+ * @param targetUnit The unit to which to transform.
+ *
+ * @return The transformed value.
+ */
+ public static float getTransformedDistanceValue(final float baseValue, final int targetUnit) {
+ switch (targetUnit) {
+ case CarUnit.METER:
+ return baseValue;
+ case CarUnit.MILE:
+ return baseValue * METERS_TO_MILES_MODIFICATOR;
+ case CarUnit.KILOMETER:
+ default:
+ return baseValue * METERS_TO_KILOMETERS_MODIFICATOR;
+ }
+ }
+
+ /**
+ * Helper method to unbox the displayUnit.
+ *
+ * @param displayUnitObj The {@link CarValue} representing the unit.
+ *
+ * @return The {@link CarUnit} of the displayDistance, defaults to {@link CarUnit#METER}.
+ */
+ public static int getDisplayDistanceUnit(final CarValue displayUnitObj) {
+ if (displayUnitObj == null || displayUnitObj.getStatus() != CarValue.STATUS_SUCCESS) {
+ return CarUnit.METER;
+ }
+ return displayUnitObj.getValue() != null ? displayUnitObj.getValue() : CarUnit.METER;
+ }
+
+ /**
+ * Helper method to get the proper text representation of the displayDistanceValue.
+ *
+ * @param carContext The calling {@link CarContext}
+ * @param unit The int representation of the target unit.
+ *
+ * @return The string representation of the provided {@link CarUnit}, defaults to
+ * {@link CarUnit#METER}.
+ */
+ public static String getDisplayUnitString(final CarContext carContext, final int unit) {
+ switch (unit) {
+ case CarUnit.KILOMETER:
+ return carContext.getString(R.string.android_auto_distance_unit_km);
+ case CarUnit.MILE:
+ return carContext.getString(R.string.android_auto_distance_unit_miles);
+ case CarUnit.METER:
+ default:
+ return carContext.getString(R.string.android_auto_distance_unit_meters);
+ }
+ }
+
+
+
+ /**
+ * Transforms the received volume value into the respective value of the given display
+ * unit.
+
+ * @param baseValue The received value.
+ * @param targetUnit The unit to which to transform.
+ *
+ * @return The transformed value.
+ */
+ @ExperimentalCarApi
+ public static float getTransformedVolumeValue(final float baseValue, final int targetUnit) {
+ switch (targetUnit) {
+ case CarUnit.US_GALLON:
+ return baseValue * LITERS_TO_US_GALLON_MODIFICATOR;
+ case CarUnit.IMPERIAL_GALLON:
+ return baseValue * LITERS_TO_IMPERIAL_GALLON_MODIFIER;
+ case CarUnit.MILLILITER:
+ return baseValue * LITERS_TO_MILLILITERS_MODIFIER;
+ case CarUnit.LITER:
+ default:
+ return baseValue;
+ }
+ }
+
+ /**
+ * Helper method to unbox the displayUnit.
+ *
+ * @param displayUnitObj The {@link CarValue} representing the unit.
+ *
+ * @return The {@link CarUnit} of the displayVolume, defaults to {@link CarUnit#LITER}.
+ */
+ @ExperimentalCarApi
+ public static int getDisplayVolumeUnit(final CarValue displayUnitObj) {
+ if (displayUnitObj == null || displayUnitObj.getStatus() != CarValue.STATUS_SUCCESS) {
+ return CarUnit.LITER;
+ }
+ return displayUnitObj.getValue() != null ? displayUnitObj.getValue() : CarUnit.LITER;
+ }
+
+ /**
+ * Helper method to get the proper text representation of the displayVolumeValue.
+ *
+ * @param carContext The calling {@link CarContext}
+ * @param unit The int representation of the target unit.
+ *
+ * @return The string representation of the provided {@link CarUnit}, defaults to
+ * {@link CarUnit#LITER}.
+ */
+ @ExperimentalCarApi
+ public static String getDisplayVolumeString(final CarContext carContext, final int unit) {
+ switch (unit) {
+ case CarUnit.US_GALLON:
+ return carContext.getString(R.string.android_auto_volume_unit_us_gallon);
+ case CarUnit.IMPERIAL_GALLON:
+ return carContext.getString(R.string.android_auto_volume_unit_imperial_gallon);
+ case CarUnit.MILLILITER:
+ return carContext.getString(R.string.android_auto_volume_unit_milliliter);
+ case CarUnit.LITER:
+ default:
+ return carContext.getString(R.string.android_auto_volume_unit_liter);
+ }
+ }
+
+ /**
+ * Helper method to safely unwrap a {@link CarValue} of type {@link Float} to make it a primitive
+ * type.
+ *
+ * @param floatValue The {@link CarValue} of type {@link Float}.
+ *
+ * @return The unwrapped primitive type.
+ */
+ public static float unwrapFloatValue(final CarValue floatValue) {
+ final Float actualValue = floatValue.getValue();
+ if (actualValue == null || actualValue.isNaN() || actualValue.isInfinite()) {
+ return 0.0f;
+ }
+ return actualValue;
+ }
+
+ /**
+ * Helper method to safely unwrap a {@link CarValue} of type {@link Integer} to make it a primitive
+ * type.
+ *
+ * @param intValue The {@link CarValue} of type {@link Integer}.
+ *
+ * @return The unwrapped primitive type.
+ */
+ public static int unwrapIntValue(final CarValue intValue) {
+ final Integer actualValue = intValue.getValue();
+ if (actualValue == null) {
+ return 0;
+ }
+ return actualValue;
+ }
+
+ /**
+ * Helper method to safely unwrap a {@link CarValue} of type {@link Boolean} to make it a primitive
+ * type.
+ *
+ * @param boolValue The {@link CarValue} of type {@link Boolean}.
+ *
+ * @return The unwrapped primitive type.
+ */
+ public static boolean unwrapBooleanValue(final CarValue boolValue) {
+ final Boolean actualValue = boolValue.getValue();
+ if (actualValue == null) {
+ return false;
+ }
+ return actualValue;
+ }
+
+ /**
+ * Helper method to get the proper text representation of the fuel type.
+ *
+ * @param carContext The calling {@link CarContext}
+ * @param fuelType The int representation of the fuel type.
+ *
+ * @return The string representation of the provided
+ * {@link androidx.car.app.hardware.info.EnergyProfile.FuelType}, defaults to
+ * {@link androidx.car.app.hardware.info.EnergyProfile#FUEL_TYPE_UNKNOWN}.
+ */
+ public static String getFuelTypeString(final CarContext carContext, final int fuelType ) {
+ switch (fuelType) {
+ case FUEL_TYPE_UNLEADED:
+ return carContext.getString(R.string.android_auto_fuel_type_unleaded);
+ case FUEL_TYPE_LEADED:
+ return carContext.getString(R.string.android_auto_fuel_type_leaded);
+ case FUEL_TYPE_DIESEL_1:
+ return carContext.getString(R.string.android_auto_fuel_type_diesel_1);
+ case FUEL_TYPE_DIESEL_2:
+ return carContext.getString(R.string.android_auto_fuel_type_diesel_2);
+ case FUEL_TYPE_BIODIESEL:
+ return carContext.getString(R.string.android_auto_fuel_type_biodiesel);
+ case FUEL_TYPE_E85:
+ return carContext.getString(R.string.android_auto_fuel_type_e85);
+ case FUEL_TYPE_LPG:
+ return carContext.getString(R.string.android_auto_fuel_type_lpg);
+ case FUEL_TYPE_CNG:
+ return carContext.getString(R.string.android_auto_fuel_type_cng);
+ case FUEL_TYPE_LNG:
+ return carContext.getString(R.string.android_auto_fuel_type_lng);
+ case FUEL_TYPE_ELECTRIC:
+ return carContext.getString(R.string.android_auto_fuel_type_electric);
+ case FUEL_TYPE_HYDROGEN:
+ return carContext.getString(R.string.android_auto_fuel_type_hydrogen);
+ case FUEL_TYPE_OTHER:
+ return carContext.getString(R.string.android_auto_fuel_type_other);
+ case FUEL_TYPE_UNKNOWN:
+ default:
+ return carContext.getString(R.string.android_auto_fuel_type_unknown);
+ }
+ }
+
+ /**
+ * Helper method to get the proper text representation of the connector type.
+ *
+ * @param carContext The calling {@link CarContext}
+ * @param conType The int representation of the connector type.
+ *
+ * @return The string representation of the provided
+ * {@link androidx.car.app.hardware.info.EnergyProfile.EvConnectorType}, defaults to
+ * {@link androidx.car.app.hardware.info.EnergyProfile#EVCONNECTOR_TYPE_UNKNOWN}.
+ */
+ public static String getEvConnectorTypeString(final CarContext carContext, final int conType) {
+ switch (conType) {
+ case EVCONNECTOR_TYPE_J1772:
+ return carContext.getString(R.string.android_auto_ev_connector_j1772);
+ case EVCONNECTOR_TYPE_MENNEKES:
+ return carContext.getString(R.string.android_auto_ev_connector_mennekes);
+ case EVCONNECTOR_TYPE_CHADEMO:
+ return carContext.getString(R.string.android_auto_ev_connector_chademo);
+ case EVCONNECTOR_TYPE_COMBO_1:
+ return carContext.getString(R.string.android_auto_ev_connector_combo_1);
+ case EVCONNECTOR_TYPE_COMBO_2:
+ return carContext.getString(R.string.android_auto_ev_connector_combo_2);
+ case EVCONNECTOR_TYPE_TESLA_ROADSTER:
+ return carContext.getString(R.string.android_auto_ev_connector_tesla_roadster);
+ case EVCONNECTOR_TYPE_TESLA_HPWC:
+ return carContext.getString(R.string.android_auto_ev_connector_tesla_hpwc);
+ case EVCONNECTOR_TYPE_TESLA_SUPERCHARGER:
+ return carContext.getString(R.string.android_auto_ev_connector_tesla_supercharger);
+ case EVCONNECTOR_TYPE_GBT:
+ return carContext.getString(R.string.android_auto_ev_connector_gbt);
+ case EVCONNECTOR_TYPE_GBT_DC:
+ return carContext.getString(R.string.android_auto_ev_connector_gbt_dc);
+ case EVCONNECTOR_TYPE_SCAME:
+ return carContext.getString(R.string.android_auto_ev_connector_scame);
+ case EVCONNECTOR_TYPE_OTHER:
+ return carContext.getString(R.string.android_auto_ev_connector_other);
+ case EVCONNECTOR_TYPE_UNKNOWN:
+ default:
+ return carContext.getString(R.string.android_auto_ev_connector_unknown);
+
+ }
+ }
}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/auto/utils/CarInfoCallbackRepository.java b/app/src/main/java/com/aldo/apps/familyhelpers/auto/utils/CarInfoCallbackRepository.java
new file mode 100644
index 0000000..7511c75
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/auto/utils/CarInfoCallbackRepository.java
@@ -0,0 +1,71 @@
+package com.aldo.apps.familyhelpers.auto.utils;
+
+import android.util.Log;
+
+import com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+/**
+ * Helper class offering the {@link BehaviorSubject}s for the different supported values and utility
+ * methods to access them.
+ */
+public class CarInfoCallbackRepository {
+
+ /**
+ * Tag for debugging purpose.
+ */
+ private static final String TAG = "CarInfoCallbackRepository";
+
+ /**
+ * The {@link Map} holding a mapping of {@link SupportedCarInfo} to actual {@link BehaviorSubject}.
+ */
+ private Map> mObservableMap = new HashMap<>();
+
+ /**
+ * C'Tor.
+ */
+ public CarInfoCallbackRepository() {
+ Log.d(TAG, "CarInfoCallbackRepository: Creating all observables.");
+ for (final SupportedCarInfo supportedCarInfo : SupportedCarInfo.values()) {
+ mObservableMap.put(supportedCarInfo, BehaviorSubject.create());
+ }
+ }
+
+ /**
+ * Invoked when the to be shown message has changed, will call onNext on the respective
+ * {@link BehaviorSubject}.
+ *
+ * @param supportedCarInfo The {@link SupportedCarInfo} that changed.
+ * @param newMessage The new message to be shown.
+ */
+ public void onMessageChanged(final SupportedCarInfo supportedCarInfo, final String newMessage) {
+ Log.d(TAG, "onDataChanged() called with: supportedCarInfo = [" + supportedCarInfo
+ + "], newMessage = [" + newMessage + "]");
+ final BehaviorSubject topicObservable = mObservableMap.get(supportedCarInfo);
+ if (topicObservable == null) {
+ Log.w(TAG, "onDataChanged: No observable, cannot update value for [" + supportedCarInfo + "]");
+ return;
+ }
+ topicObservable.onNext(newMessage);
+ }
+
+ /**
+ * Returns the message observable for the given topic.
+ *
+ * @param carInfo The {@link SupportedCarInfo}.
+ *
+ * @return The message to be shown.
+ */
+ public BehaviorSubject getObservableForTopic(final SupportedCarInfo carInfo) {
+ final BehaviorSubject topicObservable = mObservableMap.get(carInfo);
+ if (topicObservable == null) {
+ Log.w(TAG, "onDataChanged: No observable, return empty observable for [" + carInfo + "]");
+ return BehaviorSubject.createDefault("");
+ }
+ return topicObservable;
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/ActiveShareAdapter.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/ActiveShareAdapter.java
index 1f9a38e..a135e5b 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/ui/ActiveShareAdapter.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/ActiveShareAdapter.java
@@ -103,15 +103,6 @@ public class ActiveShareAdapter extends RecyclerView.Adapter
+
+
diff --git a/app/src/main/res/drawable/ic_ev_connector.xml b/app/src/main/res/drawable/ic_ev_connector.xml
new file mode 100644
index 0000000..3b041ad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_ev_connector.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_fuel_percentage.xml b/app/src/main/res/drawable/ic_fuel_percentage.xml
new file mode 100644
index 0000000..14ba7e3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fuel_percentage.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_low_energy.xml b/app/src/main/res/drawable/ic_low_energy.xml
new file mode 100644
index 0000000..9fbed90
--- /dev/null
+++ b/app/src/main/res/drawable/ic_low_energy.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_mileage.xml b/app/src/main/res/drawable/ic_mileage.xml
new file mode 100644
index 0000000..d056956
--- /dev/null
+++ b/app/src/main/res/drawable/ic_mileage.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_range_remaining.xml b/app/src/main/res/drawable/ic_range_remaining.xml
new file mode 100644
index 0000000..8cf146f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_range_remaining.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_speed.xml b/app/src/main/res/drawable/ic_speed.xml
new file mode 100644
index 0000000..56f4405
--- /dev/null
+++ b/app/src/main/res/drawable/ic_speed.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_toll_card.xml b/app/src/main/res/drawable/ic_toll_card.xml
new file mode 100644
index 0000000..f072fc5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_toll_card.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0d62cc5..cee5421 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -107,4 +107,94 @@
Location
CarInfo
+
+ Value not yet received
+ Requested value is currently unavailable
+ Requested value is not implemented
+ Requested value state is currently unknown
+
+ Raw Vehicle Speed
+ Display Vehicle Speed
+ Current Raw Speed is %.2f %s
+ Current Display Speed is %.2f %s
+ m/s
+ Mph
+ km/h
+ m
+ mi
+ km
+ l
+ ml
+ gal
+ imp. gal
+
+ Toll Card
+ The current state of the TollCard is %s
+ UNKNOWN
+ VALID
+ INVALID
+ NOT_INSERTED
+
+ Energy
+ Battery \%
+ Fuel \%
+ Range Remaining
+ Is low Energy?
+ The battery is currently at %.2f\%
+ The current fuel level is at %.2f\%
+ The car is currently in energy warning more
+ The care operates normal and energy is fine
+ Range remaining: %.2f %s
+
+ ChargePort Connection
+ Charge Port is currently connected
+ Charge Port is currently disconnected
+ Charge Port Open
+ Charge Port is currently open
+ Charge Port is currently closed
+
+ Odometer
+ Current Odometer Value is %.2f %s
+
+ Manufacturer
+ The Car-Manufacturer is %s
+ Model Name
+ The Car-Name is %s
+ Model Year
+ The Car was built in %d
+
+ Fuel Type
+ Supported Fuel Types: %s
+ EV-Connectors
+ Supported Connector Types: %s
+
+ Unknown
+ Unleaded Gasoline
+ Leaded Gasoline
+ #1 Grade Diesel
+ #2 Grade Diesel
+ Biodiesel
+ 85% ethanol/gasoline blend
+ Liquified Petroleum Gas
+ Compressed Natural Gas
+ Liquified Natural Gas
+ Electric
+ Hydrogen Fuel Cell
+ Other
+
+ Unknown
+ SAE J1772
+ IEC 62196 Type 2
+ CHAdeMo Fast Charger
+ Combined Charging System Combo 1
+ Combined Charging System Combo 2
+ Tesla Roadster
+ Tesla High Power Wall Charger
+ Tesla Supercharger
+ GBT_AC Fast Charging Standard
+ GBT_DC Fast Charging Standard
+ IEC_TYPE_3_AC connector
+ Other
+
+
\ No newline at end of file