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