1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.hal;
18 
19 import static android.os.SystemClock.elapsedRealtime;
20 
21 import android.hardware.automotive.vehicle.V2_0.IVehicle;
22 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
23 import android.hardware.automotive.vehicle.V2_0.StatusCode;
24 import android.hardware.automotive.vehicle.V2_0.SubscribeOptions;
25 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
26 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.RemoteException;
31 import android.os.ServiceSpecificException;
32 
33 import com.android.car.CarLog;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.server.utils.Slogf;
36 
37 import java.lang.ref.WeakReference;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 
41 /**
42  * Vehicle HAL client. Interacts directly with Vehicle HAL interface {@link IVehicle}. Contains
43  * some logic for retriable properties, redirects Vehicle notifications into given looper thread.
44  */
45 final class HalClient {
46 
47     private static final String TAG = CarLog.tagFor(HalClient.class);
48 
49     /**
50      * If call to vehicle HAL returns StatusCode.TRY_AGAIN, than {@link HalClient} will retry to
51      * invoke that method again for this amount of milliseconds.
52      */
53     private static final int WAIT_CAP_FOR_RETRIABLE_RESULT_MS = 2000;
54 
55     private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50;
56 
57     private final IVehicle mVehicle;
58     private final IVehicleCallback mInternalCallback;
59     private final int mWaitCapMs;
60     private final int mSleepMs;
61 
62     /**
63      * Create HalClient object
64      *
65      * @param vehicle interface to the vehicle HAL
66      * @param looper looper that will be used to propagate notifications from vehicle HAL
67      * @param callback to propagate notifications from Vehicle HAL in the provided looper thread
68      */
HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback)69     HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback) {
70         this(vehicle, looper, callback, WAIT_CAP_FOR_RETRIABLE_RESULT_MS,
71                 SLEEP_BETWEEN_RETRIABLE_INVOKES_MS);
72     }
73 
74     @VisibleForTesting
HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback, int waitCapMs, int sleepMs)75     HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback,
76             int waitCapMs, int sleepMs) {
77         mVehicle = vehicle;
78         Handler handler = new CallbackHandler(looper, callback);
79         mInternalCallback = new VehicleCallback(handler);
80         mWaitCapMs = waitCapMs;
81         mSleepMs = sleepMs;
82     }
83 
getAllPropConfigs()84     ArrayList<VehiclePropConfig> getAllPropConfigs() throws RemoteException {
85         return mVehicle.getAllPropConfigs();
86     }
87 
subscribe(SubscribeOptions... options)88     public void subscribe(SubscribeOptions... options) throws RemoteException {
89         mVehicle.subscribe(mInternalCallback, new ArrayList<>(Arrays.asList(options)));
90     }
91 
unsubscribe(int prop)92     public void unsubscribe(int prop) throws RemoteException {
93         mVehicle.unsubscribe(mInternalCallback, prop);
94     }
95 
setValue(VehiclePropValue propValue)96     public void setValue(VehiclePropValue propValue) {
97         int status = invokeRetriable(() -> {
98             try {
99                 return mVehicle.set(propValue);
100             } catch (RemoteException e) {
101                 Slogf.e(TAG, getValueErrorMessage("set", propValue), e);
102                 return StatusCode.TRY_AGAIN;
103             }
104         }, mWaitCapMs, mSleepMs);
105 
106         if (StatusCode.INVALID_ARG == status) {
107             throw new IllegalArgumentException(getValueErrorMessage("set", propValue));
108         }
109 
110         if (StatusCode.OK != status) {
111             Slogf.e(TAG, getPropertyErrorMessage("set", propValue, status));
112             throw new ServiceSpecificException(status,
113                     "Failed to set property: 0x" + Integer.toHexString(propValue.prop)
114                             + " in areaId: 0x" + Integer.toHexString(propValue.areaId));
115         }
116     }
117 
getValueErrorMessage(String action, VehiclePropValue propValue)118     private String getValueErrorMessage(String action, VehiclePropValue propValue) {
119         return String.format("Failed to %s value for: 0x%s, areaId: 0x%s", action,
120                 Integer.toHexString(propValue.prop), Integer.toHexString(propValue.areaId));
121     }
122 
getPropertyErrorMessage(String action, VehiclePropValue propValue, int status)123     private String getPropertyErrorMessage(String action, VehiclePropValue propValue, int status) {
124         return String.format("Failed to %s property: 0x%s, areaId: 0x%s, code: %d (%s)", action,
125                 Integer.toHexString(propValue.prop), Integer.toHexString(propValue.areaId),
126                 status, StatusCode.toString(status));
127     }
128 
getValue(VehiclePropValue requestedPropValue)129     VehiclePropValue getValue(VehiclePropValue requestedPropValue) {
130         final ObjectWrapper<VehiclePropValue> valueWrapper = new ObjectWrapper<>();
131         int status = invokeRetriable(() -> {
132             ValueResult res = internalGet(requestedPropValue);
133             valueWrapper.object = res.propValue;
134             return res.status;
135         }, mWaitCapMs, mSleepMs);
136 
137         if (StatusCode.INVALID_ARG == status) {
138             throw new IllegalArgumentException(getValueErrorMessage("get", requestedPropValue));
139         }
140 
141         if (StatusCode.OK != status || valueWrapper.object == null) {
142             // If valueWrapper.object is null and status is StatusCode.Ok, change the status to be
143             // NOT_AVAILABLE.
144             if (StatusCode.OK == status) {
145                 status = StatusCode.NOT_AVAILABLE;
146             }
147             Slogf.e(TAG, getPropertyErrorMessage("get", requestedPropValue, status));
148             throw new ServiceSpecificException(status,
149                     "Failed to get property: 0x" + Integer.toHexString(requestedPropValue.prop)
150                             + " in areaId: 0x" + Integer.toHexString(requestedPropValue.areaId));
151         }
152 
153         return valueWrapper.object;
154     }
155 
internalGet(VehiclePropValue requestedPropValue)156     private ValueResult internalGet(VehiclePropValue requestedPropValue) {
157         final ValueResult result = new ValueResult();
158         try {
159             mVehicle.get(requestedPropValue,
160                     (status, propValue) -> {
161                         result.status = status;
162                         result.propValue = propValue;
163                     });
164         } catch (RemoteException e) {
165             Slogf.e(TAG, getValueErrorMessage("get", requestedPropValue), e);
166             result.status = StatusCode.TRY_AGAIN;
167         }
168 
169         return result;
170     }
171 
172     interface RetriableCallback {
173         /** Returns {@link StatusCode} */
action()174         int action();
175     }
176 
invokeRetriable(RetriableCallback callback, long timeoutMs, long sleepMs)177     private static int invokeRetriable(RetriableCallback callback, long timeoutMs, long sleepMs) {
178         int status = callback.action();
179         long startTime = elapsedRealtime();
180         while (StatusCode.TRY_AGAIN == status && (elapsedRealtime() - startTime) < timeoutMs) {
181             Slogf.d(TAG, "Status before sleeping %d ms: %s", sleepMs, StatusCode.toString(status));
182             try {
183                 Thread.sleep(sleepMs);
184             } catch (InterruptedException e) {
185                 Thread.currentThread().interrupt();
186                 Slogf.e(TAG, "Thread was interrupted while waiting for vehicle HAL.", e);
187                 break;
188             }
189 
190             status = callback.action();
191             Slogf.d(TAG, "Status after waking up: %s", StatusCode.toString(status));
192         }
193         Slogf.d(TAG, "Returning status: %s", StatusCode.toString(status));
194         return status;
195     }
196 
197     private static final class ObjectWrapper<T> {
198         public T object;
199     }
200 
201     private static final class ValueResult {
202         public int status;
203         public VehiclePropValue propValue;
204     }
205 
206     private static final class PropertySetError {
207         public final int errorCode;
208         public final int propId;
209         public final int areaId;
210 
PropertySetError(int errorCode, int propId, int areaId)211         PropertySetError(int errorCode, int propId, int areaId) {
212             this.errorCode = errorCode;
213             this.propId = propId;
214             this.areaId = areaId;
215         }
216     }
217 
218     private static final class CallbackHandler extends Handler {
219         private static final int MSG_ON_PROPERTY_SET = 1;
220         private static final int MSG_ON_PROPERTY_EVENT = 2;
221         private static final int MSG_ON_SET_ERROR = 3;
222 
223         private final WeakReference<IVehicleCallback> mCallback;
224 
CallbackHandler(Looper looper, IVehicleCallback callback)225         CallbackHandler(Looper looper, IVehicleCallback callback) {
226             super(looper);
227             mCallback = new WeakReference<IVehicleCallback>(callback);
228         }
229 
230         @Override
handleMessage(Message msg)231         public void handleMessage(Message msg) {
232             IVehicleCallback callback = mCallback.get();
233             if (callback == null) {
234                 Slogf.i(TAG, "handleMessage null callback");
235                 return;
236             }
237 
238             try {
239                 switch (msg.what) {
240                     case MSG_ON_PROPERTY_EVENT:
241                         callback.onPropertyEvent((ArrayList<VehiclePropValue>) msg.obj);
242                         break;
243                     case MSG_ON_PROPERTY_SET:
244                         callback.onPropertySet((VehiclePropValue) msg.obj);
245                         break;
246                     case MSG_ON_SET_ERROR:
247                         PropertySetError obj = (PropertySetError) msg.obj;
248                         callback.onPropertySetError(obj.errorCode, obj.propId, obj.areaId);
249                         break;
250                     default:
251                         Slogf.e(TAG, "Unexpected message: %d", msg.what);
252                 }
253             } catch (RemoteException e) {
254                 Slogf.e(TAG, e, "Message failed: %d", msg.what);
255             }
256         }
257     }
258 
259     private static final class VehicleCallback extends IVehicleCallback.Stub {
260         private final Handler mHandler;
261 
VehicleCallback(Handler handler)262         VehicleCallback(Handler handler) {
263             mHandler = handler;
264         }
265 
266         @Override
onPropertyEvent(ArrayList<VehiclePropValue> propValues)267         public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
268             mHandler.sendMessage(Message.obtain(
269                     mHandler, CallbackHandler.MSG_ON_PROPERTY_EVENT, propValues));
270         }
271 
272         @Override
onPropertySet(VehiclePropValue propValue)273         public void onPropertySet(VehiclePropValue propValue) {
274             mHandler.sendMessage(Message.obtain(
275                     mHandler, CallbackHandler.MSG_ON_PROPERTY_SET, propValue));
276         }
277 
278         @Override
onPropertySetError(int errorCode, int propId, int areaId)279         public void onPropertySetError(int errorCode, int propId, int areaId) {
280             mHandler.sendMessage(Message.obtain(
281                     mHandler, CallbackHandler.MSG_ON_SET_ERROR,
282                     new PropertySetError(errorCode, propId, areaId)));
283         }
284     }
285 }
286