Block camera access for apps running on VirtualDevices
Bug: 170935709 Test: atest CameraAccessControllerTest Change-Id: I15beeb0d4aa5aa3588e70ef37542192997121499
This commit is contained in:
@@ -6266,4 +6266,8 @@ ul.</string>
|
||||
</string>
|
||||
<!-- Action label of notification for user to check background apps. [CHAR LIMIT=NONE] -->
|
||||
<string name="notification_action_check_bg_apps">Check active apps</string>
|
||||
|
||||
<!-- Strings for VirtualDeviceManager -->
|
||||
<!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] -->
|
||||
<string name="vdm_camera_access_denied">Cannot access camera from this device</string>
|
||||
</resources>
|
||||
|
||||
@@ -4724,5 +4724,8 @@
|
||||
<java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" />
|
||||
<java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" />
|
||||
|
||||
<!-- For VirtualDeviceManager -->
|
||||
<java-symbol type="string" name="vdm_camera_access_denied" />
|
||||
|
||||
<java-symbol type="color" name="camera_privacy_light"/>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.server.companion.virtual;
|
||||
|
||||
import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraInjectionSession;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
/**
|
||||
* Handles blocking access to the camera for apps running on virtual devices.
|
||||
*/
|
||||
class CameraAccessController extends CameraManager.AvailabilityCallback {
|
||||
private static final String TAG = "CameraAccessController";
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private final Context mContext;
|
||||
private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
|
||||
CameraAccessBlockedCallback mBlockedCallback;
|
||||
private CameraManager mCameraManager;
|
||||
private boolean mListeningForCameraEvents;
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
|
||||
|
||||
static class InjectionSessionData {
|
||||
public int appUid;
|
||||
public ArrayMap<String, CameraInjectionSession> cameraIdToSession = new ArrayMap<>();
|
||||
}
|
||||
|
||||
interface CameraAccessBlockedCallback {
|
||||
/**
|
||||
* Called whenever an app was blocked from accessing a camera.
|
||||
* @param appUid uid for the app which was blocked
|
||||
*/
|
||||
void onCameraAccessBlocked(int appUid);
|
||||
}
|
||||
|
||||
CameraAccessController(Context context,
|
||||
VirtualDeviceManagerInternal virtualDeviceManagerInternal,
|
||||
CameraAccessBlockedCallback blockedCallback) {
|
||||
mContext = context;
|
||||
mVirtualDeviceManagerInternal = virtualDeviceManagerInternal;
|
||||
mBlockedCallback = blockedCallback;
|
||||
mCameraManager = mContext.getSystemService(CameraManager.class);
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts watching for camera access by uids running on a virtual device, if we were not
|
||||
* already doing so.
|
||||
*/
|
||||
public void startObservingIfNeeded() {
|
||||
synchronized (mLock) {
|
||||
if (!mListeningForCameraEvents) {
|
||||
mCameraManager.registerAvailabilityCallback(mContext.getMainExecutor(), this);
|
||||
mListeningForCameraEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching for camera access.
|
||||
*/
|
||||
public void stopObserving() {
|
||||
synchronized (mLock) {
|
||||
mCameraManager.unregisterAvailabilityCallback(this);
|
||||
mListeningForCameraEvents = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
final ApplicationInfo ainfo =
|
||||
mPackageManager.getApplicationInfo(packageName, 0);
|
||||
InjectionSessionData data = mPackageToSessionData.get(packageName);
|
||||
if (!mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(ainfo.uid)) {
|
||||
CameraInjectionSession existingSession =
|
||||
(data != null) ? data.cameraIdToSession.get(cameraId) : null;
|
||||
if (existingSession != null) {
|
||||
existingSession.close();
|
||||
data.cameraIdToSession.remove(cameraId);
|
||||
if (data.cameraIdToSession.isEmpty()) {
|
||||
mPackageToSessionData.remove(packageName);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data == null) {
|
||||
data = new InjectionSessionData();
|
||||
data.appUid = ainfo.uid;
|
||||
mPackageToSessionData.put(packageName, data);
|
||||
}
|
||||
if (data.cameraIdToSession.containsKey(cameraId)) {
|
||||
return;
|
||||
}
|
||||
startBlocking(packageName, cameraId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Slog.e(TAG, "onCameraOpened - unknown package " + packageName, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraClosed(@NonNull String cameraId) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mPackageToSessionData.size() - 1; i >= 0; i--) {
|
||||
InjectionSessionData data = mPackageToSessionData.valueAt(i);
|
||||
CameraInjectionSession session = data.cameraIdToSession.get(cameraId);
|
||||
if (session != null) {
|
||||
session.close();
|
||||
data.cameraIdToSession.remove(cameraId);
|
||||
if (data.cameraIdToSession.isEmpty()) {
|
||||
mPackageToSessionData.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on blocking for a particular camera and package.
|
||||
*/
|
||||
private void startBlocking(String packageName, String cameraId) {
|
||||
try {
|
||||
mCameraManager.injectCamera(packageName, cameraId, /* externalCamId */ "",
|
||||
mContext.getMainExecutor(),
|
||||
new CameraInjectionSession.InjectionStatusCallback() {
|
||||
@Override
|
||||
public void onInjectionSucceeded(
|
||||
@NonNull CameraInjectionSession session) {
|
||||
CameraAccessController.this.onInjectionSucceeded(cameraId, packageName,
|
||||
session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInjectionError(@NonNull int errorCode) {
|
||||
CameraAccessController.this.onInjectionError(cameraId, packageName,
|
||||
errorCode);
|
||||
}
|
||||
});
|
||||
} catch (CameraAccessException e) {
|
||||
Slog.e(TAG,
|
||||
"Failed to injectCamera for cameraId:" + cameraId + " package:" + packageName,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onInjectionSucceeded(String cameraId, String packageName,
|
||||
@NonNull CameraInjectionSession session) {
|
||||
synchronized (mLock) {
|
||||
InjectionSessionData data = mPackageToSessionData.get(packageName);
|
||||
if (data == null) {
|
||||
Slog.e(TAG, "onInjectionSucceeded didn't find expected entry for package "
|
||||
+ packageName);
|
||||
session.close();
|
||||
return;
|
||||
}
|
||||
CameraInjectionSession existingSession = data.cameraIdToSession.put(cameraId, session);
|
||||
if (existingSession != null) {
|
||||
Slog.e(TAG, "onInjectionSucceeded found unexpected existing session for camera "
|
||||
+ cameraId);
|
||||
existingSession.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onInjectionError(String cameraId, String packageName, @NonNull int errorCode) {
|
||||
if (errorCode != ERROR_INJECTION_UNSUPPORTED) {
|
||||
// ERROR_INJECTION_UNSUPPORTED means that there wasn't an external camera to map to the
|
||||
// internal camera, which is expected when using the injection interface as we are in
|
||||
// this class to simply block camera access. Any other error is unexpected.
|
||||
Slog.e(TAG, "Unexpected injection error code:" + errorCode + " for camera:" + cameraId
|
||||
+ " and package:" + packageName);
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
InjectionSessionData data = mPackageToSessionData.get(packageName);
|
||||
if (data != null) {
|
||||
mBlockedCallback.onCameraAccessBlocked(data.appUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.StringRes;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.PendingIntent;
|
||||
@@ -57,6 +58,8 @@ import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Display;
|
||||
import android.widget.Toast;
|
||||
import android.window.DisplayWindowPolicyController;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -583,6 +586,26 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a toast on virtual displays owned by this device which have a given uid running.
|
||||
*/
|
||||
void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) {
|
||||
synchronized (mVirtualDeviceLock) {
|
||||
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
|
||||
final int size = mWindowPolicyControllers.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
|
||||
int displayId = mWindowPolicyControllers.keyAt(i);
|
||||
Display display = displayManager.getDisplay(displayId);
|
||||
if (display != null && display.isValid()) {
|
||||
Toast.makeText(mContext.createDisplayContext(display), resId,
|
||||
duration).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OnDeviceCloseListener {
|
||||
void onClose(int associationId);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import android.os.RemoteException;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.Toast;
|
||||
import android.window.DisplayWindowPolicyController;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -62,8 +63,10 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
|
||||
private final Object mVirtualDeviceManagerLock = new Object();
|
||||
private final VirtualDeviceManagerImpl mImpl;
|
||||
private VirtualDeviceManagerInternal mLocalService;
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
|
||||
private final CameraAccessController mCameraAccessController;
|
||||
|
||||
/**
|
||||
* Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
|
||||
@@ -90,6 +93,9 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
public VirtualDeviceManagerService(Context context) {
|
||||
super(context);
|
||||
mImpl = new VirtualDeviceManagerImpl();
|
||||
mLocalService = new LocalService();
|
||||
mCameraAccessController = new CameraAccessController(getContext(), mLocalService,
|
||||
this::onCameraAccessBlocked);
|
||||
}
|
||||
|
||||
private final ActivityInterceptorCallback mActivityInterceptorCallback =
|
||||
@@ -118,8 +124,7 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
|
||||
publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
|
||||
|
||||
publishLocalService(VirtualDeviceManagerInternal.class, mLocalService);
|
||||
ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
|
||||
ActivityTaskManagerInternal.class);
|
||||
activityTaskManagerInternal.registerActivityStartInterceptor(
|
||||
@@ -169,6 +174,16 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
void onCameraAccessBlocked(int appUid) {
|
||||
synchronized (mVirtualDeviceManagerLock) {
|
||||
int size = mVirtualDevices.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
|
||||
com.android.internal.R.string.vdm_camera_access_denied, Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
|
||||
VirtualDeviceImpl.PendingTrampolineCallback {
|
||||
|
||||
@@ -205,10 +220,14 @@ public class VirtualDeviceManagerService extends SystemService {
|
||||
public void onClose(int associationId) {
|
||||
synchronized (mVirtualDeviceManagerLock) {
|
||||
mVirtualDevices.remove(associationId);
|
||||
if (mVirtualDevices.size() == 0) {
|
||||
mCameraAccessController.stopObserving();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
this, activityListener, params);
|
||||
mCameraAccessController.startObservingIfNeeded();
|
||||
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
|
||||
return virtualDevice;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ android_test {
|
||||
"service-jobscheduler",
|
||||
"service-permission.impl",
|
||||
"service-supplementalprocess.impl",
|
||||
"services.companion",
|
||||
"services.core",
|
||||
"services.devicepolicy",
|
||||
"services.net",
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.server.companion.virtual;
|
||||
|
||||
import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraInjectionSession;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.os.Process;
|
||||
import android.testing.TestableContext;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CameraAccessControllerTest {
|
||||
private static final String FRONT_CAMERA = "0";
|
||||
private static final String REAR_CAMERA = "1";
|
||||
private static final String TEST_APP_PACKAGE = "some.package";
|
||||
private static final String OTHER_APP_PACKAGE = "other.package";
|
||||
|
||||
private CameraAccessController mController;
|
||||
|
||||
@Rule
|
||||
public final TestableContext mContext = new TestableContext(
|
||||
InstrumentationRegistry.getContext());
|
||||
|
||||
@Mock
|
||||
private CameraManager mCameraManager;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock
|
||||
private VirtualDeviceManagerInternal mDeviceManagerInternal;
|
||||
@Mock
|
||||
private CameraAccessController.CameraAccessBlockedCallback mBlockedCallback;
|
||||
|
||||
private ApplicationInfo mTestAppInfo = new ApplicationInfo();
|
||||
private ApplicationInfo mOtherAppInfo = new ApplicationInfo();
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<CameraInjectionSession.InjectionStatusCallback> mInjectionCallbackCaptor;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws PackageManager.NameNotFoundException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext.addMockSystemService(CameraManager.class, mCameraManager);
|
||||
mContext.setMockPackageManager(mPackageManager);
|
||||
LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
|
||||
LocalServices.addService(VirtualDeviceManagerInternal.class, mDeviceManagerInternal);
|
||||
mController = new CameraAccessController(mContext, mDeviceManagerInternal,
|
||||
mBlockedCallback);
|
||||
mTestAppInfo.uid = Process.FIRST_APPLICATION_UID;
|
||||
mOtherAppInfo.uid = Process.FIRST_APPLICATION_UID + 1;
|
||||
when(mPackageManager.getApplicationInfo(eq(TEST_APP_PACKAGE), anyInt())).thenReturn(
|
||||
mTestAppInfo);
|
||||
when(mPackageManager.getApplicationInfo(eq(OTHER_APP_PACKAGE), anyInt())).thenReturn(
|
||||
mOtherAppInfo);
|
||||
mController.startObservingIfNeeded();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCameraOpened_uidNotRunning_noCameraBlocking() throws CameraAccessException {
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mTestAppInfo.uid))).thenReturn(false);
|
||||
mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
|
||||
verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void onCameraOpened_uidRunning_cameraBlocked() throws CameraAccessException {
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mTestAppInfo.uid))).thenReturn(true);
|
||||
mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
|
||||
verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
|
||||
any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCameraClosed_injectionWasActive_cameraUnblocked() throws CameraAccessException {
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mTestAppInfo.uid))).thenReturn(true);
|
||||
mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
|
||||
verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
|
||||
any(), mInjectionCallbackCaptor.capture());
|
||||
CameraInjectionSession session = mock(CameraInjectionSession.class);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
|
||||
|
||||
mController.onCameraClosed(FRONT_CAMERA);
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void onCameraClosed_otherCameraClosed_cameraNotUnblocked() throws CameraAccessException {
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mTestAppInfo.uid))).thenReturn(true);
|
||||
mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
|
||||
verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
|
||||
any(), mInjectionCallbackCaptor.capture());
|
||||
CameraInjectionSession session = mock(CameraInjectionSession.class);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
|
||||
|
||||
mController.onCameraClosed(REAR_CAMERA);
|
||||
verify(session, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCameraClosed_twoCamerasBlocked_correctCameraUnblocked()
|
||||
throws CameraAccessException {
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mTestAppInfo.uid))).thenReturn(true);
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mOtherAppInfo.uid))).thenReturn(true);
|
||||
|
||||
mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
|
||||
mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE);
|
||||
|
||||
verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
|
||||
any(), mInjectionCallbackCaptor.capture());
|
||||
CameraInjectionSession frontCameraSession = mock(CameraInjectionSession.class);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionSucceeded(frontCameraSession);
|
||||
|
||||
verify(mCameraManager).injectCamera(eq(OTHER_APP_PACKAGE), eq(REAR_CAMERA), anyString(),
|
||||
any(), mInjectionCallbackCaptor.capture());
|
||||
CameraInjectionSession rearCameraSession = mock(CameraInjectionSession.class);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionSucceeded(rearCameraSession);
|
||||
|
||||
mController.onCameraClosed(REAR_CAMERA);
|
||||
verify(frontCameraSession, never()).close();
|
||||
verify(rearCameraSession).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onInjectionError_callbackFires() throws CameraAccessException {
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mTestAppInfo.uid))).thenReturn(true);
|
||||
mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
|
||||
verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
|
||||
any(), mInjectionCallbackCaptor.capture());
|
||||
CameraInjectionSession session = mock(CameraInjectionSession.class);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
|
||||
verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twoCameraAccesses_onlyOneOnVirtualDisplay_callbackFiresForCorrectUid()
|
||||
throws CameraAccessException {
|
||||
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
|
||||
eq(mTestAppInfo.uid))).thenReturn(true);
|
||||
mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
|
||||
mController.onCameraOpened(REAR_CAMERA, OTHER_APP_PACKAGE);
|
||||
|
||||
verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
|
||||
any(), mInjectionCallbackCaptor.capture());
|
||||
CameraInjectionSession session = mock(CameraInjectionSession.class);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
|
||||
mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
|
||||
verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user