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