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:
128
core/java/android/hardware/camera2/CameraInjectionSession.java
Normal file
128
core/java/android/hardware/camera2/CameraInjectionSession.java
Normal 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() {
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 -->
|
||||
<!-- =========================================== -->
|
||||
|
||||
Reference in New Issue
Block a user