1 /*
2  * Copyright (C) 2022 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.remoteaccess.hal;
18 
19 import android.annotation.Nullable;
20 import android.car.builtin.os.ServiceManagerHelper;
21 import android.car.builtin.os.TraceHelper;
22 import android.car.builtin.util.Slogf;
23 import android.car.builtin.util.TimingsTraceLog;
24 import android.hardware.automotive.remoteaccess.ApState;
25 import android.hardware.automotive.remoteaccess.IRemoteAccess;
26 import android.hardware.automotive.remoteaccess.IRemoteTaskCallback;
27 import android.hardware.automotive.remoteaccess.ScheduleInfo;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.ServiceSpecificException;
31 import android.util.Log;
32 
33 import com.android.car.CarLog;
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.lang.ref.WeakReference;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.concurrent.atomic.AtomicBoolean;
41 
42 /**
43  * Class to mediate communication between CarRemoteAccessSerevice and remote access HAL.
44  */
45 public final class RemoteAccessHalWrapper implements IBinder.DeathRecipient {
46 
47     private static final String TAG = CarLog.tagFor(RemoteAccessHalWrapper.class);
48     private static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
49     private static final String NOT_SUPPORTED_V1_MESSAGE =
50             "is not supported by remote access HAL v1";
51 
52     private final Object mLock = new Object();
53     // Callback which is given by the instance creator.
54     private final RemoteAccessHalCallback mRemoteAccessHalCallback;
55     // Callback which is registered to remote access HAL.
56     private final IRemoteTaskCallback mRemoteTaskCallback = new RemoteTaskCallbackImpl(this);
57 
58     private final AtomicBoolean mConnecting = new AtomicBoolean();
59     @GuardedBy("mLock")
60     private IBinder mBinder;
61     @GuardedBy("mLock")
62     private IRemoteAccess mRemoteAccessHal;
63 
64     private IRemoteAccess mTestRemoteAccessHal;
65 
RemoteAccessHalWrapper(RemoteAccessHalCallback callback)66     public RemoteAccessHalWrapper(RemoteAccessHalCallback callback) {
67         this(callback, /* testRemoteAccessHal= */ null);
68     }
69 
70     /* For car service test. */
71     @VisibleForTesting
RemoteAccessHalWrapper(RemoteAccessHalCallback callback, IRemoteAccess testRemoteAccessHal)72     public RemoteAccessHalWrapper(RemoteAccessHalCallback callback,
73             IRemoteAccess testRemoteAccessHal) {
74         mRemoteAccessHalCallback = Objects.requireNonNull(callback, "Callback cannot be null");
75         mTestRemoteAccessHal = testRemoteAccessHal;
76     }
77 
78     /** Initializes connection to remote task HAL service. */
init()79     public void init() {
80         try {
81             connectToHal();
82         } catch (Exception e) {
83             Slogf.wtf(TAG, e, "Cannot connect to remote access HAL");
84         }
85     }
86 
87     /** Releases the internal resources. */
release()88     public void release() {
89         IRemoteAccess remoteAccessHal;
90         synchronized (mLock) {
91             remoteAccessHal = mRemoteAccessHal;
92             mRemoteAccessHal = null;
93         }
94         try {
95             if (remoteAccessHal != null) {
96                 remoteAccessHal.clearRemoteTaskCallback();
97             }
98         } catch (RemoteException e) {
99             Slogf.w(TAG, e, "Failed to clear remote task callback");
100         }
101         clearBinder();
102     }
103 
104     @Override
binderDied()105     public void binderDied() {
106         Slogf.w(TAG, "Remote access HAL service died");
107         synchronized (mLock) {
108             mRemoteAccessHal = null;
109             mBinder = null;
110         }
111         try {
112             connectToHal();
113         } catch (Exception e) {
114             Slogf.wtf(TAG, e, "Cannot connect to remote access HAL");
115         }
116     }
117 
118     /** Check {@link IRemoteAccess#getVehicleId()}. */
getVehicleId()119     public String getVehicleId() {
120         IRemoteAccess remoteAccessHal = getRemoteAccessHal();
121         try {
122             return remoteAccessHal.getVehicleId();
123         } catch (RemoteException | RuntimeException e) {
124             throw new IllegalStateException("Failed to get vehicle ID", e);
125         }
126     }
127 
128     /** Check {@link IRemoteAccess#getWakeupServiceName()}. */
getWakeupServiceName()129     public String getWakeupServiceName() {
130         IRemoteAccess remoteAccessHal = getRemoteAccessHal();
131         try {
132             return remoteAccessHal.getWakeupServiceName();
133         } catch (RemoteException | RuntimeException e) {
134             throw new IllegalStateException("Failed to get wakeup service name", e);
135         }
136     }
137 
138     /** Check {@link IRemoteAccess#getProcessorId()}. */
getProcessorId()139     public String getProcessorId() {
140         IRemoteAccess remoteAccessHal = getRemoteAccessHal();
141         try {
142             return remoteAccessHal.getProcessorId();
143         } catch (RemoteException | RuntimeException e) {
144             throw new IllegalStateException("Failed to get processor ID", e);
145         }
146     }
147 
148     /** Check {@link IRemoteAccess#notifyApStateChange(ApState)}. */
notifyApStateChange(boolean isReadyForRemoteTask, boolean isWakeupRequired)149     public boolean notifyApStateChange(boolean isReadyForRemoteTask, boolean isWakeupRequired) {
150         try {
151             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
152             ApState state = new ApState();
153             state.isReadyForRemoteTask = isReadyForRemoteTask;
154             state.isWakeupRequired = isWakeupRequired;
155             remoteAccessHal.notifyApStateChange(state);
156         } catch (RemoteException | RuntimeException e) {
157             Slogf.w(TAG, e, "Failed to notify power state change: isReadyForRemoteTask=%b, "
158                     + "isWakeupRequired=%b", isReadyForRemoteTask, isWakeupRequired);
159             return false;
160         }
161         return true;
162     }
163 
164     /**
165      * Check {@link IRemoteAccess#isTaskScheduleSupported}.
166      *
167      * <p>Returns {@code false} if error happens.
168      */
isTaskScheduleSupported()169     public boolean isTaskScheduleSupported() {
170         try {
171             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
172             if (remoteAccessHal.getInterfaceVersion() < 2) {
173                 Slogf.w(TAG, "isTaskScheduleSupported %s, default to false",
174                         NOT_SUPPORTED_V1_MESSAGE);
175                 return false;
176             }
177             return remoteAccessHal.isTaskScheduleSupported();
178         } catch (RemoteException | ServiceSpecificException e) {
179             Slogf.w(TAG, e, "Failed to call isTaskScheduleSupported, default to false");
180             return false;
181         }
182     }
183 
184     /**
185      * Check {@link IRemoteAccess#scheduleTask}.
186      *
187      * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this.
188      *
189      * <p>Client should handle the thrown exception.
190      */
scheduleTask(ScheduleInfo scheduleInfo)191     public void scheduleTask(ScheduleInfo scheduleInfo)
192             throws RemoteException, ServiceSpecificException {
193         try {
194             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
195             remoteAccessHal.scheduleTask(scheduleInfo);
196         } catch (RemoteException | ServiceSpecificException e) {
197             Slogf.w(TAG, e, "Failed to call scheduleTask with scheduleInfo: %s", scheduleInfo);
198             throw e;
199         }
200     }
201 
202     /**
203      * Check {@link IRemoteAccess#unscheduleTask}.
204      *
205      * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this.
206      *
207      * <p>Do nothing if error happens.
208      */
unscheduleTask(String clientId, String scheduleId)209     public void unscheduleTask(String clientId, String scheduleId)
210             throws RemoteException, ServiceSpecificException {
211         try {
212             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
213             remoteAccessHal.unscheduleTask(clientId, scheduleId);
214         } catch (RemoteException | ServiceSpecificException e) {
215             Slogf.w(TAG, e, "Failed to call unscheduleTask with clientId: %s, scheduleId: %s",
216                     clientId, scheduleId);
217             throw e;
218         }
219     }
220 
221     /**
222      * Check {@link IRemoteAccess#unscheduleAllTasks}.
223      *
224      * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this.
225      *
226      * <p>Do nothing if error happens.
227      */
unscheduleAllTasks(String clientId)228     public void unscheduleAllTasks(String clientId)
229             throws RemoteException, ServiceSpecificException {
230         try {
231             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
232             remoteAccessHal.unscheduleAllTasks(clientId);
233         } catch (RemoteException | ServiceSpecificException e) {
234             Slogf.w(TAG, e, "Failed to call unscheduleAllTasks with clientId: %s", clientId);
235             throw e;
236         }
237     }
238 
239     /**
240      * Check {@link IRemoteAccess#isTaskScheduled}.
241      *
242      * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this.
243      *
244      * <p>Returns {@code false} if error happens.
245      */
isTaskScheduled(String clientId, String scheduleId)246     public boolean isTaskScheduled(String clientId, String scheduleId)
247             throws RemoteException, ServiceSpecificException {
248         try {
249             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
250             return remoteAccessHal.isTaskScheduled(clientId, scheduleId);
251         } catch (RemoteException | ServiceSpecificException e) {
252             Slogf.w(TAG, e, "Failed to call isTaskScheduled with clientId: %s, scheduleId: %s,"
253                     + " default to false", clientId, scheduleId);
254             throw e;
255         }
256     }
257 
258     /**
259      * Check {@link IRemoteAccess#getAllPendingScheduledTasks}.
260      *
261      * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this.
262      *
263      * <p>Client should handle the thrown exception.
264      */
getAllPendingScheduledTasks(String clientId)265     public List<ScheduleInfo> getAllPendingScheduledTasks(String clientId)
266             throws RemoteException, ServiceSpecificException {
267         try {
268             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
269             return remoteAccessHal.getAllPendingScheduledTasks(clientId);
270         } catch (RemoteException | ServiceSpecificException e) {
271             Slogf.w(TAG, e, "Failed to call getAllPendingScheduledTasks with clientId: %s",
272                     clientId);
273             throw e;
274         }
275     }
276 
277     /**
278      * Check {@link IRemoteAccess#getSupportedTaskTypesForScheduling}.
279      *
280      * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this.
281      *
282      * <p>Client should handle the thrown exception.
283      */
getSupportedTaskTypesForScheduling()284     public int[] getSupportedTaskTypesForScheduling()
285             throws RemoteException, ServiceSpecificException {
286         try {
287             IRemoteAccess remoteAccessHal = getRemoteAccessHal();
288             return remoteAccessHal.getSupportedTaskTypesForScheduling();
289         } catch (RemoteException | ServiceSpecificException e) {
290             Slogf.w(TAG, e, "Failed to call getSupportedTaskTypesForScheduling");
291             throw e;
292         }
293     }
294 
295 
connectToHal()296     private void connectToHal() {
297         if (!mConnecting.compareAndSet(/* expect= */ false, /* update= */ true)) {
298             Slogf.w(TAG, "Connecting to remote access HAL is in progress");
299             return;
300         }
301         TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
302 
303         IRemoteAccess remoteAccessHal;
304         if (mTestRemoteAccessHal != null) {
305             remoteAccessHal = mTestRemoteAccessHal;
306         } else {
307             t.traceBegin("connect-to-remote-access-hal");
308             IBinder binder = getRemoteAccessHalService();
309             t.traceEnd();
310             if (binder == null) {
311                 mConnecting.set(/* newValue= */ false);
312                 throw new IllegalStateException("Remote access HAL not found");
313             }
314 
315             try {
316                 binder.linkToDeath(this, /* flags= */ 0);
317             } catch (RemoteException e) {
318                 mConnecting.set(/* newValue= */ false);
319                 throw new IllegalStateException(
320                         "Failed to link a death recipient to remote access HAL", e);
321             }
322 
323             synchronized (mLock) {
324                 if (mBinder != null) {
325                     Slogf.w(TAG, "Remote access HAL is already connected");
326                     binder.unlinkToDeath(this, /* flags= */ 0);
327                     mConnecting.set(/* newValue= */ false);
328                     return;
329                 }
330                 mBinder = binder;
331                 mRemoteAccessHal = IRemoteAccess.Stub.asInterface(mBinder);
332                 remoteAccessHal = mRemoteAccessHal;
333             }
334             mConnecting.set(/* newValue= */ false);
335         }
336         try {
337             remoteAccessHal.setRemoteTaskCallback(mRemoteTaskCallback);
338         } catch (RemoteException e) {
339             throw new IllegalStateException("Failed to set remote task callback", e);
340         }
341         Slogf.i(TAG, "Connected to remote access HAL");
342     }
343 
clearBinder()344     private void clearBinder() {
345         synchronized (mLock) {
346             if (mBinder == null) {
347                 return;
348             }
349             mBinder.unlinkToDeath(this, /* flags= */ 0);
350             mBinder = null;
351         }
352     }
353 
getRemoteAccessHal()354     private IRemoteAccess getRemoteAccessHal() {
355         synchronized (mLock) {
356             if (mTestRemoteAccessHal != null) {
357                 return mTestRemoteAccessHal;
358             }
359             if (mRemoteAccessHal == null) {
360                 throw new IllegalStateException("Remote access HAL is not ready");
361             }
362             return mRemoteAccessHal;
363         }
364     }
365 
onRemoteTaskRequested(String clientId, byte[] data)366     private void onRemoteTaskRequested(String clientId, byte[] data) {
367         mRemoteAccessHalCallback.onRemoteTaskRequested(clientId, data);
368     }
369 
370     /**
371      * Connects to remote access HAL.
372      *
373      * <p>If there are multiple service implementations, connection is made in an order of service
374      * declaration.
375      */
376     @VisibleForTesting
377     @Nullable
getRemoteAccessHalService()378     public static IBinder getRemoteAccessHalService() {
379         String[] instances = ServiceManagerHelper.getDeclaredInstances(IRemoteAccess.DESCRIPTOR);
380         if (instances == null || instances.length == 0) {
381             Slogf.e(TAG, "No remote access HAL service found");
382             return null;
383         }
384         // If there are many remote access HAL instances, they are traversed in a declaring order.
385         for (int i = 0; i < instances.length; i++) {
386             String fqName = IRemoteAccess.DESCRIPTOR + "/" + instances[i];
387             IBinder binder = ServiceManagerHelper.waitForDeclaredService(fqName);
388             if (binder != null) {
389                 Slogf.i(TAG, "Connected to remote access HAL instance(%s)", fqName);
390                 return binder;
391             }
392         }
393         return null;
394     }
395 
396     private static final class RemoteTaskCallbackImpl extends IRemoteTaskCallback.Stub {
397         private final WeakReference<RemoteAccessHalWrapper> mHalWrapper;
398 
RemoteTaskCallbackImpl(RemoteAccessHalWrapper halWrapper)399         RemoteTaskCallbackImpl(RemoteAccessHalWrapper halWrapper) {
400             mHalWrapper = new WeakReference<>(halWrapper);
401         }
402 
403         @Override
onRemoteTaskRequested(String clientId, byte[] data)404         public void onRemoteTaskRequested(String clientId, byte[] data) {
405             if (DEBUG) {
406                 Slogf.d(TAG, "onRemoteTaskRequested is called: clientId = %s, data size = %d",
407                         clientId, data == null ? 0 : data.length);
408             }
409             RemoteAccessHalWrapper halWrapper = mHalWrapper.get();
410             if (halWrapper == null) {
411                 Slogf.w(TAG, "RemoteAccessHalWrapper is not available: clientId = %s", clientId);
412                 return;
413             }
414             halWrapper.onRemoteTaskRequested(clientId, data);
415         }
416 
417         @Override
getInterfaceHash()418         public String getInterfaceHash() {
419             return IRemoteTaskCallback.HASH;
420         }
421 
422         @Override
getInterfaceVersion()423         public int getInterfaceVersion() {
424             return IRemoteTaskCallback.VERSION;
425         }
426     }
427 }
428