blob: 0200559f2cb26c4df79201ffa504348e558ec150 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import android.annotation.NonNull;
import android.car.Car;
import android.car.builtin.os.ServiceManagerHelper;
import android.car.builtin.util.Slogf;
import android.car.occupantawareness.IOccupantAwarenessEventCallback;
import android.car.occupantawareness.OccupantAwarenessDetection;
import android.car.occupantawareness.OccupantAwarenessDetection.VehicleOccupantRole;
import android.car.occupantawareness.SystemStatusEvent;
import android.car.occupantawareness.SystemStatusEvent.DetectionTypeFlags;
import android.content.Context;
import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.proto.ProtoOutputStream;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.ref.WeakReference;
/**
* A service that listens to an Occupant Awareness Detection system across a HAL boundary and
* exposes the data to system clients in Android via a {@link
* android.car.occupantawareness.OccupantAwarenessManager}.
*
* <p>The service exposes the following detections types:
*
* <h1>Presence Detection</h1>
*
* Detects whether a person is present for each seat location.
*
* <h1>Gaze Detection</h1>
*
* Detects where an occupant is looking and for how long they have been looking at the specified
* target.
*
* <h1>Driver Monitoring</h1>
*
* Detects whether a driver is looking on or off-road and for how long they have been looking there.
*/
public class OccupantAwarenessService
extends android.car.occupantawareness.IOccupantAwarenessManager.Stub
implements CarServiceBase {
private static final String TAG = CarLog.tagFor(OccupantAwarenessService.class);
private static final boolean DBG = false;
// HAL service identifier name.
@VisibleForTesting
static final String OAS_SERVICE_ID =
"android.hardware.automotive.occupant_awareness.IOccupantAwareness/default";
private final Object mLock = new Object();
private final Context mContext;
@GuardedBy("mLock")
private IOccupantAwareness mOasHal;
private final ChangeListenerToHalService mHalListener = new ChangeListenerToHalService(this);
private class ChangeCallbackList extends RemoteCallbackList<IOccupantAwarenessEventCallback> {
private final WeakReference<OccupantAwarenessService> mOasService;
ChangeCallbackList(OccupantAwarenessService oasService) {
mOasService = new WeakReference<>(oasService);
}
/** Handle callback death. */
@Override
public void onCallbackDied(IOccupantAwarenessEventCallback listener) {
Slogf.i(TAG, "binderDied: " + listener.asBinder());
OccupantAwarenessService service = mOasService.get();
if (service != null) {
service.handleClientDisconnected();
}
}
}
private final ChangeCallbackList mListeners = new ChangeCallbackList(this);
/** Creates an OccupantAwarenessService instance given a {@link Context}. */
public OccupantAwarenessService(Context context) {
mContext = context;
}
/** Creates an OccupantAwarenessService instance given a {@link Context}. */
@VisibleForTesting
OccupantAwarenessService(Context context, IOccupantAwareness oasInterface) {
mContext = context;
mOasHal = oasInterface;
}
@Override
public void init() {
logd("Initializing service");
connectToHalServiceIfNotConnected(true);
}
@Override
public void release() {
logd("Will stop detection and disconnect listeners");
stopDetectionGraph();
mListeners.kill();
}
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dump(IndentingPrintWriter writer) {
writer.println("*OccupantAwarenessService*");
synchronized (mLock) {
writer.println(String.format(
"%s to HAL service", mOasHal == null ? "NOT connected" : "Connected"));
}
writer.println(
String.format(
"%d change listeners subscribed.",
mListeners.getRegisteredCallbackCount()));
}
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dumpProto(ProtoOutputStream proto) {}
/** Attempts to connect to the HAL service if it is not already connected. */
private void connectToHalServiceIfNotConnected(boolean forceConnect) {
logd("connectToHalServiceIfNotConnected()");
synchronized (mLock) {
// If already connected, nothing more needs to be done.
if (mOasHal != null && !forceConnect) {
logd("Client is already connected, nothing more to do");
return;
}
// Attempt to find the HAL service.
if (mOasHal == null) {
logd("Attempting to connect to client at: " + OAS_SERVICE_ID);
mOasHal =
android.hardware.automotive.occupant_awareness.IOccupantAwareness.Stub
.asInterface(ServiceManagerHelper.getService(OAS_SERVICE_ID));
if (mOasHal == null) {
Slogf.e(TAG, "Failed to find OAS hal_service at: [" + OAS_SERVICE_ID + "]");
return;
}
}
// Register for callbacks.
try {
mOasHal.setCallback(mHalListener);
} catch (RemoteException e) {
mOasHal = null;
Slogf.e(TAG, "Failed to set callback: " + e);
return;
}
logd("Successfully connected to hal_service at: [" + OAS_SERVICE_ID + "]");
}
}
/** Sends a message via the HAL to start the detection graph. */
private void startDetectionGraph() {
logd("Attempting to start detection graph");
// Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
IOccupantAwareness hal;
synchronized (mLock) {
hal = mOasHal;
}
if (hal != null) {
try {
hal.startDetection();
} catch (RemoteException e) {
Slogf.e(TAG, "startDetection() HAL invocation failed: " + e, e);
synchronized (mLock) {
mOasHal = null;
}
}
} else {
Slogf.e(TAG, "No HAL is connected. Cannot request graph start");
}
}
/** Sends a message via the HAL to stop the detection graph. */
private void stopDetectionGraph() {
logd("Attempting to stop detection graph.");
// Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
IOccupantAwareness hal;
synchronized (mLock) {
hal = mOasHal;
}
if (hal != null) {
try {
hal.stopDetection();
} catch (RemoteException e) {
Slogf.e(TAG, "stopDetection() HAL invocation failed: " + e, e);
synchronized (mLock) {
mOasHal = null;
}
}
} else {
Slogf.e(TAG, "No HAL is connected. Cannot request graph stop");
}
}
/**
* Gets the vehicle capabilities for a given role.
*
* <p>Capabilities are static for a given vehicle configuration and need only be queried once
* per vehicle. Once capability is determined, clients should query system status to see if the
* subsystem is currently ready to serve.
*
* <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
* permissions to access.
*
* @param role {@link VehicleOccupantRole} to query for.
* @return Flags indicating supported capabilities for the role.
*/
public @DetectionTypeFlags int getCapabilityForRole(@VehicleOccupantRole int role) {
CarServiceUtils.assertPermission(mContext,
Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
connectToHalServiceIfNotConnected(false);
// Grab a copy of 'mOasHal' to avoid sitting on the lock longer than is necessary.
IOccupantAwareness hal;
synchronized (mLock) {
hal = mOasHal;
}
if (hal != null) {
try {
return hal.getCapabilityForRole(role);
} catch (RemoteException e) {
Slogf.e(TAG, "getCapabilityForRole() HAL invocation failed: " + e, e);
synchronized (mLock) {
mOasHal = null;
}
return SystemStatusEvent.DETECTION_TYPE_NONE;
}
} else {
Slogf.e(TAG, "getCapabilityForRole(): No HAL interface has been provided. Cannot get"
+ " capabilities");
return SystemStatusEvent.DETECTION_TYPE_NONE;
}
}
/**
* Registers a {@link IOccupantAwarenessEventCallback} to be notified for changes in the system
* state.
*
* <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
* permissions to access.
*
* @param listener {@link IOccupantAwarenessEventCallback} listener to register.
*/
@Override
public void registerEventListener(@NonNull IOccupantAwarenessEventCallback listener) {
CarServiceUtils.assertPermission(mContext,
Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
connectToHalServiceIfNotConnected(false);
synchronized (mLock) {
if (mOasHal == null) {
Slogf.e(TAG, "Attempting to register a listener, but could not connect to HAL.");
return;
}
logd("Registering a new listener");
mListeners.register(listener);
// After the first client connects, request that the detection graph start.
if (mListeners.getRegisteredCallbackCount() == 1) {
startDetectionGraph();
}
}
}
/**
* Unregister the given {@link IOccupantAwarenessEventCallback} listener from receiving events.
*
* <p>Requires {@link android.car.Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE} read
* permissions to access.
*
* @param listener {@link IOccupantAwarenessEventCallback} client to unregister.
*/
@Override
public void unregisterEventListener(@NonNull IOccupantAwarenessEventCallback listener) {
CarServiceUtils.assertPermission(mContext,
Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE);
connectToHalServiceIfNotConnected(false);
synchronized (mLock) {
mListeners.unregister(listener);
}
// When the last client disconnects, request that the detection graph stop.
handleClientDisconnected();
}
/** Processes a detection event and propagates it to registered clients. */
@VisibleForTesting
void processStatusEvent(@NonNull SystemStatusEvent statusEvent) {
int idx = mListeners.beginBroadcast();
while (idx-- > 0) {
IOccupantAwarenessEventCallback listener = mListeners.getBroadcastItem(idx);
try {
listener.onStatusChanged(statusEvent);
} catch (RemoteException e) {
// It's likely the connection snapped. Let binder death handle the situation.
Slogf.e(TAG, "onStatusChanged() invocation failed: " + e, e);
}
}
mListeners.finishBroadcast();
}
/** Processes a detection event and propagates it to registered clients. */
@VisibleForTesting
void processDetectionEvent(@NonNull OccupantAwarenessDetection detection) {
int idx = mListeners.beginBroadcast();
while (idx-- > 0) {
IOccupantAwarenessEventCallback listener = mListeners.getBroadcastItem(idx);
try {
listener.onDetectionEvent(detection);
} catch (RemoteException e) {
// It's likely the connection snapped. Let binder death handle the situation.
Slogf.e(TAG, "onDetectionEvent() invocation failed: " + e, e);
}
}
mListeners.finishBroadcast();
}
/** Handle client disconnections, possibly stopping the detection graph. */
void handleClientDisconnected() {
// If the last client disconnects, requests that the graph stops.
synchronized (mLock) {
if (mListeners.getRegisteredCallbackCount() == 0) {
stopDetectionGraph();
}
}
}
private static void logd(String msg) {
if (DBG) {
Slogf.d(TAG, msg);
}
}
/**
* Class that implements the listener interface and gets called back from the {@link
* android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback} across the
* binder interface.
*/
private static class ChangeListenerToHalService extends IOccupantAwarenessClientCallback.Stub {
private final WeakReference<OccupantAwarenessService> mOasService;
ChangeListenerToHalService(OccupantAwarenessService oasService) {
mOasService = new WeakReference<>(oasService);
}
@Override
public void onSystemStatusChanged(int inputDetectionFlags, byte inputStatus) {
OccupantAwarenessService service = mOasService.get();
if (service != null) {
service.processStatusEvent(
OccupantAwarenessUtils.convertToStatusEvent(
inputDetectionFlags, inputStatus));
}
}
@Override
public void onDetectionEvent(
android.hardware.automotive.occupant_awareness.OccupantDetections detections) {
OccupantAwarenessService service = mOasService.get();
if (service != null) {
for (android.hardware.automotive.occupant_awareness.OccupantDetection detection :
detections.detections) {
service.processDetectionEvent(
OccupantAwarenessUtils.convertToDetectionEvent(
detections.timeStampMillis, detection));
}
}
}
@Override
public int getInterfaceVersion() {
return this.VERSION;
}
@Override
public String getInterfaceHash() {
return this.HASH;
}
}
}