Explore the important APIs of Android cars from the window lift: vehicle properties CarProperty

Foreword

Previously we introduced several important contents of Android car Automotive OS:

  • An article to understand how Android car handles the knob input of the central control
  • Looking at the custom event mechanism of Android cars from the perspective of physical buttons
  • An in-depth introduction to the composition and links of Android car core CarService

In this article, we focus on the most important and commonly used interface on Android cars: vehicle properties CarPropertyManager.

And combine it with the typical scenario of window lifting to explore its complete link.

Realize car window lifting

CarPropertyManager usually initiates reading and writing for a certain Property. There are many properties, from car windows to air conditioning, fuel level to battery life, etc.

To control them, you need to know its unique identifier and keep it consistent with the system-defined ID. Then the ID corresponding to the car window is WINDOW_POS in VehiclePropertyIds, which requires the app to have dedicated permissions:

android.car.Car.PERMISSION_CONTROL_CAR_WINDOWS

Attribute monitoring

When the target property changes, the requesting App can be notified through CarPropertyEventCallback. In order to meet various scenarios, the system provides the possibility to set the notification frequency.

There are a total of the following types:

Notification frequency type Frequency (HZ)
SENSOR_RATE_ONCHANGE
SENSOR_RATE_FASTEST 100
SENSOR_RATE_FAST 10
SENSOR_RATE_NORMAL 1
SENSOR_RATE_UI 5

For real-time signals such as car windows and seating, use the SENSOR_RATE_ONCHANGE type, which means that it will only be notified when it changes. Of course, when registering, it will be called back immediately to notify the current value.

The code is very simple. Build a CarPropertyEventCallback instance and pass the target Property ID and the above notification type to complete the monitoring of the property.

class CarEventCallBack: CarPropertyManager.CarPropertyEventCallback {<!-- -->
    override fun onChangeEvent(value: CarPropertyValue<*>?) {<!-- --> }
}

val car = Car.createCar(context)
val carPropertyManager =
    car?.getCarManager(Car.PROPERTY_SERVICE) as CarPropertyManager

carPropertyManager.registerCallback(
    CarEventCallBack(),
    VehiclePropertyIds.WINDOW_POS,
    CarPropertyManager.SENSOR_RATE_ONCHANGE
)

Attribute reading and writing

For car window hardware, users are concerned about its lifting status. The system uses 0 to 100 to define it, and then determines its value to be of type Int.

Then the read API is getIntProperty(), parameters:

  • prop: The property ID you wish to read, such as the above car window Property ID: WINDOW_POS
  • area: The location information zone where you want to read the attribute, corresponding to the constant in the VehicleAreaWindow type

Note: This method is synchronous, and because the operation of attributes such as car windows is time-consuming, it is recommended to invoke it in a child thread.

The written API is setIntProperty(), parameters:

  • prop: the ID of the property you want to override,
  • areaId: the position salary corresponding to this attribute
  • val: Value to set, for example, car window is 0 ~ 100, corresponding to fully open to fully closed

Like getIntProperty(), set is also time-consuming and needs to be run in a child thread.

The system’s default zone areaIds related to Window are as follows, such as front row, driver’s side, passenger side, passenger side, sunroof, windshield, etc.

package android.hardware.automotive.vehicle;

public @interface VehicleAreaWindow {<!-- -->
  public static final int FRONT_WINDSHIELD = 1;
  public static final int REAR_WINDSHIELD = 2;
  public static final int ROW_1_LEFT = 16;
  public static final int ROW_1_RIGHT = 64;
  public static final int ROW_2_LEFT = 256;
  public static final int ROW_2_RIGHT = 1024;
  public static final int ROW_3_LEFT = 4096;
  public static final int ROW_3_RIGHT = 16384;
  public static final int ROOF_TOP_1 = 65536;
  public static final int ROOF_TOP_2 = 131072;
}

The following code shows how to fully open the driver’s window.

Thread().run {<!-- -->
    carPropertyManager.setIntProperty(
        VehiclePropertyIds.WINDOW_POS,
        VehicleAreaWindow.WINDOW_ROW_1_LEFT,
        0
   )
}

How it works

First of all, the area related to the car window has corresponding definitions in the HAL layer:

// android/hardware/automotive/vehicle/2.0/types.h

/**
 * Various windshields/windows in the car.
 */
enum class VehicleAreaWindow : int32_t {
    FRONT_WINDSHIELD = 1 /* 0x00000001 */,
    REAR_WINDSHIELD = 2 /* 0x00000002 */,
    ROW_1_LEFT = 16 /* 0x00000010 */,
    ROW_1_RIGHT = 64 /* 0x00000040 */,
    ROW_2_LEFT = 256 /* 0x00000100 */,
    ROW_2_RIGHT = 1024 /* 0x00000400 */,
    ROW_3_LEFT = 4096 /* 0x00001000 */,
    ROW_3_RIGHT = 16384 /* 0x00004000 */,
    ROOF_TOP_1 = 65536 /* 0x00010000 */,
    ROOF_TOP_2 = 131072 /* 0x00020000 */,
};

Read

Looking directly at getIntProperty(), first call checkSupportedProperty() to check whether the property is supported. If it is not supported, it will throw:

IllegalArgumentException: “Unsupported property:xxx”

Then call getProperty(), but specify the returned data type.

public class CarPropertyManager extends CarManagerBase {<!-- -->
    public int getIntProperty(int prop, int area) {<!-- -->
        checkSupportedProperty(prop);
        CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
        return handleNullAndPropertyStatus(carProp, area, 0);
    }

    private void checkSupportedProperty(int propId) {<!-- -->
        switch (propId) {<!-- -->
            case VehiclePropertyIds.INITIAL_USER_INFO:
            case VehiclePropertyIds.SWITCH_USER:
            case VehiclePropertyIds.CREATE_USER:
            case VehiclePropertyIds.REMOVE_USER:
            case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:
                throw new IllegalArgumentException("Unsupported property: "
                         + VehiclePropertyIds.toString(propId) + " (" + propId + ")");
        }
    }
    ...
}

The implementation of getProperty() is in CarPropertyService.

public class CarPropertyManager extends CarManagerBase {<!-- -->
    public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) {<!-- -->
        checkSupportedProperty(propId);

        try {<!-- -->
            CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);
            if (propVal != null & amp; & amp; propVal.getValue() != null) {<!-- -->
                Class<?> actualClass = propVal.getValue().getClass();
            }
            return propVal;
        }
        ...
    }
    ...
}

CarPropertyService Follow the steps below:

  1. First go to the SparseArray that stores all Property IDs to check whether the Property actually exists. If it does not exist, print an error reminder and end

  2. Get the permission configuration of this Property. If it does not exist, throw:

    SecurityException: Platform does not have permission to read value for property Id: 0x…

  3. assertPermission() Checks whether the current CarService is indeed granted the above permission

  4. Finally call the held PropertyHalService to continue issuing read calls

public class CarPropertyService extends ICarProperty.Stub
        implements CarServiceBase, PropertyHalService.PropertyHalListener {<!-- -->
    @Override
    public CarPropertyValue getProperty(int prop, int zone) ... {<!-- -->
        synchronized (mLock) {<!-- -->
            if (mConfigs.get(prop) == null) {<!-- -->
                // Do not attempt to register an invalid propId
                Slogf.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop));
                return null;
            }
        }

        // Checks if android has permission to read property.
        String permission = mHal.getReadPermission(prop);
        if (permission == null) {<!-- -->
            throw new SecurityException("Platform does not have permission to read value for "
                     + "property Id: 0x" + Integer.toHexString(prop));
        }
        CarServiceUtils.assertPermission(mContext, permission);
        return runSyncOperationCheckLimit(() -> {<!-- -->
            return mHal.getProperty(prop, zone);
        });
    }
    ...
}

PropertyHalService first calls managerToHalPropId() to convert the Property ID to the definition of the ID in HAL, and then checks again whether the HAL ID actually exists. Also throws if it does not exist:

IllegalArgumentException: Invalid property ID : 0x…

Then, pass the ID in HAL through VehicleHal and continue reading to get HalPropValue. When the read value exists, you must first obtain the HalPropConfig rules.

Finally, the value is parsed into the CarPropertyValue type according to config and returned.

public class PropertyHalService extends HalServiceBase {<!-- -->
'/ ' ...
    public CarPropertyValue getProperty(int mgrPropId, int areaId)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        int halPropId = managerToHalPropId(mgrPropId);
        if (!isPropertySupportedInVehicle(halPropId)) {<!-- -->
            throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));
        }

        // CarPropertyManager catches and rethrows exception, no need to handle here.
        HalPropValue value = mVehicleHal.get(halPropId, areaId);
        if (value == null) {<!-- -->
            return null;
        }
        HalPropConfig propConfig;
        synchronized (mLock) {<!-- -->
            propConfig = mHalPropIdToPropConfig.get(halPropId);
        }
        return value.toCarPropertyValue(mgrPropId, propConfig);
    }
    ...
}

In fact, VehicleHal directly handed it over to HalClient for processing without doing much processing.

public class VehicleHal implements HalClientCallback {<!-- -->
    ...
    public HalPropValue get(int propertyId)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        return get(propertyId, NO_AREA);
    }
    ...
    public HalPropValue get(int propertyId, int areaId)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        return mHalClient.getValue(mPropValueBuilder.build(propertyId, areaId));
    }
    ...
}

HalClient makes an internalGet() call via invokeRetriable() with a timeout of 50ms: if the result is TRY_AGAIN and it has not timed out yet If so, call it again; otherwise, if it times out or the result is successfully obtained, it ends.

Subsequently, the status in the Result will be checked again to see if it is illegal, empty, etc. If it passes the check, HalPropValue will be returned.

final class HalClient {<!-- -->
    ...
    private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50;

    HalPropValue getValue(HalPropValue requestedPropValue)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        ObjectWrapper<ValueResult> resultWrapper = new ObjectWrapper<>();
        resultWrapper.object = new ValueResult();
        int status = invokeRetriable(() -> {<!-- -->
            resultWrapper.object = internalGet(requestedPropValue);
            return resultWrapper.object.status;
        }, mWaitCapMs, mSleepMs);

        ValueResult result = resultWrapper.object;

        if (StatusCode.INVALID_ARG == status) {<!-- -->
            throw new IllegalArgumentException(
                    getValueErrorMessage("get", requestedPropValue, result.errorMsg));
        }

        if (StatusCode.OK != status || result.propValue == null) {<!-- -->
            if (StatusCode.OK == status) {<!-- -->
                status = StatusCode.NOT_AVAILABLE;
            }
            throw new ServiceSpecificException(
                    status, getValueErrorMessage("get", requestedPropValue, result.errorMsg));
        }

        return result.propValue;
    }

    private ValueResult internalGet(HalPropValue requestedPropValue) {<!-- -->
        final ValueResult result = new ValueResult();
        try {<!-- -->
            result.propValue = mVehicle.get(requestedPropValue);
            result.status = StatusCode.OK;
            result.errorMsg = new String();
        }
        ...
        return result;
    }
    ...
}

The implementation of internalGet() is completed by the get method of the held VehicleStub instance, and its implementation corresponds to calling the HAL side to obtain the corresponding data based on the HIDL configuration.

public abstract class VehicleStub {<!-- -->
    ...
    @Nullable
    public abstract HalPropValue get(HalPropValue requestedPropValue)
            throws RemoteException, ServiceSpecificException;
    ...
}

Write

The link written by set is similar to that of get. The main differences are:

  1. Construct the property instance CarPropertyValue to be written in advance and pass it in
  2. The CarPropertyEventListenerToService instance used by the callback when the incoming property changes
public class CarPropertyManager extends CarManagerBase {<!-- -->
    public void setIntProperty(int prop, int areaId, int val) {<!-- -->
        setProperty(Integer.class, prop, areaId, val);
    }

    public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) {<!-- -->
        checkSupportedProperty(propId);
        try {<!-- -->
            runSyncOperation(() -> {<!-- -->
                mService.setProperty(new CarPropertyValue<>(propId, areaId, val),
                        mCarPropertyEventToService);
                return null;
            });
        }
        ...
    }
}

The implementation of the next layer CarPropertyService is also carried out through PropertyHalService.

The incoming CarPropertyEventListenerToService is actually the ICarPropertyEventListener AIDL proxy, which will be converted into a Binder object, cached according to the source client of the call, and used when the properties change.

public class CarPropertyService extends ICarProperty.Stub
        implements CarServiceBase, PropertyHalService.PropertyHalListener {<!-- -->
    public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        int propId = prop.getPropertyId();
        ...
        runSyncOperationCheckLimit(() -> {<!-- -->
            mHal.setProperty(prop);
            return null;
        });
        IBinder listenerBinder = listener.asBinder();
        synchronized (mLock) {<!-- -->
            Client client = mClientMap.get(listenerBinder);
            if (client == null) {<!-- -->
                client = new Client(listener);
            }
            if (client.isDead()) {<!-- -->
                Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
                return;
            }
            mClientMap.put(listenerBinder, client);
            updateSetOperationRecorderLocked(propId, prop.getAreaId(), client);
        }
    }
    ...
}

Continue distribution to the VehicleHal side.

public class PropertyHalService extends HalServiceBase {<!-- -->
    public void setProperty(CarPropertyValue prop)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        int halPropId = managerToHalPropId(prop.getPropertyId());
        ...
        HalPropValue halPropValue = mPropValueBuilder.build(prop, halPropId, propConfig);
        // CarPropertyManager catches and rethrows exception, no need to handle here.
        mVehicleHal.set(halPropValue);
    }
    ...
}

The subsequent steps are the same through VehicleHal to HalClient, then to VehicleStub, and finally to HAL.

public class VehicleHal implements HalClientCallback {<!-- -->
    ...
    public void set(HalPropValue propValue)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        mHalClient.setValue(propValue);
    }
}

final class HalClient {<!-- -->
    ...
    public void setValue(HalPropValue propValue)
            throws IllegalArgumentException, ServiceSpecificException {<!-- -->
        ObjectWrapper<String> errorMsgWrapper = new ObjectWrapper<>();
        errorMsgWrapper.object = new String();

        int status = invokeRetriable(() -> {<!-- -->
            try {<!-- -->
                mVehicle.set(propValue);
                errorMsgWrapper.object = new String();
                return StatusCode.OK;
            }
            ...
        }, mWaitCapMs, mSleepMs);
        ...
    }
    ...
}

public abstract class VehicleStub {<!-- -->
    ...
    public abstract void set(HalPropValue propValue)
            throws RemoteException, ServiceSpecificException;
    ...
}

Conclusion

Let’s review the entire process with a picture:

  1. App first gets the Car instance of CarService through Car lib. CarService will initialize all Car-related implementations, such as the initialization of vehicle attributes, and will initialize CarPropertyService and PropertyHalService etc.
  2. Then, the App will obtain an instance of a certain interface of the vehicle from the Car instance. For example, to control vehicle properties, you need to obtain CarPropertyManager, and CarService will return the prepared corresponding object from the initialized map.
  3. The App’s attribute reading and writing will reach the directly responsible CarPropertyService through the AIDL interface, then to the PropertyHalService that interacts with the vehicle attribute module in HAL, and then to the comprehensive VehicleHal, and finally reach the Hal below through the HIDL interface, and change the relevant attributes of the ECU according to the defined data type

I hope this article can give you a general understanding of vehicle attributes in a concise and comprehensive manner. Thank you for reading.

Recommended reading

  • An article to understand how Android car handles the knob input of the central control
  • Looking at the custom event mechanism of Android car machines from the perspective of physical buttons
  • An in-depth introduction to the composition and links of Android car core CarService
  • First experience with Android cars: Can’t tell the difference between Auto and Automotive?

Reference materials

  • CarPropertyManager