/* * Copyright (C) 2015 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 com.android.car.hal; import static android.os.SystemClock.uptimeMillis; import static com.android.car.hal.property.HalPropertyDebugUtils.toAccessString; import static com.android.car.hal.property.HalPropertyDebugUtils.toAreaIdString; import static com.android.car.hal.property.HalPropertyDebugUtils.toAreaTypeString; import static com.android.car.hal.property.HalPropertyDebugUtils.toChangeModeString; import static com.android.car.hal.property.HalPropertyDebugUtils.toGroupString; import static com.android.car.hal.property.HalPropertyDebugUtils.toPropertyIdString; import static com.android.car.hal.property.HalPropertyDebugUtils.toValueTypeString; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.annotation.CheckResult; import android.annotation.Nullable; import android.car.VehiclePropertyIds; import android.car.builtin.os.TraceHelper; import android.car.builtin.util.Slogf; import android.car.feature.FeatureFlags; import android.car.feature.FeatureFlagsImpl; import android.content.Context; import android.hardware.automotive.vehicle.StatusCode; import android.hardware.automotive.vehicle.SubscribeOptions; import android.hardware.automotive.vehicle.VehiclePropError; import android.hardware.automotive.vehicle.VehicleProperty; import android.hardware.automotive.vehicle.VehiclePropertyAccess; import android.hardware.automotive.vehicle.VehiclePropertyChangeMode; import android.hardware.automotive.vehicle.VehiclePropertyStatus; import android.hardware.automotive.vehicle.VehiclePropertyType; import android.os.Handler; import android.os.HandlerThread; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import com.android.car.CarLog; import com.android.car.CarServiceUtils; import com.android.car.CarSystemService; import com.android.car.VehicleStub; import com.android.car.VehicleStub.SubscriptionClient; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.car.internal.util.IndentingPrintWriter; import com.android.car.internal.util.Lists; import com.android.car.internal.util.PairSparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; /** * Abstraction for vehicle HAL. This class handles interface with native HAL and does basic parsing * of received data (type check). Then each event is sent to corresponding {@link HalServiceBase} * implementation. It is the responsibility of {@link HalServiceBase} to convert data to * corresponding Car*Service for Car*Manager API. */ public class VehicleHal implements VehicleHalCallback, CarSystemService { private static final boolean DBG = Slogf.isLoggable(CarLog.TAG_HAL, Log.DEBUG);; private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE; private static final int GLOBAL_AREA_ID = 0; /** * If call to vehicle HAL returns StatusCode.TRY_AGAIN, we will retry to invoke that method * again for this amount of milliseconds. */ private static final int MAX_DURATION_FOR_RETRIABLE_RESULT_MS = 2000; private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 100; private static final float PRECISION_THRESHOLD = 0.001f; private final HandlerThread mHandlerThread; private final Handler mHandler; private final SubscriptionClient mSubscriptionClient; private final PowerHalService mPowerHal; private final PropertyHalService mPropertyHal; private final InputHalService mInputHal; private final VmsHalService mVmsHal; private final UserHalService mUserHal; private final DiagnosticHalService mDiagnosticHal; private final ClusterHalService mClusterHalService; private final EvsHalService mEvsHal; private final TimeHalService mTimeHalService; private final HalPropValueBuilder mPropValueBuilder; private final VehicleStub mVehicleStub; private final Object mLock = new Object(); private FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); // Only changed for test. private int mMaxDurationForRetryMs = MAX_DURATION_FOR_RETRIABLE_RESULT_MS; // Only changed for test. private int mSleepBetweenRetryMs = SLEEP_BETWEEN_RETRIABLE_INVOKES_MS; /** Stores handler for each HAL property. Property events are sent to handler. */ @GuardedBy("mLock") private final SparseArray mPropertyHandlers = new SparseArray<>(); /** This is for iterating all HalServices with fixed order. */ @GuardedBy("mLock") private final List mAllServices; @GuardedBy("mLock") private PairSparseArray mRateInfoByPropIdAreaId = new PairSparseArray<>(); @GuardedBy("mLock") private final SparseArray mAllProperties = new SparseArray<>(); @GuardedBy("mLock") private final PairSparseArray mAccessByPropIdAreaId = new PairSparseArray(); @GuardedBy("mLock") private final SparseArray mEventLog = new SparseArray<>(); // Used by injectVHALEvent for testing purposes. Delimiter for an array of data private static final String DATA_DELIMITER = ","; /** A structure to store update rate in hz and whether to enable VUR. */ private static final class RateInfo { public float updateRateHz; public boolean enableVariableUpdateRate; public float resolution; RateInfo(float updateRateHz, boolean enableVariableUpdateRate, float resolution) { this.updateRateHz = updateRateHz; this.enableVariableUpdateRate = enableVariableUpdateRate; this.resolution = resolution; } } /* package */ static final class HalSubscribeOptions { private final int mHalPropId; private final int[] mAreaIds; private final float mUpdateRateHz; private final boolean mEnableVariableUpdateRate; private final float mResolution; HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz) { this(halPropId, areaIds, updateRateHz, /* enableVariableUpdateRate= */ false, /* resolution= */ 0.0f); } HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, boolean enableVariableUpdateRate) { this(halPropId, areaIds, updateRateHz, enableVariableUpdateRate, /* resolution= */ 0.0f); } HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, boolean enableVariableUpdateRate, float resolution) { mHalPropId = halPropId; mAreaIds = areaIds; mUpdateRateHz = updateRateHz; mEnableVariableUpdateRate = enableVariableUpdateRate; mResolution = resolution; } int getHalPropId() { return mHalPropId; } int[] getAreaId() { return mAreaIds; } float getUpdateRateHz() { return mUpdateRateHz; } boolean isVariableUpdateRateEnabled() { return mEnableVariableUpdateRate; } float getResolution() { return mResolution; } @Override public boolean equals(Object other) { if (other == this) { return true; } if (!(other instanceof VehicleHal.HalSubscribeOptions)) { return false; } VehicleHal.HalSubscribeOptions o = (VehicleHal.HalSubscribeOptions) other; return mHalPropId == o.getHalPropId() && mUpdateRateHz == o.getUpdateRateHz() && Arrays.equals(mAreaIds, o.getAreaId()) && mEnableVariableUpdateRate == o.isVariableUpdateRateEnabled() && mResolution == o.getResolution(); } @Override public String toString() { return "HalSubscribeOptions{" + "PropertyId: " + mHalPropId + ", AreaId: " + Arrays.toString(mAreaIds) + ", UpdateRateHz: " + mUpdateRateHz + ", enableVariableUpdateRate: " + mEnableVariableUpdateRate + ", Resolution: " + mResolution + "}"; } @Override public int hashCode() { return Objects.hash(mHalPropId, Arrays.hashCode(mAreaIds), mUpdateRateHz, mEnableVariableUpdateRate, mResolution); } } /** * Constructs a new {@link VehicleHal} object given the {@link Context} and {@link IVehicle} * both passed as parameters. */ public VehicleHal(Context context, VehicleStub vehicle) { this(context, /* powerHal= */ null, /* propertyHal= */ null, /* inputHal= */ null, /* vmsHal= */ null, /* userHal= */ null, /* diagnosticHal= */ null, /* clusterHalService= */ null, /* timeHalService= */ null, CarServiceUtils.getHandlerThread(VehicleHal.class.getSimpleName()), vehicle); } /** * Constructs a new {@link VehicleHal} object given the services passed as parameters. * This method must be used by tests only. */ @VisibleForTesting VehicleHal(Context context, PowerHalService powerHal, PropertyHalService propertyHal, InputHalService inputHal, VmsHalService vmsHal, UserHalService userHal, DiagnosticHalService diagnosticHal, ClusterHalService clusterHalService, TimeHalService timeHalService, HandlerThread handlerThread, VehicleStub vehicle) { // Must be initialized before HalService so that HalService could use this. mPropValueBuilder = vehicle.getHalPropValueBuilder(); mHandlerThread = handlerThread; mHandler = new Handler(mHandlerThread.getLooper()); mPowerHal = powerHal != null ? powerHal : new PowerHalService(context, mFeatureFlags, this); mPropertyHal = propertyHal != null ? propertyHal : new PropertyHalService(this); mInputHal = inputHal != null ? inputHal : new InputHalService(this); mVmsHal = vmsHal != null ? vmsHal : new VmsHalService(context, this); mUserHal = userHal != null ? userHal : new UserHalService(this); mDiagnosticHal = diagnosticHal != null ? diagnosticHal : new DiagnosticHalService(this); mClusterHalService = clusterHalService != null ? clusterHalService : new ClusterHalService(context, this); mEvsHal = new EvsHalService(this); mTimeHalService = timeHalService != null ? timeHalService : new TimeHalService(context, this); mAllServices = List.of( mPowerHal, mInputHal, mDiagnosticHal, mVmsHal, mUserHal, mClusterHalService, mEvsHal, mTimeHalService, // mPropertyHal must be the last so that on init/release it can be used for all // other HAL services properties. mPropertyHal); mVehicleStub = vehicle; mSubscriptionClient = vehicle.newSubscriptionClient(this); } /** Sets fake feature flag for unit testing. */ @VisibleForTesting public void setFeatureFlags(FeatureFlags fakeFeatureFlags) { mFeatureFlags = fakeFeatureFlags; } @VisibleForTesting void setMaxDurationForRetryMs(int maxDurationForRetryMs) { mMaxDurationForRetryMs = maxDurationForRetryMs; } @VisibleForTesting void setSleepBetweenRetryMs(int sleepBetweenRetryMs) { mSleepBetweenRetryMs = sleepBetweenRetryMs; } @VisibleForTesting void fetchAllPropConfigs() { synchronized (mLock) { if (mAllProperties.size() != 0) { // already set Slogf.i(CarLog.TAG_HAL, "fetchAllPropConfigs already fetched"); return; } } HalPropConfig[] configs; try { configs = getAllPropConfigs(); if (configs == null || configs.length == 0) { Slogf.e(CarLog.TAG_HAL, "getAllPropConfigs returned empty configs"); return; } } catch (RemoteException | ServiceSpecificException e) { throw new RuntimeException("Unable to retrieve vehicle property configuration", e); } synchronized (mLock) { // Create map of all properties for (HalPropConfig p : configs) { if (DBG) { Slogf.d(CarLog.TAG_HAL, "Add config for prop: 0x%x config: %s", p.getPropId(), p.toString()); } mAllProperties.put(p.getPropId(), p); if (p.getAreaConfigs().length == 0) { mAccessByPropIdAreaId.put(p.getPropId(), /* areaId */ 0, p.getAccess()); } else { for (HalAreaConfig areaConfig : p.getAreaConfigs()) { mAccessByPropIdAreaId.put(p.getPropId(), areaConfig.getAreaId(), areaConfig.getAccess()); } } } } } private void handleOnPropertyEvent(List propValues) { synchronized (mLock) { for (int i = 0; i < propValues.size(); i++) { HalPropValue v = propValues.get(i); int propId = v.getPropId(); HalServiceBase service = mPropertyHandlers.get(propId); if (service == null) { Slogf.e(CarLog.TAG_HAL, "handleOnPropertyEvent: HalService not found for %s", v); continue; } service.getDispatchList().add(v); mServicesToDispatch.add(service); VehiclePropertyEventInfo info = mEventLog.get(propId); if (info == null) { info = new VehiclePropertyEventInfo(v); mEventLog.put(propId, info); } else { info.addNewEvent(v); } } } for (HalServiceBase s : mServicesToDispatch) { s.onHalEvents(s.getDispatchList()); s.getDispatchList().clear(); } mServicesToDispatch.clear(); } private void handleOnPropertySetError(List errors) { SparseArray> errorsByPropId = new SparseArray>(); for (int i = 0; i < errors.size(); i++) { VehiclePropError error = errors.get(i); int errorCode = error.errorCode; int propId = error.propId; int areaId = error.areaId; Slogf.w(CarLog.TAG_HAL, "onPropertySetError, errorCode: %d, prop: 0x%x, area: 0x%x", errorCode, propId, areaId); if (propId == VehicleProperty.INVALID) { continue; } ArrayList propErrors; if (errorsByPropId.get(propId) == null) { propErrors = new ArrayList(); errorsByPropId.put(propId, propErrors); } else { propErrors = errorsByPropId.get(propId); } propErrors.add(error); } for (int i = 0; i < errorsByPropId.size(); i++) { int propId = errorsByPropId.keyAt(i); HalServiceBase service; synchronized (mLock) { service = mPropertyHandlers.get(propId); } if (service == null) { Slogf.e(CarLog.TAG_HAL, "handleOnPropertySetError: HalService not found for prop: 0x%x", propId); continue; } ArrayList propErrors = errorsByPropId.get(propId); service.onPropertySetError(propErrors); } } private static String errorMessage(String action, HalPropValue propValue, String errorMsg) { return String.format("Failed to %s value for: %s, error: %s", action, propValue, errorMsg); } private HalPropValue getValueWithRetry(HalPropValue value) { return getValueWithRetry(value, /* maxRetries= */ 0); } private HalPropValue getValueWithRetry(HalPropValue value, int maxRetries) { HalPropValue result; Trace.traceBegin(TRACE_TAG, "VehicleStub#getValueWithRetry"); try { result = invokeRetriable((requestValue) -> { Trace.traceBegin(TRACE_TAG, "VehicleStub#get"); try { return mVehicleStub.get(requestValue); } finally { Trace.traceEnd(TRACE_TAG); } }, "get", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, maxRetries); } finally { Trace.traceEnd(TRACE_TAG); } if (result == null) { // If VHAL returns null result, but the status is OKAY. We treat that as NOT_AVAILABLE. throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, errorMessage("get", value, "VHAL returns null for property value")); } return result; } private void setValueWithRetry(HalPropValue value) { invokeRetriable((requestValue) -> { Trace.traceBegin(TRACE_TAG, "VehicleStub#set"); mVehicleStub.set(requestValue); Trace.traceEnd(TRACE_TAG); return null; }, "set", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, /* maxRetries= */ 0); } /** * Inits the vhal configurations. */ @Override public void init() { // nothing to init as everything was done on priorityInit } /** * PriorityInit for the vhal configurations. */ public void priorityInit() { fetchAllPropConfigs(); // PropertyHalService will take most properties, so make it big enough. ArrayMap> configsForAllServices; synchronized (mLock) { configsForAllServices = new ArrayMap<>(mAllServices.size()); for (int i = 0; i < mAllServices.size(); i++) { ArrayList configsForService = new ArrayList(); HalServiceBase service = mAllServices.get(i); configsForAllServices.put(service, configsForService); int[] supportedProps = service.getAllSupportedProperties(); if (supportedProps.length == 0) { for (int j = 0; j < mAllProperties.size(); j++) { Integer propId = mAllProperties.keyAt(j); if (service.isSupportedProperty(propId)) { HalPropConfig config = mAllProperties.get(propId); mPropertyHandlers.append(propId, service); configsForService.add(config); } } } else { for (int prop : supportedProps) { HalPropConfig config = mAllProperties.get(prop); if (config == null) { continue; } mPropertyHandlers.append(prop, service); configsForService.add(config); } } } } for (Map.Entry> entry : configsForAllServices.entrySet()) { HalServiceBase service = entry.getKey(); ArrayList configsForService = entry.getValue(); service.takeProperties(configsForService); service.init(); } } /** * Releases all connected services (power management service, input service, etc). */ @Override public void release() { ArraySet subscribedProperties = new ArraySet<>(); synchronized (mLock) { // release in reverse order from init for (int i = mAllServices.size() - 1; i >= 0; i--) { mAllServices.get(i).release(); } for (int i = 0; i < mRateInfoByPropIdAreaId.size(); i++) { int propertyId = mRateInfoByPropIdAreaId.keyPairAt(i)[0]; subscribedProperties.add(propertyId); } mRateInfoByPropIdAreaId.clear(); mAllProperties.clear(); mAccessByPropIdAreaId.clear(); } for (int i = 0; i < subscribedProperties.size(); i++) { try { mSubscriptionClient.unsubscribe(subscribedProperties.valueAt(i)); } catch (RemoteException | ServiceSpecificException e) { // Ignore exceptions on shutdown path. Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe", e); } } // keep the looper thread as should be kept for the whole life cycle. } public DiagnosticHalService getDiagnosticHal() { return mDiagnosticHal; } public PowerHalService getPowerHal() { return mPowerHal; } public PropertyHalService getPropertyHal() { return mPropertyHal; } public InputHalService getInputHal() { return mInputHal; } public UserHalService getUserHal() { return mUserHal; } public VmsHalService getVmsHal() { return mVmsHal; } public ClusterHalService getClusterHal() { return mClusterHalService; } public EvsHalService getEvsHal() { return mEvsHal; } public TimeHalService getTimeHalService() { return mTimeHalService; } public HalPropValueBuilder getHalPropValueBuilder() { return mPropValueBuilder; } @GuardedBy("mLock") private void assertServiceOwnerLocked(HalServiceBase service, int property) { if (service != mPropertyHandlers.get(property)) { throw new IllegalArgumentException(String.format( "Property 0x%x is not owned by service: %s", property, service)); } } /** * Subscribes given properties with sampling rate defaults to 0 and no special flags provided. * * @throws IllegalArgumentException thrown if property is not supported by VHAL * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL. * @see #subscribeProperty(HalServiceBase, int, float) */ public void subscribeProperty(HalServiceBase service, int property) throws IllegalArgumentException, ServiceSpecificException { subscribeProperty(service, property, /* samplingRateHz= */ 0f); } /** * Similar to {@link #subscribeProperty(HalServiceBase, int)} except that all exceptions * are caught and are logged. */ public void subscribePropertySafe(HalServiceBase service, int property) { try { subscribeProperty(service, property); } catch (IllegalArgumentException | ServiceSpecificException e) { Slogf.w(CarLog.TAG_HAL, "Failed to subscribe for property: " + VehiclePropertyIds.toString(property), e); } } /** * Subscribe given property. Only Hal service owning the property can subscribe it. * * @param service HalService that owns this property * @param property property id (VehicleProperty) * @param samplingRateHz sampling rate in Hz for continuous properties * @throws IllegalArgumentException thrown if property is not supported by VHAL * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL. */ public void subscribeProperty(HalServiceBase service, int property, float samplingRateHz) throws IllegalArgumentException, ServiceSpecificException { HalSubscribeOptions options = new HalSubscribeOptions(property, new int[0], samplingRateHz); subscribeProperty(service, List.of(options)); } /** * Similar to {@link #subscribeProperty(HalServiceBase, int, float)} except that all exceptions * are caught and converted to logs. */ public void subscribePropertySafe(HalServiceBase service, int property, float sampleRateHz) { try { subscribeProperty(service, property, sampleRateHz); } catch (IllegalArgumentException | ServiceSpecificException e) { Slogf.w(CarLog.TAG_HAL, e, "Failed to subscribe for property: %s, sample rate: %f hz", VehiclePropertyIds.toString(property), sampleRateHz); } } /** * Subscribe given property. Only Hal service owning the property can subscribe it. * * @param service HalService that owns this property * @param halSubscribeOptions Information needed to subscribe to VHAL * @throws IllegalArgumentException thrown if property is not supported by VHAL * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL. */ public void subscribeProperty(HalServiceBase service, List halSubscribeOptions) throws IllegalArgumentException, ServiceSpecificException { synchronized (mLock) { PairSparseArray previousState = cloneState(mRateInfoByPropIdAreaId); SubscribeOptions[] subscribeOptions = createVhalSubscribeOptionsLocked( service, halSubscribeOptions); if (subscribeOptions.length == 0) { if (DBG) { Slogf.d(CarLog.TAG_HAL, "Ignore the subscribeProperty request, SubscribeOptions is length 0"); } return; } try { mSubscriptionClient.subscribe(subscribeOptions); } catch (RemoteException e) { mRateInfoByPropIdAreaId = previousState; Slogf.w(CarLog.TAG_HAL, "Failed to subscribe, connection to VHAL failed", e); // Convert RemoteException to ServiceSpecificException so that it could be passed // back to the client. throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, "Failed to subscribe, connection to VHAL failed, error: " + e); } catch (ServiceSpecificException e) { mRateInfoByPropIdAreaId = previousState; Slogf.w(CarLog.TAG_HAL, "Failed to subscribe, received error from VHAL", e); throw e; } } } /** * Converts {@link HalSubscribeOptions} to {@link SubscribeOptions} which is the data structure * used by VHAL. */ @GuardedBy("mLock") private SubscribeOptions[] createVhalSubscribeOptionsLocked(HalServiceBase service, List halSubscribeOptions) throws IllegalArgumentException { if (DBG) { Slogf.d(CarLog.TAG_HAL, "creating subscribeOptions from HalSubscribeOptions of size: " + halSubscribeOptions.size()); } List subscribeOptionsList = new ArrayList<>(); for (int i = 0; i < halSubscribeOptions.size(); i++) { HalSubscribeOptions halSubscribeOption = halSubscribeOptions.get(i); int property = halSubscribeOption.getHalPropId(); int[] areaIds = halSubscribeOption.getAreaId(); float samplingRateHz = halSubscribeOption.getUpdateRateHz(); boolean enableVariableUpdateRate = halSubscribeOption.isVariableUpdateRateEnabled(); float resolution = halSubscribeOption.getResolution(); HalPropConfig config; config = mAllProperties.get(property); if (config == null) { throw new IllegalArgumentException("subscribe error: " + toPropertyIdString(property) + " is not supported"); } if (enableVariableUpdateRate) { if (config.getChangeMode() != VehiclePropertyChangeMode.CONTINUOUS) { // enableVur should be ignored if property is not continuous, but we set it to // false to be safe. enableVariableUpdateRate = false; Slogf.w(CarLog.TAG_HAL, "VUR is always off for non-continuous property: " + toPropertyIdString(property)); } if (!mFeatureFlags.variableUpdateRate()) { enableVariableUpdateRate = false; Slogf.w(CarLog.TAG_HAL, "VUR feature is not enabled, VUR is always off"); } } if (resolution != 0.0f) { if (config.getChangeMode() != VehiclePropertyChangeMode.CONTINUOUS) { // resolution should be ignored if property is not continuous, but we set it to // 0 to be safe. resolution = 0.0f; Slogf.w(CarLog.TAG_HAL, "resolution is always 0 for non-continuous property: " + toPropertyIdString(property)); } if (!mFeatureFlags.subscriptionWithResolution()) { resolution = 0.0f; Slogf.w(CarLog.TAG_HAL, "Resolution feature is not enabled, resolution is always 0"); } } if (isStaticProperty(config)) { Slogf.w(CarLog.TAG_HAL, "Ignore subscribing to static property: " + toPropertyIdString(property)); continue; } if (areaIds.length == 0) { if (!isPropertySubscribable(config)) { throw new IllegalArgumentException("Property: " + toPropertyIdString(property) + " is not subscribable"); } areaIds = getAllAreaIdsFromPropertyId(config); } else { for (int j = 0; j < areaIds.length; j++) { Integer access = mAccessByPropIdAreaId.get(config.getPropId(), areaIds[j]); if (access == null) { throw new IllegalArgumentException( "Cannot subscribe to " + toPropertyIdString(property) + " at areaId " + toAreaIdString(property, areaIds[j]) + " the property does not have the requested areaId"); } if (!isPropIdAreaIdReadable(config, access.intValue())) { throw new IllegalArgumentException( "Cannot subscribe to " + toPropertyIdString(property) + " at areaId " + toAreaIdString(property, areaIds[j]) + " the property's access mode does not contain READ"); } } } SubscribeOptions opts = new SubscribeOptions(); opts.propId = property; opts.sampleRate = samplingRateHz; opts.enableVariableUpdateRate = enableVariableUpdateRate; opts.resolution = resolution; RateInfo rateInfo = new RateInfo(samplingRateHz, enableVariableUpdateRate, resolution); int[] filteredAreaIds = filterAreaIdsWithSameRateInfo(property, areaIds, rateInfo); opts.areaIds = filteredAreaIds; if (opts.areaIds.length == 0) { if (DBG) { Slogf.d(CarLog.TAG_HAL, "property: " + VehiclePropertyIds.toString(property) + " is already subscribed at rate: " + samplingRateHz + " hz"); } continue; } assertServiceOwnerLocked(service, property); for (int j = 0; j < filteredAreaIds.length; j++) { if (DBG) { Slogf.d(CarLog.TAG_HAL, "Update subscription rate for propertyId:" + " %s, areaId: %d, SampleRateHz: %f, enableVur: %b," + " resolution: %f", VehiclePropertyIds.toString(opts.propId), filteredAreaIds[j], samplingRateHz, enableVariableUpdateRate, resolution); } mRateInfoByPropIdAreaId.put(property, filteredAreaIds[j], rateInfo); } subscribeOptionsList.add(opts); } return subscribeOptionsList.toArray(new SubscribeOptions[0]); } private int[] filterAreaIdsWithSameRateInfo(int property, int[] areaIds, RateInfo rateInfo) { List areaIdList = new ArrayList<>(); synchronized (mLock) { for (int i = 0; i < areaIds.length; i++) { RateInfo savedRateInfo = mRateInfoByPropIdAreaId.get(property, areaIds[i]); // Strict equality (==) is used here for comparing resolutions. This approach does // not introduce a margin of error through PRECISION_THRESHOLD, and thus can allow // clients to request the highest possible resolution without being limited by a // predefined threshold. This approach is assumed to be feasible under the // hypothesis that the floating point representation of numbers is consistent // across the system. That is, if two clients specify a resolution of 0.01f, // their internal representations will match, enabling an exact comparison despite // floating point inaccuracies. If this is inaccurate, we must introduce a margin // of error (ideally 1e-7 as floats can reliably represent up to 7 significant // figures, but can be higher if necessary), and update the documentation in {@link // android.car.hardware.property.Subscription.Builder#setResolution(float)} // appropriately. if (savedRateInfo != null && (Math.abs(savedRateInfo.updateRateHz - rateInfo.updateRateHz) < PRECISION_THRESHOLD) && (savedRateInfo.enableVariableUpdateRate == rateInfo.enableVariableUpdateRate) && savedRateInfo.resolution == rateInfo.resolution) { if (DBG) { Slogf.d(CarLog.TAG_HAL, "Property: %s is already subscribed at rate: %f hz" + ", enableVur: %b, resolution: %f", toPropertyIdString(property), rateInfo.updateRateHz, rateInfo.enableVariableUpdateRate, rateInfo.resolution); } continue; } areaIdList.add(areaIds[i]); } } return CarServiceUtils.toIntArray(areaIdList); } private int[] getAllAreaIdsFromPropertyId(HalPropConfig config) { HalAreaConfig[] allAreaConfigs = config.getAreaConfigs(); if (allAreaConfigs.length == 0) { return new int[]{/* areaId= */ 0}; } int[] areaId = new int[allAreaConfigs.length]; for (int i = 0; i < allAreaConfigs.length; i++) { areaId[i] = allAreaConfigs[i].getAreaId(); } return areaId; } /** * Like {@link unsubscribeProperty} except that exceptions are logged. */ public void unsubscribePropertySafe(HalServiceBase service, int property) { try { unsubscribeProperty(service, property); } catch (ServiceSpecificException e) { Slogf.w(CarLog.TAG_SERVICE, "Failed to unsubscribe: " + toPropertyIdString(property), e); } } /** * Unsubscribes from receiving notifications for the property and HAL services passed * as parameters. */ public void unsubscribeProperty(HalServiceBase service, int property) throws ServiceSpecificException { if (DBG) { Slogf.d(CarLog.TAG_HAL, "unsubscribeProperty, service:" + service + ", " + toPropertyIdString(property)); } synchronized (mLock) { HalPropConfig config = mAllProperties.get(property); if (config == null) { Slogf.w(CarLog.TAG_HAL, "unsubscribeProperty " + toPropertyIdString(property) + " does not exist"); return; } if (isStaticProperty(config)) { Slogf.w(CarLog.TAG_HAL, "Unsubscribe to a static property: " + toPropertyIdString(property) + ", do nothing"); return; } assertServiceOwnerLocked(service, property); HalAreaConfig[] halAreaConfigs = config.getAreaConfigs(); boolean isSubscribed = false; PairSparseArray previousState = cloneState(mRateInfoByPropIdAreaId); if (halAreaConfigs.length == 0) { int index = mRateInfoByPropIdAreaId.indexOfKeyPair(property, 0); if (hasReadAccess(config.getAccess()) && index >= 0) { mRateInfoByPropIdAreaId.removeAt(index); isSubscribed = true; } } else { for (int i = 0; i < halAreaConfigs.length; i++) { if (!isPropIdAreaIdReadable(config, halAreaConfigs[i].getAccess())) { Slogf.w(CarLog.TAG_HAL, "Cannot unsubscribe to " + toPropertyIdString(property) + " at areaId " + toAreaIdString(property, halAreaConfigs[i].getAreaId()) + " the property's access mode does not contain READ"); continue; } int index = mRateInfoByPropIdAreaId.indexOfKeyPair(property, halAreaConfigs[i].getAreaId()); if (index >= 0) { mRateInfoByPropIdAreaId.removeAt(index); isSubscribed = true; } } } if (!isSubscribed) { if (DBG) { Slogf.d(CarLog.TAG_HAL, "Property " + toPropertyIdString(property) + " was not subscribed, do nothing"); } return; } try { mSubscriptionClient.unsubscribe(property); } catch (RemoteException e) { mRateInfoByPropIdAreaId = previousState; Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe, connection to VHAL failed", e); throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, "Failed to unsubscribe, connection to VHAL failed, error: " + e); } catch (ServiceSpecificException e) { mRateInfoByPropIdAreaId = previousState; Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe, received error from VHAL", e); throw e; } } } /** * Indicates if the property passed as parameter is supported. */ public boolean isPropertySupported(int propertyId) { synchronized (mLock) { return mAllProperties.contains(propertyId); } } /** * Gets given property with retries. * *

If getting the property fails after all retries, it will throw * {@code IllegalStateException}. If the property is not supported, it will simply return * {@code null}. */ @Nullable public HalPropValue getIfSupportedOrFail(int propertyId, int maxRetries) { if (!isPropertySupported(propertyId)) { return null; } try { return getValueWithRetry(mPropValueBuilder.build(propertyId, GLOBAL_AREA_ID), maxRetries); } catch (Exception e) { throw new IllegalStateException(e); } } /** * This works similar to {@link #getIfSupportedOrFail(int, int)} except that this can be called * before {@code init()} is called. * *

This call will check if requested vhal property is supported by querying directly to vhal * and can have worse performance. Use this only for accessing vhal properties before * {@code ICarImpl.init()} phase. */ @Nullable public HalPropValue getIfSupportedOrFailForEarlyStage(int propertyId, int maxRetries) { fetchAllPropConfigs(); return getIfSupportedOrFail(propertyId, maxRetries); } /** * Returns the property's {@link HalPropValue} for the property id passed as parameter and * not specified area. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ public HalPropValue get(int propertyId) throws IllegalArgumentException, ServiceSpecificException { return get(propertyId, GLOBAL_AREA_ID); } /** * Returns the property's {@link HalPropValue} for the property id and area id passed as * parameters. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ public HalPropValue get(int propertyId, int areaId) throws IllegalArgumentException, ServiceSpecificException { if (DBG) { Slogf.d(CarLog.TAG_HAL, "get, " + toPropertyIdString(propertyId) + toAreaIdString(propertyId, areaId)); } return getValueWithRetry(mPropValueBuilder.build(propertyId, areaId)); } /** * Returns the property object value for the class and property id passed as parameter and * no area specified. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ public T get(Class clazz, int propertyId) throws IllegalArgumentException, ServiceSpecificException { return get(clazz, propertyId, GLOBAL_AREA_ID); } /** * Returns the property object value for the class, property id, and area id passed as * parameter. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ public T get(Class clazz, int propertyId, int areaId) throws IllegalArgumentException, ServiceSpecificException { return get(clazz, mPropValueBuilder.build(propertyId, areaId)); } /** * Returns the property object value for the class and requested property value passed as * parameter. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ @SuppressWarnings("unchecked") public T get(Class clazz, HalPropValue requestedPropValue) throws IllegalArgumentException, ServiceSpecificException { HalPropValue propValue; propValue = getValueWithRetry(requestedPropValue); if (clazz == Long.class || clazz == long.class) { Long value = propValue.getInt64Value(0); return (T) value; } else if (clazz == Integer.class || clazz == int.class) { Integer value = propValue.getInt32Value(0); return (T) value; } else if (clazz == Boolean.class || clazz == boolean.class) { Boolean value = Boolean.valueOf(propValue.getInt32Value(0) == 1); return (T) value; } else if (clazz == Float.class || clazz == float.class) { Float value = propValue.getFloatValue(0); return (T) value; } else if (clazz == Long[].class) { int size = propValue.getInt64ValuesSize(); Long[] longArray = new Long[size]; for (int i = 0; i < size; i++) { longArray[i] = propValue.getInt64Value(i); } return (T) longArray; } else if (clazz == Integer[].class) { int size = propValue.getInt32ValuesSize(); Integer[] intArray = new Integer[size]; for (int i = 0; i < size; i++) { intArray[i] = propValue.getInt32Value(i); } return (T) intArray; } else if (clazz == Float[].class) { int size = propValue.getFloatValuesSize(); Float[] floatArray = new Float[size]; for (int i = 0; i < size; i++) { floatArray[i] = propValue.getFloatValue(i); } return (T) floatArray; } else if (clazz == long[].class) { int size = propValue.getInt64ValuesSize(); long[] longArray = new long[size]; for (int i = 0; i < size; i++) { longArray[i] = propValue.getInt64Value(i); } return (T) longArray; } else if (clazz == int[].class) { int size = propValue.getInt32ValuesSize(); int[] intArray = new int[size]; for (int i = 0; i < size; i++) { intArray[i] = propValue.getInt32Value(i); } return (T) intArray; } else if (clazz == float[].class) { int size = propValue.getFloatValuesSize(); float[] floatArray = new float[size]; for (int i = 0; i < size; i++) { floatArray[i] = propValue.getFloatValue(i); } return (T) floatArray; } else if (clazz == byte[].class) { return (T) propValue.getByteArray(); } else if (clazz == String.class) { return (T) propValue.getStringValue(); } else { throw new IllegalArgumentException("Unexpected type: " + clazz); } } /** * Returns the vehicle's {@link HalPropValue} for the requested property value passed * as parameter. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ public HalPropValue get(HalPropValue requestedPropValue) throws IllegalArgumentException, ServiceSpecificException { return getValueWithRetry(requestedPropValue); } /** * Set property. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ public void set(HalPropValue propValue) throws IllegalArgumentException, ServiceSpecificException { setValueWithRetry(propValue); } @CheckResult HalPropValueSetter set(int propId) { return set(propId, GLOBAL_AREA_ID); } @CheckResult HalPropValueSetter set(int propId, int areaId) { return new HalPropValueSetter(propId, areaId); } private static boolean hasReadAccess(int accessLevel) { return accessLevel == VehiclePropertyAccess.READ || accessLevel == VehiclePropertyAccess.READ_WRITE; } private static boolean isPropIdAreaIdReadable(HalPropConfig config, int areaIdAccess) { return (areaIdAccess == VehiclePropertyAccess.NONE) ? hasReadAccess(config.getAccess()) : hasReadAccess(areaIdAccess); } /** * Returns whether the property is readable and not static. */ static boolean isPropertySubscribable(HalPropConfig config) { if (isStaticProperty(config)) { Slogf.w(CarLog.TAG_HAL, "Subscribe to a static property: " + toPropertyIdString(config.getPropId()) + ", do nothing"); return false; } if (config.getAreaConfigs().length == 0) { boolean hasReadAccess = hasReadAccess(config.getAccess()); if (!hasReadAccess) { Slogf.w(CarLog.TAG_HAL, "Cannot subscribe to " + toPropertyIdString(config.getPropId()) + " the property's access mode does not contain READ"); } return hasReadAccess; } for (HalAreaConfig halAreaConfig : config.getAreaConfigs()) { if (!isPropIdAreaIdReadable(config, halAreaConfig.getAccess())) { Slogf.w(CarLog.TAG_HAL, "Cannot subscribe to " + toPropertyIdString(config.getPropId()) + " at areaId " + toAreaIdString(config.getPropId(), halAreaConfig.getAreaId()) + " the property's access mode does not contain READ"); return false; } } return true; } /** * Sets a passed propertyId+areaId from the shell command. * * @param propertyId Property ID * @param areaId Area ID * @param data Comma-separated value. */ public void setPropertyFromCommand(int propertyId, int areaId, String data, IndentingPrintWriter writer) throws IllegalArgumentException, ServiceSpecificException { long timestampNanos = SystemClock.elapsedRealtimeNanos(); HalPropValue halPropValue = createPropValueForInjecting(mPropValueBuilder, propertyId, areaId, List.of(data.split(DATA_DELIMITER)), timestampNanos); if (halPropValue == null) { throw new IllegalArgumentException( "Unsupported property type: propertyId=" + toPropertyIdString(propertyId) + ", areaId=" + toAreaIdString(propertyId, areaId)); } set(halPropValue); } private final ArraySet mServicesToDispatch = new ArraySet<>(); @Override public void onPropertyEvent(ArrayList propValues) { mHandler.post(() -> handleOnPropertyEvent(propValues)); } @Override public void onPropertySetError(ArrayList errors) { mHandler.post(() -> handleOnPropertySetError(errors)); } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dump(IndentingPrintWriter writer) { synchronized (mLock) { writer.println("**dump HAL services**"); for (int i = 0; i < mAllServices.size(); i++) { mAllServices.get(i).dump(writer); } // Dump all VHAL property configure. dumpPropertyConfigs(writer, -1); writer.printf("**All Events, now ns:%d**\n", SystemClock.elapsedRealtimeNanos()); for (int i = 0; i < mEventLog.size(); i++) { VehiclePropertyEventInfo info = mEventLog.valueAt(i); writer.printf("event count:%d, lastEvent: ", info.mEventCount); dumpPropValue(writer, info.mLastEvent); } writer.println("**Property handlers**"); for (int i = 0; i < mPropertyHandlers.size(); i++) { int propId = mPropertyHandlers.keyAt(i); HalServiceBase service = mPropertyHandlers.valueAt(i); writer.printf("Property Id: %d // 0x%x name: %s, service: %s\n", propId, propId, VehiclePropertyIds.toString(propId), service); } } } /** * Dumps or debug VHAL. */ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dumpVhal(ParcelFileDescriptor fd, List options) throws RemoteException { mVehicleStub.dump(fd.getFileDescriptor(), options); } /** * Dumps the list of HALs. */ public void dumpListHals(PrintWriter writer) { synchronized (mLock) { for (int i = 0; i < mAllServices.size(); i++) { writer.println(mAllServices.get(i).getClass().getName()); } } } /** * Dumps the given HALs. */ public void dumpSpecificHals(PrintWriter writer, String... halNames) { synchronized (mLock) { ArrayMap byName = new ArrayMap<>(); for (int index = 0; index < mAllServices.size(); index++) { HalServiceBase halService = mAllServices.get(index); byName.put(halService.getClass().getSimpleName(), halService); } for (String halName : halNames) { HalServiceBase service = byName.get(halName); if (service == null) { writer.printf("No HAL named %s. Valid options are: %s\n", halName, byName.keySet()); continue; } service.dump(writer); } } } /** * Dumps vehicle property values. * * @param propertyId property id, dump all properties' value if it is {@code -1}. * @param areaId areaId of the property, dump the property for all areaIds in the config * if it is {@code -1} */ public void dumpPropertyValueByCommand(PrintWriter writer, int propertyId, int areaId) { if (propertyId == -1) { writer.println("**All property values**"); synchronized (mLock) { for (int i = 0; i < mAllProperties.size(); i++) { HalPropConfig config = mAllProperties.valueAt(i); dumpPropertyValueByConfig(writer, config); } } } else if (areaId == -1) { synchronized (mLock) { HalPropConfig config = mAllProperties.get(propertyId); if (config == null) { writer.printf("Property: %s not supported by HAL\n", toPropertyIdString(propertyId)); return; } dumpPropertyValueByConfig(writer, config); } } else { try { HalPropValue value = get(propertyId, areaId); dumpPropValue(writer, value); } catch (RuntimeException e) { writer.printf("Cannot get property value for property: %s in areaId: %s.\n", toPropertyIdString(propertyId), toAreaIdString(propertyId, areaId)); } } } /** * Gets all property configs from VHAL. */ public HalPropConfig[] getAllPropConfigs() throws RemoteException, ServiceSpecificException { return mVehicleStub.getAllPropConfigs(); } /** * Gets the property config for a property, returns {@code null} if not supported. */ public @Nullable HalPropConfig getPropConfig(int propId) { synchronized (mLock) { return mAllProperties.get(propId); } } /** * Checks whether we are connected to AIDL VHAL: {@code true} or HIDL VHAL: {@code false}. */ public boolean isAidlVhal() { return mVehicleStub.isAidlVhal(); } /** * Checks if fake VHAL mode is enabled. * * @return {@code true} if car service is connected to FakeVehicleStub. */ public boolean isFakeModeEnabled() { return mVehicleStub.isFakeModeEnabled(); } private void dumpPropertyValueByConfig(PrintWriter writer, HalPropConfig config) { int propertyId = config.getPropId(); HalAreaConfig[] areaConfigs = config.getAreaConfigs(); if (areaConfigs == null || areaConfigs.length == 0) { try { HalPropValue value = get(config.getPropId()); dumpPropValue(writer, value); } catch (RuntimeException e) { writer.printf("Can not get property value for property: %s, areaId: %s\n", toPropertyIdString(propertyId), toAreaIdString(propertyId, /*areaId=*/0)); } } else { for (HalAreaConfig areaConfig : areaConfigs) { int areaId = areaConfig.getAreaId(); try { HalPropValue value = get(propertyId, areaId); dumpPropValue(writer, value); } catch (RuntimeException e) { writer.printf( "Can not get property value for property: %s in areaId: %s\n", toPropertyIdString(propertyId), toAreaIdString(propertyId, areaId)); } } } } /** * Dump VHAL property configs. * Dump all properties if {@code propertyId} is equal to {@code -1}. * * @param propertyId the property ID */ public void dumpPropertyConfigs(PrintWriter writer, int propertyId) { HalPropConfig[] configs; synchronized (mLock) { configs = new HalPropConfig[mAllProperties.size()]; for (int i = 0; i < mAllProperties.size(); i++) { configs[i] = mAllProperties.valueAt(i); } } if (propertyId == -1) { writer.println("**All properties**"); for (HalPropConfig config : configs) { dumpPropertyConfigsHelp(writer, config); } return; } for (HalPropConfig config : configs) { if (config.getPropId() == propertyId) { dumpPropertyConfigsHelp(writer, config); return; } } } /** Dumps VehiclePropertyConfigs */ private static void dumpPropertyConfigsHelp(PrintWriter writer, HalPropConfig config) { int propertyId = config.getPropId(); writer.printf( "Property:%s, group:%s, areaType:%s, valueType:%s,\n access:%s, changeMode:%s, " + "configArray:%s, minSampleRateHz:%f, maxSampleRateHz:%f\n", toPropertyIdString(propertyId), toGroupString(propertyId), toAreaTypeString(propertyId), toValueTypeString(propertyId), toAccessString(config.getAccess()), toChangeModeString(config.getChangeMode()), Arrays.toString(config.getConfigArray()), config.getMinSampleRate(), config.getMaxSampleRate()); if (config.getAreaConfigs() == null) { return; } for (HalAreaConfig area : config.getAreaConfigs()) { writer.printf(" areaId:%s, access:%s, f min:%f, f max:%f, i min:%d, i max:%d," + " i64 min:%d, i64 max:%d\n", toAreaIdString(propertyId, area.getAreaId()), toAccessString(area.getAccess()), area.getMinFloatValue(), area.getMaxFloatValue(), area.getMinInt32Value(), area.getMaxInt32Value(), area.getMinInt64Value(), area.getMaxInt64Value()); } } /** * Inject a VHAL event * * @param propertyId the property ID as defined in the HAL * @param areaId the area ID that this event services * @param value the data value of the event * @param delayTimeSeconds add a certain duration to event timestamp */ public void injectVhalEvent(int propertyId, int areaId, String value, int delayTimeSeconds) throws NumberFormatException { long timestampNanos = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos( delayTimeSeconds); HalPropValue v = createPropValueForInjecting(mPropValueBuilder, propertyId, areaId, Arrays.asList(value.split(DATA_DELIMITER)), timestampNanos); if (v == null) { return; } mHandler.post(() -> handleOnPropertyEvent(Lists.newArrayList(v))); } /** * Injects continuous VHAL events. * * @param property the Vehicle property Id as defined in the HAL * @param zone the zone that this event services * @param value the data value of the event * @param sampleRate the sample rate for events in Hz * @param timeDurationInSec the duration for injecting events in seconds */ public void injectContinuousVhalEvent(int property, int zone, String value, float sampleRate, long timeDurationInSec) { HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone, new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), 0); if (v == null) { return; } // rate in Hz if (sampleRate <= 0) { Slogf.e(CarLog.TAG_HAL, "Inject events at an invalid sample rate: " + sampleRate); return; } long period = (long) (1000 / sampleRate); long stopTime = timeDurationInSec * 1000 + SystemClock.elapsedRealtime(); Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { if (stopTime < SystemClock.elapsedRealtime()) { timer.cancel(); timer.purge(); } else { // Avoid the fake events be covered by real Event long timestamp = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(timeDurationInSec); HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone, new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), timestamp); mHandler.post(() -> handleOnPropertyEvent(Lists.newArrayList(v))); } } }, /* delay= */0, period); } // Returns null if the property type is unsupported. @Nullable private static HalPropValue createPropValueForInjecting(HalPropValueBuilder builder, int propId, int zoneId, List dataList, long timestamp) { int propertyType = propId & VehiclePropertyType.MASK; // Values can be comma separated list switch (propertyType) { case VehiclePropertyType.BOOLEAN: boolean boolValue = Boolean.parseBoolean(dataList.get(0)); return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, boolValue ? 1 : 0); case VehiclePropertyType.INT64: case VehiclePropertyType.INT64_VEC: long[] longValues = new long[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { longValues[i] = Long.decode(dataList.get(i)); } return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, longValues); case VehiclePropertyType.INT32: case VehiclePropertyType.INT32_VEC: int[] intValues = new int[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { intValues[i] = Integer.decode(dataList.get(i)); } return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, intValues); case VehiclePropertyType.FLOAT: case VehiclePropertyType.FLOAT_VEC: float[] floatValues = new float[dataList.size()]; for (int i = 0; i < dataList.size(); i++) { floatValues[i] = Float.parseFloat(dataList.get(i)); } return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE, floatValues); default: Slogf.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType); return null; } } private static class VehiclePropertyEventInfo { private int mEventCount; private HalPropValue mLastEvent; private VehiclePropertyEventInfo(HalPropValue event) { mEventCount = 1; mLastEvent = event; } private void addNewEvent(HalPropValue event) { mEventCount++; mLastEvent = event; } } final class HalPropValueSetter { final int mPropId; final int mAreaId; private HalPropValueSetter(int propId, int areaId) { mPropId = propId; mAreaId = areaId; } /** * Set the property to the given value. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ void to(boolean value) throws IllegalArgumentException, ServiceSpecificException { to(value ? 1 : 0); } /** * Set the property to the given value. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ void to(int value) throws IllegalArgumentException, ServiceSpecificException { HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, value); submit(propValue); } /** * Set the property to the given values. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ void to(int[] values) throws IllegalArgumentException, ServiceSpecificException { HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, values); submit(propValue); } /** * Set the property to the given values. * * @throws IllegalArgumentException if argument is invalid * @throws ServiceSpecificException if VHAL returns error */ void to(Collection values) throws IllegalArgumentException, ServiceSpecificException { int[] intValues = new int[values.size()]; int i = 0; for (int value : values) { intValues[i] = value; i++; } HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, intValues); submit(propValue); } void submit(HalPropValue propValue) throws IllegalArgumentException, ServiceSpecificException { if (DBG) { Slogf.d(CarLog.TAG_HAL, "set - " + propValue); } setValueWithRetry(propValue); } } private static void dumpPropValue(PrintWriter writer, HalPropValue value) { writer.println(value); } interface RetriableAction { @Nullable HalPropValue run(HalPropValue requestValue) throws ServiceSpecificException, RemoteException; } private static HalPropValue invokeRetriable(RetriableAction action, String operation, HalPropValue requestValue, long maxDurationForRetryMs, long sleepBetweenRetryMs, int maxRetries) throws ServiceSpecificException, IllegalArgumentException { Retrier retrier = new Retrier(action, operation, requestValue, maxDurationForRetryMs, sleepBetweenRetryMs, maxRetries); HalPropValue result = retrier.invokeAction(); if (DBG) { Slogf.d(CarLog.TAG_HAL, "Invoked retriable action for %s - RequestValue: %s - ResultValue: %s, for " + "retrier: %s", operation, requestValue, result, retrier); } return result; } private PairSparseArray cloneState(PairSparseArray state) { PairSparseArray cloned = new PairSparseArray<>(); for (int i = 0; i < state.size(); i++) { int[] keyPair = state.keyPairAt(i); cloned.put(keyPair[0], keyPair[1], state.valueAt(i)); } return cloned; } private static boolean isStaticProperty(HalPropConfig config) { return config.getChangeMode() == VehiclePropertyChangeMode.STATIC; } private static final class Retrier { private final RetriableAction mAction; private final String mOperation; private final HalPropValue mRequestValue; private final long mMaxDurationForRetryMs; private final long mSleepBetweenRetryMs; private final int mMaxRetries; private final long mStartTime; private int mRetryCount = 0; Retrier(RetriableAction action, String operation, HalPropValue requestValue, long maxDurationForRetryMs, long sleepBetweenRetryMs, int maxRetries) { mAction = action; mOperation = operation; mRequestValue = requestValue; mMaxDurationForRetryMs = maxDurationForRetryMs; mSleepBetweenRetryMs = sleepBetweenRetryMs; mMaxRetries = maxRetries; mStartTime = uptimeMillis(); } HalPropValue invokeAction() throws ServiceSpecificException, IllegalArgumentException { mRetryCount++; try { return mAction.run(mRequestValue); } catch (ServiceSpecificException e) { switch (e.errorCode) { case StatusCode.INVALID_ARG: throw new IllegalArgumentException(errorMessage(mOperation, mRequestValue, e.toString())); case StatusCode.TRY_AGAIN: return sleepAndTryAgain(e); default: throw e; } } catch (RemoteException e) { return sleepAndTryAgain(e); } } public String toString() { return "Retrier{" + ", Operation=" + mOperation + ", RequestValue=" + mRequestValue + ", MaxDurationForRetryMs=" + mMaxDurationForRetryMs + ", SleepBetweenRetriesMs=" + mSleepBetweenRetryMs + ", MaxRetries=" + mMaxDurationForRetryMs + ", StartTime=" + mStartTime + "}"; } private HalPropValue sleepAndTryAgain(Exception e) throws ServiceSpecificException, IllegalArgumentException { Slogf.d(CarLog.TAG_HAL, "trying the request: " + toPropertyIdString(mRequestValue.getPropId()) + ", " + toAreaIdString(mRequestValue.getPropId(), mRequestValue.getAreaId()) + " again..."); try { Thread.sleep(mSleepBetweenRetryMs); } catch (InterruptedException interruptedException) { Thread.currentThread().interrupt(); Slogf.w(CarLog.TAG_HAL, "Thread was interrupted while waiting for vehicle HAL.", interruptedException); throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, errorMessage(mOperation, mRequestValue, interruptedException.toString())); } if (mMaxRetries != 0) { // If mMaxRetries is specified, check the retry count. if (mMaxRetries == mRetryCount) { throw new ServiceSpecificException(StatusCode.TRY_AGAIN, errorMessage(mOperation, mRequestValue, "cannot get property after " + mRetryCount + " retires, " + "last exception: " + e)); } } else if ((uptimeMillis() - mStartTime) >= mMaxDurationForRetryMs) { // Otherwise, check whether we have reached timeout. throw new ServiceSpecificException(StatusCode.TRY_AGAIN, errorMessage(mOperation, mRequestValue, "cannot get property within " + mMaxDurationForRetryMs + "ms, last exception: " + e)); } return invokeAction(); } } /** * Queries HalPropValue with list of GetVehicleHalRequest objects. * *

This method gets the HalPropValue using async methods. */ public void getAsync(List getVehicleStubAsyncRequests, VehicleStub.VehicleStubCallbackInterface getVehicleStubAsyncCallback) { mVehicleStub.getAsync(getVehicleStubAsyncRequests, getVehicleStubAsyncCallback); } /** * Sets vehicle property value asynchronously. */ public void setAsync(List setVehicleStubAsyncRequests, VehicleStub.VehicleStubCallbackInterface setVehicleStubAsyncCallback) { mVehicleStub.setAsync(setVehicleStubAsyncRequests, setVehicleStubAsyncCallback); } /** * Cancels all the on-going async requests with the given request IDs. */ public void cancelRequests(List vehicleStubRequestIds) { mVehicleStub.cancelRequests(vehicleStubRequestIds); } }