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

Commit 4169cf92 authored by Mina Granic's avatar Mina Granic Committed by Android (Google) Code Review
Browse files

Merge "Use Robot pattern in CameraStateMonitorTests." into main

parents 5a6c251f a9ac0611
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++;
    }
}