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