/* * Copyright (C) 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 com.android.car; import static com.android.car.internal.property.CarPropertyErrorCodes.convertVhalStatusCodeToCarPropertyManagerErrorCodes; import android.annotation.Nullable; import android.car.builtin.os.ServiceManagerHelper; import android.car.builtin.os.TraceHelper; import android.car.builtin.util.Slogf; import android.car.hardware.property.CarPropertyManager; import android.car.util.concurrent.AndroidFuture; import android.hardware.automotive.vehicle.GetValueRequest; import android.hardware.automotive.vehicle.GetValueRequests; import android.hardware.automotive.vehicle.GetValueResult; import android.hardware.automotive.vehicle.GetValueResults; import android.hardware.automotive.vehicle.IVehicle; import android.hardware.automotive.vehicle.IVehicleCallback; import android.hardware.automotive.vehicle.SetValueRequest; import android.hardware.automotive.vehicle.SetValueRequests; import android.hardware.automotive.vehicle.SetValueResult; import android.hardware.automotive.vehicle.SetValueResults; import android.hardware.automotive.vehicle.StatusCode; import android.hardware.automotive.vehicle.SubscribeOptions; import android.hardware.automotive.vehicle.VehiclePropConfig; import android.hardware.automotive.vehicle.VehiclePropConfigs; import android.hardware.automotive.vehicle.VehiclePropError; import android.hardware.automotive.vehicle.VehiclePropErrors; import android.hardware.automotive.vehicle.VehiclePropValue; import android.hardware.automotive.vehicle.VehiclePropValues; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseArray; import com.android.car.hal.AidlHalPropConfig; import com.android.car.hal.HalPropConfig; import com.android.car.hal.HalPropValue; import com.android.car.hal.HalPropValueBuilder; import com.android.car.hal.VehicleHalCallback; import com.android.car.internal.LargeParcelable; import com.android.car.internal.LongPendingRequestPool; import com.android.car.internal.LongPendingRequestPool.TimeoutCallback; import com.android.car.internal.LongRequestIdWithTimeout; import com.android.car.internal.property.CarPropertyErrorCodes; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.expresslog.Histogram; import java.io.FileDescriptor; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; final class AidlVehicleStub extends VehicleStub { private static final Histogram sVehicleHalGetSyncLatencyHistogram = new Histogram( "automotive_os.value_sync_hal_get_property_latency", new Histogram.ScaledRangeOptions(/* binCount= */ 20, /* minValue= */ 0, /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f)); private static final Histogram sVehicleHalSetSyncLatencyHistogram = new Histogram( "automotive_os.value_sync_hal_set_property_latency", new Histogram.ScaledRangeOptions(/* binCount= */ 20, /* minValue= */ 0, /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f)); private static final String AIDL_VHAL_SERVICE = "android.hardware.automotive.vehicle.IVehicle/default"; // default timeout: 10s private static final long DEFAULT_TIMEOUT_MS = 10_000; private static final String TAG = CarLog.tagFor(AidlVehicleStub.class); private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE; private final IVehicle mAidlVehicle; private final HalPropValueBuilder mPropValueBuilder; private final GetSetValuesCallback mGetSetValuesCallback; private final HandlerThread mHandlerThread; private final Handler mHandler; private final AtomicLong mRequestId = new AtomicLong(0); private final Object mLock = new Object(); // PendingSyncRequestPool is thread-safe. private final PendingSyncRequestPool mPendingSyncGetValueRequestPool = new PendingSyncRequestPool<>(); private final PendingSyncRequestPool mPendingSyncSetValueRequestPool = new PendingSyncRequestPool<>(); // PendingAsyncRequestPool is thread-safe. private final PendingAsyncRequestPool mPendingAsyncRequestPool; // This might be modifed during tests. private long mSyncOpTimeoutInMs = DEFAULT_TIMEOUT_MS; private static class AsyncRequestInfo implements LongRequestIdWithTimeout { private final int mServiceRequestId; private final VehicleStubCallbackInterface mClientCallback; private final long mTimeoutUptimeMs; private final long mVhalRequestId; private AsyncRequestInfo( long vhalRequestId, int serviceRequestId, VehicleStubCallbackInterface clientCallback, long timeoutUptimeMs) { mVhalRequestId = vhalRequestId; mServiceRequestId = serviceRequestId; mClientCallback = clientCallback; mTimeoutUptimeMs = timeoutUptimeMs; } @Override public long getRequestId() { return mVhalRequestId; } @Override public long getTimeoutUptimeMs() { return mTimeoutUptimeMs; } public int getServiceRequestId() { return mServiceRequestId; } public VehicleStubCallbackInterface getClientCallback() { return mClientCallback; } } AidlVehicleStub() { this(getAidlVehicle()); } @VisibleForTesting AidlVehicleStub(IVehicle aidlVehicle) { this(aidlVehicle, CarServiceUtils.getHandlerThread(AidlVehicleStub.class.getSimpleName())); } @VisibleForTesting AidlVehicleStub(IVehicle aidlVehicle, HandlerThread handlerThread) { mAidlVehicle = aidlVehicle; mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true); mHandlerThread = handlerThread; mHandler = new Handler(mHandlerThread.getLooper()); mGetSetValuesCallback = new GetSetValuesCallback(); mPendingAsyncRequestPool = new PendingAsyncRequestPool(mHandler.getLooper()); } /** * Sets the timeout for getValue/setValue requests in milliseconds. */ @VisibleForTesting void setSyncOpTimeoutInMs(long timeoutMs) { mSyncOpTimeoutInMs = timeoutMs; } @VisibleForTesting int countPendingRequests() { synchronized (mLock) { return mPendingAsyncRequestPool.size() + mPendingSyncGetValueRequestPool.size() + mPendingSyncSetValueRequestPool.size(); } } /** * Checks whether we are connected to AIDL VHAL: {@code true} or HIDL VHAL: {@code false}. */ @Override public boolean isAidlVhal() { return true; } /** * Gets a HalPropValueBuilder that could be used to build a HalPropValue. * * @return a builder to build HalPropValue. */ @Override public HalPropValueBuilder getHalPropValueBuilder() { return mPropValueBuilder; } /** * Returns whether this vehicle stub is connecting to a valid vehicle HAL. * * @return Whether this vehicle stub is connecting to a valid vehicle HAL. */ @Override public boolean isValid() { return mAidlVehicle != null; } /** * Gets the interface descriptor for the connecting vehicle HAL. * * @return the interface descriptor. * @throws IllegalStateException If unable to get the descriptor. */ @Override public String getInterfaceDescriptor() throws IllegalStateException { try { return mAidlVehicle.asBinder().getInterfaceDescriptor(); } catch (RemoteException e) { throw new IllegalStateException("Unable to get Vehicle HAL interface descriptor", e); } } /** * Registers a death recipient that would be called when vehicle HAL died. * * @param recipient A death recipient. * @throws IllegalStateException If unable to register the death recipient. */ @Override public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException { try { mAidlVehicle.asBinder().linkToDeath(recipient, /*flag=*/ 0); } catch (RemoteException e) { throw new IllegalStateException("Failed to linkToDeath Vehicle HAL"); } } /** * Unlinks a previously linked death recipient. * * @param recipient A previously linked death recipient. */ @Override public void unlinkToDeath(IVehicleDeathRecipient recipient) { mAidlVehicle.asBinder().unlinkToDeath(recipient, /*flag=*/ 0); } /** * Gets all property configs. * * @return All the property configs. * @throws RemoteException if the remote operation fails. * @throws ServiceSpecificException if VHAL returns service specific error. */ @Override public HalPropConfig[] getAllPropConfigs() throws RemoteException, ServiceSpecificException { VehiclePropConfigs propConfigs = (VehiclePropConfigs) LargeParcelable.reconstructStableAIDLParcelable( mAidlVehicle.getAllPropConfigs(), /* keepSharedMemory= */ false); VehiclePropConfig[] payloads = propConfigs.payloads; int size = payloads.length; HalPropConfig[] configs = new HalPropConfig[size]; for (int i = 0; i < size; i++) { configs[i] = new AidlHalPropConfig(payloads[i]); } return configs; } /** * Gets a new {@code SubscriptionClient} that could be used to subscribe/unsubscribe. * * @param callback A callback that could be used to receive events. * @return a {@code SubscriptionClient} that could be used to subscribe/unsubscribe. */ @Override public SubscriptionClient newSubscriptionClient(VehicleHalCallback callback) { return new AidlSubscriptionClient(callback, mPropValueBuilder); } /** * Gets a property. * * @param requestedPropValue The property to get. * @return The vehicle property value. * @throws RemoteException if the remote operation fails. * @throws ServiceSpecificException if VHAL returns service specific error. */ @Override @Nullable public HalPropValue get(HalPropValue requestedPropValue) throws RemoteException, ServiceSpecificException { long currentTime = System.currentTimeMillis(); HalPropValue halPropValue = getOrSetSync(requestedPropValue, mPendingSyncGetValueRequestPool, new AsyncGetRequestsHandler(), (result) -> { if (result.status != StatusCode.OK) { throw new ServiceSpecificException(result.status, "failed to get value for " + printPropIdAreaId(requestedPropValue)); } if (result.prop == null) { return null; } return mPropValueBuilder.build(result.prop); }); sVehicleHalGetSyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime)); return halPropValue; } /** * Sets a property. * * @param requestedPropValue The property to set. * @throws RemoteException if the remote operation fails. * @throws ServiceSpecificException if VHAL returns service specific error. */ @Override public void set(HalPropValue requestedPropValue) throws RemoteException, ServiceSpecificException { long currentTime = System.currentTimeMillis(); getOrSetSync(requestedPropValue, mPendingSyncSetValueRequestPool, new AsyncSetRequestsHandler(), (result) -> { if (result.status != StatusCode.OK) { throw new ServiceSpecificException(result.status, "failed to set value for " + printPropIdAreaId(requestedPropValue)); } return null; }); sVehicleHalSetSyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime)); } @Override public void getAsync(List getVehicleStubAsyncRequests, VehicleStubCallbackInterface getCallback) { getOrSetAsync(getVehicleStubAsyncRequests, getCallback, new AsyncGetRequestsHandler(), new AsyncGetResultsHandler(mPropValueBuilder)); } @Override public void setAsync(List setVehicleStubAsyncRequests, VehicleStubCallbackInterface setCallback) { getOrSetAsync(setVehicleStubAsyncRequests, setCallback, new AsyncSetRequestsHandler(), new AsyncSetResultsHandler()); } @Override public void dump(FileDescriptor fd, List args) throws RemoteException { mAidlVehicle.asBinder().dump(fd, args.toArray(new String[args.size()])); } // Get all the VHAL request IDs according to the service request IDs and remove them from // pending requests map. @Override public void cancelRequests(List serviceRequestIds) { mPendingAsyncRequestPool.cancelRequests(serviceRequestIds); } /** * A thread-safe pending sync request pool. */ private static final class PendingSyncRequestPool { private final Object mSyncRequestPoolLock = new Object(); @GuardedBy("mSyncRequestPoolLock") private final LongSparseArray> mPendingRequestsByVhalRequestId = new LongSparseArray(); AndroidFuture addRequest(long vhalRequestId) { synchronized (mSyncRequestPoolLock) { AndroidFuture resultFuture = new AndroidFuture(); mPendingRequestsByVhalRequestId.put(vhalRequestId, resultFuture); return resultFuture; } } @Nullable AndroidFuture finishRequestIfFound(long vhalRequestId) { synchronized (mSyncRequestPoolLock) { AndroidFuture pendingRequest = mPendingRequestsByVhalRequestId.get(vhalRequestId); mPendingRequestsByVhalRequestId.remove(vhalRequestId); return pendingRequest; } } int size() { synchronized (mSyncRequestPoolLock) { return mPendingRequestsByVhalRequestId.size(); } } } /** * A thread-safe pending async request pool. */ private static final class PendingAsyncRequestPool { private final Object mAsyncRequestPoolLock = new Object(); private final TimeoutCallback mTimeoutCallback = new AsyncRequestTimeoutCallback(); private final Looper mLooper; @GuardedBy("mAsyncRequestPoolLock") private final LongPendingRequestPool mPendingRequestPool; PendingAsyncRequestPool(Looper looper) { mLooper = looper; mPendingRequestPool = new LongPendingRequestPool<>(mLooper, mTimeoutCallback); } private class AsyncRequestTimeoutCallback implements TimeoutCallback { @Override public void onRequestsTimeout(List vhalRequestIds) { ArrayMap> serviceRequestIdsByCallback = new ArrayMap<>(); for (int i = 0; i < vhalRequestIds.size(); i++) { long vhalRequestId = vhalRequestIds.get(i); AsyncRequestInfo requestInfo = finishRequestIfFound(vhalRequestId); if (requestInfo == null) { // We already finished the request or the callback is already dead, ignore. Slogf.w(TAG, "onRequestsTimeout: the request for VHAL request ID: %d is " + "already finished or the callback is already dead, ignore", vhalRequestId); continue; } VehicleStubCallbackInterface getAsyncCallback = requestInfo.getClientCallback(); if (serviceRequestIdsByCallback.get(getAsyncCallback) == null) { serviceRequestIdsByCallback.put(getAsyncCallback, new ArrayList<>()); } serviceRequestIdsByCallback.get(getAsyncCallback).add( requestInfo.getServiceRequestId()); } for (int i = 0; i < serviceRequestIdsByCallback.size(); i++) { serviceRequestIdsByCallback.keyAt(i).onRequestsTimeout( serviceRequestIdsByCallback.valueAt(i)); } } } void addRequests(List requestInfo) { synchronized (mAsyncRequestPoolLock) { mPendingRequestPool.addPendingRequests(requestInfo); } } @Nullable AsyncRequestInfo finishRequestIfFound(long vhalRequestId) { synchronized (mAsyncRequestPoolLock) { AsyncRequestInfo requestInfo = mPendingRequestPool.getRequestIfFound(vhalRequestId); mPendingRequestPool.removeRequest(vhalRequestId); return requestInfo; } } int size() { synchronized (mAsyncRequestPoolLock) { return mPendingRequestPool.size(); } } boolean contains(long vhalRequestId) { synchronized (mAsyncRequestPoolLock) { return mPendingRequestPool.getRequestIfFound(vhalRequestId) != null; } } void cancelRequests(List serviceRequestIds) { Set serviceRequestIdsSet = new ArraySet<>(serviceRequestIds); List vhalRequestIdsToCancel = new ArrayList<>(); synchronized (mAsyncRequestPoolLock) { for (int i = 0; i < mPendingRequestPool.size(); i++) { int serviceRequestId = mPendingRequestPool.valueAt(i) .getServiceRequestId(); if (serviceRequestIdsSet.contains(serviceRequestId)) { vhalRequestIdsToCancel.add(mPendingRequestPool.keyAt(i)); } } for (int i = 0; i < vhalRequestIdsToCancel.size(); i++) { long vhalRequestIdToCancel = vhalRequestIdsToCancel.get(i); Slogf.w(TAG, "the request for VHAL request ID: %d is cancelled", vhalRequestIdToCancel); mPendingRequestPool.removeRequest(vhalRequestIdToCancel); } } } void removeRequestsForCallback(VehicleStubCallbackInterface callback) { synchronized (mAsyncRequestPoolLock) { List requestIdsToRemove = new ArrayList<>(); for (int i = 0; i < mPendingRequestPool.size(); i++) { if (mPendingRequestPool.valueAt(i).getClientCallback() == callback) { requestIdsToRemove.add(mPendingRequestPool.keyAt(i)); } } for (int i = 0; i < requestIdsToRemove.size(); i++) { mPendingRequestPool.removeRequest(requestIdsToRemove.get(i)); } } } } /** * An abstract interface for handling async get/set value requests from vehicle stub. */ private abstract static class AsyncRequestsHandler { /** * Preallocsate size array for storing VHAL requests. */ abstract void allocateVhalRequestSize(int size); /** * Add a vhal request to be sent later. */ abstract void addVhalRequest(long vhalRequestId, HalPropValue halPropValue); /** * Get the list of stored request items. */ abstract VhalRequestType[] getRequestItems(); /** * Send the prepared requests to VHAL. */ abstract void sendRequestsToVhal(IVehicle iVehicle, GetSetValuesCallback callbackForVhal) throws RemoteException, ServiceSpecificException; /** * Get the request ID for the request. */ abstract long getVhalRequestId(VhalRequestType vhalRequest); } /** * An abstract class to handle async get/set value results from VHAL. */ private abstract static class AsyncResultsHandler { protected Map> mCallbackToResults; /** * Add an error result to be sent to vehicleStub through the callback later. */ abstract void addErrorResult(VehicleStubCallbackInterface callback, int serviceRequestId, CarPropertyErrorCodes errorCodes); /** * Add a VHAL result to be sent to vehicleStub through the callback later. */ abstract void addVhalResult(VehicleStubCallbackInterface callback, int serviceRequestId, VhalResultType result); /** * Send all the stored results to vehicleStub. */ abstract void callVehicleStubCallback(); /** * Get the request ID for the result. */ abstract long getVhalRequestId(VhalResultType vhalRequest); protected void addVehicleStubResult(VehicleStubCallbackInterface callback, VehicleStubResultType vehicleStubResult) { if (mCallbackToResults.get(callback) == null) { mCallbackToResults.put(callback, new ArrayList<>()); } mCallbackToResults.get(callback).add(vehicleStubResult); } } @Nullable private static IVehicle getAidlVehicle() { try { return IVehicle.Stub.asInterface( ServiceManagerHelper.waitForDeclaredService(AIDL_VHAL_SERVICE)); } catch (RuntimeException e) { Slogf.w(TAG, "Failed to get \"" + AIDL_VHAL_SERVICE + "\" service", e); } return null; } private class AidlSubscriptionClient extends IVehicleCallback.Stub implements SubscriptionClient { private final VehicleHalCallback mCallback; private final HalPropValueBuilder mBuilder; AidlSubscriptionClient(VehicleHalCallback callback, HalPropValueBuilder builder) { mCallback = callback; mBuilder = builder; } @Override public void onGetValues(GetValueResults responses) throws RemoteException { // We use GetSetValuesCallback for getValues and setValues operation. throw new UnsupportedOperationException( "onGetValues should never be called on AidlSubscriptionClient"); } @Override public void onSetValues(SetValueResults responses) throws RemoteException { // We use GetSetValuesCallback for getValues and setValues operation. throw new UnsupportedOperationException( "onSetValues should never be called on AidlSubscriptionClient"); } @Override public void onPropertyEvent(VehiclePropValues propValues, int sharedMemoryFileCount) throws RemoteException { VehiclePropValues origPropValues = (VehiclePropValues) LargeParcelable.reconstructStableAIDLParcelable(propValues, /* keepSharedMemory= */ false); ArrayList values = new ArrayList<>(origPropValues.payloads.length); for (VehiclePropValue value : origPropValues.payloads) { values.add(mBuilder.build(value)); } mCallback.onPropertyEvent(values); } @Override public void onPropertySetError(VehiclePropErrors errors) throws RemoteException { VehiclePropErrors origErrors = (VehiclePropErrors) LargeParcelable.reconstructStableAIDLParcelable(errors, /* keepSharedMemory= */ false); ArrayList errorList = new ArrayList<>(origErrors.payloads.length); for (VehiclePropError error : origErrors.payloads) { errorList.add(error); } mCallback.onPropertySetError(errorList); } @Override public void subscribe(SubscribeOptions[] options) throws RemoteException, ServiceSpecificException { mAidlVehicle.subscribe(this, options, /* maxSharedMemoryFileCount= */ 2); } @Override public void unsubscribe(int prop) throws RemoteException, ServiceSpecificException { mAidlVehicle.unsubscribe(this, new int[]{prop}); } @Override public String getInterfaceHash() { return IVehicleCallback.HASH; } @Override public int getInterfaceVersion() { return IVehicleCallback.VERSION; } } private void onGetValues(GetValueResults responses) { Trace.traceBegin(TRACE_TAG, "AidlVehicleStub#onGetValues"); GetValueResults origResponses = (GetValueResults) LargeParcelable.reconstructStableAIDLParcelable(responses, /* keepSharedMemory= */ false); onGetSetValues(origResponses.payloads, new AsyncGetResultsHandler(mPropValueBuilder), mPendingSyncGetValueRequestPool); Trace.traceEnd(TRACE_TAG); } private void onSetValues(SetValueResults responses) { Trace.traceBegin(TRACE_TAG, "AidlVehicleStub#onSetValues"); SetValueResults origResponses = (SetValueResults) LargeParcelable.reconstructStableAIDLParcelable(responses, /* keepSharedMemory= */ false); onGetSetValues(origResponses.payloads, new AsyncSetResultsHandler(), mPendingSyncSetValueRequestPool); Trace.traceEnd(TRACE_TAG); } /** * A generic function for {@link onGetValues} / {@link onSetValues}. */ private void onGetSetValues(VhalResultType[] vhalResults, AsyncResultsHandler asyncResultsHandler, PendingSyncRequestPool pendingSyncRequestPool) { synchronized (mLock) { for (VhalResultType result : vhalResults) { long vhalRequestId = asyncResultsHandler.getVhalRequestId(result); if (!mPendingAsyncRequestPool.contains(vhalRequestId)) { // If we cannot find the request Id in the async map, we assume it is for a // sync request. completePendingSyncRequestLocked(pendingSyncRequestPool, vhalRequestId, result); continue; } AsyncRequestInfo requestInfo = mPendingAsyncRequestPool.finishRequestIfFound( vhalRequestId); if (requestInfo == null) { Slogf.w(TAG, "No pending request for ID: %s, possibly already timed out, " + "or cancelled, or the client already died", vhalRequestId); continue; } asyncResultsHandler.addVhalResult(requestInfo.getClientCallback(), requestInfo.getServiceRequestId(), result); } } Trace.traceBegin(TRACE_TAG, "AidlVehicleStub call async result callback"); asyncResultsHandler.callVehicleStubCallback(); Trace.traceEnd(TRACE_TAG); } private static String printPropIdAreaId(HalPropValue value) { return "propID: " + value.getPropId() + ", areaID: " + value.getAreaId(); } private final class GetSetValuesCallback extends IVehicleCallback.Stub { @Override public void onGetValues(GetValueResults responses) throws RemoteException { AidlVehicleStub.this.onGetValues(responses); } @Override public void onSetValues(SetValueResults responses) throws RemoteException { AidlVehicleStub.this.onSetValues(responses); } @Override public void onPropertyEvent(VehiclePropValues propValues, int sharedMemoryFileCount) throws RemoteException { throw new UnsupportedOperationException( "GetSetValuesCallback only support onGetValues or onSetValues"); } @Override public void onPropertySetError(VehiclePropErrors errors) throws RemoteException { throw new UnsupportedOperationException( "GetSetValuesCallback only support onGetValues or onSetValues"); } @Override public String getInterfaceHash() { return IVehicleCallback.HASH; } @Override public int getInterfaceVersion() { return IVehicleCallback.VERSION; } } /** * Mark a pending sync get/set property request as complete and deliver the result. */ @GuardedBy("mLock") private void completePendingSyncRequestLocked( PendingSyncRequestPool pendingSyncRequestPool, long vhalRequestId, VhalResultType result) { Trace.traceBegin(TRACE_TAG, "AidlVehicleStub#completePendingSyncRequestLocked"); AndroidFuture pendingRequest = pendingSyncRequestPool.finishRequestIfFound(vhalRequestId); if (pendingRequest == null) { Slogf.w(TAG, "No pending request for ID: " + vhalRequestId + ", possibly already timed out"); return; } Trace.traceBegin(TRACE_TAG, "AidlVehicleStub#complete pending request"); // This might fail if the request already timed out. pendingRequest.complete(result); Trace.traceEnd(TRACE_TAG); Trace.traceEnd(TRACE_TAG); } private static final class AsyncGetRequestsHandler extends AsyncRequestsHandler { private GetValueRequest[] mVhalRequestItems; private int mIndex; @Override public void allocateVhalRequestSize(int size) { mVhalRequestItems = new GetValueRequest[size]; } @Override public void addVhalRequest(long vhalRequestId, HalPropValue halPropValue) { mVhalRequestItems[mIndex] = new GetValueRequest(); mVhalRequestItems[mIndex].requestId = vhalRequestId; mVhalRequestItems[mIndex].prop = (VehiclePropValue) halPropValue.toVehiclePropValue(); mIndex++; } @Override public GetValueRequest[] getRequestItems() { return mVhalRequestItems; } @Override public void sendRequestsToVhal(IVehicle iVehicle, GetSetValuesCallback callbackForVhal) throws RemoteException, ServiceSpecificException { Trace.traceBegin(TRACE_TAG, "Prepare LargeParcelable"); GetValueRequests largeParcelableRequest = new GetValueRequests(); largeParcelableRequest.payloads = mVhalRequestItems; // TODO(b/269669729): Don't try to use large parcelable if the request size is too // small. largeParcelableRequest = (GetValueRequests) LargeParcelable.toLargeParcelable( largeParcelableRequest, () -> { GetValueRequests newRequests = new GetValueRequests(); newRequests.payloads = new GetValueRequest[0]; return newRequests; }); Trace.traceEnd(TRACE_TAG); Trace.traceBegin(TRACE_TAG, "IVehicle#getValues"); iVehicle.getValues(callbackForVhal, largeParcelableRequest); Trace.traceEnd(TRACE_TAG); } @Override public long getVhalRequestId(GetValueRequest request) { return request.requestId; } } private static final class AsyncSetRequestsHandler extends AsyncRequestsHandler { private SetValueRequest[] mVhalRequestItems; private int mIndex; @Override public void allocateVhalRequestSize(int size) { mVhalRequestItems = new SetValueRequest[size]; } @Override public void addVhalRequest(long vhalRequestId, HalPropValue halPropValue) { mVhalRequestItems[mIndex] = new SetValueRequest(); mVhalRequestItems[mIndex].requestId = vhalRequestId; mVhalRequestItems[mIndex].value = (VehiclePropValue) halPropValue.toVehiclePropValue(); mIndex++; } @Override public SetValueRequest[] getRequestItems() { return mVhalRequestItems; } @Override public void sendRequestsToVhal(IVehicle iVehicle, GetSetValuesCallback callbackForVhal) throws RemoteException, ServiceSpecificException { SetValueRequests largeParcelableRequest = new SetValueRequests(); largeParcelableRequest.payloads = mVhalRequestItems; largeParcelableRequest = (SetValueRequests) LargeParcelable.toLargeParcelable( largeParcelableRequest, () -> { SetValueRequests newRequests = new SetValueRequests(); newRequests.payloads = new SetValueRequest[0]; return newRequests; }); iVehicle.setValues(callbackForVhal, largeParcelableRequest); } @Override public long getVhalRequestId(SetValueRequest request) { return request.requestId; } } private static final class AsyncGetResultsHandler extends AsyncResultsHandler { private HalPropValueBuilder mPropValueBuilder; AsyncGetResultsHandler(HalPropValueBuilder propValueBuilder) { mPropValueBuilder = propValueBuilder; mCallbackToResults = new ArrayMap>(); } @Override void addErrorResult(VehicleStubCallbackInterface callback, int serviceRequestId, CarPropertyErrorCodes errorCodes) { addVehicleStubResult(callback, new GetVehicleStubAsyncResult(serviceRequestId, errorCodes)); } @Override void addVhalResult(VehicleStubCallbackInterface callback, int serviceRequestId, GetValueResult result) { addVehicleStubResult(callback, toVehicleStubResult(serviceRequestId, result)); } @Override void callVehicleStubCallback() { for (Map.Entry> entry : mCallbackToResults.entrySet()) { entry.getKey().onGetAsyncResults(entry.getValue()); } } @Override long getVhalRequestId(GetValueResult result) { return result.requestId; } private GetVehicleStubAsyncResult toVehicleStubResult(int serviceRequestId, GetValueResult vhalResult) { if (vhalResult.status != StatusCode.OK) { CarPropertyErrorCodes carPropertyErrorCodes = convertVhalStatusCodeToCarPropertyManagerErrorCodes(vhalResult.status); return new GetVehicleStubAsyncResult(serviceRequestId, carPropertyErrorCodes); } else if (vhalResult.prop == null) { // If status is OKAY but no property is returned, treat it as not_available. return new GetVehicleStubAsyncResult(serviceRequestId, new CarPropertyErrorCodes( CarPropertyManager.STATUS_ERROR_NOT_AVAILABLE, /* vendorErrorCode= */ 0, /* systemErrorCode= */ 0)); } return new GetVehicleStubAsyncResult(serviceRequestId, mPropValueBuilder.build(vhalResult.prop)); } } private static final class AsyncSetResultsHandler extends AsyncResultsHandler { AsyncSetResultsHandler() { mCallbackToResults = new ArrayMap>(); } @Override void addErrorResult(VehicleStubCallbackInterface callback, int serviceRequestId, CarPropertyErrorCodes errorCodes) { addVehicleStubResult(callback, new SetVehicleStubAsyncResult(serviceRequestId, errorCodes)); } @Override void addVhalResult(VehicleStubCallbackInterface callback, int serviceRequestId, SetValueResult result) { addVehicleStubResult(callback, toVehicleStubResult(serviceRequestId, result)); } @Override void callVehicleStubCallback() { for (Map.Entry> entry : mCallbackToResults.entrySet()) { entry.getKey().onSetAsyncResults(entry.getValue()); } } @Override long getVhalRequestId(SetValueResult result) { return result.requestId; } private SetVehicleStubAsyncResult toVehicleStubResult(int serviceRequestId, SetValueResult vhalResult) { if (vhalResult.status != StatusCode.OK) { CarPropertyErrorCodes carPropertyErrorCodes = convertVhalStatusCodeToCarPropertyManagerErrorCodes(vhalResult.status); return new SetVehicleStubAsyncResult(serviceRequestId, carPropertyErrorCodes); } return new SetVehicleStubAsyncResult(serviceRequestId); } } /** * Generic function for {@link get} or {@link set}. */ private HalPropValue getOrSetSync( HalPropValue requestedPropValue, PendingSyncRequestPool pendingSyncRequestPool, AsyncRequestsHandler requestsHandler, Function resultHandler) throws RemoteException, ServiceSpecificException { Trace.traceBegin(TRACE_TAG, "AidlVehicleStub#getOrSetSync"); long vhalRequestId = mRequestId.getAndIncrement(); AndroidFuture resultFuture = pendingSyncRequestPool.addRequest( vhalRequestId); requestsHandler.allocateVhalRequestSize(1); requestsHandler.addVhalRequest(vhalRequestId, requestedPropValue); requestsHandler.sendRequestsToVhal(mAidlVehicle, mGetSetValuesCallback); boolean gotResult = false; try { Trace.traceBegin(TRACE_TAG, "AidlVehicleStub#waitingForSyncResult"); VhalResultType result = resultFuture.get(mSyncOpTimeoutInMs, TimeUnit.MILLISECONDS); gotResult = true; return resultHandler.apply(result); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Restore the interrupted status throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, "thread interrupted, possibly exiting the thread"); } catch (ExecutionException e) { throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, "failed to resolve future, error: " + e); } catch (TimeoutException e) { throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR, "get/set value request timeout for: " + printPropIdAreaId(requestedPropValue)); } finally { Trace.traceEnd(TRACE_TAG); if (!gotResult) { resultFuture = pendingSyncRequestPool.finishRequestIfFound(vhalRequestId); // Something wrong happened, the future is guaranteed not to be used again. resultFuture.cancel(/* mayInterruptIfRunning= */ false); } Trace.traceEnd(TRACE_TAG); } } /** * Generic function for {@link getAsync} or {@link setAsync}. */ private void getOrSetAsync( List vehicleStubAsyncRequests, VehicleStubCallbackInterface vehicleStubCallback, AsyncRequestsHandler asyncRequestsHandler, AsyncResultsHandler asyncResultsHandler) { prepareAndConvertAsyncRequests(vehicleStubAsyncRequests, vehicleStubCallback, asyncRequestsHandler); try { asyncRequestsHandler.sendRequestsToVhal(mAidlVehicle, mGetSetValuesCallback); } catch (RemoteException e) { handleAsyncExceptionFromVhal( asyncRequestsHandler, vehicleStubCallback, new CarPropertyErrorCodes( CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR, /* vendorErrorCode= */ 0, /* systemErrorCode= */ 0), asyncResultsHandler); return; } catch (ServiceSpecificException e) { CarPropertyErrorCodes carPropertyErrorCodes = convertVhalStatusCodeToCarPropertyManagerErrorCodes(e.errorCode); handleAsyncExceptionFromVhal(asyncRequestsHandler, vehicleStubCallback, carPropertyErrorCodes, asyncResultsHandler); return; } } /** * Prepare an async get/set request from client and convert it to vhal requests. * *

It does the following things: *

    *
  • Add a client callback death listener which will clear the pending requests when client * died *
  • Store the async requests to a pending request map. *
  • For each client request, generate a unique VHAL request ID and convert the request to * VHAL request type. *
  • Stores the time-out information for each request into a map so that we can register * timeout handlers later. *
  • Convert the vhal request items to a single large parcelable class. */ private void prepareAndConvertAsyncRequests( List vehicleStubRequests, VehicleStubCallbackInterface clientCallback, AsyncRequestsHandler asyncRequestsHandler) { asyncRequestsHandler.allocateVhalRequestSize(vehicleStubRequests.size()); synchronized (mLock) { // Add the death recipient so that all client info for a dead callback will be cleaned // up. Note that this must be in the same critical section as the following code to // store the client info into the map. This makes sure that even if the client is // died half way while adding the client info, it will wait until all the clients are // added and then remove them all. try { clientCallback.linkToDeath(() -> { // This function will be invoked from a different thread. It needs to be // guarded by a lock so that the whole 'prepareAndConvertAsyncRequests' finishes // before we remove the callback. synchronized (mLock) { mPendingAsyncRequestPool.removeRequestsForCallback(clientCallback); } }); } catch (RemoteException e) { // The binder is already died. throw new IllegalStateException("Failed to link callback to death recipient, the " + "client maybe already died"); } List requestInfoList = new ArrayList<>(); for (int i = 0; i < vehicleStubRequests.size(); i++) { AsyncGetSetRequest vehicleStubRequest = vehicleStubRequests.get(i); long vhalRequestId = mRequestId.getAndIncrement(); asyncRequestsHandler.addVhalRequest(vhalRequestId, vehicleStubRequest.getHalPropValue()); requestInfoList.add(new AsyncRequestInfo( vhalRequestId, vehicleStubRequest.getServiceRequestId(), clientCallback, vehicleStubRequest.getTimeoutUptimeMs())); } mPendingAsyncRequestPool.addRequests(requestInfoList); } } /** * Callback to deliver async get/set error results back to the client. * *

    When an exception is received, the callback delivers the error results on the same thread * where the caller is. */ private void handleAsyncExceptionFromVhal( AsyncRequestsHandler asyncRequestsHandler, VehicleStubCallbackInterface vehicleStubCallback, CarPropertyErrorCodes errorCodes, AsyncResultsHandler asyncResultsHandler) { Slogf.w(TAG, "Received RemoteException or ServiceSpecificException from VHAL. VHAL is likely " + "dead, system error code: %d, vendor error code: %d", errorCodes.getCarPropertyManagerErrorCode(), errorCodes.getVendorErrorCode()); synchronized (mLock) { VhalRequestType[] requests = asyncRequestsHandler.getRequestItems(); for (int i = 0; i < requests.length; i++) { long vhalRequestId = asyncRequestsHandler.getVhalRequestId(requests[i]); AsyncRequestInfo requestInfo = mPendingAsyncRequestPool.finishRequestIfFound( vhalRequestId); if (requestInfo == null) { Slogf.w(TAG, "No pending request for ID: %s, possibly already timed out or " + "the client already died", vhalRequestId); continue; } asyncResultsHandler.addErrorResult( vehicleStubCallback, requestInfo.getServiceRequestId(), errorCodes); } } asyncResultsHandler.callVehicleStubCallback(); } }