Loading services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java +181 −168 Original line number Diff line number Diff line Loading @@ -19,17 +19,13 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import android.content.ComponentName; import android.app.IApplicationThread; import android.hardware.camera2.CameraManager; import android.os.Handler; import android.platform.test.annotations.Presubmit; Loading @@ -37,17 +33,16 @@ import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Tests for {@link CameraStateMonitor}. * * Build/Install/Run: * <p>Build/Install/Run: * atest WmTests:CameraStateMonitorTests */ @SmallTest Loading @@ -55,214 +50,232 @@ import java.util.concurrent.Executor; @RunWith(WindowTestRunner.class) public final class CameraStateMonitorTests extends WindowTestsBase { private static final String TEST_PACKAGE_1 = "com.test.package.one"; private static final String TEST_PACKAGE_2 = "com.test.package.two"; private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests"; private static final String CAMERA_ID_1 = "camera-1"; private static final String CAMERA_ID_2 = "camera-2"; private static final String TEST_PACKAGE_1_LABEL = "testPackage1"; private CameraManager mMockCameraManager; private Handler mMockHandler; private AppCompatConfiguration mAppCompatConfiguration; private CameraStateMonitor mCameraStateMonitor; private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; private ActivityRecord mActivity; private Task mTask; @Test public void testOnCameraOpened_policyAdded_notifiesCameraOpened() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); // Simulates a listener which will react to the change on a particular activity - for example // put the activity in a camera compat mode. private final FakeCameraCompatStateListener mListener = new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ false); // Simulates a listener which for some reason cannot process `onCameraClosed` event once it // first arrives - this means that the update needs to be postponed. private final FakeCameraCompatStateListener mListenerCannotClose = new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ true); @Before public void setUp() throws Exception { mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; spyOn(mAppCompatConfiguration); when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(true); when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) .thenReturn(true); when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(true); mMockCameraManager = mock(CameraManager.class); doAnswer(invocation -> { mCameraAvailabilityCallback = invocation.getArgument(1); return null; }).when(mMockCameraManager).registerAvailabilityCallback( any(Executor.class), any(CameraManager.AvailabilityCallback.class)); robot.checkCameraOpenedCalledForCanClosePolicy(1); }); } spyOn(mContext); when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager); @Test public void testOnCameraOpened_policyAdded_cameraRegistersAsOpenedDuringTheCallback() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); spyOn(mDisplayContent); robot.checkCameraRegisteresAsOpenedForCanClosePolicy(true); }); } mDisplayContent.setIgnoreOrientationRequest(true); @Test public void testOnCameraOpened_cameraClosed_notifyCameraClosed() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mMockHandler = mock(Handler.class); robot.onCameraClosed(CAMERA_ID_1); when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; robot.checkCanCloseCalledForCanClosePolicy(1); robot.checkCameraClosedCalledForCanClosePolicy(1); }); mCameraStateMonitor = new CameraStateMonitor(mDisplayContent, mMockHandler); configureActivity(TEST_PACKAGE_1); configureActivity(TEST_PACKAGE_2); mCameraStateMonitor.startListeningToCameraState(); } @After public void tearDown() { // Remove all listeners. mCameraStateMonitor.removeCameraStateListener(mListener); mCameraStateMonitor.removeCameraStateListener(mListenerCannotClose); @Test public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); robot.onCameraClosed(CAMERA_ID_1); // Reset the listener's state. mListener.resetCounters(); mListenerCannotClose.resetCounters(); robot.checkCameraRegisteresAsOpenedForCanClosePolicy(false); }); } @Test public void testOnCameraOpened_listenerAdded_notifiesCameraOpened() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); public void testOnCameraOpened_policyCannotCloseYet_notifyCameraClosedAgain() { runTestScenario((robot) -> { robot.addListenerThatCannotCloseOnce(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); robot.onCameraClosed(CAMERA_ID_1); assertEquals(1, mListener.mOnCameraOpenedCounter); robot.checkCanCloseCalledForCannotCloseOncePolicy(2); robot.checkCameraClosedCalledForCannotCloseOncePolicy(1); }); } @Test public void testOnCameraOpened_listenerAdded_cameraRegistersAsOpenedDuringTheCallback() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); public void testReconnectedToDifferentCamera_notifiesPolicy() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); robot.onCameraClosed(CAMERA_ID_1); robot.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1); robot.checkCameraOpenedCalledForCanClosePolicy(2); }); } assertTrue(mListener.mIsCameraOpened); /** * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<CameraStateMonitorRobotTests> consumer) { final CameraStateMonitorRobotTests robot = new CameraStateMonitorRobotTests(mWm, mAtm, mSupervisor, this); consumer.accept(robot); } @Test public void testOnCameraOpened_cameraClosed_notifyCameraClosed() { mCameraStateMonitor.addCameraStateListener(mListener); // Listener returns true on `onCameraOpened`. mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); private static class CameraStateMonitorRobotTests extends AppCompatRobotBase { private final WindowTestsBase mWindowTestsBase; mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); // Simulates a listener which will react to the change on a particular activity - for // example put the activity in a camera compat mode. private FakeCameraCompatStateListener mFakeListenerCanClose; // Simulates a listener which for some reason cannot process `onCameraClosed` event once it // first arrives - this means that the update needs to be postponed. private FakeCameraCompatStateListener mFakeListenerCannotCloseOnce; assertEquals(1, mListener.mCheckCanCloseCounter); assertEquals(1, mListener.mOnCameraClosedCounter); private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; CameraStateMonitorRobotTests(@NonNull WindowManagerService wm, @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor, @NonNull WindowTestsBase windowTestsBase) { super(wm, atm, supervisor); mWindowTestsBase = windowTestsBase; setupCameraManager(); setupAppCompatConfiguration(); configureActivityAndDisplay(); } @Test public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() { mCameraStateMonitor.addCameraStateListener(mListener); // Listener returns true on `onCameraOpened`. mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); @Override void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) { super.onPostDisplayContentCreation(displayContent); spyOn(displayContent.mAppCompatCameraPolicy); if (displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy != null) { spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy); } mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); assertFalse(mListener.mIsCameraOpened); mFakeListenerCannotCloseOnce = new FakeCameraCompatStateListener(true); mFakeListenerCanClose = new FakeCameraCompatStateListener(false); } @Test public void testOnCameraOpened_listenerCannotCloseYet_notifyCameraClosedAgain() { mCameraStateMonitor.addCameraStateListener(mListenerCannotClose); // Listener returns true on `onCameraOpened`. mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); @Override void onPostActivityCreation(@NonNull ActivityRecord activity) { super.onPostActivityCreation(activity); setupCameraManager(); setupHandler(); setupMockApplicationThread(); } mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); private void setupMockApplicationThread() { IApplicationThread mockApplicationThread = mock(IApplicationThread.class); spyOn(activity().top().app); doReturn(mockApplicationThread).when(activity().top().app).getThread(); } assertEquals(2, mListenerCannotClose.mCheckCanCloseCounter); assertEquals(1, mListenerCannotClose.mOnCameraClosedCounter); private void setupAppCompatConfiguration() { applyOnConf((c) -> { c.enableCameraCompatTreatment(true); c.enableCameraCompatTreatmentAtBuildTime(true); c.enableCameraCompatRefresh(true); c.enableCameraCompatRefreshCycleThroughStop(true); c.enableCameraCompatSplitScreenAspectRatio(false); }); } @Test public void testReconnectedToDifferentCamera_notifiesListener() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1); private void setupCameraManager() { final CameraManager mockCameraManager = mock(CameraManager.class); doAnswer(invocation -> { mCameraAvailabilityCallback = invocation.getArgument(1); return null; }).when(mockCameraManager).registerAvailabilityCallback( any(Executor.class), any(CameraManager.AvailabilityCallback.class)); assertEquals(2, mListener.mOnCameraOpenedCounter); doReturn(mockCameraManager).when(mWindowTestsBase.mWm.mContext).getSystemService( CameraManager.class); } @Test public void testDifferentAppConnectedToCamera_notifiesListener() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); private void setupHandler() { final Handler handler = activity().top().mWmService.mH; spyOn(handler); assertEquals(2, mListener.mOnCameraOpenedCounter); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(handler).postDelayed(any(Runnable.class), anyLong()); } private void configureActivity(@NonNull String packageName) { mTask = new TaskBuilder(mSupervisor) .setDisplay(mDisplayContent) .build(); private void configureActivityAndDisplay() { applyOnActivity(a -> { a.createActivityWithComponentInNewTaskAndDisplay(); a.setIgnoreOrientationRequest(true); spyOn(a.top().mAppCompatController.getCameraOverrides()); spyOn(a.top().info); doReturn(a.displayContent().getDisplayInfo()).when( a.displayContent().mWmService.mDisplayManagerInternal).getDisplayInfo( a.displayContent().mDisplayId); }); } mActivity = new ActivityBuilder(mAtm) .setComponent(new ComponentName(packageName, ".TestActivity")) .setTask(mTask) .build(); private void addListenerThatCanClose() { getCameraStateMonitor().addCameraStateListener(mFakeListenerCanClose); } spyOn(mActivity.mAtmService.getLifecycleManager()); private void addListenerThatCannotCloseOnce() { getCameraStateMonitor().addCameraStateListener(mFakeListenerCannotCloseOnce); } doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean()); private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName); waitHandlerIdle(); } private class FakeCameraCompatStateListener implements CameraStateMonitor.CameraCompatStateListener { private void onCameraClosed(@NonNull String cameraId) { mCameraAvailabilityCallback.onCameraClosed(cameraId); } int mOnCameraOpenedCounter = 0; int mCheckCanCloseCounter = 0; int mOnCameraClosedCounter = 0; private void checkCameraRegisteresAsOpenedForCanClosePolicy(boolean expectedIsOpened) { assertEquals(expectedIsOpened, activity().top().getDisplayContent() .mAppCompatCameraPolicy.mCameraStateMonitor.isCameraRunningForActivity( activity().top())); } boolean mIsCameraOpened; private void checkCameraOpenedCalledForCanClosePolicy(int times) { assertEquals(times, mFakeListenerCanClose.mOnCameraOpenedCounter); } private boolean mCheckCanCloseReturnValue = true; private void checkCanCloseCalledForCanClosePolicy(int times) { assertEquals(times, mFakeListenerCanClose.mCheckCanCloseCounter); } /** * @param simulateCannotCloseOnce When false, returns `true` on every * `checkCanClose`. When true, returns `false` on the * first `checkCanClose` callback, and `true on the * subsequent calls. This fake implementation tests the * retry mechanism in {@link CameraStateMonitor}. */ FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) { mCheckCanCloseReturnValue = !simulateCannotCloseOnce; private void checkCanCloseCalledForCannotCloseOncePolicy(int times) { assertEquals(times, mFakeListenerCannotCloseOnce.mCheckCanCloseCounter); } @Override public void onCameraOpened(@NonNull ActivityRecord cameraActivity) { mOnCameraOpenedCounter++; mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(cameraActivity); private void checkCameraClosedCalledForCanClosePolicy(int times) { assertEquals(times, mFakeListenerCanClose.mOnCameraClosedCounter); } @Override public boolean canCameraBeClosed(@NonNull String cameraId) { mCheckCanCloseCounter++; final boolean returnValue = mCheckCanCloseReturnValue; // If false, return false only the first time, so it doesn't fall in the infinite retry // loop. mCheckCanCloseReturnValue = true; return returnValue; private void checkCameraClosedCalledForCannotCloseOncePolicy(int times) { assertEquals(times, mFakeListenerCannotCloseOnce.mOnCameraClosedCounter); } @Override public void onCameraClosed() { mOnCameraClosedCounter++; mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(mActivity); private void waitHandlerIdle() { mWindowTestsBase.waitHandlerIdle(activity().displayContent().mWmService.mH); } void resetCounters() { mOnCameraOpenedCounter = 0; mCheckCanCloseCounter = 0; mOnCameraClosedCounter = 0; private CameraStateMonitor getCameraStateMonitor() { return activity().top().mDisplayContent.mAppCompatCameraPolicy.mCameraStateMonitor; } } } No newline at end of file services/tests/wmtests/src/com/android/server/wm/FakeCameraCompatStateListener.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.wm; import androidx.annotation.NonNull; /** Fake {@link CameraStateMonitor.CameraCompatStateListener} for testing. */ class FakeCameraCompatStateListener implements CameraStateMonitor.CameraCompatStateListener { int mOnCameraOpenedCounter = 0; int mCheckCanCloseCounter = 0; int mOnCameraClosedCounter = 0; private boolean mCheckCanCloseReturnValue; /** * @param simulateCannotCloseOnce When false, returns `true` on every * `checkCanClose`. When true, returns `false` on the * first `checkCanClose` callback, and `true on the * subsequent calls. This fake implementation tests the * retry mechanism in {@link CameraStateMonitor}. */ FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) { mCheckCanCloseReturnValue = !simulateCannotCloseOnce; } @Override public void onCameraOpened(@NonNull ActivityRecord cameraActivity) { mOnCameraOpenedCounter++; } @Override public boolean canCameraBeClosed(@NonNull String cameraId) { mCheckCanCloseCounter++; final boolean returnValue = mCheckCanCloseReturnValue; // If false, return false only the first time, so it doesn't fall in the infinite retry // loop. mCheckCanCloseReturnValue = true; return returnValue; } @Override public void onCameraClosed() { mOnCameraClosedCounter++; } } Loading
services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java +181 −168 Original line number Diff line number Diff line Loading @@ -19,17 +19,13 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import android.content.ComponentName; import android.app.IApplicationThread; import android.hardware.camera2.CameraManager; import android.os.Handler; import android.platform.test.annotations.Presubmit; Loading @@ -37,17 +33,16 @@ import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Tests for {@link CameraStateMonitor}. * * Build/Install/Run: * <p>Build/Install/Run: * atest WmTests:CameraStateMonitorTests */ @SmallTest Loading @@ -55,214 +50,232 @@ import java.util.concurrent.Executor; @RunWith(WindowTestRunner.class) public final class CameraStateMonitorTests extends WindowTestsBase { private static final String TEST_PACKAGE_1 = "com.test.package.one"; private static final String TEST_PACKAGE_2 = "com.test.package.two"; private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests"; private static final String CAMERA_ID_1 = "camera-1"; private static final String CAMERA_ID_2 = "camera-2"; private static final String TEST_PACKAGE_1_LABEL = "testPackage1"; private CameraManager mMockCameraManager; private Handler mMockHandler; private AppCompatConfiguration mAppCompatConfiguration; private CameraStateMonitor mCameraStateMonitor; private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; private ActivityRecord mActivity; private Task mTask; @Test public void testOnCameraOpened_policyAdded_notifiesCameraOpened() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); // Simulates a listener which will react to the change on a particular activity - for example // put the activity in a camera compat mode. private final FakeCameraCompatStateListener mListener = new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ false); // Simulates a listener which for some reason cannot process `onCameraClosed` event once it // first arrives - this means that the update needs to be postponed. private final FakeCameraCompatStateListener mListenerCannotClose = new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ true); @Before public void setUp() throws Exception { mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; spyOn(mAppCompatConfiguration); when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()) .thenReturn(true); when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()) .thenReturn(true); when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(true); mMockCameraManager = mock(CameraManager.class); doAnswer(invocation -> { mCameraAvailabilityCallback = invocation.getArgument(1); return null; }).when(mMockCameraManager).registerAvailabilityCallback( any(Executor.class), any(CameraManager.AvailabilityCallback.class)); robot.checkCameraOpenedCalledForCanClosePolicy(1); }); } spyOn(mContext); when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager); @Test public void testOnCameraOpened_policyAdded_cameraRegistersAsOpenedDuringTheCallback() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); spyOn(mDisplayContent); robot.checkCameraRegisteresAsOpenedForCanClosePolicy(true); }); } mDisplayContent.setIgnoreOrientationRequest(true); @Test public void testOnCameraOpened_cameraClosed_notifyCameraClosed() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mMockHandler = mock(Handler.class); robot.onCameraClosed(CAMERA_ID_1); when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; robot.checkCanCloseCalledForCanClosePolicy(1); robot.checkCameraClosedCalledForCanClosePolicy(1); }); mCameraStateMonitor = new CameraStateMonitor(mDisplayContent, mMockHandler); configureActivity(TEST_PACKAGE_1); configureActivity(TEST_PACKAGE_2); mCameraStateMonitor.startListeningToCameraState(); } @After public void tearDown() { // Remove all listeners. mCameraStateMonitor.removeCameraStateListener(mListener); mCameraStateMonitor.removeCameraStateListener(mListenerCannotClose); @Test public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); robot.onCameraClosed(CAMERA_ID_1); // Reset the listener's state. mListener.resetCounters(); mListenerCannotClose.resetCounters(); robot.checkCameraRegisteresAsOpenedForCanClosePolicy(false); }); } @Test public void testOnCameraOpened_listenerAdded_notifiesCameraOpened() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); public void testOnCameraOpened_policyCannotCloseYet_notifyCameraClosedAgain() { runTestScenario((robot) -> { robot.addListenerThatCannotCloseOnce(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); robot.onCameraClosed(CAMERA_ID_1); assertEquals(1, mListener.mOnCameraOpenedCounter); robot.checkCanCloseCalledForCannotCloseOncePolicy(2); robot.checkCameraClosedCalledForCannotCloseOncePolicy(1); }); } @Test public void testOnCameraOpened_listenerAdded_cameraRegistersAsOpenedDuringTheCallback() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); public void testReconnectedToDifferentCamera_notifiesPolicy() { runTestScenario((robot) -> { robot.addListenerThatCanClose(); robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); robot.onCameraClosed(CAMERA_ID_1); robot.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1); robot.checkCameraOpenedCalledForCanClosePolicy(2); }); } assertTrue(mListener.mIsCameraOpened); /** * Runs a test scenario providing a Robot. */ void runTestScenario(@NonNull Consumer<CameraStateMonitorRobotTests> consumer) { final CameraStateMonitorRobotTests robot = new CameraStateMonitorRobotTests(mWm, mAtm, mSupervisor, this); consumer.accept(robot); } @Test public void testOnCameraOpened_cameraClosed_notifyCameraClosed() { mCameraStateMonitor.addCameraStateListener(mListener); // Listener returns true on `onCameraOpened`. mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); private static class CameraStateMonitorRobotTests extends AppCompatRobotBase { private final WindowTestsBase mWindowTestsBase; mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); // Simulates a listener which will react to the change on a particular activity - for // example put the activity in a camera compat mode. private FakeCameraCompatStateListener mFakeListenerCanClose; // Simulates a listener which for some reason cannot process `onCameraClosed` event once it // first arrives - this means that the update needs to be postponed. private FakeCameraCompatStateListener mFakeListenerCannotCloseOnce; assertEquals(1, mListener.mCheckCanCloseCounter); assertEquals(1, mListener.mOnCameraClosedCounter); private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; CameraStateMonitorRobotTests(@NonNull WindowManagerService wm, @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor, @NonNull WindowTestsBase windowTestsBase) { super(wm, atm, supervisor); mWindowTestsBase = windowTestsBase; setupCameraManager(); setupAppCompatConfiguration(); configureActivityAndDisplay(); } @Test public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() { mCameraStateMonitor.addCameraStateListener(mListener); // Listener returns true on `onCameraOpened`. mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); @Override void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) { super.onPostDisplayContentCreation(displayContent); spyOn(displayContent.mAppCompatCameraPolicy); if (displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy != null) { spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy); } mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); assertFalse(mListener.mIsCameraOpened); mFakeListenerCannotCloseOnce = new FakeCameraCompatStateListener(true); mFakeListenerCanClose = new FakeCameraCompatStateListener(false); } @Test public void testOnCameraOpened_listenerCannotCloseYet_notifyCameraClosedAgain() { mCameraStateMonitor.addCameraStateListener(mListenerCannotClose); // Listener returns true on `onCameraOpened`. mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); @Override void onPostActivityCreation(@NonNull ActivityRecord activity) { super.onPostActivityCreation(activity); setupCameraManager(); setupHandler(); setupMockApplicationThread(); } mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); private void setupMockApplicationThread() { IApplicationThread mockApplicationThread = mock(IApplicationThread.class); spyOn(activity().top().app); doReturn(mockApplicationThread).when(activity().top().app).getThread(); } assertEquals(2, mListenerCannotClose.mCheckCanCloseCounter); assertEquals(1, mListenerCannotClose.mOnCameraClosedCounter); private void setupAppCompatConfiguration() { applyOnConf((c) -> { c.enableCameraCompatTreatment(true); c.enableCameraCompatTreatmentAtBuildTime(true); c.enableCameraCompatRefresh(true); c.enableCameraCompatRefreshCycleThroughStop(true); c.enableCameraCompatSplitScreenAspectRatio(false); }); } @Test public void testReconnectedToDifferentCamera_notifiesListener() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1); private void setupCameraManager() { final CameraManager mockCameraManager = mock(CameraManager.class); doAnswer(invocation -> { mCameraAvailabilityCallback = invocation.getArgument(1); return null; }).when(mockCameraManager).registerAvailabilityCallback( any(Executor.class), any(CameraManager.AvailabilityCallback.class)); assertEquals(2, mListener.mOnCameraOpenedCounter); doReturn(mockCameraManager).when(mWindowTestsBase.mWm.mContext).getSystemService( CameraManager.class); } @Test public void testDifferentAppConnectedToCamera_notifiesListener() { mCameraStateMonitor.addCameraStateListener(mListener); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); private void setupHandler() { final Handler handler = activity().top().mWmService.mH; spyOn(handler); assertEquals(2, mListener.mOnCameraOpenedCounter); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(handler).postDelayed(any(Runnable.class), anyLong()); } private void configureActivity(@NonNull String packageName) { mTask = new TaskBuilder(mSupervisor) .setDisplay(mDisplayContent) .build(); private void configureActivityAndDisplay() { applyOnActivity(a -> { a.createActivityWithComponentInNewTaskAndDisplay(); a.setIgnoreOrientationRequest(true); spyOn(a.top().mAppCompatController.getCameraOverrides()); spyOn(a.top().info); doReturn(a.displayContent().getDisplayInfo()).when( a.displayContent().mWmService.mDisplayManagerInternal).getDisplayInfo( a.displayContent().mDisplayId); }); } mActivity = new ActivityBuilder(mAtm) .setComponent(new ComponentName(packageName, ".TestActivity")) .setTask(mTask) .build(); private void addListenerThatCanClose() { getCameraStateMonitor().addCameraStateListener(mFakeListenerCanClose); } spyOn(mActivity.mAtmService.getLifecycleManager()); private void addListenerThatCannotCloseOnce() { getCameraStateMonitor().addCameraStateListener(mFakeListenerCannotCloseOnce); } doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean()); private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName); waitHandlerIdle(); } private class FakeCameraCompatStateListener implements CameraStateMonitor.CameraCompatStateListener { private void onCameraClosed(@NonNull String cameraId) { mCameraAvailabilityCallback.onCameraClosed(cameraId); } int mOnCameraOpenedCounter = 0; int mCheckCanCloseCounter = 0; int mOnCameraClosedCounter = 0; private void checkCameraRegisteresAsOpenedForCanClosePolicy(boolean expectedIsOpened) { assertEquals(expectedIsOpened, activity().top().getDisplayContent() .mAppCompatCameraPolicy.mCameraStateMonitor.isCameraRunningForActivity( activity().top())); } boolean mIsCameraOpened; private void checkCameraOpenedCalledForCanClosePolicy(int times) { assertEquals(times, mFakeListenerCanClose.mOnCameraOpenedCounter); } private boolean mCheckCanCloseReturnValue = true; private void checkCanCloseCalledForCanClosePolicy(int times) { assertEquals(times, mFakeListenerCanClose.mCheckCanCloseCounter); } /** * @param simulateCannotCloseOnce When false, returns `true` on every * `checkCanClose`. When true, returns `false` on the * first `checkCanClose` callback, and `true on the * subsequent calls. This fake implementation tests the * retry mechanism in {@link CameraStateMonitor}. */ FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) { mCheckCanCloseReturnValue = !simulateCannotCloseOnce; private void checkCanCloseCalledForCannotCloseOncePolicy(int times) { assertEquals(times, mFakeListenerCannotCloseOnce.mCheckCanCloseCounter); } @Override public void onCameraOpened(@NonNull ActivityRecord cameraActivity) { mOnCameraOpenedCounter++; mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(cameraActivity); private void checkCameraClosedCalledForCanClosePolicy(int times) { assertEquals(times, mFakeListenerCanClose.mOnCameraClosedCounter); } @Override public boolean canCameraBeClosed(@NonNull String cameraId) { mCheckCanCloseCounter++; final boolean returnValue = mCheckCanCloseReturnValue; // If false, return false only the first time, so it doesn't fall in the infinite retry // loop. mCheckCanCloseReturnValue = true; return returnValue; private void checkCameraClosedCalledForCannotCloseOncePolicy(int times) { assertEquals(times, mFakeListenerCannotCloseOnce.mOnCameraClosedCounter); } @Override public void onCameraClosed() { mOnCameraClosedCounter++; mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(mActivity); private void waitHandlerIdle() { mWindowTestsBase.waitHandlerIdle(activity().displayContent().mWmService.mH); } void resetCounters() { mOnCameraOpenedCounter = 0; mCheckCanCloseCounter = 0; mOnCameraClosedCounter = 0; private CameraStateMonitor getCameraStateMonitor() { return activity().top().mDisplayContent.mAppCompatCameraPolicy.mCameraStateMonitor; } } } No newline at end of file
services/tests/wmtests/src/com/android/server/wm/FakeCameraCompatStateListener.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.wm; import androidx.annotation.NonNull; /** Fake {@link CameraStateMonitor.CameraCompatStateListener} for testing. */ class FakeCameraCompatStateListener implements CameraStateMonitor.CameraCompatStateListener { int mOnCameraOpenedCounter = 0; int mCheckCanCloseCounter = 0; int mOnCameraClosedCounter = 0; private boolean mCheckCanCloseReturnValue; /** * @param simulateCannotCloseOnce When false, returns `true` on every * `checkCanClose`. When true, returns `false` on the * first `checkCanClose` callback, and `true on the * subsequent calls. This fake implementation tests the * retry mechanism in {@link CameraStateMonitor}. */ FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) { mCheckCanCloseReturnValue = !simulateCannotCloseOnce; } @Override public void onCameraOpened(@NonNull ActivityRecord cameraActivity) { mOnCameraOpenedCounter++; } @Override public boolean canCameraBeClosed(@NonNull String cameraId) { mCheckCanCloseCounter++; final boolean returnValue = mCheckCanCloseReturnValue; // If false, return false only the first time, so it doesn't fall in the infinite retry // loop. mCheckCanCloseReturnValue = true; return returnValue; } @Override public void onCameraClosed() { mOnCameraClosedCounter++; } }