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
This commit is contained in:
Cliff Wu
2021-03-11 00:27:26 +08:00
parent 03c3c1bbad
commit 84d364df81
4 changed files with 414 additions and 0 deletions

View File

@@ -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;
/**
* <p>The CameraInjectionSession class is what determines when injection is active.</p>
*
* <p>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.</p>
*
* @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.
*
* <p>Call the method when app streaming stops or the app exits, it switch back to the internal
* camera.</p>
*/
@Override
public abstract void close();
/**
* A callback for external camera has a success or an error during injecting.
*
* <p>A callback instance must be provided to the {@link CameraManager#injectCamera} method to
* inject camera.</p>
*
* @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.
*
* <p>The Android device may need to be shut down and restarted to restore
* camera function, or there may be a persistent hardware problem.</p>
*
* <p>An attempt at recovery <i>may</i> be possible by closing the
* CameraDevice and the CameraManager, and trying to acquire all resources again from
* scratch.</p>
*
* @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() {
}
}

View File

@@ -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.
*
* <p>If injecting the external camera device fails, then the injection callback's
* {@link CameraInjectionSession.InjectionStatusCallback#onInjectionError
* onInjectionError} method will be called.</p>
*
* @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

View File

@@ -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.
*
* <p>This function may post onInjectionError if remoteInjectionSession dies
* during injecting.</p>
*/
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);
}
}
}

View File

@@ -2333,6 +2333,11 @@
<permission android:name="android.permission.CAMERA_SEND_SYSTEM_EVENTS"
android:protectionLevel="signature|privileged" />
<!-- Allows injecting the external camera to replace the internal camera.
@hide -->
<permission android:name="android.permission.CAMERA_INJECT_EXTERNAL_CAMERA"
android:protectionLevel="signature" />
<!-- =========================================== -->
<!-- Permissions associated with telephony state -->
<!-- =========================================== -->