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