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