Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a9ac0611 authored by Mina Granic's avatar Mina Granic
Browse files

Use Robot pattern in CameraStateMonitorTests.

Flag: EXEMPT test change
Fixes: 421893253
Test: atest WmTests:CameraStateMonitorTests
Change-Id: I39f2ab9066fc4058840db06f475701b6d813be96
parent 7c99ec11
Loading
Loading
Loading
Loading
+181 −168
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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
+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++;
    }
}