PropertyRequestProcessor.java

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.car.app.hardware.common;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

import android.car.Car;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.content.Context;
import android.util.ArraySet;
import android.util.Pair;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nullable;

/**
 * A class for interacting with the {@link CarPropertyManager} for getting any vehicle property.
 *
 * @hide
 */
@RestrictTo(LIBRARY)
final class PropertyRequestProcessor {
    private final CarPropertyManager mCarPropertyManager;
    private PropertyEventCallback mPropertyEventCallback;

    /**
     *  Registers this listener to get results from
     *  {@link #fetchCarPropertyValues(List, OnGetPropertiesListener)}.
     */
    interface OnGetPropertiesListener {
        /**
         * Called when get all properties' value or errors.
         *
         * @param propertyValues    a list of {@link CarPropertyValue}, empty if there are no values
         * @param errors            a list of {@link CarInternalError}, empty if there are no errors
         */
        void onGetProperties(List<CarPropertyValue<?>> propertyValues,
                List<CarInternalError> errors);
    }

    /**
     * Registers this callback to receive property updates from cars.
     */
    abstract static class PropertyEventCallback implements
            CarPropertyManager.CarPropertyEventCallback {
        /**
         * Called when a property is updated.
         *
         * @param carPropertyValue property that has been updated
         */
        @Override
        public abstract void onChangeEvent(CarPropertyValue carPropertyValue);

        /**
         * Called when a property error detected in the car.
         *
         * @param carInternalError {@link CarInternalError} in the car
         */
        public abstract void onErrorEvent(CarInternalError carInternalError);

        /**
         * Create a {@link CarInternalError} with default status {@link CarValue#STATUS_UNKNOWN}.
         *
         * @param propertyId    in {@link android.car.VehiclePropertyIds}
         * @param areaId        in {@link CarPropertyValue#getAreaId()}
         */
        @Override
        public final void onErrorEvent(int propertyId, int areaId) {
            CarInternalError error = CarInternalError.create(propertyId, areaId,
                    CarValue.STATUS_UNKNOWN);
            onErrorEvent(error);
        }

        /**
         * Create a {@link CarInternalError} based on different status code from cars.
         *
         * @param propertyId    in {@link android.car.VehiclePropertyIds}
         * @param areaId        in {@link CarPropertyValue#getAreaId()}
         * @param statusCode    in {@link CarPropertyValue.PropertyStatus}
         */
        @Override
        public final void onErrorEvent(int propertyId, int areaId, int statusCode) {
            CarInternalError error = CarInternalError.create(propertyId, areaId,
                    PropertyUtils.mapToStatusCodeInCarValue(statusCode));
            onErrorEvent(error);
        }
    }

    /**
     * Gets {@link CarPropertyValue} and returns results by
     * {@link OnGetPropertiesListener#onGetProperties(List, List)}.
     *
     * @param requests  a list of {@Code Pair<Integer, Integer>}, {@Code Pair.first} is the
     *                  property id, {@Code Pair.second} is the area id
     * @param listener  the listener that will be invoked with the results of the request
     */
    public void fetchCarPropertyValues(
            @NonNull List<Pair<Integer, Integer>> requests,
            @NonNull OnGetPropertiesListener listener) {
        List<CarPropertyValue<?>> values = new ArrayList<>();
        List<CarInternalError> errors = new ArrayList<>();
        for (Pair<Integer, Integer> request : requests) {
            try {
                CarPropertyConfig<?> propertyConfig = getPropertyConfig(request.first);
                if (propertyConfig == null) {
                    errors.add(CarInternalError.create(request.first, request.second,
                            CarValue.STATUS_UNIMPLEMENTED));
                } else {
                    Class<?> clazz = propertyConfig.getPropertyType();
                    CarPropertyValue<?> propertyValue = mCarPropertyManager.getProperty(clazz,
                            request.first, request.second);
                    values.add(propertyValue);
                }
            } catch (IllegalArgumentException e) {
                errors.add(CarInternalError.create(request.first, request.second,
                        CarValue.STATUS_UNIMPLEMENTED));
            } catch (Exception e) {
                errors.add(CarInternalError.create(request.first, request.second,
                        CarValue.STATUS_UNAVAILABLE));
            }
        }
        listener.onGetProperties(values, errors);
    }

    /**
     * Registers for the property updates at the input sampling rate.
     *
     * @param propertyId    property id in {@link android.car.VehiclePropertyIds}
     * @param sampleRate    float value in hertz
     * @throws IllegalArgumentException if a property is not implemented in the car
     */
    public void registerProperty(int propertyId, float sampleRate) {
        if (getPropertyConfig(propertyId) == null) {
            throw new IllegalArgumentException("Property is not implemented in the car: "
                    + propertyId);
        }
        mCarPropertyManager.registerCallback(mPropertyEventCallback, propertyId, sampleRate);
    }

    /**
     * Unregisters from the property updates.
     *
     * @param propertyId    property id in {@link android.car.VehiclePropertyIds}
     * @throws IllegalArgumentException if a property is not implemented in the car
     */
    public void unregisterProperty(int propertyId) {
        if (getPropertyConfig(propertyId) == null) {
            throw new IllegalArgumentException("Property is not implemented in the car: "
                    + propertyId);
        }
        mCarPropertyManager.unregisterCallback(mPropertyEventCallback, propertyId);
    }

    PropertyRequestProcessor(Context context, PropertyEventCallback callback) {
        Car car = Car.createCar(context);
        mCarPropertyManager = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
        mPropertyEventCallback = callback;
    }

    @SuppressWarnings("rawtypes")
    @Nullable
    private CarPropertyConfig<?> getPropertyConfig(int propertyId) {
        ArraySet<Integer> propertySet = new ArraySet<>(1);
        propertySet.add(propertyId);
        List<CarPropertyConfig> configs = mCarPropertyManager.getPropertyList(propertySet);
        return configs.size() == 0 ? null : configs.get(0);
    }
}