1 /*
2  * Copyright (C) 2020 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;
18 
19 import android.annotation.NonNull;
20 import android.car.Car;
21 import android.car.occupantawareness.IOccupantAwarenessEventCallback;
22 import android.car.occupantawareness.OccupantAwarenessDetection;
23 import android.car.occupantawareness.OccupantAwarenessDetection.VehicleOccupantRole;
24 import android.car.occupantawareness.SystemStatusEvent;
25 import android.car.occupantawareness.SystemStatusEvent.DetectionTypeFlags;
26 import android.content.Context;
27 import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
28 import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback;
29 import android.os.RemoteCallbackList;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.util.IndentingPrintWriter;
33 import android.util.Slog;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.lang.ref.WeakReference;
39 
40 /**
41  * A service that listens to an Occupant Awareness Detection system across a HAL boundary and
42  * exposes the data to system clients in Android via a {@link
43  * android.car.occupantawareness.OccupantAwarenessManager}.
44  *
45  * <p>The service exposes the following detections types:
46  *
47  * <h1>Presence Detection</h1>
48  *
49  * Detects whether a person is present for each seat location.
50  *
51  * <h1>Gaze Detection</h1>
52  *
53  * Detects where an occupant is looking and for how long they have been looking at the specified
54  * target.
55  *
56  * <h1>Driver Monitoring</h1>
57  *
58  * Detects whether a driver is looking on or off-road and for how long they have been looking there.
59  */
60 public class OccupantAwarenessService
61         extends android.car.occupantawareness.IOccupantAwarenessManager.Stub
62         implements CarServiceBase {
63     private static final String TAG = CarLog.tagFor(OccupantAwarenessService.class);
64     private static final boolean DBG = false;
65 
66     // HAL service identifier name.
67     @VisibleForTesting
68     static final String OAS_SERVICE_ID =
69             "android.hardware.automotive.occupant_awareness.IOccupantAwareness/default";
70 
71     private final Object mLock = new Object();
72     private final Context mContext;
73 
74     @GuardedBy("mLock")
75     private IOccupantAwareness mOasHal;
76 
77     private final ChangeListenerToHalService mHalListener = new ChangeListenerToHalService(this);
78 
79     private class ChangeCallbackList extends RemoteCallbackList<IOccupantAwarenessEventCallback> {
80         private final WeakReference<OccupantAwarenessService> mOasService;
81 
ChangeCallbackList(OccupantAwarenessService oasService)82         ChangeCallbackList(OccupantAwarenessService oasService) {
83             mOasService = new WeakReference<>(oasService);
84         }
85 
86         /** Handle callback death. */
87         @Override
onCallbackDied(IOccupantAwarenessEventCallback listener)88         public void onCallbackDied(IOccupantAwarenessEventCallback listener) {
89             Slog.i(TAG, "binderDied: " + listener.asBinder());
90 
91             OccupantAwarenessService service = mOasService.get();
92             if (service != null) {
93                 service.handleClientDisconnected();
94             }
95         }
96     }
97 
98     @GuardedBy("mLock")
99     private final ChangeCallbackList mListeners = new ChangeCallbackList(this);
100 
101     /** Creates an OccupantAwarenessService instance given a {@link Context}. */
OccupantAwarenessService(Context context)102     public OccupantAwarenessService(Context context) {
103         mContext = context;
104     }
105 
106     /** Creates an OccupantAwarenessService instance given a {@link Context}. */
107     @VisibleForTesting
OccupantAwarenessService(Context context, IOccupantAwareness oasInterface)108     OccupantAwarenessService(Context context, IOccupantAwareness oasInterface) {
109         mContext = context;
110         mOasHal = oasInterface;
111     }
112 
113     @Override
init()114     public void init() {
115         logd("Initializing service");
116         connectToHalServiceIfNotConnected();
117     }
118 
119     @Override
release()120     public void release() {
121         logd("Will stop detection and disconnect listeners");
122         stopDetectionGraph();
123         mListeners.kill();
124     }
125 
126     @Override
dump(IndentingPrintWriter writer)127     public void dump(IndentingPrintWriter writer) {
128         writer.println("*OccupantAwarenessService*");
129         writer.println(
130                 String.format(
131                         "%s to HAL service", mOasHal == null ? "NOT connected" : "Connected"));
132         writer.println(
133                 String.format(
134                         "%d change listeners subscribed.",
135                         mListeners.getRegisteredCallbackCount()));
136     }
137 
138     /** Attempts to connect to the HAL service if it is not already connected. */
connectToHalServiceIfNotConnected()139     private void connectToHalServiceIfNotConnected() {
140         logd("connectToHalServiceIfNotConnected()");
141 
142         synchronized (mLock) {
143             // If already connected, nothing more needs to be done.
144             if (mOasHal != null) {
145                 logd("Client is already connected, nothing more to do");
146                 return;
147             }
148 
149             // Attempt to find the HAL service.
150             logd("Attempting to connect to client at: " + OAS_SERVICE_ID);
151             mOasHal =
152                     android.hardware.automotive.occupant_awareness.IOccupantAwareness.Stub
153                             .asInterface(ServiceManager.getService(OAS_SERVICE_ID));
154 
155             if (mOasHal == null) {
156                 Slog.e(TAG, "Failed to find OAS hal_service at: [" + OAS_SERVICE_ID + "]");
157                 return;
158             }
159 
160             // Register for callbacks.
161             try {
162                 mOasHal.setCallback(mHalListener);
163             } catch (RemoteException e) {
164                 mOasHal = null;
165                 Slog.e(TAG, "Failed to set callback: " + e);
166                 return;
167             }
168 
169             logd("Successfully connected to hal_service at: [" + OAS_SERVICE_ID + "]");
170         }
171     }
172 
173     /** Sends a message via the HAL to start the detection graph. */
startDetectionGraph()174     private void startDetectionGraph() {
175         logd("Attempting to start detection graph");
176 
177         // Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
178         IOccupantAwareness hal;
179         synchronized (mLock) {
180             hal = mOasHal;
181         }
182 
183         if (hal != null) {
184             try {
185                 hal.startDetection();
186             } catch (RemoteException e) {
187                 Slog.e(TAG, "startDetection() HAL invocation failed: " + e, e);
188 
189                 synchronized (mLock) {
190                     mOasHal = null;
191                 }
192             }
193         } else {
194             Slog.e(TAG, "No HAL is connected. Cannot request graph start");
195         }
196     }
197 
198     /** Sends a message via the HAL to stop the detection graph. */
stopDetectionGraph()199     private void stopDetectionGraph() {
200         logd("Attempting to stop detection graph.");
201 
202         // Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
203         IOccupantAwareness hal;
204         synchronized (mLock) {
205             hal = mOasHal;
206         }
207 
208         if (hal != null) {
209             try {
210                 hal.stopDetection();
211             } catch (RemoteException e) {
212                 Slog.e(TAG, "stopDetection() HAL invocation failed: " + e, e);
213 
214                 synchronized (mLock) {
215                     mOasHal = null;
216                 }
217             }
218         } else {
219             Slog.e(TAG, "No HAL is connected. Cannot request graph stop");
220         }
221     }
222 
223     /**
224      * Gets the vehicle capabilities for a given role.
225      *
226      * <p>Capabilities are static for a given vehicle configuration and need only be queried once
227      * per vehicle. Once capability is determined, clients should query system status to see if the
228      * subsystem is currently ready to serve.
229      *
230      * <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
231      * permissions to access.
232      *
233      * @param role {@link VehicleOccupantRole} to query for.
234      * @return Flags indicating supported capabilities for the role.
235      */
getCapabilityForRole(@ehicleOccupantRole int role)236     public @DetectionTypeFlags int getCapabilityForRole(@VehicleOccupantRole int role) {
237         ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
238 
239         connectToHalServiceIfNotConnected();
240 
241         // Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
242         IOccupantAwareness hal;
243         synchronized (mLock) {
244             hal = mOasHal;
245         }
246 
247         if (hal != null) {
248             try {
249                 return hal.getCapabilityForRole(role);
250             } catch (RemoteException e) {
251 
252                 Slog.e(TAG, "getCapabilityForRole() HAL invocation failed: " + e, e);
253 
254                 synchronized (mLock) {
255                     mOasHal = null;
256                 }
257 
258                 return SystemStatusEvent.DETECTION_TYPE_NONE;
259             }
260         } else {
261             Slog.e(
262                     TAG,
263                     "getCapabilityForRole(): No HAL interface has been provided. Cannot get"
264                             + " capabilities");
265             return SystemStatusEvent.DETECTION_TYPE_NONE;
266         }
267     }
268 
269     /**
270      * Registers a {@link IOccupantAwarenessEventCallback} to be notified for changes in the system
271      * state.
272      *
273      * <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
274      * permissions to access.
275      *
276      * @param listener {@link IOccupantAwarenessEventCallback} listener to register.
277      */
278     @Override
registerEventListener(@onNull IOccupantAwarenessEventCallback listener)279     public void registerEventListener(@NonNull IOccupantAwarenessEventCallback listener) {
280         ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
281 
282         connectToHalServiceIfNotConnected();
283 
284         synchronized (mLock) {
285             if (mOasHal == null) {
286                 Slog.e(TAG, "Attempting to register a listener, but could not connect to HAL.");
287                 return;
288             }
289 
290             logd("Registering a new listener");
291             mListeners.register(listener);
292 
293             // After the first client connects, request that the detection graph start.
294             if (mListeners.getRegisteredCallbackCount() == 1) {
295                 startDetectionGraph();
296             }
297         }
298     }
299 
300     /**
301      * Unregister the given {@link IOccupantAwarenessEventCallback} listener from receiving events.
302      *
303      * <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
304      * permissions to access.
305      *
306      * @param listener {@link IOccupantAwarenessEventCallback} client to unregister.
307      */
308     @Override
unregisterEventListener(@onNull IOccupantAwarenessEventCallback listener)309     public void unregisterEventListener(@NonNull IOccupantAwarenessEventCallback listener) {
310         ICarImpl.assertPermission(mContext, Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
311 
312         connectToHalServiceIfNotConnected();
313 
314         synchronized (mLock) {
315             mListeners.unregister(listener);
316         }
317 
318         // When the last client disconnects, request that the detection graph stop.
319         handleClientDisconnected();
320     }
321 
322     /** Processes a detection event and propagates it to registered clients. */
323     @VisibleForTesting
processStatusEvent(@onNull SystemStatusEvent statusEvent)324     void processStatusEvent(@NonNull SystemStatusEvent statusEvent) {
325         int idx = mListeners.beginBroadcast();
326         while (idx-- > 0) {
327             IOccupantAwarenessEventCallback listener = mListeners.getBroadcastItem(idx);
328             try {
329                 listener.onStatusChanged(statusEvent);
330             } catch (RemoteException e) {
331                 // It's likely the connection snapped. Let binder death handle the situation.
332                 Slog.e(TAG, "onStatusChanged() invocation failed: " + e, e);
333             }
334         }
335         mListeners.finishBroadcast();
336     }
337 
338     /** Processes a detection event and propagates it to registered clients. */
339     @VisibleForTesting
processDetectionEvent(@onNull OccupantAwarenessDetection detection)340     void processDetectionEvent(@NonNull OccupantAwarenessDetection detection) {
341         int idx = mListeners.beginBroadcast();
342         while (idx-- > 0) {
343             IOccupantAwarenessEventCallback listener = mListeners.getBroadcastItem(idx);
344             try {
345                 listener.onDetectionEvent(detection);
346             } catch (RemoteException e) {
347                 // It's likely the connection snapped. Let binder death handle the situation.
348                 Slog.e(TAG, "onDetectionEvent() invocation failed: " + e, e);
349             }
350         }
351         mListeners.finishBroadcast();
352     }
353 
354     /** Handle client disconnections, possibly stopping the detection graph. */
handleClientDisconnected()355     void handleClientDisconnected() {
356         // If the last client disconnects, requests that the graph stops.
357         synchronized (mLock) {
358             if (mListeners.getRegisteredCallbackCount() == 0) {
359                 stopDetectionGraph();
360             }
361         }
362     }
363 
logd(String msg)364     private static void logd(String msg) {
365         if (DBG) {
366             Slog.d(TAG, msg);
367         }
368     }
369 
370     /**
371      * Class that implements the listener interface and gets called back from the {@link
372      * android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback} across the
373      * binder interface.
374      */
375     private static class ChangeListenerToHalService extends IOccupantAwarenessClientCallback.Stub {
376         private final WeakReference<OccupantAwarenessService> mOasService;
377 
ChangeListenerToHalService(OccupantAwarenessService oasService)378         ChangeListenerToHalService(OccupantAwarenessService oasService) {
379             mOasService = new WeakReference<>(oasService);
380         }
381 
382         @Override
onSystemStatusChanged(int inputDetectionFlags, byte inputStatus)383         public void onSystemStatusChanged(int inputDetectionFlags, byte inputStatus) {
384             OccupantAwarenessService service = mOasService.get();
385             if (service != null) {
386                 service.processStatusEvent(
387                         OccupantAwarenessUtils.convertToStatusEvent(
388                                 inputDetectionFlags, inputStatus));
389             }
390         }
391 
392         @Override
onDetectionEvent( android.hardware.automotive.occupant_awareness.OccupantDetections detections)393         public void onDetectionEvent(
394                 android.hardware.automotive.occupant_awareness.OccupantDetections detections) {
395             OccupantAwarenessService service = mOasService.get();
396             if (service != null) {
397                 for (android.hardware.automotive.occupant_awareness.OccupantDetection detection :
398                         detections.detections) {
399                     service.processDetectionEvent(
400                             OccupantAwarenessUtils.convertToDetectionEvent(
401                                     detections.timeStampMillis, detection));
402                 }
403             }
404         }
405 
406         @Override
getInterfaceVersion()407         public int getInterfaceVersion() {
408             return this.VERSION;
409         }
410 
411         @Override
getInterfaceHash()412         public String getInterfaceHash() {
413             return this.HASH;
414         }
415     }
416 }
417