blob: 7738619d4a822d57bde94f9efaaf35d15a689db8 [file] [log] [blame]
/*
* 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 android.car.evs;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarManagerBase;
import android.car.annotation.AddedInOrBefore;
import android.car.annotation.ApiRequirements;
import android.car.annotation.RequiredFeature;
import android.car.builtin.util.Slogf;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.internal.annotations.GuardedBy;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* Provides an application interface for interativing with the Extended View System service.
*
* @hide
*/
@RequiredFeature(Car.CAR_EVS_SERVICE)
@SystemApi
public final class CarEvsManager extends CarManagerBase {
@AddedInOrBefore(majorVersion = 33)
public static final String EXTRA_SESSION_TOKEN = "android.car.evs.extra.SESSION_TOKEN";
private static final String TAG = CarEvsManager.class.getSimpleName();
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private final ICarEvsService mService;
private final Object mStreamLock = new Object();
@GuardedBy("mStreamLock")
private CarEvsStreamCallback mStreamCallback;
@GuardedBy("mStreamLock")
private Executor mStreamCallbackExecutor;
private final CarEvsStreamListenerToService mStreamListenerToService =
new CarEvsStreamListenerToService(this);
private final Object mStatusLock = new Object();
@GuardedBy("mStatusLock")
private CarEvsStatusListener mStatusListener;
@GuardedBy("mStatusLock")
private Executor mStatusListenerExecutor;
private final CarEvsStatusListenerToService mStatusListenerToService =
new CarEvsStatusListenerToService(this);
/**
* Service type to represent the rearview camera service.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int SERVICE_TYPE_REARVIEW = 0;
/**
* Service type to represent the surround view service.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int SERVICE_TYPE_SURROUNDVIEW = 1;
/**
* Service type to represent the front exterior view camera service.
*/
@ApiRequirements(minCarVersion =
ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
minPlatformVersion =
ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int SERVICE_TYPE_FRONTVIEW = 2;
/**
* Service type to represent the left exterior view camera service such as
* the virtual side mirror.
*/
@ApiRequirements(minCarVersion =
ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
minPlatformVersion =
ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int SERVICE_TYPE_LEFTVIEW = 3;
/**
* Service type to represent the right exterior view camera service such as
* the virtual side mirror.
*/
@ApiRequirements(minCarVersion =
ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
minPlatformVersion =
ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int SERVICE_TYPE_RIGHTVIEW = 4;
/**
* Service type to represent the camera service that captures the scene
* with the driver.
*/
@ApiRequirements(minCarVersion =
ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
minPlatformVersion =
ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int SERVICE_TYPE_DRIVERVIEW = 5;
/**
* Service type to represent the camera service that captures the scene
* with the front-seat passengers.
*/
@ApiRequirements(minCarVersion =
ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
minPlatformVersion =
ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int SERVICE_TYPE_FRONT_PASSENGERSVIEW = 6;
/**
* Service type to represent the camera service that captures the scene
* with the rear-seat passengers.
*/
@ApiRequirements(minCarVersion =
ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
minPlatformVersion =
ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int SERVICE_TYPE_REAR_PASSENGERSVIEW = 7;
/**
* Service type to represent the camera service that captures the scene
* the user defines.
*/
@ApiRequirements(minCarVersion =
ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0,
minPlatformVersion =
ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0)
public static final int SERVICE_TYPE_USER_DEFINED = 1000;
/** @hide */
@IntDef (prefix = {"SERVICE_TYPE_"}, value = {
SERVICE_TYPE_REARVIEW,
SERVICE_TYPE_SURROUNDVIEW,
SERVICE_TYPE_FRONTVIEW,
SERVICE_TYPE_LEFTVIEW,
SERVICE_TYPE_RIGHTVIEW,
SERVICE_TYPE_DRIVERVIEW,
SERVICE_TYPE_FRONT_PASSENGERSVIEW,
SERVICE_TYPE_REAR_PASSENGERSVIEW,
SERVICE_TYPE_USER_DEFINED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CarEvsServiceType {}
/**
* State that a corresponding service type is not available.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int SERVICE_STATE_UNAVAILABLE = 0;
/**
* State that a corresponding service type is inactive; it's available but not used
* by any clients.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int SERVICE_STATE_INACTIVE = 1;
/**
* State that CarEvsManager received a service request from the client.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int SERVICE_STATE_REQUESTED = 2;
/**
* State that a corresponding service type is actively being used.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int SERVICE_STATE_ACTIVE = 3;
/** @hide */
@IntDef (prefix = {"SERVICE_STATE_"}, value = {
SERVICE_STATE_UNAVAILABLE,
SERVICE_STATE_INACTIVE,
SERVICE_STATE_REQUESTED,
SERVICE_STATE_ACTIVE
})
@Retention(RetentionPolicy.SOURCE)
public @interface CarEvsServiceState {}
/**
* This is a default EVS stream event type.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_NONE = 0;
/**
* EVS stream event to notify a video stream has been started.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_STREAM_STARTED = 1;
/**
* EVS stream event to notify a video stream has been stopped.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_STREAM_STOPPED = 2;
/**
* EVS stream event to notify that a video stream is dropped.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_FRAME_DROPPED = 3;
/**
* EVS stream event occurs when a timer for a new frame's arrival is expired.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_TIMEOUT = 4;
/**
* EVS stream event occurs when a camera parameter is changed.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_PARAMETER_CHANGED = 5;
/**
* EVS stream event to notify the primary owner has been changed.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_PRIMARY_OWNER_CHANGED = 6;
/**
* Other EVS stream errors
*/
@AddedInOrBefore(majorVersion = 33)
public static final int STREAM_EVENT_OTHER_ERRORS = 7;
/** @hide */
@IntDef(prefix = {"STREAM_EVENT_"}, value = {
STREAM_EVENT_NONE,
STREAM_EVENT_STREAM_STARTED,
STREAM_EVENT_STREAM_STOPPED,
STREAM_EVENT_FRAME_DROPPED,
STREAM_EVENT_TIMEOUT,
STREAM_EVENT_PARAMETER_CHANGED,
STREAM_EVENT_PRIMARY_OWNER_CHANGED,
STREAM_EVENT_OTHER_ERRORS
})
@Retention(RetentionPolicy.SOURCE)
public @interface CarEvsStreamEvent {}
/**
* Status to tell that a request is successfully processed.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int ERROR_NONE = 0;
/**
* Status to tell a requested service is not available.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int ERROR_UNAVAILABLE = -1;
/**
* Status to tell CarEvsService is busy to serve the privileged client.
*/
@AddedInOrBefore(majorVersion = 33)
public static final int ERROR_BUSY = -2;
/** @hide */
@IntDef(prefix = {"ERROR_"}, value = {
ERROR_NONE,
ERROR_UNAVAILABLE,
ERROR_BUSY
})
@Retention(RetentionPolicy.SOURCE)
public @interface CarEvsError {}
/**
* Gets an instance of CarEvsManager
*
* CarEvsManager manages {@link com.android.car.evs.CarEvsService} and provides APIs that the
* clients can use the Extended View System service.
*
* This must not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
*
* @hide
*/
public CarEvsManager(Car car, IBinder service) {
super(car);
// Gets CarEvsService
mService = ICarEvsService.Stub.asInterface(service);
}
/** @hide */
@Override
@AddedInOrBefore(majorVersion = 33)
public void onCarDisconnected() {
synchronized (mStatusLock) {
mStatusListener = null;
mStatusListenerExecutor = null;
}
synchronized (mStreamLock) {
mStreamCallback = null;
mStreamCallbackExecutor = null;
}
}
/**
* Application registers {@link #CarEvsStatusListener} object to receive requests to control
* the activity and monitor the status of the EVS service.
*/
public interface CarEvsStatusListener {
/**
* Called when the status of EVS service is changed.
*
* @param type A type of EVS service; e.g. the rearview.
* @param state Updated service state; e.g. the service is started.
*/
@AddedInOrBefore(majorVersion = 33)
void onStatusChanged(@NonNull CarEvsStatus status);
}
/**
* Class implementing the listener interface {@link com.android.car.ICarEvsStatusListener}
* to listen status updates across the binder interface.
*/
private static class CarEvsStatusListenerToService extends ICarEvsStatusListener.Stub {
private final WeakReference<CarEvsManager> mManager;
CarEvsStatusListenerToService(CarEvsManager manager) {
mManager = new WeakReference<>(manager);
}
@Override
public void onStatusChanged(@NonNull CarEvsStatus status) {
Objects.requireNonNull(status);
CarEvsManager mgr = mManager.get();
if (mgr != null) {
mgr.handleServiceStatusChanged(status);
}
}
}
/**
* Gets the {@link #CarEvsStatus} from the service listener {@link
* #CarEvsStatusListenerToService} and forwards it to the client.
*
* @param status {@link android.car.evs.CarEvsStatus}
*/
private void handleServiceStatusChanged(CarEvsStatus status) {
if (DBG) {
Slogf.d(TAG, "Service state changed: service = " + status.getServiceType()
+ ", state = " + status.getState());
}
final CarEvsStatusListener listener;
final Executor executor;
synchronized (mStatusLock) {
listener = mStatusListener;
executor = mStatusListenerExecutor;
}
if (listener != null) {
executor.execute(() -> listener.onStatusChanged(status));
} else if (DBG) {
Slogf.w(TAG, "No client seems active; a received event is ignored.");
}
}
/**
* Sets {@link #CarEvsStatusListener} object to receive requests to control the activity
* view and EVS data.
*
* @param executor {@link java.util.concurrent.Executor} to execute callbacks.
* @param listener {@link #CarEvsStatusListener} to register.
* @throws IllegalStateException if this method is called while a registered status listener
* exists.
*/
@RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
@AddedInOrBefore(majorVersion = 33)
public void setStatusListener(@NonNull @CallbackExecutor Executor executor,
@NonNull CarEvsStatusListener listener) {
if (DBG) {
Slogf.d(TAG, "Registering a service monitoring listener.");
}
Objects.requireNonNull(listener);
Objects.requireNonNull(executor);
synchronized (mStatusLock) {
if (mStatusListener != null) {
throw new IllegalStateException("A status listener is already registered.");
}
mStatusListener = listener;
mStatusListenerExecutor = executor;
}
try {
mService.registerStatusListener(mStatusListenerToService);
} catch (RemoteException err) {
handleRemoteExceptionFromCarService(err);
}
}
/**
* Stops getting callbacks to control the camera viewing activity by clearing
* {@link #CarEvsStatusListener} object.
*/
@RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
@AddedInOrBefore(majorVersion = 33)
public void clearStatusListener() {
if (DBG) {
Slogf.d(TAG, "Unregistering a service monitoring callback.");
}
synchronized (mStatusLock) {
mStatusListener = null;
}
try{
mService.unregisterStatusListener(mStatusListenerToService);
} catch (RemoteException err) {
handleRemoteExceptionFromCarService(err);
}
}
/**
* Application registers {@link #CarEvsStreamCallback} object to listen to EVS services' status
* changes.
*
* CarEvsManager supports two client types; one is a System UI type client and another is a
* normal Android activity type client. The former client type has a priority over
* the latter type client and CarEvsManager allows only a single client of each type to
* subscribe.
*/
// TODO(b/174572385): Removes below lint suppression
@SuppressLint("CallbackInterface")
public interface CarEvsStreamCallback {
/**
* Called when any EVS stream events occur.
*
* @param event {@link #CarEvsStreamEvent}; e.g. a stream started
*/
@AddedInOrBefore(majorVersion = 33)
@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
default void onStreamEvent(@CarEvsStreamEvent int event) {}
/**
* Called when new frame arrives.
*
* @param buffer {@link android.car.evs.CarEvsBufferDescriptor} contains a EVS frame
*/
@AddedInOrBefore(majorVersion = 33)
@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
default void onNewFrame(@NonNull CarEvsBufferDescriptor buffer) {}
}
/**
* Class implementing the listener interface and gets callbacks from the
* {@link com.android.car.ICarEvsStreamCallback} across the binder interface.
*/
private static class CarEvsStreamListenerToService extends ICarEvsStreamCallback.Stub {
private final WeakReference<CarEvsManager> mManager;
CarEvsStreamListenerToService(CarEvsManager manager) {
mManager = new WeakReference<>(manager);
}
@Override
public void onStreamEvent(@CarEvsStreamEvent int event) {
CarEvsManager manager = mManager.get();
if (manager != null) {
manager.handleStreamEvent(event);
}
}
@Override
public void onNewFrame(CarEvsBufferDescriptor buffer) {
CarEvsManager manager = mManager.get();
if (manager != null) {
manager.handleNewFrame(buffer);
}
}
}
/**
* Gets the {@link #CarEvsStreamEvent} from the service listener
* {@link #CarEvsStreamListenerToService} and dispatches it to an executor provided
* to the manager.
*
* @param event {@link #CarEvsStreamEvent} from the service this manager subscribes to.
*/
private void handleStreamEvent(@CarEvsStreamEvent int event) {
if (DBG) {
Slogf.d(TAG, "Received: " + event);
}
final CarEvsStreamCallback callback;
final Executor executor;
synchronized (mStreamLock) {
callback = mStreamCallback;
executor = mStreamCallbackExecutor;
}
if (callback != null) {
executor.execute(() -> callback.onStreamEvent(event));
} else if (DBG) {
Slogf.w(TAG, "No client seems active; a current stream event is ignored.");
}
}
/**
* Gets the {@link android.car.evs.CarEvsBufferDescriptor} from the service listener
* {@link #CarEvsStreamListenerToService} and dispatches it to an executor provided
* to the manager.
*
* @param buffer {@link android.car.evs.CarEvsBufferDescriptor}
*/
private void handleNewFrame(@NonNull CarEvsBufferDescriptor buffer) {
Objects.requireNonNull(buffer);
if (DBG) {
Slogf.d(TAG, "Received a buffer: " + buffer);
}
final CarEvsStreamCallback callback;
final Executor executor;
synchronized (mStreamLock) {
callback = mStreamCallback;
executor = mStreamCallbackExecutor;
}
if (callback != null) {
executor.execute(() -> callback.onNewFrame(buffer));
} else {
if (DBG) {
Slogf.w(TAG, "A buffer is being returned back to the service because no active "
+ "clients exist.");
}
returnFrameBuffer(buffer);
}
}
/**
* Returns a consumed {@link android.car.evs.CarEvsBufferDescriptor}.
*
* @param buffer {@link android.car.evs.CarEvsBufferDescriptor} to be returned to
* the EVS service.
*/
@RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
@AddedInOrBefore(majorVersion = 33)
public void returnFrameBuffer(@NonNull CarEvsBufferDescriptor buffer) {
Objects.requireNonNull(buffer);
try {
mService.returnFrameBuffer(buffer);
} catch (RemoteException err) {
handleRemoteExceptionFromCarService(err);
} finally {
// We are done with this HardwareBuffer object.
buffer.getHardwareBuffer().close();
}
}
/**
* Requests the system to start an activity for {@link #CarEvsServiceType}.
*
* @param type A type of EVS service to start.
* @return {@link #CarEvsError} to tell the result of the request.
* {@link #ERROR_UNAVAILABLE} will be returned if the CarEvsService is not connected to
* the native EVS service or the binder transaction fails.
* {@link #ERROR_BUSY} will be returned if the CarEvsService is in the
* {@link #SERVICE_STATE_REQUESTED} for a different service type.
* If the same service type is running, this will return {@link #ERROR_NONE}.
* {@link #ERROR_NONE} will be returned for all other cases.
*/
@RequiresPermission(Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY)
@AddedInOrBefore(majorVersion = 33)
public @CarEvsError int startActivity(@CarEvsServiceType int type) {
try {
return mService.startActivity(type);
} catch (RemoteException err) {
handleRemoteExceptionFromCarService(err);
}
return ERROR_UNAVAILABLE;
}
/**
* Requests the system to stop a current activity launched via {@link #startActivity}.
*/
@RequiresPermission(Car.PERMISSION_REQUEST_CAR_EVS_ACTIVITY)
@AddedInOrBefore(majorVersion = 33)
public void stopActivity() {
try {
mService.stopActivity();
} catch (RemoteException err) {
handleRemoteExceptionFromCarService(err);
}
}
/**
* Requests to start a video stream from {@link #CarEvsServiceType}.
*
* @param type A type of EVS service.
* @param token A session token that is issued to privileged clients. SystemUI must obtain this
* token obtain this via {@link #generateSessionToken} and pass it to the activity, to
* prioritize its service requests.
* TODO(b/179517136): Defines an Intent extra
* @param callback {@link #CarEvsStreamCallback} to listen to the stream.
* @param executor {@link java.util.concurrent.Executor} to run a callback.
* @return {@link #CarEvsError} to tell the result of the request.
* {@link #ERROR_UNAVAILABLE} will be returned if the CarEvsService is not connected to
* the native EVS service or the binder transaction fails.
* {@link #ERROR_BUSY} will be returned if the CarEvsService is handling a service
* request with a valid session token.
* {@link #ERROR_NONE} for all other cases.
*/
@RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
@AddedInOrBefore(majorVersion = 33)
public @CarEvsError int startVideoStream(
@CarEvsServiceType int type,
@Nullable IBinder token,
@NonNull @CallbackExecutor Executor executor,
@NonNull CarEvsStreamCallback callback) {
if (DBG) {
Slogf.d(TAG, "Received a request to start a video stream: " + type);
}
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
synchronized (mStreamLock) {
mStreamCallback = callback;
mStreamCallbackExecutor = executor;
}
int status = ERROR_UNAVAILABLE;
try {
// Requests the service to start a video stream
status = mService.startVideoStream(type, token, mStreamListenerToService);
} catch (RemoteException err) {
handleRemoteExceptionFromCarService(err);
} finally {
return status;
}
}
/**
* Requests to stop a current {@link #CarEvsServiceType}.
*/
@RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
@AddedInOrBefore(majorVersion = 33)
public void stopVideoStream() {
synchronized (mStreamLock) {
if (mStreamCallback == null) {
Slogf.e(TAG, "The service has not started yet.");
return;
}
// We're not interested in frames and events anymore. The client can safely assume
// the service is stopped properly.
mStreamCallback = null;
mStreamCallbackExecutor = null;
}
try {
mService.stopVideoStream(mStreamListenerToService);
} catch (RemoteException err) {
handleRemoteExceptionFromCarService(err);
}
}
/**
* Queries the current status of CarEvsService
*
* @return {@link android.car.evs.CarEvsStatus} that describes current status of
* CarEvsService.
*/
@RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
@NonNull
@AddedInOrBefore(majorVersion = 33)
public CarEvsStatus getCurrentStatus() {
try {
return mService.getCurrentStatus();
} catch (RemoteException err) {
Slogf.e(TAG, "Failed to read a status of the service.");
return new CarEvsStatus(SERVICE_TYPE_REARVIEW, SERVICE_STATE_UNAVAILABLE);
}
}
/**
* Generates a service session token.
*
* @return {@link IBinder} object as a service session token.
*/
@RequiresPermission(Car.PERMISSION_CONTROL_CAR_EVS_ACTIVITY)
@NonNull
@AddedInOrBefore(majorVersion = 33)
public IBinder generateSessionToken() {
IBinder token = null;
try {
token = mService.generateSessionToken();
if (token == null) {
token = new Binder();
}
} catch (RemoteException err) {
Slogf.e(TAG, "Failed to generate a session token.");
token = new Binder();
} finally {
return token;
}
}
/**
* Returns whether or not a given service type is supported.
*
* @param type {@link CarEvsServiceType} to query
* @return true if a given service type is available on the system.
*/
@RequiresPermission(Car.PERMISSION_MONITOR_CAR_EVS_STATUS)
@AddedInOrBefore(majorVersion = 33)
public boolean isSupported(@CarEvsServiceType int type) {
try {
return mService.isSupported(type);
} catch (RemoteException err) {
Slogf.e(TAG, "Failed to query a service availability");
return false;
}
}
}