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