[AA] Added CarInfo tab to Android Auto

Added a new tab for subscribing to and showing CarProperties
within Android Auto.
The UI handling is a bit sluggish still, but can potentially be
imporved further.
This commit is contained in:
Alexander Dörflinger
2025-04-22 13:45:44 +02:00
parent a74c60a143
commit d1432ec0d6
29 changed files with 2126 additions and 72 deletions

View File

@@ -5,14 +5,6 @@ plugins {
} }
android { android {
signingConfigs {
debug {
storeFile file('/home/aldo270717/AndroidStudioProjects/AldoApps_KeystoreUpload.jks')
storePassword 'p49Js5ewYPZbkvhj'
keyAlias 'upload'
keyPassword 'p49Js5ewYPZbkvhj'
}
}
namespace 'com.aldo.apps.familyhelpers' namespace 'com.aldo.apps.familyhelpers'
compileSdk 34 compileSdk 34
@@ -29,9 +21,9 @@ android {
applicationId "com.aldo.apps.familyhelpers" applicationId "com.aldo.apps.familyhelpers"
minSdk 32 minSdk 32
targetSdk 34 targetSdk 34
versionCode 8
versionName "0.2.3"
android.buildFeatures.buildConfig true android.buildFeatures.buildConfig true
versionCode 12
versionName "0.2.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -40,6 +32,7 @@ android {
signingConfig signingConfigs.myCustomKeystore signingConfig signingConfigs.myCustomKeystore
buildConfigField "String", "GOOGLE_MAPS_API_KEY", "\"${googleMapsApiKeyRelease}\"" buildConfigField "String", "GOOGLE_MAPS_API_KEY", "\"${googleMapsApiKeyRelease}\""
manifestPlaceholders.google_maps_api_key = googleMapsApiKeyRelease manifestPlaceholders.google_maps_api_key = googleMapsApiKeyRelease
minifyEnabled false
} }
release { release {
signingConfig signingConfigs.myCustomKeystore signingConfig signingConfigs.myCustomKeystore

View File

@@ -16,6 +16,8 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.car.permission.CAR_INFO"/> <uses-permission android:name="android.car.permission.CAR_INFO"/>
<uses-permission android:name="com.google.android.gms.permission.CAR_SPEED" /> <uses-permission android:name="com.google.android.gms.permission.CAR_SPEED" />
<uses-permission android:name="com.google.android.gms.permission.CAR_FUEL" />
<uses-permission android:name="com.google.android.gms.permission.CAR_MILEAGE" />
<uses-sdk android:minSdkVersion="32" <uses-sdk android:minSdkVersion="32"
android:targetSdkVersion="34" /> android:targetSdkVersion="34" />
@@ -46,7 +48,7 @@
android:resource="@xml/automotive_app_desc"/> android:resource="@xml/automotive_app_desc"/>
<meta-data <meta-data
android:name="androidx.car.app.minCarApiLevel" android:name="androidx.car.app.minCarApiLevel"
android:value="1" /> android:value="3" />
<activity <activity
android:name=".FlashlightActivity" android:name=".FlashlightActivity"

View File

@@ -2,6 +2,8 @@ package com.aldo.apps.familyhelpers;
import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.POST_NOTIFICATIONS; import static android.Manifest.permission.POST_NOTIFICATIONS;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_FUEL_PERMISSION;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_MILEAGE_PERMISSION;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_SPEED_PERMISSION; import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_SPEED_PERMISSION;
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SIGN_IN_PROVIDERS; import static com.aldo.apps.familyhelpers.utils.GlobalConstants.SIGN_IN_PROVIDERS;
@@ -41,6 +43,9 @@ public class HelperGridActivity extends AppCompatActivity {
*/ */
private static final String TAG = "HelperGridActivity"; private static final String TAG = "HelperGridActivity";
/**
* The {@link LocationHelper} class to share and get location updates.
*/
private LocationHelper mLocationHelper; private LocationHelper mLocationHelper;
/** /**
@@ -170,6 +175,12 @@ public class HelperGridActivity extends AppCompatActivity {
if (!requestVehicleSpeedPermission()) { if (!requestVehicleSpeedPermission()) {
Log.d(TAG, "launchHelper: Permission not granted cannot read vehicle speed."); Log.d(TAG, "launchHelper: Permission not granted cannot read vehicle speed.");
} }
if (!requestVehicleFuelPermission()) {
Log.d(TAG, "launchHelper: Permission not granted, cannot read fuel");
}
if (!requestVehicleMileagePermission()) {
Log.d(TAG, "launchHelper: Permission not granted, cannot read mileage");
}
if (mLocationHelper.requestLocationPermissions(HelperGridActivity.this) if (mLocationHelper.requestLocationPermissions(HelperGridActivity.this)
&& mLocationHelper.requestBackgroundLocationPermission(HelperGridActivity.this)) { && mLocationHelper.requestBackgroundLocationPermission(HelperGridActivity.this)) {
Log.d(TAG, "launchHelper: Permission already granted"); Log.d(TAG, "launchHelper: Permission already granted");
@@ -238,7 +249,7 @@ public class HelperGridActivity extends AppCompatActivity {
/** /**
* Helper method to request the VehicleSpeed permission before launching the ShareLocationActivity. * Helper method to request the VehicleSpeed permission before launching the ShareLocationActivity.
* *
* @return true if granted, flase otherwise. * @return true if granted, false otherwise.
*/ */
private boolean requestVehicleSpeedPermission() { private boolean requestVehicleSpeedPermission() {
if (ContextCompat.checkSelfPermission(this, CAR_SPEED_PERMISSION) == if (ContextCompat.checkSelfPermission(this, CAR_SPEED_PERMISSION) ==
@@ -255,6 +266,46 @@ public class HelperGridActivity extends AppCompatActivity {
} }
} }
/**
* Helper method to request the VehicleFuel permission before launching the ShareLocationActivity.
*
* @return true if granted, false otherwise.
*/
private boolean requestVehicleFuelPermission() {
if (ContextCompat.checkSelfPermission(this, CAR_FUEL_PERMISSION) ==
PackageManager.PERMISSION_GRANTED) {
// Permission already granted
return true;
} else {
if (shouldShowRequestPermissionRationale(CAR_FUEL_PERMISSION)) {
Toast.makeText(HelperGridActivity.this, "Consider granting this for the vehicle info to be readable",
Toast.LENGTH_LONG).show();
}
mRequestPermissionLauncher.launch(CAR_FUEL_PERMISSION);
return false;
}
}
/**
* Helper method to request the VehicleMileage permission before launching the ShareLocationActivity.
*
* @return true if granted, false otherwise.
*/
private boolean requestVehicleMileagePermission() {
if (ContextCompat.checkSelfPermission(this, CAR_MILEAGE_PERMISSION) ==
PackageManager.PERMISSION_GRANTED) {
// Permission already granted
return true;
} else {
if (shouldShowRequestPermissionRationale(CAR_MILEAGE_PERMISSION)) {
Toast.makeText(HelperGridActivity.this, "Consider granting this for the vehicle info to be readable",
Toast.LENGTH_LONG).show();
}
mRequestPermissionLauncher.launch(CAR_MILEAGE_PERMISSION);
return false;
}
}
/** /**
* Helper method to request the CameraPermission as it is required to access the * Helper method to request the CameraPermission as it is required to access the
* flashlight settings. * flashlight settings.

View File

@@ -7,6 +7,7 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.car.app.CarContext; import androidx.car.app.CarContext;
import androidx.car.app.Screen; import androidx.car.app.Screen;
import androidx.car.app.annotations.ExperimentalCarApi; import androidx.car.app.annotations.ExperimentalCarApi;
@@ -19,9 +20,11 @@ import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import com.aldo.apps.familyhelpers.auto.model.VehicleInformationHelper;
import com.aldo.apps.familyhelpers.auto.screencontent.CarInfoTab; import com.aldo.apps.familyhelpers.auto.screencontent.CarInfoTab;
import com.aldo.apps.familyhelpers.auto.screencontent.IScreenContentChangedListener; import com.aldo.apps.familyhelpers.auto.screencontent.IScreenContentChangedListener;
import com.aldo.apps.familyhelpers.auto.screencontent.ShareLocationTab; import com.aldo.apps.familyhelpers.auto.screencontent.ShareLocationTab;
import com.aldo.apps.familyhelpers.auto.utils.CarInfoCallbackRepository;
/** /**
* The {@link AndroidAutoLandingPage} to be shown as a starting point for Android Auto. * The {@link AndroidAutoLandingPage} to be shown as a starting point for Android Auto.
@@ -50,32 +53,50 @@ public class AndroidAutoLandingPage extends Screen
private String mCurrentlySelectedTabId = TAB_KEY_LOCATION; private String mCurrentlySelectedTabId = TAB_KEY_LOCATION;
/** /**
* C'Tor. * {@link VehicleInformationHelper} to read vehicle information in AA use case.
*
* @param carContext The {@link CarContext} this was instantiated in.
*/ */
protected AndroidAutoLandingPage(@NonNull CarContext carContext) { private final VehicleInformationHelper mVehicleInformationHelper;
super(carContext);
Log.d(TAG, "AndroidAutoLandingPage() called with: carContext = [" + carContext + "]");
getLifecycle().addObserver(mLifecycleObserver);
mShareLocationTab = new ShareLocationTab(carContext, this);
mCarInfoTab = new CarInfoTab(carContext, this);
}
/** /**
* {@link LifecycleObserver} to stop listening to CarInformation updates when AA ends. * The {@link CarInfoCallbackRepository} to hold all registered callbacks.
*/ */
private LifecycleObserver mLifecycleObserver = new LifecycleEventObserver() { private final CarInfoCallbackRepository mCallbackRepo = new CarInfoCallbackRepository();
/**
* {@link LifecycleObserver} to stop listening to Vehicle Speed updates when AA ends.
*/
private final LifecycleObserver mLifecycleObserver = new LifecycleEventObserver() {
@OptIn(markerClass = ExperimentalCarApi.class)
@Override @Override
public void onStateChanged(@NonNull final LifecycleOwner lifecycleOwner, public void onStateChanged(@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final Lifecycle.Event event) { @NonNull final Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_STOP) { if (event == Lifecycle.Event.ON_STOP) {
Log.d(TAG, "onStateChanged: Activity stopped, unsubscribe"); Log.d(TAG, "onStateChanged: Activity stopped, unsubscribe");
mVehicleInformationHelper.unsubscribeFromAllCarInfo();
} else if (event == Lifecycle.Event.ON_RESUME) {
Log.d(TAG, "onStateChanged: Activity resumed, subscribing");
mVehicleInformationHelper.subscribeToAllCarInfo(getCarContext());
} }
} }
}; };
/**
* C'Tor.
*
* @param carContext The {@link CarContext} this was instantiated in.
*/
@OptIn(markerClass = ExperimentalCarApi.class)
protected AndroidAutoLandingPage(@NonNull CarContext carContext) {
super(carContext);
Log.d(TAG, "AndroidAutoLandingPage() called with: carContext = [" + carContext + "]");
getLifecycle().addObserver(mLifecycleObserver);
mShareLocationTab = new ShareLocationTab(carContext, this);
mVehicleInformationHelper = new VehicleInformationHelper(carContext, mCallbackRepo);
mCarInfoTab = new CarInfoTab(carContext, this, mCallbackRepo);
getLifecycle().addObserver(mLifecycleObserver);
}
@ExperimentalCarApi @ExperimentalCarApi
@NonNull @NonNull
@Override @Override
@@ -111,10 +132,14 @@ public class AndroidAutoLandingPage extends Screen
invalidate(); invalidate();
} }
@OptIn(markerClass = ExperimentalCarApi.class)
@Override @Override
public void onScreenContentChanged(final String contentId) { public void onScreenContentChanged(final String contentId) {
if (TextUtils.equals(contentId, mCurrentlySelectedTabId)) { if (TextUtils.equals(contentId, mCurrentlySelectedTabId)) {
Log.d(TAG, "onScreenContentChanged: Current screen [" + contentId + "] changed"); Log.d(TAG, "onScreenContentChanged: Current screen [" + contentId + "] changed");
if (TAB_KEY_CAR_INFO.equals(contentId)) {
mCarInfoTab.refreshTemplate();
}
invalidate(); invalidate();
} }
} }

View File

@@ -0,0 +1,27 @@
package com.aldo.apps.familyhelpers.auto.model;
/**
* Enum representing the type of CarProperty value.
*/
public enum CarPropertyType {
/**
* {@link androidx.car.app.hardware.common.CarValue} holds a float value.
*/
FLOAT,
/**
* {@link androidx.car.app.hardware.common.CarValue} holds an int value.
*/
INT,
/**
* {@link androidx.car.app.hardware.common.CarValue} holds a String value.
*/
STRING,
/**
* {@link androidx.car.app.hardware.common.CarValue} holds a boolean value.
*/
BOOL;
}

View File

@@ -1,18 +1,55 @@
package com.aldo.apps.familyhelpers.auto.model; package com.aldo.apps.familyhelpers.auto.model;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.BATTERY_PERCENTAGE;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.CONNECTOR_TYPE;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.DISPLAY_SPEED;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.EV_CONNECTOR_OPEN;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.EV_CONNECTOR_STATE;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.FUEL_PERCENTAGE;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.FUEL_TYPE;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.LOW_ENERGY;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.MANUFACTURER;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.MILEAGE;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.NAME;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.RANGE_REMAINING;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.RAW_SPEED;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.TOLL_CARD;
import static com.aldo.apps.familyhelpers.auto.model.carinfo.SupportedCarInfo.YEAR;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_FUEL_PERMISSION;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_MILEAGE_PERMISSION;
import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_SPEED_PERMISSION; import static com.aldo.apps.familyhelpers.auto.utils.AndroidAutoConstants.CAR_SPEED_PERMISSION;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.car.app.CarContext; import androidx.car.app.CarContext;
import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.hardware.CarHardwareManager; import androidx.car.app.hardware.CarHardwareManager;
import androidx.car.app.hardware.common.OnCarDataAvailableListener;
import androidx.car.app.hardware.info.CarInfo; import androidx.car.app.hardware.info.CarInfo;
import androidx.car.app.hardware.info.EnergyLevel;
import androidx.car.app.hardware.info.EnergyProfile;
import androidx.car.app.hardware.info.EvStatus;
import androidx.car.app.hardware.info.Mileage;
import androidx.car.app.hardware.info.Model;
import androidx.car.app.hardware.info.Speed; import androidx.car.app.hardware.info.Speed;
import androidx.car.app.hardware.info.TollCard;
import com.aldo.apps.familyhelpers.auto.model.carinfo.AbstractCarInfo;
import com.aldo.apps.familyhelpers.auto.model.carinfo.CarInfoEnergyLevel;
import com.aldo.apps.familyhelpers.auto.model.carinfo.CarInfoEnergyProfile;
import com.aldo.apps.familyhelpers.auto.model.carinfo.CarInfoEvStatus;
import com.aldo.apps.familyhelpers.auto.model.carinfo.CarInfoMileage;
import com.aldo.apps.familyhelpers.auto.model.carinfo.CarInfoModel;
import com.aldo.apps.familyhelpers.auto.model.carinfo.CarInfoSpeed;
import com.aldo.apps.familyhelpers.auto.model.carinfo.CarInfoTollCard;
import com.aldo.apps.familyhelpers.auto.utils.CarInfoCallbackRepository;
import com.aldo.apps.familyhelpers.workers.LocationHelper; import com.aldo.apps.familyhelpers.workers.LocationHelper;
import java.lang.ref.WeakReference;
/** /**
* Helper class to retrieve a set of {@link CarInfo} to be used in other functionalities. * Helper class to retrieve a set of {@link CarInfo} to be used in other functionalities.
*/ */
@@ -33,12 +70,91 @@ public class VehicleInformationHelper {
*/ */
private LocationHelper mLocationHelper; private LocationHelper mLocationHelper;
/**
* {@link WeakReference} to the calling {@link CarContext}.
*/
private final WeakReference<CarContext> mContextRef;
/**
* The {@link AbstractCarInfo} implementation for the {@link Speed} value.
*/
private final AbstractCarInfo<Speed> mSpeedValue = new CarInfoSpeed();
/**
* The {@link AbstractCarInfo} implementation for the {@link TollCard} value.
*/
private final AbstractCarInfo<TollCard> mTollCardValue = new CarInfoTollCard();
/**
* The {@link AbstractCarInfo} implementation for the {@link EnergyLevel} value.
*/
private final AbstractCarInfo<EnergyLevel> mEnergyLevel = new CarInfoEnergyLevel();
/**
* The {@link AbstractCarInfo} implementation for the {@link EvStatus} value.
*/
private final AbstractCarInfo<EvStatus> mEvStatus = new CarInfoEvStatus();
/**
* The {@link AbstractCarInfo} implementation for the {@link Mileage} value.
*/
private final AbstractCarInfo<Mileage> mMileage = new CarInfoMileage();
/**
* The {@link AbstractCarInfo} implementation for the {@link Model} value.
*/
private final AbstractCarInfo<Model> mModel = new CarInfoModel();
/**
* The {@link AbstractCarInfo} implementation for the {@link EnergyProfile} value.
*/
private final AbstractCarInfo<EnergyProfile> mEnergyProfile = new CarInfoEnergyProfile();
private final CarInfoCallbackRepository mCallbacks;
/**
* The {@link OnCarDataAvailableListener} to be invoked when the {@link Speed} value changed
*/
private final OnCarDataAvailableListener<Speed> mSpeedListener = this::handleReceivedSpeed;
/**
* The {@link OnCarDataAvailableListener} to be invoked when the {@link EnergyProfile} value changed
*/
private final OnCarDataAvailableListener<EnergyProfile> mEnergyProfileListener = this::handleEnergyProfile;
/**
* The {@link OnCarDataAvailableListener} to be invoked when the {@link Model} value changed
*/
private final OnCarDataAvailableListener<Model> mModelListener = this::handleModel;
/**
* The {@link OnCarDataAvailableListener} to be invoked when the {@link EnergyLevel} value changed
*/
private final OnCarDataAvailableListener<EnergyLevel> mEnergyLevelListener = this::handleReceivedEnergyLevel;
/**
* The {@link OnCarDataAvailableListener} to be invoked when the {@link EvStatus} value changed
*/
private final OnCarDataAvailableListener<EvStatus> mEvStatusListener = this::handleReceivedEvStatus;
/**
* The {@link OnCarDataAvailableListener} to be invoked when the {@link Mileage} value changed
*/
private final OnCarDataAvailableListener<Mileage> mMileageListener = this::handleReceivedMileage;
/**
* The {@link OnCarDataAvailableListener} to be invoked when the {@link TollCard} value changed
*/
private final OnCarDataAvailableListener<TollCard> mTollCardListener = this::handleReceivedTollInformation;
/** /**
* C'Tor. * C'Tor.
* *
* @param carContext The {@link CarContext} from which this was instantiated. * @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) { if (carContext.checkCallingOrSelfPermission(CAR_SPEED_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "VehicleInformationHelper: Nothing to do, permission not granted"); Log.w(TAG, "VehicleInformationHelper: Nothing to do, permission not granted");
return; return;
@@ -47,36 +163,199 @@ public class VehicleInformationHelper {
} }
/** /**
* Subscribe to the Vehicle Speed and handle it. * Helper method to subscribe to all available {@link CarInfo} values.
* * @param carContext
* @param carContext The {@link CarContext} from which this was called.
*/ */
public void subscribeToSpeedValues(final CarContext carContext) { @OptIn(markerClass = ExperimentalCarApi.class)
if (carContext.checkCallingOrSelfPermission(CAR_SPEED_PERMISSION) == PackageManager.PERMISSION_GRANTED) { public void subscribeToAllCarInfo(final CarContext carContext) {
mCarInfo = carContext.getCarService(CarHardwareManager.class).getCarInfo(); mCarInfo = carContext.getCarService(CarHardwareManager.class).getCarInfo();
if (mCarInfo == null) { if (mCarInfo == null) {
Log.e(TAG, "subscribeToSpeedValues: No car info available"); Log.d(TAG, "subscribeToAllCarInfo: No CarInfo available, return...");
return; return;
} }
mCarInfo.addSpeedListener(carContext.getMainExecutor(), this::handleReceivedSpeed); mCarInfo.addTollListener(carContext.getMainExecutor(), mTollCardListener);
if (carContext.checkCallingOrSelfPermission(CAR_SPEED_PERMISSION) == PackageManager.PERMISSION_GRANTED) {
mCarInfo.addSpeedListener(carContext.getMainExecutor(), mSpeedListener);
} else { } else {
Log.w(TAG, "subscribeToSpeedValues: No Speed permission granted, cannot subscribe"); 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() { @ExperimentalCarApi
mCarInfo.removeSpeedListener(this::handleReceivedSpeed); 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) { private void handleReceivedSpeed(@NonNull final Speed speed) {
mLocationHelper.updateVehicleSpeed(data.getDisplaySpeedMetersPerSecond()); 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);
} }
} }

View File

@@ -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 <T>
*/
public abstract class AbstractCarInfo<T> {
/**
* 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;
}
}

View File

@@ -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<EnergyLevel>{
/**
* 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);
}
}

View File

@@ -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<EnergyProfile> {
/**
* 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<Integer> 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<Integer> 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;
}
}

View File

@@ -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<EvStatus> {
/**
* 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);
}
}

View File

@@ -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<Mileage> {
/**
* 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);
}
}

View File

@@ -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<Model> {
/**
* 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);
}
}

View File

@@ -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<Speed> {
/**
* 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);
}
}

View File

@@ -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<TollCard> {
/**
* 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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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 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.CarContext;
import androidx.car.app.CarToast; import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.model.ItemList; import androidx.car.app.model.ItemList;
import androidx.car.app.model.ListTemplate; import androidx.car.app.model.ListTemplate;
import androidx.car.app.model.Row; import androidx.car.app.model.Row;
import androidx.car.app.model.Tab; import androidx.car.app.model.Tab;
import androidx.car.app.model.Template; import androidx.car.app.model.Template;
import androidx.car.app.model.Toggle;
import com.aldo.apps.familyhelpers.R; 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 { public class CarInfoTab extends AbstractTabContent {
/** /**
@@ -20,25 +32,52 @@ public class CarInfoTab extends AbstractTabContent {
*/ */
private static final String TAG = "CarInfoTab"; 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. * The {@link Template} for the car info tab.
*/ */
private Template mCarInfoTab; 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<SupportedCarInfo, Disposable> mSubscriptionMap = new HashMap<>();
/**
* The {@link Map} holding the last received message for each {@link SupportedCarInfo} to be
* read from.
*/
private final Map<SupportedCarInfo, String> mMessageMap = new HashMap<>();
/**
* The currently selected {@link SupportedCarInfo} item.
*/
private SupportedCarInfo mCurrentlySelectedItem = SupportedCarInfo.RAW_SPEED;
/** /**
* C'Tor. * C'Tor.
* *
* @param carContext Calling {@link CarContext}. * @param carContext Calling {@link CarContext}.
* @param listener The {@link IScreenContentChangedListener} to be invoked if page content changed. * @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); super(carContext, listener);
mVehicleInformationHelper = new VehicleInformationHelper(carContext); mCallbacks = callbacks;
mContentChangeListener = listener;
refreshTemplate(); refreshTemplate();
} }
@@ -57,16 +96,133 @@ public class CarInfoTab extends AbstractTabContent {
} }
@Override @Override
@ExperimentalCarApi
public void refreshTemplate() { public void refreshTemplate() {
final Row vehicleInfo = new Row.Builder() final Row rawSpeed = new Row.Builder()
.setTitle(getString(R.string.android_auto_vehicle_info_title)) .setTitle(getString(R.string.android_auto_raw_speed_title))
.setImage(getIconForResource(R.drawable.ic_car_info), Row.IMAGE_TYPE_ICON) .setImage(getIconForResource(R.drawable.ic_speed), Row.IMAGE_TYPE_ICON)
.setOnClickListener(this::startCarInfoScreen) .addText(mMessageMap.getOrDefault(SupportedCarInfo.RAW_SPEED, ""))
.setToggle(createAndAssigneToggle(SupportedCarInfo.RAW_SPEED))
.build(); .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() final ItemList carList = new ItemList.Builder()
.setNoItemsMessage(getString(R.string.android_auto_share_location_no_items_yet)) .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(); .build();
mCarInfoTab = new ListTemplate.Builder() 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() { private Toggle createAndAssigneToggle(final SupportedCarInfo supportedCarInfo) {
CarToast.makeText(getCarContext(), "Not yet implemented", CarToast.LENGTH_LONG).show(); 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 + "]");
} }
} }

View File

@@ -1,5 +1,39 @@
package com.aldo.apps.familyhelpers.auto.utils; 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. * Constants class for Android Auto.
*/ */
@@ -19,4 +53,360 @@ public class AndroidAutoConstants {
* Name of the permission for the VehicleSpeed. * Name of the permission for the VehicleSpeed.
*/ */
public static final String CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED"; 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<Integer>} representing the unit.
*
* @return The {@link CarUnit} of the displaySpeed, defaults to {@link CarUnit#METERS_PER_SEC}.
*/
public static int getDisplaySpeedUnit(final CarValue<Integer> 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<Integer>} representing the unit.
*
* @return The {@link CarUnit} of the displayDistance, defaults to {@link CarUnit#METER}.
*/
public static int getDisplayDistanceUnit(final CarValue<Integer> 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<Integer>} representing the unit.
*
* @return The {@link CarUnit} of the displayVolume, defaults to {@link CarUnit#LITER}.
*/
@ExperimentalCarApi
public static int getDisplayVolumeUnit(final CarValue<Integer> 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<Float> 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<Integer> 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<Boolean> 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);
}
}
} }

View File

@@ -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<SupportedCarInfo, BehaviorSubject<String>> 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<String> 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<String> getObservableForTopic(final SupportedCarInfo carInfo) {
final BehaviorSubject<String> topicObservable = mObservableMap.get(carInfo);
if (topicObservable == null) {
Log.w(TAG, "onDataChanged: No observable, return empty observable for [" + carInfo + "]");
return BehaviorSubject.createDefault("");
}
return topicObservable;
}
}

View File

@@ -103,15 +103,6 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
return mCurrentlySelectedSubject; return mCurrentlySelectedSubject;
} }
/**
* Helper method to clear the selected state of all views, when a new one is tapped.
*/
private void clearFocusedView() {
for (final View view : mAllItems) {
view.setSelected(false);
}
}
/** /**
* {@link RecyclerView.ViewHolder} implementation for the {@link ActiveShareViewHolder}. * {@link RecyclerView.ViewHolder} implementation for the {@link ActiveShareViewHolder}.
*/ */
@@ -179,5 +170,14 @@ public class ActiveShareAdapter extends RecyclerView.Adapter<ActiveShareAdapter.
.placeholder(R.drawable.ic_unknown_user) .placeholder(R.drawable.ic_unknown_user)
.into(mProfilePicture); .into(mProfilePicture);
} }
/**
* Helper method to clear the selected state of all views, when a new one is tapped.
*/
private void clearFocusedView() {
for (final View view : mAllItems) {
view.setSelected(false);
}
}
} }
} }

View File

@@ -0,0 +1,4 @@
<vector android:height="200dp" android:viewportHeight="16"
android:viewportWidth="16" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#2e3434" android:pathData="m7,0c-1,0 -1,1 -1,1v1h-1s-0.707,-0.016 -1.445,0.355c-0.742,0.371 -1.555,1.313 -1.555,2.645v8s-0.016,0.707 0.355,1.449c0.371,0.738 1.313,1.551 2.645,1.551h6s0.707,0.016 1.449,-0.355c0.738,-0.371 1.551,-1.313 1.551,-2.645v-8c0,-1.332 -0.813,-2.273 -1.551,-2.645c-0.742,-0.371 -1.449,-0.355 -1.449,-0.355h-1v-1c0,-1 -1,-1 -1,-1zM8,4h3c0.555,0 1,0.445 1,1v8c0,0.555 -0.445,1 -1,1h-6c-0.555,0 -1,-0.445 -1,-1v-8c0,-0.555 0.445,-1 1,-1zM5,7v6h6v-6zM5,7"/>
</vector>

View File

@@ -0,0 +1,4 @@
<vector android:height="200dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,2A10,10 0,1 0,22 12,10 10,0 0,0 12,2ZM12,20a8,8 0,1 1,8 -8A8,8 0,0 1,12 20ZM18,12.75a1.25,1.25 0,1 1,-1.25 -1.25A1.25,1.25 0,0 1,18 12.75ZM13.5,16.5A1.5,1.5 0,1 1,12 15,1.5 1.5,0 0,1 13.5,16.5ZM8.5,12.75A1.25,1.25 0,1 1,7.25 11.5,1.25 1.25,0 0,1 8.5,12.75ZM9.5,10A1.5,1.5 0,1 1,11 8.5,1.5 1.5,0 0,1 9.5,10ZM14.5,10A1.5,1.5 0,1 1,16 8.5,1.5 1.5,0 0,1 14.5,10Z"/>
</vector>

View File

@@ -0,0 +1,4 @@
<vector android:height="200dp" android:viewportHeight="15"
android:viewportWidth="15" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M13,6L13,6v5.5c0,0.276 -0.224,0.5 -0.5,0.5S12,11.776 12,11.5v-2C12,8.672 11.328,8 10.5,8H9V2c0,-0.552 -0.448,-1 -1,-1H2C1.448,1 1,1.448 1,2v11c0,0.552 0.448,1 1,1h6c0.552,0 1,-0.448 1,-1V9h1.5C10.776,9 11,9.224 11,9.5v2c0,0.828 0.672,1.5 1.5,1.5s1.5,-0.672 1.5,-1.5V5c0,-0.552 -0.448,-1 -1,-1l0,0V2.49C12.995,2.218 12.772,2 12.5,2c-0.282,0.005 -0.506,0.237 -0.502,0.518C11.999,2.529 11.999,2.539 12,2.55V5C12,5.552 12.448,6 13,6s1,-0.448 1,-1s-0.448,-1 -1,-1M8,6.5C8,6.776 7.776,7 7.5,7h-5C2.224,7 2,6.776 2,6.5v-3C2,3.224 2.224,3 2.5,3h5C7.776,3 8,3.224 8,3.5V6.5z"/>
</vector>

View File

@@ -0,0 +1,14 @@
<vector android:height="200dp" android:viewportHeight="25"
android:viewportWidth="25" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="M18.15,17L3.5,17C3.22,17 3,16.78 3,16.5L3,8.5C3,8.22 3.22,8 3.5,8L18.15,8C18.43,8 18.65,8.22 18.65,8.5L18.65,16.5C18.65,16.78 18.42,17 18.15,17Z"
android:strokeColor="#0F0F0F" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1"/>
<path android:fillColor="#00000000"
android:pathData="M20.88,10.81L21.5,10.81C21.78,10.81 22,11.03 22,11.31L22,13.69C22,13.97 21.78,14.19 21.5,14.19L20.88,14.19"
android:strokeColor="#0F0F0F" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1"/>
<path android:fillColor="#00000000"
android:pathData="M5.23,10.25L5.23,14.75"
android:strokeColor="#0F0F0F" android:strokeLineCap="round" android:strokeWidth="1"/>
</vector>

View File

@@ -0,0 +1,4 @@
<vector android:height="200dp" android:viewportHeight="501.02"
android:viewportWidth="501.02" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000000" android:pathData="M242.1,115.92L242.1,115.92h-0.01V96.01V56.19h0.01h16.8v59.73h-0.02v0.01l-8.39,-0.01H242.1zM39.56,317.1l41.92,-7.39l16.91,-2.98v-0.01h0.01l-1.48,-8.33l-1.44,-8.19h-0.01v-0.01l-58.83,10.37L39.56,317.1zM411.27,169.23l-30.23,17.44v0.01l0,0l4.66,8.09l3.73,6.45l0,0l51.73,-29.86l-8.4,-14.55L411.27,169.23zM150.24,390.92l-6.62,-5.56l0,0l0,0l-38.4,45.75l12.88,10.81l16.71,-19.92l21.69,-25.84l0,0L150.24,390.92zM432.56,386.81l8.39,-14.56l-18.05,-10.42l-33.67,-19.46v0.01l0,0l-4.64,8.03l-3.75,6.51l0,0v0.01L432.56,386.81zM36.7,242.28l20.98,3.69l37.84,6.69v-0.01h0.01l2.5,-14.22l0.41,-2.33v-0.01l-58.83,-10.38L36.7,242.28zM461.04,226.16l-15.99,2.81l-42.84,7.54v0.01h-0.01l2.16,12.26l0.76,4.28l0,0v0.01l58.84,-10.37L461.04,226.16zM189.35,128.1v0.01l13.64,-4.97l2.14,-0.78h0.01l-20.42,-56.14l-15.79,5.76l2.91,8L189.35,128.1L189.35,128.1zM67.81,386.43l51.72,-29.86v-0.01l0,0l-6.47,-11.2l-1.93,-3.35l0,0l0,0l-51.73,29.86l8.4,14.55h0.01V386.43zM343.78,396.43l38.4,45.77L395.04,431.4v-0.01l-38.38,-45.76v0.01v-0.01l-8.93,7.49L343.78,396.43L343.78,396.43zM59.63,170.99L59.63,170.99l51.73,29.88l0,0l7.28,-12.61l1.12,-1.94v-0.01l-51.73,-29.86L59.63,170.99zM463.88,300.97L463.88,300.97l-58.82,-10.38l-0.63,3.55l-2.29,13l58.83,10.38L463.88,300.97zM118.41,101.05l-12.87,10.8l14.11,16.81l24.29,28.95l0,0l0,0l7.6,-6.38l5.26,-4.41l0,0l0.01,-0.01L118.41,101.05zM311.25,80.2L295.85,122.5l0.01,0.01l0,0l10.52,3.82l5.26,1.91l0,0h0.01l20.43,-56.13l-15.79,-5.74L311.25,80.2zM353.71,155.15l3.26,2.73l0,0l0,0l38.4,-45.77l-12.87,-10.79h-0.01l0,0l-38.4,45.75l0,0v0.01L353.71,155.15zM250.51,27.02C112.38,27.02 0,139.39 0,277.53c0,79.57 37.33,150.54 95.36,196.46h25.36C57.04,431.78 14.94,359.49 14.94,277.53c0,-129.9 105.67,-235.57 235.57,-235.57c129.9,0 235.57,105.67 235.57,235.57c0,81.96 -42.1,154.25 -105.79,196.46h25.36c58.02,-45.93 95.36,-116.89 95.36,-196.46C501.02,139.4 388.63,27.02 250.51,27.02zM201.61,387.23c5.26,0 7.88,-5.24 7.88,-15.7c0,-10.83 -2.57,-16.25 -7.71,-16.25c-5.42,0 -8.14,5.5 -8.14,16.52C193.63,382.09 196.29,387.23 201.61,387.23zM179.15,336.71h44.55v69.7h-44.55V336.71zM189.18,372.01c0,6.12 1.05,10.79 3.16,14.03c2.11,3.23 5.05,4.86 8.82,4.86c4.03,0 7.16,-1.7 9.41,-5.08c2.25,-3.4 3.37,-8.3 3.37,-14.73c0,-12.99 -3.98,-19.48 -11.93,-19.48c-4.16,0 -7.33,1.71 -9.53,5.17C190.28,360.21 189.18,365.29 189.18,372.01zM229.56,336.71h43.75v69.7H229.56V336.71zM241.05,389.33c1.74,1.04 4.36,1.57 7.87,1.57c4.17,0 7.46,-1.12 9.85,-3.37s3.59,-5.21 3.59,-8.89c0,-3.55 -1.11,-6.35 -3.33,-8.39c-2.23,-2.03 -5.38,-3.06 -9.44,-3.06c-0.99,0 -2.07,0.04 -3.23,0.11v-11.14h14.29v-3.92h-18.4V371.3c2.92,-0.21 4.91,-0.32 5.96,-0.32c3.14,0 5.54,0.7 7.21,2.09c1.68,1.4 2.52,3.35 2.52,5.83c0,2.52 -0.82,4.53 -2.47,6.05c-1.65,1.52 -3.81,2.28 -6.49,2.28c-2.69,0 -5.33,-0.84 -7.93,-2.52v4.61H241.05zM277.85,336.71h44.01v69.7h-44.01V336.71zM288.79,372.01c0,6.12 1.05,10.79 3.17,14.03c2.11,3.23 5.05,4.86 8.82,4.86c4.03,0 7.17,-1.7 9.41,-5.08c2.25,-3.4 3.37,-8.3 3.37,-14.73c0,-12.99 -3.98,-19.48 -11.94,-19.48c-4.15,0 -7.33,1.71 -9.53,5.17C289.89,360.21 288.79,365.29 288.79,372.01zM301.23,387.23c5.25,0 7.88,-5.24 7.88,-15.7c0,-10.83 -2.57,-16.25 -7.71,-16.25c-5.43,0 -8.14,5.5 -8.14,16.52C293.24,382.09 295.9,387.23 301.23,387.23zM257.82,300.73c-1.05,0 -1.97,0.38 -2.94,0.58L140.06,200.02L242.19,314.97c-0.07,0.58 -0.36,1.1 -0.36,1.67c0,8.85 7.14,16 15.98,16c8.78,0 15.92,-7.15 15.92,-16C273.75,307.88 266.6,300.73 257.82,300.73z"/>
</vector>

View File

@@ -0,0 +1,16 @@
<vector android:height="200dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="M9.11,5.08c0,2.39 -3.81,6 -3.81,6s-3.82,-3.58 -3.82,-6A3.7,3.7 0,0 1,5.3 1.5,3.7 3.7,0 0,1 9.11,5.08Z"
android:strokeColor="#020202" android:strokeWidth="1.91"/>
<path android:fillColor="#020202" android:pathData="M5.3,5.32m-0.95,0a0.95,0.95 0,1 1,1.9 0a0.95,0.95 0,1 1,-1.9 0"/>
<path android:fillColor="#00000000"
android:pathData="M4.34,13h4.3A2.39,2.39 0,0 1,11 15.34h0a2.39,2.39 0,0 1,-2.38 2.39H3.86a2.39,2.39 0,0 0,-2.38 2.38h0A2.39,2.39 0,0 0,3.86 22.5H17.7"
android:strokeColor="#020202" android:strokeWidth="1.91"/>
<path android:fillColor="#00000000"
android:pathData="M16.75,9.14L16.75,20.59"
android:strokeColor="#020202" android:strokeWidth="1.91"/>
<path android:fillColor="#00000000"
android:pathData="M16.75,14.86l4.77,0l-0.95,-1.9l0.95,-1.92l-4.77,0l0,3.82z"
android:strokeColor="#020202" android:strokeWidth="1.91"/>
</vector>

View File

@@ -0,0 +1,30 @@
<vector android:height="200dp" android:viewportHeight="48"
android:viewportWidth="48" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="0.01" android:fillColor="#ffffff" android:pathData="M0,0h48v48h-48z"/>
<path android:fillColor="#2F88FF"
android:pathData="M30.297,18.779C30.297,18.779 27.068,27.881 25.533,29.47C23.999,31.059 21.466,31.103 19.877,29.569C18.288,28.034 18.244,25.502 19.779,23.913C21.313,22.324 30.297,18.779 30.297,18.779Z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="4"/>
<path android:fillColor="#00000000"
android:pathData="M38.849,38.849C42.65,35.049 45,29.799 45,24C45,12.402 35.598,3 24,3C12.402,3 3,12.402 3,24C3,29.799 5.351,35.049 9.151,38.849"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="4"/>
<path android:fillColor="#00000000" android:pathData="M24,4V8"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="4"/>
<path android:fillColor="#00000000"
android:pathData="M38.845,11.142L35.737,13.659"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="4"/>
<path android:fillColor="#00000000"
android:pathData="M42.522,27.233L38.625,26.333"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="4"/>
<path android:fillColor="#00000000"
android:pathData="M5.477,27.233L9.375,26.333"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="4"/>
<path android:fillColor="#00000000"
android:pathData="M9.155,11.142L12.263,13.659"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="4"/>
</vector>

View File

@@ -0,0 +1,8 @@
<vector android:height="200dp" android:viewportHeight="398.8"
android:viewportWidth="398.8" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000000" android:pathData="M295.23,142.7c-9.9,-44.67 -19.8,-89.34 -29.71,-134c-16.72,0 -33.44,0 -50.15,0c2.39,44.67 4.78,89.34 7.17,134H295.23z"/>
<path android:fillColor="#000000" android:pathData="M226.35,214c3.14,58.7 6.29,117.4 9.43,176.11c38.09,0 76.19,0 114.28,0c-13.01,-58.7 -26.02,-117.4 -39.03,-176.11H226.35z"/>
<path android:fillColor="#000000" android:pathData="M183.43,8.69c-16.72,0 -33.43,0 -50.15,0c-9.9,44.67 -19.8,89.33 -29.7,134h72.68C178.66,98.03 181.04,53.36 183.43,8.69z"/>
<path android:fillColor="#000000" android:pathData="M48.74,390.11c38.1,0 76.19,0 114.28,0c3.15,-58.7 6.29,-117.4 9.43,-176.11H87.79C74.78,272.71 61.76,331.41 48.74,390.11z"/>
<path android:fillColor="#000000" android:pathData="M394.18,161.21H4.63c-2.56,0 -4.63,2.07 -4.63,4.63v25.02c0,2.56 2.07,4.63 4.63,4.63h25.05v37.48c0,2.56 2.07,4.63 4.63,4.63h25c2.12,0 3.96,-1.44 4.48,-3.49l9.82,-38.62h251.6l9.82,38.62c0.52,2.05 2.37,3.49 4.49,3.49h24.99c2.56,0 4.63,-2.07 4.63,-4.63v-37.48h25.05c2.56,0 4.63,-2.07 4.63,-4.63v-25.02C398.8,163.28 396.73,161.21 394.18,161.21z"/>
</vector>

View File

@@ -107,4 +107,94 @@
<string name="android_auto_tab_name_location">Location</string> <string name="android_auto_tab_name_location">Location</string>
<string name="android_auto_tab_name_car_info">CarInfo</string> <string name="android_auto_tab_name_car_info">CarInfo</string>
<!--Android Auto - CarInfo generic Strings -->
<string name="android_auto_car_info_not_received" translatable="false">Value not yet received</string>
<string name="android_auto_car_info_unavailable" translatable="false">Requested value is currently unavailable</string>
<string name="android_auto_car_info_unimplemented" translatable="false">Requested value is not implemented</string>
<string name="android_auto_car_info_unknown" translatable="false">Requested value state is currently unknown</string>
<!--Android Auto - Speed Value Strings -->
<string name="android_auto_raw_speed_title" translatable="false">Raw Vehicle Speed</string>
<string name="android_auto_display_speed_title" translatable="false">Display Vehicle Speed</string>
<string name="android_auto_raw_speed_base_string" translatable="false">Current Raw Speed is %.2f %s</string>
<string name="android_auto_display_speed_base_string" translatable="false">Current Display Speed is %.2f %s</string>
<string name="android_auto_speed_unit_mps" translatable="false">m/s</string>
<string name="android_auto_speed_unit_mph" translatable="false">Mph</string>
<string name="android_auto_speed_unit_kmh" translatable="false">km/h</string>
<string name="android_auto_distance_unit_meters" translatable="false">m</string>
<string name="android_auto_distance_unit_miles" translatable="false">mi</string>
<string name="android_auto_distance_unit_km" translatable="false">km</string>
<string name="android_auto_volume_unit_liter" translatable="false">l</string>
<string name="android_auto_volume_unit_milliliter" translatable="false">ml</string>
<string name="android_auto_volume_unit_us_gallon" translatable="false">gal</string>
<string name="android_auto_volume_unit_imperial_gallon" translatable="false">imp. gal</string>
<!--Android Auto - Toll Card Strings -->
<string name="android_auto_toll_card_title" translatable="false">Toll Card</string>
<string name="android_auto_toll_card_state_base" translatable="false">The current state of the TollCard is %s</string>
<string name="android_auto_toll_card_state_unknown" translatable="false">UNKNOWN</string>
<string name="android_auto_toll_card_state_valid" translatable="false">VALID</string>
<string name="android_auto_toll_card_state_invalid" translatable="false">INVALID</string>
<string name="android_auto_toll_card_state_not_inserted" translatable="false">NOT_INSERTED</string>
<!-- Android Auto - EnergyLevel -->
<string name="android_auto_energy_level_title" translatable="false">Energy</string>
<string name="android_auto_energy_level_battery_percentage_title" translatable="false">Battery \%</string>
<string name="android_auto_energy_level_fuel_percentage_title" translatable="false">Fuel \%</string>
<string name="android_auto_energy_level_range_remaining_title" translatable="false">Range Remaining</string>
<string name="android_auto_energy_level_low_energy_title" translatable="false">Is low Energy?</string>
<string name="android_auto_energy_level_battery_percentage_base" translatable="false">The battery is currently at %.2f\%</string>
<string name="android_auto_energy_level_fuel_percentage_base" translatable="false">The current fuel level is at %.2f\%</string>
<string name="android_auto_energy_level_low_energy_true" translatable="false">The car is currently in energy warning more</string>
<string name="android_auto_energy_level_low_energy_false" translatable="false">The care operates normal and energy is fine</string>
<string name="android_auto_energy_level_range_remaining_base" translatable="false">Range remaining: %.2f %s</string>
<!-- Android Auto - EV-Status -->
<string name="android_auto_ev_status_charge_port_connected_title" translatable="false">ChargePort Connection</string>
<string name="android_auto_ev_status_charge_port_connected_true" translatable="false">Charge Port is currently connected</string>
<string name="android_auto_ev_status_charge_port_connected_false" translatable="false">Charge Port is currently disconnected</string>
<string name="android_auto_ev_status_charge_port_open_title" translatable="false">Charge Port Open</string>
<string name="android_auto_ev_status_charge_port_open_true" translatable="false">Charge Port is currently open</string>
<string name="android_auto_ev_status_charge_port_open_false" translatable="false">Charge Port is currently closed</string>
<!-- Android Auto - Mileage -->
<string name="android_auto_mileage_title" translatable="false">Odometer</string>
<string name="android_auto_ev_mileage_odometer_base" translatable="false">Current Odometer Value is %.2f %s</string>
<!-- Android Auto - Model -->
<string name="android_auto_model_manufacturer_title" translatable="false">Manufacturer</string>
<string name="android_auto_model_manufacturer_base" translatable="false">The Car-Manufacturer is %s</string>
<string name="android_auto_model_name_title" translatable="false">Model Name</string>
<string name="android_auto_model_name_base" translatable="false">The Car-Name is %s</string>
<string name="android_auto_model_year_title" translatable="false">Model Year</string>
<string name="android_auto_model_year_base" translatable="false">The Car was built in %d</string>
<!-- Android Auto - Energy Profile -->
<string name="android_auto_energy_profile_fuel_type_title" translatable="false">Fuel Type</string>
<string name="android_auto_energy_profile_fuel_type_base" translatable="false">Supported Fuel Types: %s</string>
<string name="android_auto_energy_profile_connector_type_title" translatable="false">EV-Connectors</string>
<string name="android_auto_energy_profile_connector_type_base" translatable="false">Supported Connector Types: %s</string>
<!-- Android Auto - FuelType -->
<string name="android_auto_fuel_type_unknown" translatable="false">Unknown</string>
<string name="android_auto_fuel_type_unleaded" translatable="false">Unleaded Gasoline</string>
<string name="android_auto_fuel_type_leaded" translatable="false">Leaded Gasoline</string>
<string name="android_auto_fuel_type_diesel_1" translatable="false">#1 Grade Diesel</string>
<string name="android_auto_fuel_type_diesel_2" translatable="false">#2 Grade Diesel</string>
<string name="android_auto_fuel_type_biodiesel" translatable="false">Biodiesel</string>
<string name="android_auto_fuel_type_e85" translatable="false">85% ethanol/gasoline blend</string>
<string name="android_auto_fuel_type_lpg" translatable="false">Liquified Petroleum Gas</string>
<string name="android_auto_fuel_type_cng" translatable="false">Compressed Natural Gas</string>
<string name="android_auto_fuel_type_lng" translatable="false">Liquified Natural Gas</string>
<string name="android_auto_fuel_type_electric" translatable="false">Electric</string>
<string name="android_auto_fuel_type_hydrogen" translatable="false">Hydrogen Fuel Cell</string>
<string name="android_auto_fuel_type_other" translatable="false">Other</string>
<!-- Android Auto - EV-ConnectorType -->
<string name="android_auto_ev_connector_unknown" translatable="false">Unknown</string>
<string name="android_auto_ev_connector_j1772" translatable="false">SAE J1772</string>
<string name="android_auto_ev_connector_mennekes" translatable="false">IEC 62196 Type 2</string>
<string name="android_auto_ev_connector_chademo" translatable="false">CHAdeMo Fast Charger</string>
<string name="android_auto_ev_connector_combo_1" translatable="false">Combined Charging System Combo 1</string>
<string name="android_auto_ev_connector_combo_2" translatable="false">Combined Charging System Combo 2</string>
<string name="android_auto_ev_connector_tesla_roadster" translatable="false">Tesla Roadster</string>
<string name="android_auto_ev_connector_tesla_hpwc" translatable="false">Tesla High Power Wall Charger</string>
<string name="android_auto_ev_connector_tesla_supercharger" translatable="false">Tesla Supercharger</string>
<string name="android_auto_ev_connector_gbt" translatable="false">GBT_AC Fast Charging Standard</string>
<string name="android_auto_ev_connector_gbt_dc" translatable="false">GBT_DC Fast Charging Standard</string>
<string name="android_auto_ev_connector_scame" translatable="false">IEC_TYPE_3_AC connector</string>
<string name="android_auto_ev_connector_other" translatable="false">Other</string>
</resources> </resources>