From 84d364df81c9ebf0b7f0830df5f104142c4d220e Mon Sep 17 00:00:00 2001 From: Cliff Wu Date: Thu, 11 Mar 2021 00:27:26 +0800 Subject: [PATCH] DynamicCamera: Add new hidden APIs in CameraManager and new permission for injection camera - Add new hidden APIs: injectCamera() and stopInjection() in CameraManager. - Add a new permission: android.permission.CAMERA_INJECT_EXTERNAL_CAMERA to allow injecting the external camera to switch the internal camera. - Add a new CameraInjectionSession class with stopInjection function, which is called back by CameraInjectionCallback.onInjectionSucceeded(), then the lifetime of CameraInjectionSession is what determines when injection is active. Bug: 181395010 Test: Verified camera manager use cases function as expected; no denials Change-Id: I0b218dc6a55c7827e2446502b7a22382c6fe9f3e --- .../camera2/CameraInjectionSession.java | 128 ++++++++++ .../hardware/camera2/CameraManager.java | 62 +++++ .../impl/CameraInjectionSessionImpl.java | 219 ++++++++++++++++++ core/res/AndroidManifest.xml | 5 + 4 files changed, 414 insertions(+) create mode 100644 core/java/android/hardware/camera2/CameraInjectionSession.java create mode 100644 core/java/android/hardware/camera2/impl/CameraInjectionSessionImpl.java diff --git a/core/java/android/hardware/camera2/CameraInjectionSession.java b/core/java/android/hardware/camera2/CameraInjectionSession.java new file mode 100644 index 0000000000000..bd5a4bcd77d7b --- /dev/null +++ b/core/java/android/hardware/camera2/CameraInjectionSession.java @@ -0,0 +1,128 @@ +/* + * 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.hardware.camera2; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + *

The CameraInjectionSession class is what determines when injection is active.

+ * + *

Your application must declare the + * {@link android.Manifest.permission#CAMERA_INJECT_EXTERNAL_CAMERA CAMERA} permission in its + * manifest in order to use camera injection function.

+ * + * @hide + * @see CameraManager#injectCamera + * @see android.Manifest.permission#CAMERA_INJECT_EXTERNAL_CAMERA + */ +public abstract class CameraInjectionSession implements AutoCloseable { + + /** + * Close the external camera and switch back to the internal camera. + * + *

Call the method when app streaming stops or the app exits, it switch back to the internal + * camera.

+ */ + @Override + public abstract void close(); + + /** + * A callback for external camera has a success or an error during injecting. + * + *

A callback instance must be provided to the {@link CameraManager#injectCamera} method to + * inject camera.

+ * + * @hide + * @see CameraManager#injectCamera + */ + public abstract static class InjectionStatusCallback { + + /** + * An error code that can be reported by {@link #onInjectionError} indicating that the + * camera injection session has encountered a fatal error. + * + * @see #onInjectionError + */ + public static final int ERROR_INJECTION_SESSION = 0; + + /** + * An error code that can be reported by {@link #onInjectionError} indicating that the + * camera service has encountered a fatal error. + * + *

The Android device may need to be shut down and restarted to restore + * camera function, or there may be a persistent hardware problem.

+ * + *

An attempt at recovery may be possible by closing the + * CameraDevice and the CameraManager, and trying to acquire all resources again from + * scratch.

+ * + * @see #onInjectionError + */ + public static final int ERROR_INJECTION_SERVICE = 1; + + /** + * An error code that can be reported by {@link #onInjectionError} indicating that the + * injection camera does not support certain camera functions. When this error occurs, the + * default processing is still in the inject state, and the app is notified to display an + * error message and a black screen. + * + * @see #onInjectionError + */ + public static final int ERROR_INJECTION_UNSUPPORTED = 2; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ERROR_"}, value = + {ERROR_INJECTION_SESSION, + ERROR_INJECTION_SERVICE, + ERROR_INJECTION_UNSUPPORTED}) + public @interface ErrorCode {}; + + /** + * The method will be called when an external camera has been injected and replaced + * internal camera's feed. + * + * @param injectionSession The camera injection session that has been injected. + */ + public abstract void onInjectionSucceeded( + @NonNull CameraInjectionSession injectionSession); + + /** + * The method will be called when an error occurs in the injected external camera. + * + * @param errorCode The error code. + * @see #ERROR_INJECTION_SESSION + * @see #ERROR_INJECTION_SERVICE + * @see #ERROR_INJECTION_UNSUPPORTED + */ + public abstract void onInjectionError(@NonNull int errorCode); + } + + /** + * To be inherited by android.hardware.camera2.* code only. + * + * @hide + */ + public CameraInjectionSession() { + } +} diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 651f02591b4d0..2c5ec25ee62da 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -28,6 +28,7 @@ import android.hardware.CameraStatus; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; import android.hardware.camera2.impl.CameraDeviceImpl; +import android.hardware.camera2.impl.CameraInjectionSessionImpl; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.SessionConfiguration; @@ -1118,6 +1119,67 @@ public final class CameraManager { return false; } + /** + * Inject the external camera to replace the internal camera session. + * + *

If injecting the external camera device fails, then the injection callback's + * {@link CameraInjectionSession.InjectionStatusCallback#onInjectionError + * onInjectionError} method will be called.

+ * + * @param packageName It scopes the injection to a particular app. + * @param internalCamId The id of one of the physical or logical cameras on the phone. + * @param externalCamId The id of one of the remote cameras that are provided by the dynamic + * camera HAL. + * @param executor The executor which will be used when invoking the callback. + * @param callback The callback which is invoked once the external camera is injected. + * + * @throws CameraAccessException If the camera device has been disconnected. + * {@link CameraAccessException#CAMERA_DISCONNECTED} will be + * thrown if camera service is not available. + * @throws SecurityException If the specific application that can cast to external + * devices does not have permission to inject the external + * camera. + * @throws IllegalArgumentException If cameraId doesn't match any currently or previously + * available camera device or some camera functions might not + * work properly or the injection camera runs into a fatal + * error. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) + public void injectCamera(@NonNull String packageName, @NonNull String internalCamId, + @NonNull String externalCamId, @NonNull @CallbackExecutor Executor executor, + @NonNull CameraInjectionSession.InjectionStatusCallback callback) + throws CameraAccessException, SecurityException, + IllegalArgumentException { + if (CameraManagerGlobal.sCameraServiceDisabled) { + throw new IllegalArgumentException("No cameras available on device"); + } + ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); + if (cameraService == null) { + throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable"); + } + synchronized (mLock) { + try { + CameraInjectionSessionImpl injectionSessionImpl = + new CameraInjectionSessionImpl(callback, executor); + ICameraInjectionCallback cameraInjectionCallback = + injectionSessionImpl.getCallback(); + ICameraInjectionSession injectionSession = cameraService.injectCamera(packageName, + internalCamId, externalCamId, cameraInjectionCallback); + injectionSessionImpl.setRemoteInjectionSession(injectionSession); + } catch (ServiceSpecificException e) { + throwAsPublicException(e); + } catch (RemoteException e) { + // Camera service died - act as if it's a CAMERA_DISCONNECTED case + ServiceSpecificException sse = new ServiceSpecificException( + ICameraService.ERROR_DISCONNECTED, + "Camera service is currently unavailable"); + throwAsPublicException(sse); + } + } + } + /** * A per-process global camera manager instance, to retain a connection to the camera service, * and to distribute camera availability notices to API-registered callbacks diff --git a/core/java/android/hardware/camera2/impl/CameraInjectionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraInjectionSessionImpl.java new file mode 100644 index 0000000000000..231cc05ad7cff --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraInjectionSessionImpl.java @@ -0,0 +1,219 @@ +/* + * 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.hardware.camera2.impl; + +import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; + +import android.hardware.camera2.CameraInjectionSession; +import android.hardware.camera2.ICameraInjectionCallback; +import android.hardware.camera2.ICameraInjectionSession; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.concurrent.Executor; + + +/** + * The class inherits CameraInjectionSession. Use CameraManager#injectCamera to instantiate. + */ +public class CameraInjectionSessionImpl extends CameraInjectionSession + implements IBinder.DeathRecipient { + private static final String TAG = "CameraInjectionSessionImpl"; + + private final CameraInjectionCallback mCallback = new CameraInjectionCallback(); + private final CameraInjectionSession.InjectionStatusCallback mInjectionStatusCallback; + private final Executor mExecutor; + private final Object mInterfaceLock = new Object(); + private ICameraInjectionSession mInjectionSession; + + public CameraInjectionSessionImpl(InjectionStatusCallback callback, Executor executor) { + mInjectionStatusCallback = callback; + mExecutor = executor; + } + + @Override + public void close() { + synchronized (mInterfaceLock) { + try { + if (mInjectionSession != null) { + mInjectionSession.stopInjection(); + mInjectionSession.asBinder().unlinkToDeath(this, /*flags*/0); + mInjectionSession = null; + } + } catch (RemoteException e) { + // Ignore binder errors for disconnect + } + } + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + @Override + public void binderDied() { + synchronized (mInterfaceLock) { + Log.w(TAG, "CameraInjectionSessionImpl died unexpectedly"); + + if (mInjectionSession == null) { + return; // CameraInjectionSession already closed + } + + Runnable r = new Runnable() { + @Override + public void run() { + mInjectionStatusCallback.onInjectionError( + CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_SERVICE); + } + }; + final long ident = Binder.clearCallingIdentity(); + try { + CameraInjectionSessionImpl.this.mExecutor.execute(r); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public CameraInjectionCallback getCallback() { + return mCallback; + } + + /** + * Set remote injection session, which triggers initial onInjectionSucceeded callbacks. + * + *

This function may post onInjectionError if remoteInjectionSession dies + * during injecting.

+ */ + public void setRemoteInjectionSession(ICameraInjectionSession injectionSession) { + synchronized (mInterfaceLock) { + if (injectionSession == null) { + Log.e(TAG, "The camera injection session has encountered a serious error"); + scheduleNotifyError( + CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_SESSION); + return; + } + + mInjectionSession = injectionSession; + + IBinder remoteSessionBinder = injectionSession.asBinder(); + if (remoteSessionBinder == null) { + Log.e(TAG, "The camera injection session has encountered a serious error"); + scheduleNotifyError( + CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_SESSION); + return; + } + + final long ident = Binder.clearCallingIdentity(); + try { + remoteSessionBinder.linkToDeath(this, /*flag*/ 0); + mExecutor.execute(new Runnable() { + @Override + public void run() { + mInjectionStatusCallback + .onInjectionSucceeded(CameraInjectionSessionImpl.this); + } + }); + } catch (RemoteException e) { + scheduleNotifyError( + CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_SESSION); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + /** + * The method called when the injection camera has encountered a serious error. + * + * @param errorCode The error code. + * @see #ERROR_INJECTION_SESSION + * @see #ERROR_INJECTION_SERVICE + * @see #ERROR_INJECTION_UNSUPPORTED + */ + public void onInjectionError(final int errorCode) { + Log.v(TAG, String.format( + "Injection session error received, code %d", errorCode)); + + synchronized (mInterfaceLock) { + if (mInjectionSession == null) { + return; // mInjectionSession already closed + } + + switch (errorCode) { + case CameraInjectionCallback.ERROR_INJECTION_SESSION: + scheduleNotifyError( + CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_SESSION); + break; + case CameraInjectionCallback.ERROR_INJECTION_SERVICE: + scheduleNotifyError( + CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_SERVICE); + break; + case CameraInjectionCallback.ERROR_INJECTION_UNSUPPORTED: + scheduleNotifyError( + CameraInjectionSession.InjectionStatusCallback + .ERROR_INJECTION_UNSUPPORTED); + break; + default: + Log.e(TAG, "Unknown error from injection session: " + errorCode); + scheduleNotifyError( + CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_SERVICE); + } + } + } + + private void scheduleNotifyError(final int errorCode) { + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(obtainRunnable( + CameraInjectionSessionImpl::notifyError, + this, errorCode).recycleOnUse()); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void notifyError(final int errorCode) { + if (mInjectionSession != null) { + mInjectionStatusCallback.onInjectionError(errorCode); + } + } + + /** + * The class inherits ICameraInjectionCallbacks.Stub. Use CameraManager#injectCamera to + * instantiate. + */ + public class CameraInjectionCallback extends ICameraInjectionCallback.Stub { + + @Override + public IBinder asBinder() { + return this; + } + + @Override + public void onInjectionError(int errorCode) { + CameraInjectionSessionImpl.this.onInjectionError(errorCode); + } + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9c65dac8efb93..4dc4bef10475f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2333,6 +2333,11 @@ + + +