/* * Copyright (C) 2021 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.evs; import static android.car.evs.CarEvsManager.ERROR_NONE; import static android.car.evs.CarEvsManager.ERROR_UNAVAILABLE; import static com.android.car.CarLog.TAG_EVS; import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_NORMAL; import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_HIGH; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.annotation.NonNull; import android.annotation.Nullable; import android.car.Car; import android.car.VehiclePropertyIds; import android.car.builtin.content.pm.PackageManagerHelper; import android.car.builtin.os.BuildHelper; import android.car.builtin.util.Slogf; import android.car.evs.CarEvsBufferDescriptor; import android.car.evs.CarEvsManager; import android.car.evs.CarEvsManager.CarEvsError; import android.car.evs.CarEvsManager.CarEvsServiceState; import android.car.evs.CarEvsManager.CarEvsServiceType; import android.car.evs.CarEvsStatus; import android.car.evs.ICarEvsStatusListener; import android.car.evs.ICarEvsStreamCallback; import android.car.hardware.CarPropertyValue; import android.car.hardware.property.CarPropertyEvent; import android.car.hardware.property.ICarPropertyEventListener; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.automotive.vehicle.VehicleGear; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.Display; import com.android.car.CarPropertyService; import com.android.car.CarServiceBase; import com.android.car.CarServiceUtils; import com.android.car.R; import com.android.car.hal.EvsHalService; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.car.internal.evs.CarEvsUtils; import com.android.car.internal.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.WeakReference; import java.util.List; import java.util.Objects; import java.util.Set; /** * A service that listens to the Extended View System across a HAL boundary and exposes the data to * system clients in Android via {@link android.car.evs.CarEvsManager}. * * Because of Fast Message Queue usages, android.hardware.automotive.evs@1.1 interfaces does not * support Java backend and, therefore, actual API calls are done in native methods. * * * CarEvsService consists of four states: * * UNAVAILABLE: CarEvsService is not connected to the Extended View System service. In this * state, any service request will be declined. * * INACTIVE: CarEvsService has a valid, live connection the Extended View System service and * ready for any service requests. * * REQUESTED: CarEvsService received a service requeste from a privileged client and requested * the System UI to launch the camera viewing activity. * * ACTIVE: CarEvsService is actively streaming a video to the client. * * See CarEvsService.StateMachine class for more details. */ public final class CarEvsService extends android.car.evs.ICarEvsService.Stub implements CarServiceBase { private static final boolean DBG = Slogf.isLoggable(TAG_EVS, Log.DEBUG); private static final String EVS_INTERFACE_NAME = "android.hardware.automotive.evs.IEvsEnumerator"; private static final String EVS_DEFAULT_INSTANCE_NAME = "default"; static final class EvsHalEvent { private long mTimestamp; private int mServiceType; private boolean mOn; public EvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on) { mTimestamp = timestamp; mServiceType = type; mOn = on; } public long getTimestamp() { return mTimestamp; } public @CarEvsServiceType int getServiceType() { return mServiceType; } public boolean isRequestingToStartActivity() { return mOn; } @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public String toString() { return "ServiceType=" + CarEvsUtils.convertToString(mServiceType) + ", mOn=" + mOn + ", Timestamp=" + mTimestamp; } } private final Context mContext; private final Context mBuiltinContext; private final EvsHalService mEvsHalService; private final CarPropertyService mPropertyService; private final DisplayManager mDisplayManager; // To monitor the default display's state private final Object mLock = new Object(); private final ArraySet mSessionTokens = new ArraySet<>(); private final boolean mIsEvsAvailable; // This handler is to monitor the client sends a video stream request within a given time // after a state transition to the REQUESTED state. private final Handler mHandler = new Handler(Looper.getMainLooper()); private static final class StatusListenerList extends RemoteCallbackList { private final WeakReference mService; StatusListenerList(CarEvsService evsService) { mService = new WeakReference<>(evsService); } /** Handle callback death */ @Override public void onCallbackDied(ICarEvsStatusListener listener) { Slogf.w(TAG_EVS, "StatusListener has died: " + listener.asBinder()); CarEvsService svc = mService.get(); if (svc != null) { svc.handleClientDisconnected(listener); } } } private final StatusListenerList mStatusListeners = new StatusListenerList(this); /** * {@link CarPropertyEvent} listener registered with {@link CarPropertyService} to listen to * {@link VehicleProperty.GEAR_SELECTION} change notifications. */ private final ICarPropertyEventListener mGearSelectionPropertyListener = new ICarPropertyEventListener.Stub() { @Override public void onEvent(List events) throws RemoteException { if (events.isEmpty()) { return; } // Handle only the latest event Slogf.i(TAG_EVS, "Handling GearSelection event"); handlePropertyEvent(events.get(events.size() - 1)); } }; private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayAdded(int displayId) { // Nothing to do } @Override public void onDisplayRemoved(int displayId) { // Nothing to do } @Override public void onDisplayChanged(int displayId) { if (displayId != Display.DEFAULT_DISPLAY) { // We are interested only in the default display. return; } Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); if (mCurrentDisplayState == display.getState()) { // We already handled this display state change. if (DBG) { Slogf.d(TAG_EVS, "We already handled a reported display status, %d", display.getState()); } return; } // TODO(b/292155786): Current implementation is optimized for the device with a // single display and therefore we may need to consider the // source of the display event and start/stop activities // accordingly. switch (display.getState()) { case Display.STATE_ON: // Requests each StateMachine to launch a registered activity if it's // necessary. for (int i = 0; i < mServiceInstances.size(); i++) { if (mServiceInstances.valueAt(i) .requestStartActivityIfNecessary() == ERROR_NONE) { continue; } Slogf.e(TAG_EVS, "Failed to start %s's activity.", CarEvsUtils.convertToString(mServiceInstances.keyAt(i))); } break; case Display.STATE_OFF: // Each StateMachine stores a valid session token that was used for // recognizing a streaming callback from a launched activity. // CarEvsService will request each StateMachine to stop those callbacks // and let other callbacks continue running. Activities not launched by // CarEvsService must handle display's state changes properly by // themselves. for (int i = 0; i < mServiceInstances.size(); i++) { mServiceInstances.valueAt(i) .requestStopActivity(REQUEST_PRIORITY_HIGH); } break; default: // Nothing to do for all other state changes break; } mCurrentDisplayState = display.getState(); } }; // Service instances per each type. private final SparseArray mServiceInstances; // Associates callback objects with their service types. private final ArrayMap> mCallbackToServiceType = new ArrayMap<>(); // The latest display state we have processed. private int mCurrentDisplayState = Display.STATE_OFF; // This boolean flag is true if CarEvsService uses GEAR_SELECTION VHAL property instead of // EVS_SERVICE_REQUEST. private boolean mUseGearSelection = true; // The last event EvsHalService reported. This will be set to null when a related service // request is handled. // // To properly handle a HAL event that occurred before CarEvsService is ready, we initialize // mLastEvsHalEvent with a zero timestamp here. @GuardedBy("mLock") private EvsHalEvent mLastEvsHalEvent = new EvsHalEvent(/* timestamp= */ 0, CarEvsManager.SERVICE_TYPE_REARVIEW, /* on= */ false); /** Creates an Extended View System service instance given a {@link Context}. */ public CarEvsService(Context context, Context builtinContext, EvsHalService halService, CarPropertyService propertyService) { this(context, builtinContext, halService, propertyService, /* checkDependencies= */ true); } @VisibleForTesting CarEvsService(Context context, Context builtinContext, EvsHalService halService, CarPropertyService propertyService, boolean checkDependencies) { mContext = context; mBuiltinContext = builtinContext; // CarEvsService should become ineffective if the EVS service is not available. We confirm // this by checking whether IEvsEnumerator/default instance is declared in VINTF. This check // could be skipped only for testing purposes. String instanceName = EVS_INTERFACE_NAME + "/" + EVS_DEFAULT_INSTANCE_NAME; mIsEvsAvailable = !checkDependencies || ServiceManager.isDeclared(instanceName); if (!mIsEvsAvailable) { Slogf.e(TAG_EVS, "%s does not exist. CarEvsService won't be available.", instanceName); // Set all final variables ineffective. mPropertyService = null; mEvsHalService = null; mServiceInstances = new SparseArray<>(); mDisplayManager = null; return; } mPropertyService = propertyService; mEvsHalService = halService; // Reads the service configuration and initializes service instances. String[] rawConfigurationStrings = mContext.getResources() .getStringArray(R.array.config_carEvsService); if (rawConfigurationStrings != null && rawConfigurationStrings.length > 0) { mServiceInstances = new SparseArray<>(rawConfigurationStrings.length); for (String rawString : rawConfigurationStrings) { CarEvsServiceUtils.Parameters params = CarEvsServiceUtils.parse(rawString); StateMachine s = new StateMachine(context, builtinContext, this, params.getActivityComponentName(), params.getType(), params.getCameraId()); mServiceInstances.put(params.getType(), s); } if (mServiceInstances.size() < 1) { Slogf.e(TAG_EVS, "No valid configuration has been found. " + "CarEvsService won't be available."); mDisplayManager = null; return; } } else { mServiceInstances = new SparseArray<>(/* capacity= */ 1); Slogf.i(TAG_EVS, "CarEvsService will be initialized only for the rearview service " + "because no service configuration was available via " + "config_carEvsService."); String activityName = mContext.getResources() .getString(R.string.config_evsCameraActivity); ComponentName activityComponentName; if (!activityName.isEmpty()) { activityComponentName = ComponentName.unflattenFromString(activityName); } else { activityComponentName = null; } if (DBG) Slogf.d(TAG_EVS, "evsCameraActivity=" + activityName); String cameraId = context.getString(R.string.config_evsRearviewCameraId); StateMachine s = new StateMachine(context, builtinContext, this, activityComponentName, CarEvsManager.SERVICE_TYPE_REARVIEW, cameraId); mServiceInstances.put(CarEvsManager.SERVICE_TYPE_REARVIEW, s); } mDisplayManager = context.getSystemService(DisplayManager.class); mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); } @VisibleForTesting final class EvsTriggerListener implements EvsHalService.EvsHalEventListener { /** Implements EvsHalService.EvsHalEventListener to monitor VHAL properties. */ @Override public void onEvent(@CarEvsServiceType int type, boolean on) { if (DBG) { Slogf.d(TAG_EVS, "Received an event from EVS HAL: type = " + type + ", on = " + on); } StateMachine instance = mServiceInstances.get(type); if (instance == null) { Slogf.w(TAG_EVS, "CarEvsService is not configured for %s", type); return; } // Stores the last event. synchronized (mLock) { mLastEvsHalEvent = new EvsHalEvent(SystemClock.elapsedRealtimeNanos(), type, on); } if (on) { // Request a camera activity. if (instance.requestStartActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) { Slogf.e(TAG_EVS, "Fail to request a registered activity."); } } else { // Stop a video stream and close an activity. if (instance.requestStopActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) { Slogf.e(TAG_EVS, "Fail to stop a registered activity."); } } } } @VisibleForTesting final EvsTriggerListener mEvsTriggerListener = new EvsTriggerListener(); @Override public void init() { if (DBG) { Slogf.d(TAG_EVS, "Initializing the service"); } if (!mIsEvsAvailable) { Slogf.e(TAG_EVS, "CarEvsService cannot be initialized due to missing dependencies."); return; } for (int i = mServiceInstances.size() - 1; i >= 0; i--) { StateMachine instance = mServiceInstances.valueAt(i); if (instance.init()) { continue; } Slogf.e(TAG_EVS, "Failed to initialize a service handle for %s.", mServiceInstances.keyAt(i)); mServiceInstances.removeAt(i); } if (mEvsHalService.isEvsServiceRequestSupported()) { try { mEvsHalService.setListener(mEvsTriggerListener); if (DBG) { Slogf.d(TAG_EVS, "CarEvsService listens to EVS_SERVICE_REQUEST property."); } mUseGearSelection = false; } catch (IllegalStateException e) { Slogf.w(TAG_EVS, "Failed to set a EvsHalService listener. Try to use " + "GEAR_SELECTION."); } } if (mUseGearSelection) { if (mPropertyService == null || mPropertyService.getPropertySafe( VehiclePropertyIds.GEAR_SELECTION, /*areaId=*/ 0) == null) { Slogf.w(TAG_EVS, "GEAR_SELECTION property is also not available. " + "CarEvsService may not respond to the system events."); mUseGearSelection = false; } else { if (DBG) { Slogf.d(TAG_EVS, "CarEvsService listens to GEAR_SELECTION property."); } if (!mPropertyService.registerListenerSafe( VehiclePropertyIds.GEAR_SELECTION, /*updateRateHz=*/0, mGearSelectionPropertyListener)) { Slogf.w(TAG_EVS, "Failed to register a listener for GEAR_SELECTION property."); mUseGearSelection = false; } } } StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW); if (instance == null) { Slogf.w(TAG_EVS, "The service is not initialized for the rearview service."); return; } instance.connectToHalServiceIfNecessary(); } @Override public void release() { if (DBG) { Slogf.d(TAG_EVS, "Finalizing the service"); } mDisplayManager.unregisterDisplayListener(mDisplayListener); if (mUseGearSelection && mPropertyService != null) { if (DBG) { Slogf.d(TAG_EVS, "Unregister a property listener in release()"); } mPropertyService.unregisterListenerSafe(VehiclePropertyIds.GEAR_SELECTION, mGearSelectionPropertyListener); } for (int i = 0; i < mServiceInstances.size(); i++) { StateMachine instance = mServiceInstances.valueAt(i); instance.release(); } mStatusListeners.kill(); } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dump(IndentingPrintWriter writer) { writer.println("*CarEvsService*"); writer.increaseIndent(); for (int i = 0; i < mServiceInstances.size(); i++) { mServiceInstances.valueAt(i).dump(writer); } writer.decreaseIndent(); writer.printf("\n"); synchronized (mLock) { writer.printf("%d service listeners subscribed.\n", mStatusListeners.getRegisteredCallbackCount()); writer.printf("Last HAL event: %s\n", mLastEvsHalEvent); } } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dumpProto(ProtoOutputStream proto) {} /** * Registers a {@link ICarEvsStatusListener} to listen requests to control the camera * previewing activity. * *

Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to * access. * * @param listener {@link ICarEvsStatusListener} listener to register. */ @Override public void registerStatusListener(@NonNull ICarEvsStatusListener listener) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS); Objects.requireNonNull(listener); if (DBG) { Slogf.d(TAG_EVS, "Registering a new service listener"); } mStatusListeners.register(listener); } /** * Unregister the given {@link ICarEvsStatusListener} listener from receiving events. * *

Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to * access. * * @param listener {@link ICarEvsStatusListener} listener to unregister. */ @Override public void unregisterStatusListener(@NonNull ICarEvsStatusListener listener) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS); Objects.requireNonNull(listener); mStatusListeners.unregister(listener); } /** * Requests the system to start an activity to show the preview from a given EVS service type. * *

Requires {@link android.car.Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY} permissions to * access. * * @param type {@link android.car.evs.CarEvsManager#CarEvsServiceType} * @return {@link android.car.evs.CarEvsManager#CarEvsError} */ @Override public @CarEvsError int startActivity(int type) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY); if (type == CarEvsManager.SERVICE_TYPE_SURROUNDVIEW) { // TODO(b/179029031): Removes below when Surround View service is integrated. Slogf.e(TAG_EVS, "Surround view is not supported yet."); return ERROR_UNAVAILABLE; } StateMachine instance = mServiceInstances.get(type); if (instance == null) { return ERROR_UNAVAILABLE; } return instance.requestStartActivity(REQUEST_PRIORITY_NORMAL); } /** * Requests to stop a current previewing activity launched via {@link #startActivity}. * *

Requires {@link android.car.Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY} permissions to * access. */ @Override public void stopActivity() { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY); StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW); if (instance == null) { return; } instance.requestStopActivity(REQUEST_PRIORITY_NORMAL); } /** * Starts a video stream. * *

Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access. * * @param type {@link android.car.evs.CarEvsManager#CarEvsServiceType} * @param token IBinder object as a session token. If this is not null, CarEvsService handles a * coming client as a privileged client. * @param callback {@link ICarEvsStreamCallback} listener to register. * @return {@link android.car.evs.CarEvsManager.CarEvsError} */ @Override public @CarEvsError int startVideoStream(@CarEvsServiceType int type, @Nullable IBinder token, @NonNull ICarEvsStreamCallback callback) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA); Objects.requireNonNull(callback); StateMachine instance = mServiceInstances.get(type); if (instance == null) { Slogf.e(TAG_EVS, "CarEvsService is not configured for a service type %d.", type); return ERROR_UNAVAILABLE; } // Single client can subscribe to multiple services. // ArrayMap> // Remembers which service a given callback is subscribing to. ArraySet types = mCallbackToServiceType.get(callback.asBinder()); if (types == null) { mCallbackToServiceType.put(callback.asBinder(), new ArraySet<>(Set.of(type))); } else { types.add(type); } return instance.requestStartVideoStream(callback, token); } /** * Requests to stop a video stream from the current client. * *

Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access. * * @param callback {@link ICarEvsStreamCallback} listener to unregister. */ @Override public void stopVideoStream(@NonNull ICarEvsStreamCallback callback) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA); Objects.requireNonNull(callback); ArraySet types = mCallbackToServiceType.get(callback.asBinder()); if (types == null || types.isEmpty()) { Slogf.i(TAG_EVS, "Ignores a request to stop a video stream for unknown callback %s.", callback); return; } for (int i = 0; i < types.size(); i++) { int type = types.valueAt(i); StateMachine instance = mServiceInstances.get(type); if (instance == null) { Slogf.w(TAG_EVS, "CarEvsService is not configured for a service type %d.", type); continue; } instance.requestStopVideoStream(callback); } mCallbackToServiceType.remove(callback.asBinder()); } /** * Requests to stop a video stream from a given service type. * *

Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access. * * @param type {@link CarEvsServiceType} to stop listening. * @param callback {@link ICarEvsStreamCallback} listener to unregister. */ @Override public void stopVideoStreamFrom(@CarEvsServiceType int type, @NonNull ICarEvsStreamCallback callback) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA); Objects.requireNonNull(callback); ArraySet types = mCallbackToServiceType.get(callback.asBinder()); int idx = types.indexOf(type); if (idx < 0) { Slogf.i(TAG_EVS, "Ignores a request to stop a video stream that is not active."); return; } StateMachine instance = mServiceInstances.get(type); if (instance == null) { Slogf.w(TAG_EVS, "CarEvsService is not configured for a service type %d.", type); return; } instance.requestStopVideoStream(callback); types.remove(type); if (types.isEmpty()) { // No more active stream for this callback object. mCallbackToServiceType.remove(callback.asBinder()); } } /** * Returns an used buffer to EVS service. * *

Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access. * * @param buffer A consumed CarEvsBufferDescriptor object. This would not be used and returned * to the native EVS service. * @throws IllegalArgumentException if a passed buffer has an unregistered identifier. */ @Override public void returnFrameBuffer(@NonNull CarEvsBufferDescriptor buffer) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA); Objects.requireNonNull(buffer); // 8 MSB tells the service type of this buffer. mServiceInstances.get(buffer.getType()).doneWithFrame(buffer.getId()); } /** * Returns a current status of CarEvsService's REARVIEW service type. * *

Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to * access. * * @return {@link android.car.evs.CarEvsStatus} */ @Override @Nullable public CarEvsStatus getCurrentStatus(@CarEvsServiceType int type) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS); // This public API only returns current status of SERVICE_TYPE_REARVIEW. To get other // services' status, please register a status listener via // CarEvsService.registerStatusListener() API. StateMachine instance = mServiceInstances.get(type); if (instance == null) { return null; } return instance.getCurrentStatus(); } /** * Returns a session token to be used to request the services. * *

Requires {@link android.car.Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY} permission to access. * * @return IBinder object as a session token. * @throws IllegalStateException if we fail to find System UI package. */ @Override public IBinder generateSessionToken() { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY); // TODO(b/191940626): With the unlimited multi-client supports, a validity of a session // token does not make any different in handling streaming clients. This // needs to be modified with a logic to manage the maximum number of // streaming clients per service. String systemUiPackageName = PackageManagerHelper.getSystemUiPackageName(mContext); IBinder token = new Binder(); try { int systemUiUid = PackageManagerHelper.getPackageUidAsUser(mContext.getPackageManager(), systemUiPackageName, UserHandle.SYSTEM.getIdentifier()); int callerUid = Binder.getCallingUid(); if (systemUiUid == callerUid) { mSessionTokens.add(token); } else { throw new SecurityException("SystemUI only can generate SessionToken"); } } catch (NameNotFoundException e) { throw new IllegalStateException(systemUiPackageName + " package not found", e); } finally { return token; } } /** * Returns whether or not a given service type is supported. * *

Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to * access. */ @Override public boolean isSupported(@CarEvsServiceType int type) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS); StateMachine instance = mServiceInstances.get(type); if (instance == null) { return false; } return instance.isConnected(); } /** * Sets a camera device for the rearview. * *

Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access. * * @param id A string identifier of a target camera device. * @return This method return a false if this runs in a release build; otherwise, this returns * true. */ public boolean setRearviewCameraIdFromCommand(@NonNull String id) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA); Objects.requireNonNull(id); if (!mIsEvsAvailable) { Slogf.e(TAG_EVS, "CarEvsService is not available."); return false; } if (!BuildHelper.isDebuggableBuild()) { // This method is not allowed in the release build. Slogf.e(TAG_EVS, "It is not allowed to change a camera assigned to the rearview " + "in the release build."); return false; } mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW).setCameraId(id); return true; } /** * Sets a camera device for a given service type. * *

Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access. * * @param type A service type to assign a camera associated with a given string identifier. * Please use '*' part of CarEvsManager.SERVICE_TYPE_* constants. * @param id A string identifier of a target camera device. * @return This method return true if it successfully programs a camera id for a given service * type. Otherwise, this will return false. */ public boolean setCameraIdFromCommand(@NonNull String type, @NonNull String id) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA); if (!BuildHelper.isDebuggableBuild()) { // This method is not allowed in the release build. Slogf.e(TAG_EVS, "It is not allowed to change a camera id assigned to the service " + "in the release build."); return false; } @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type); StateMachine instance = mServiceInstances.get(serviceType); if (instance == null) { Slogf.e(TAG_EVS, "Ignores a request to set a camera %s for unavailable service %s.", id, type); return false; } instance.setCameraId(id); return true; } /** * Gets an identifier of a current camera device for the rearview. * *

Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to * access. * * @return A string identifier of current rearview camera device. */ @Nullable public String getRearviewCameraIdFromCommand() { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS); StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW); if (instance == null) { Slogf.e(TAG_EVS, "Ignores a request to get a camera id for unavailable " + "REARVIEW service."); return null; } return instance.getCameraId(); } /** * Gets a String identifier of a camera assigned to a given service type. * *

Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to * access. * * @param type A service type to get a camera identifier. Please use "*" part of * CarEvsManager.SERVICE_TYPE_* constants. * @return A string identifier of a camera assigned to a given service type. */ @Nullable public String getCameraIdFromCommand(@NonNull String type) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS); @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type); StateMachine instance = mServiceInstances.get(serviceType); if (instance == null) { Slogf.e(TAG_EVS, "Ignores a request to get a camera id for unavailable service %s.", type); return null; } return instance.getCameraId(); } /** * Enables a given service type with a specified camera device. * *

Requires {@link android.car.Car.PERMISSION_USE_CAR_EVS_CAMERA} permissions to access. * * @param typeString A service type to get a camera identifier. Please use "*" part of * CarEvsManager.SERVICE_TYPE_* constants. * @param cameraId A string identifier of a target camera device. A camera associated with this * id must not be assigned to any service type. * @return false if a requested service type is already enabled or a specific camera id is * already assigned to other service types. * true otherwise. */ public boolean enableServiceTypeFromCommand(@NonNull String typeString, @NonNull String cameraId) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA); if (!mIsEvsAvailable) { Slogf.e(TAG_EVS, "Failed to enable %s service due to missing dependencies.", typeString); return false; } @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(typeString); for (int i = 0; i < mServiceInstances.size(); i++) { int type = mServiceInstances.keyAt(i); StateMachine instance = mServiceInstances.valueAt(i); if (type == serviceType || cameraId.equals(instance.getCameraId())) { Slogf.e(TAG_EVS, "A requested service type is already provided by " + " or a given camera id is used by %s.", instance); return false; } } StateMachine s = new StateMachine(mContext, mBuiltinContext, this, null, serviceType, cameraId); if (!s.init()) { Slogf.e(TAG_EVS, "Failed to initialize a requested service type."); return false; } s.connectToHalServiceIfNecessary(); mServiceInstances.put(serviceType, s); return true; } /** * Checks whether or not a given service type is enabled. * *

Requires {@link android.car.Car.PERMISSION_MONITOR_CAR_EVS_STATUS} permissions to * access. * * @param type A service type to get a camera identifier. Please use "*" part of * CarEvsManager.SERVICE_TYPE_* constants. * @return true if a given service type is available. * false otherwise. */ public boolean isServiceTypeEnabledFromCommand(@NonNull String type) { CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS); @CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type); return mServiceInstances.get(serviceType) != null; } /** Checks whether or not a given token is valid. */ boolean isSessionToken(IBinder token) { return mSessionTokens.contains(token); } /** Invalidate a given token. */ void invalidateSessionToken(IBinder token) { mSessionTokens.remove(token); } /** Package-private version of generateSessionToken() method. */ @NonNull IBinder generateSessionTokenInternal() { IBinder token = new Binder(); mSessionTokens.add(token); return token; } /** * Manually sets a stream callback. */ @VisibleForTesting void addStreamCallback(@CarEvsServiceType int type, @Nullable ICarEvsStreamCallback callback) { StateMachine instance = mServiceInstances.get(type); if (instance == null || callback == null) { return; } instance.addStreamCallback(callback); ArraySet types = mCallbackToServiceType.get(callback.asBinder()); if (types == null) { mCallbackToServiceType.put(callback.asBinder(), new ArraySet<>(Set.of(type))); } else { types.add(type); } } /** Tells whether or not the latest EVS HAL event was requesting to start an activity. */ boolean needToStartActivity() { synchronized (mLock) { return mLastEvsHalEvent != null && mLastEvsHalEvent.isRequestingToStartActivity(); } } /** * Manually sets a current service state. */ @VisibleForTesting void setServiceState(@CarEvsServiceType int type, @CarEvsServiceState int newState) { StateMachine instance = mServiceInstances.get(type); if (instance == null) { return; } instance.setState(newState); } /** * Manually chooses to use a gear selection property or not. */ @VisibleForTesting void setToUseGearSelection(boolean useGearSelection) { mUseGearSelection = useGearSelection; } /** * Manually sets the last EVS HAL event. */ @VisibleForTesting void setLastEvsHalEvent(long timestamp, @CarEvsServiceType int type, boolean on) { synchronized (mLock) { mLastEvsHalEvent = new EvsHalEvent(timestamp, type, on); } } /** Notifies the service status gets changed */ void broadcastStateTransition(int type, int state) { int idx = mStatusListeners.beginBroadcast(); while (idx-- > 0) { ICarEvsStatusListener listener = mStatusListeners.getBroadcastItem(idx); try { listener.onStatusChanged(new CarEvsStatus(type, state)); } catch (RemoteException e) { // Likely the binder death incident. Slogf.e(TAG_EVS, Log.getStackTraceString(e)); } } mStatusListeners.finishBroadcast(); } private void handlePropertyEvent(CarPropertyEvent event) { if (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) { // CarEvsService is interested only in the property change event. return; } CarPropertyValue value = event.getCarPropertyValue(); if (value.getPropertyId() != VehiclePropertyIds.GEAR_SELECTION) { // CarEvsService is interested only in the GEAR_SELECTION property. return; } long timestamp = value.getTimestamp(); boolean isReverseGear; synchronized (mLock) { if (timestamp != 0 && timestamp <= mLastEvsHalEvent.getTimestamp()) { if (DBG) { Slogf.d(TAG_EVS, "Ignoring GEAR_SELECTION change happened past, timestamp = " + timestamp + ", last event was at " + mLastEvsHalEvent.getTimestamp()); } return; } isReverseGear = (Integer) value.getValue() == VehicleGear.GEAR_REVERSE; mLastEvsHalEvent = new EvsHalEvent(timestamp, CarEvsManager.SERVICE_TYPE_REARVIEW, isReverseGear); } StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW); if (instance == null) { Slogf.i(TAG_EVS, "Ignore a GEAR_SELECTION event because the rearview service is not available."); return; } if (isReverseGear) { // Request to start the rearview activity when the gear is shifted into the reverse // position. if (instance.requestStartActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) { Slogf.w(TAG_EVS, "Failed to request the rearview activity."); } } else { // Request to stop the rearview activity when the gear is shifted from the reverse // position to other positions. if (instance.requestStopActivity(REQUEST_PRIORITY_HIGH) != ERROR_NONE) { Slogf.i(TAG_EVS, "Failed to stop the rearview activity."); } } } /** Handles a disconnection of a status monitoring client. */ private void handleClientDisconnected(ICarEvsStatusListener listener) { mStatusListeners.unregister(listener); if (mStatusListeners.getRegisteredCallbackCount() == 0) { Slogf.d(TAG_EVS, "Last status listener has been disconnected."); } } }