| /* |
| * Copyright (C) 2016 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.elapsedRealtime; |
| |
| import android.hardware.automotive.vehicle.V2_0.IVehicle; |
| import android.hardware.automotive.vehicle.V2_0.IVehicleCallback; |
| import android.hardware.automotive.vehicle.V2_0.StatusCode; |
| import android.hardware.automotive.vehicle.V2_0.SubscribeOptions; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import com.android.car.CarLog; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| |
| /** |
| * Vehicle HAL client. Interacts directly with Vehicle HAL interface {@link IVehicle}. Contains |
| * some logic for retriable properties, redirects Vehicle notifications into given looper thread. |
| */ |
| class HalClient { |
| /** |
| * If call to vehicle HAL returns StatusCode.TRY_AGAIN, than {@link HalClient} will retry to |
| * invoke that method again for this amount of milliseconds. |
| */ |
| private static final int WAIT_CAP_FOR_RETRIABLE_RESULT_MS = 2000; |
| |
| private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50; |
| |
| private final IVehicle mVehicle; |
| |
| private final IVehicleCallback mInternalCallback; |
| |
| /** |
| * Create HalClient object |
| * |
| * @param vehicle interface to the vehicle HAL |
| * @param looper looper that will be used to propagate notifications from vehicle HAL |
| * @param callback to propagate notifications from Vehicle HAL in the provided looper thread |
| */ |
| HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback) { |
| mVehicle = vehicle; |
| Handler handler = new CallbackHandler(looper, callback); |
| mInternalCallback = new VehicleCallback(handler); |
| } |
| |
| ArrayList<VehiclePropConfig> getAllPropConfigs() throws RemoteException { |
| return mVehicle.getAllPropConfigs(); |
| } |
| |
| public void subscribe(SubscribeOptions... options) throws RemoteException { |
| mVehicle.subscribe(mInternalCallback, new ArrayList<>(Arrays.asList(options))); |
| } |
| |
| public void unsubscribe(int prop) throws RemoteException { |
| mVehicle.unsubscribe(mInternalCallback, prop); |
| } |
| |
| public void setValue(VehiclePropValue propValue) throws PropertyTimeoutException { |
| int status = invokeRetriable(() -> { |
| try { |
| return mVehicle.set(propValue); |
| } catch (RemoteException e) { |
| Log.e(CarLog.TAG_HAL, "Failed to set value", e); |
| return StatusCode.TRY_AGAIN; |
| } |
| }, WAIT_CAP_FOR_RETRIABLE_RESULT_MS, SLEEP_BETWEEN_RETRIABLE_INVOKES_MS); |
| |
| if (StatusCode.INVALID_ARG == status) { |
| throw new IllegalArgumentException( |
| String.format("Failed to set value for: 0x%x, areaId: 0x%x", |
| propValue.prop, propValue.areaId)); |
| } |
| |
| if (StatusCode.TRY_AGAIN == status) { |
| throw new PropertyTimeoutException(propValue.prop); |
| } |
| |
| if (StatusCode.OK != status) { |
| throw new IllegalStateException( |
| String.format("Failed to set property: 0x%x, areaId: 0x%x, " |
| + "code: %d", propValue.prop, propValue.areaId, status)); |
| } |
| } |
| |
| VehiclePropValue getValue(VehiclePropValue requestedPropValue) throws PropertyTimeoutException { |
| final ObjectWrapper<VehiclePropValue> valueWrapper = new ObjectWrapper<>(); |
| int status = invokeRetriable(() -> { |
| ValueResult res = internalGet(requestedPropValue); |
| valueWrapper.object = res.propValue; |
| return res.status; |
| }, WAIT_CAP_FOR_RETRIABLE_RESULT_MS, SLEEP_BETWEEN_RETRIABLE_INVOKES_MS); |
| |
| int propId = requestedPropValue.prop; |
| int areaId = requestedPropValue.areaId; |
| if (StatusCode.INVALID_ARG == status) { |
| throw new IllegalArgumentException( |
| String.format("Failed to get value for: 0x%x, areaId: 0x%x", propId, areaId)); |
| } |
| |
| if (StatusCode.TRY_AGAIN == status) { |
| throw new PropertyTimeoutException(propId); |
| } |
| |
| if (StatusCode.OK != status || valueWrapper.object == null) { |
| throw new IllegalStateException( |
| String.format("Failed to get property: 0x%x, areaId: 0x%x, " |
| + "code: %d", propId, areaId, status)); |
| } |
| |
| return valueWrapper.object; |
| } |
| |
| private ValueResult internalGet(VehiclePropValue requestedPropValue) { |
| final ValueResult result = new ValueResult(); |
| try { |
| mVehicle.get(requestedPropValue, |
| (status, propValue) -> { |
| result.status = status; |
| result.propValue = propValue; |
| }); |
| } catch (RemoteException e) { |
| Log.e(CarLog.TAG_HAL, "Failed to get value from vehicle HAL", e); |
| result.status = StatusCode.TRY_AGAIN; |
| } |
| |
| return result; |
| } |
| |
| interface RetriableCallback { |
| /** Returns {@link StatusCode} */ |
| int action(); |
| } |
| |
| private static int invokeRetriable(RetriableCallback callback, long timeoutMs, long sleepMs) { |
| int status = callback.action(); |
| long startTime = elapsedRealtime(); |
| while (StatusCode.TRY_AGAIN == status && (elapsedRealtime() - startTime) < timeoutMs) { |
| try { |
| Thread.sleep(sleepMs); |
| } catch (InterruptedException e) { |
| Log.e(CarLog.TAG_HAL, "Thread was interrupted while waiting for vehicle HAL.", e); |
| break; |
| } |
| |
| status = callback.action(); |
| } |
| return status; |
| } |
| |
| private static class ObjectWrapper<T> { |
| T object; |
| } |
| |
| private static class ValueResult { |
| int status; |
| VehiclePropValue propValue; |
| } |
| |
| private static class PropertySetError { |
| final int errorCode; |
| final int propId; |
| final int areaId; |
| |
| PropertySetError(int errorCode, int propId, int areaId) { |
| this.errorCode = errorCode; |
| this.propId = propId; |
| this.areaId = areaId; |
| } |
| } |
| |
| private static class CallbackHandler extends Handler { |
| private static final int MSG_ON_PROPERTY_SET = 1; |
| private static final int MSG_ON_PROPERTY_EVENT = 2; |
| private static final int MSG_ON_SET_ERROR = 3; |
| |
| private final IVehicleCallback mCallback; |
| |
| CallbackHandler(Looper looper, IVehicleCallback callback) { |
| super(looper); |
| mCallback = callback; |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| super.handleMessage(msg); |
| |
| try { |
| switch (msg.what) { |
| case MSG_ON_PROPERTY_EVENT: |
| mCallback.onPropertyEvent((ArrayList<VehiclePropValue>) msg.obj); |
| break; |
| case MSG_ON_PROPERTY_SET: |
| mCallback.onPropertySet((VehiclePropValue) msg.obj); |
| break; |
| case MSG_ON_SET_ERROR: |
| PropertySetError obj = (PropertySetError) msg.obj; |
| mCallback.onPropertySetError(obj.errorCode, obj.propId, obj.areaId); |
| break; |
| default: |
| Log.e(CarLog.TAG_HAL, "Unexpected message: " + msg.what); |
| } |
| } catch (RemoteException e) { |
| Log.e(CarLog.TAG_HAL, "Message failed: " + msg.what); |
| } |
| } |
| } |
| |
| private static class VehicleCallback extends IVehicleCallback.Stub { |
| private Handler mHandler; |
| |
| VehicleCallback(Handler handler) { |
| mHandler = handler; |
| } |
| |
| @Override |
| public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) { |
| mHandler.sendMessage(Message.obtain( |
| mHandler, CallbackHandler.MSG_ON_PROPERTY_EVENT, propValues)); |
| } |
| |
| @Override |
| public void onPropertySet(VehiclePropValue propValue) { |
| mHandler.sendMessage(Message.obtain( |
| mHandler, CallbackHandler.MSG_ON_PROPERTY_SET, propValue)); |
| } |
| |
| @Override |
| public void onPropertySetError(int errorCode, int propId, int areaId) { |
| mHandler.sendMessage(Message.obtain( |
| mHandler, CallbackHandler.MSG_ON_SET_ERROR, |
| new PropertySetError(errorCode, propId, areaId))); |
| } |
| } |
| } |