Android Automotive #4 — Consuming the VHAL in a managed application
In our last post, we explained what interventions you need to do in order to set up a custom VHAL implementation by taking advantage of the default implementation. On this it will be all about the application side, we will cover the following:
- How to access the VHAL using a java application.
- How to access vendor properties in your application
- How to give your java application privileged permissions
- How to generate a new car lib (android.car.jar) with your added properties
- How to take advantage of Android Studio in order to quickly iterate your applications.
In this post, we will use a java application compiled with the rest of the AOSP, but nothing prohibits you from using a Kotlin application, or even an APK built in any language. We will also focus mainly on VHAL interaction defering any other Android Automotive API details to google documentation.
To start things off, we will create a folder in our working directory (we will use our <android_src>/vendor/imaginationoverflow described here) in order to hold the source of our application:
# on the sources root
cd vendor/imaginationoverflow
mkdir myandroidautoapp
To start things off you can download a sample application from our github, with the folder structure and necessary files like the Android.mk.
Accessing the VHAL in your java application
To access the car VHAL you first need to get a Car instance. The Car class is the entry point to all car services included in Android Automotive:
Car carApi = Car.createCar(<activity/application context>, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
(Car car, boolean ready) -> {
if (ready) {
//carApi is ready to be ysed
}
});
With the car instance, you can request the CarPropertyManager service in order to interact with the VHAL directly:
CarPropertyManager propMgr = (CarPropertyManager) carApi.getCarManager(android.car.Car.PROPERTY_SERVICE);
The CarPropertyManager allows you to read, write and subscribe to any system and vendor properties, for example using the vendor property that we created in the last post:
int vendorWritePropId = 0x21401000; //The result of the #define VENDOR_WRITE_PROP (...)
propMgr.getIntProperty(vendorWritePropId, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
propMgr.setIntProperty(vendorWritePropId,VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL, 0xbeef);
propMgr.registerCallback(new CarPropertyManager.CarPropertyEventCallback() {
@Override
public void onChangeEvent(CarPropertyValue carPropertyValue) {
Log.d("Test", "vendorWritePropId value is " + (int)carPropertyValue.getValue());
}
@Override
public void onErrorEvent(int i, int i1) {
}
}, vendorWritePropId, CarPropertyManager.SENSOR_RATE_NORMAL);
For nonvendor properties, you can use the VehiclePropertyIds class in order to index any property you wish to get from the VHAL. Remember to set the desired permissions though, since some properties require you to declare specific permissions on your app manifest.
Access Vendor Permissions on an application
If you try to run the source above (assuming you have made the VHAL changes) you probably will get an error in the logcat stating that the permission does not exist. That is because vendor properties can’t be accessed without the CAR_VENDOR_EXTENSION permission. If you try to add that permission and just run the application as is, it will also fail to launch since the app isn’t a whitelisted app in the system nor is it signed. So let's do just that.
Giving an app privilege permissions in AOSP
Under your work directory, create a folder for permissions:
# on the sources root
cd vendor/imaginationoverflow
mkdir permissions
Assuming that your package id is com.imaginationoverflow.myandroidautoapp (replace with your own), create an Android.bp file with the following content:
prebuilt_etc {
name: "privapp_whitelist_com.imaginationoverflow.myandroidautoapp",
sub_dir: "permissions",
src: "com.imaginationoverflow.myandroidautoapp.xml",
filename_from_src: true,
}
Now create the com.imaginationoverflow.myandroidautoapp.xml with the following content:
<?xml version="1.0" encoding="utf-8"?>
<permissions>
<privapp-permissions package="com.imaginationoverflow.myandroidautoapp">
<permission name="android.permission.ACCESS_NETWORK_STATE"/>
<permission name="android.permission.ACCESS_WIFI_STATE"/>
<permission name="android.permission.ACTIVITY_EMBEDDING"/>
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.INJECT_EVENTS"/>
<!-- use for CarServiceUnitTest and CarServiceTest -->
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<!-- use for CarServiceUnitTest -->
<permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.LOCATION_HARDWARE"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
<!-- use for CarServiceTest -->
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MONITOR_INPUT"/>
<permission name="android.permission.PROVIDE_TRUST_AGENT"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.REBOOT"/>
<!-- use for CarServiceTest -->
<permission name="android.permission.SET_ACTIVITY_WATCHER"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<!-- use for rotary fragment to enable/disable packages related to rotary -->
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
</privapp-permissions>
</permissions>
This gives you permission to do whatever you need with the system. The file's content is a direct copy of the permissions given to the kitchen sink application.
Finally, you need to change the application Android.mk to include the following line:
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.imaginationoverflow.myandroidautoapp
Now build Android and your application should have privileged permissions.
An important note, after you do this change it’s possible that you won’t be able to access your vendor property, we don’t know 100% why, but we suspect is something regarding the build system that isn’t refreshing the whitelisted apps when we add a new one, to fix this simply run these commands:
# on the sources root
rm -r out/target/product/generic_car_x86/obj/ETC/
rm -r out/target/product/generic_car_x86/system/etc/permissions/
rm -r out/soong/.intermediates/frameworks/base/data/etc/
# probably not necessary
rm -r out/target/common/obj/APPS/<your_app_name>_intermediates/
What we do here is clear up the permissions generated in previous builds, since it seems the android build system doesn’t do this automatically.
Creating a new Car lib (android.car.jar)
Almost all of the interactions exclusive to the Android Automotive car sub-system are done through the car lib, the VHAL access is no different and it’s integrated directly into this library.
When you add vendor VHAL properties since they aren’t standard their ids won’t be available on the VehiclePropertyIds class. Still, it would be a huge bonus if they appeared there, that way application developers can work independently of the VHAL developers, without needing to know the exact property id or any possible changes made to them during the VHAL implementation.
The first change you need to do in order to make this happen is to change the types.hal on the VHAL:
# on the sources root
cd hardware/interfaces/automotive/vehicle/2.0/
If you scroll down you will find the stubs for the properties on an enum called VehicleProperty.
enum VehicleProperty : int32_t {
/** Undefined property. */
INVALID = 0x00000000,
/**
* VENDOR_WRITE_PROP
*
* @change_mode VehiclePropertyChangeMode:ON_CHANGE
* @access VehiclePropertyAccess:READ_WRITE
*/
VENDOR_WRITE_PROP = (
0x1000
| VehiclePropertyGroup:VENDOR
| VehiclePropertyType:INT32
| VehicleArea:GLOBAL),
....
Here you can add your properties, just like we did on the VHAL property store. After you added all your properties simply build the VHAL (or everything if you prefer). You will find the generated values in the VehicleProperty.java under:
# on the sources root
cd out/soong/.intermediates/hardware/interfaces/automotive/vehicle/2.0/android.hardware.automotive.vehicle-V2.0-java_gen_java/gen/srcs/android/hardware/automotive/vehicle/V2_0
public final class VehicleProperty {
/**
* Undefined property.
*/
public static final int INVALID = 0 /* 0x00000000 */;
/**
* VENDOR_WRITE_PROP
*
* @change_mode VehiclePropertyChangeMode:CONTINUOUS
* @access VehiclePropertyAccess:READ_WRITE
*/
public static final int VENDOR_WRITE_PROP = 557846528/* (0x1007 | VehiclePropertyGroup:VENDOR | VehiclePropertyType:FLOAT | VehicleArea:GLOBAL) */;
....
Now you need to copy the new vendor properties to the actual car lib, to do that find the VehiclePropertyIds.java under:
# on the sources root
cd packages/services/Car/car-lib/src/android/car
public final class VehiclePropertyIds {
/**
* Undefined property. */
public static final int INVALID = 0;
/**
* VENDOR_WRITE_PROP
* @hide
* @change_mode VehiclePropertyChangeMode:CONTINUOUS
* @access VehiclePropertyAccess:READ_WRITE
*/
public static final int VENDOR_WRITE_PROP = 557846528/* (0x1007 | VehiclePropertyGroup:VENDOR | VehiclePropertyType:FLOAT | VehicleArea:GLOBAL) */;
...
The annotation @hide is mandatory otherwise the build will fail!
Besides adding your new properties you might also want to change the toString method, to help with any debugging:
//still in VehiclePropertyIds.java
public static String toString(int property) {
switch (property) {
case VENDOR_WRITE_PROP:
return "VENDOR_WRITE_PROP";
....
Finally, build the system (this can take a while), and go the get the android.car.jar file from the following location:
# on the sources root
cd out\soong\.intermediates\packages\services\Car\car-lib\android.car\android_common\combined\
It might seem weird the location to fetch the car lib, but from our tests with Android Studio, it seems it doesn’t like when the .jar is already in .dex format, so this intermediate will work just fine for testing.
Using your custom car-lib on Android Studio
You can use the custom car-lib exactly how you would use the default one present in the SDKs. You just need to import the jar to your Android Studio Project under the libs folder, just don’t forget to right-click on it and press Add as library.
If you deploy directly to your custom emulator you can test everything directly from Android Studio, if you prefer (or can’t) use your custom emulator, I suggest abstracting any car API calls of your application so that you can test the functionality of your application even on a mobile device. For that the following line is key:
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
//android automotive present
else
//android phone/tv/etc
In the application that we are currently working we use mock data to be able to quickly prototype on a mobile device and do the final integration tests directly on the actual device.
Final Remarks
In the past four posts, we described how we set up an entire development environment for both applications and VHAL, with limited knowledge and a prototyping mindset, as we go further we expect to understand the entire AOSP system and Car subsystem a lot better and write more about it
Right now our next focus will be finishing our custom VHAL so we won’t be tackling anything interesting to write about, still, the car launcher is still a beast we want to dominate, but for a quick bonus tip about it, open the CarLauncher.java under:
# on the sources root
cd packages/apps/Car/Launcher/src/com/android/car/carlauncher
And change the method getMapsIntent with the following:
private Intent getMapsIntent() {
Intent i = new Intent();
i.setClassName("com.imaginationoverflow.myandroidautoapp","com.imaginationoverflow.myandroidautoapp.MainActivity");
return i;
//return Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_MAPS);
}
This will make the activity you set on the intent to appear alongside the rest of the launcher system, and depending on the size of your activity content it will disable the other gizmos present.
Until the next time.