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

Commit 1ac9b5e9 authored by Mina Granic's avatar Mina Granic
Browse files

Notify all camera listeners on open/close.

Since there is a close delay, a new open can overwrite a camera
listener, and the camera compat might not be undone for the
original close.

Remove the return variable for camera opened, as the camera
monitor should not track the state of its listeners.

Flag: com.android.window.flags.camera_compat_for_freeform
Test: atest WmTests:CameraStateMonitorTests
Fixes: 347882285
Change-Id: I65d80904aabde3507ac2ac2c6d370a7ee2dd800a
parent 006eabd6
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -109,10 +109,10 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
    }

    @Override
    public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
        if (!isTreatmentEnabledForActivity(cameraActivity)) {
            return false;
            return;
        }
        final int existingCameraCompatMode = cameraActivity.mAppCompatController
                .getAppCompatCameraOverrides()
@@ -124,11 +124,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
            cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
                    .setFreeformCameraCompatMode(newCameraCompatMode);
            forceUpdateActivityAndTask(cameraActivity);
            return true;
        } else {
            mIsCameraCompatTreatmentPending = false;
        }
        return false;
    }

    @Override
+22 −28
Original line number Diff line number Diff line
@@ -73,16 +73,6 @@ class CameraStateMonitor {

    private final ArrayList<CameraCompatStateListener> mCameraStateListeners = new ArrayList<>();

    /**
     * {@link CameraCompatStateListener} which returned {@code true} on the last {@link
     * CameraCompatStateListener#onCameraOpened(ActivityRecord, String)}, if any.
     *
     * <p>This allows the {@link CameraStateMonitor} to notify a particular listener when camera
     * closes, so they can revert any changes.
     */
    @Nullable
    private CameraCompatStateListener mCurrentListenerForCameraActivity;

    private final CameraManager.AvailabilityCallback mAvailabilityCallback =
            new  CameraManager.AvailabilityCallback() {
                @Override
@@ -167,12 +157,7 @@ class CameraStateMonitor {
            @NonNull String cameraId) {
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            CameraCompatStateListener listener = mCameraStateListeners.get(i);
            boolean activeCameraTreatment = listener.onCameraOpened(
                    cameraActivity, cameraId);
            if (activeCameraTreatment) {
                mCurrentListenerForCameraActivity = listener;
                break;
            }
            listener.onCameraOpened(cameraActivity, cameraId);
        }
    }

@@ -226,17 +211,28 @@ class CameraStateMonitor {
                return;
            }

            if (mCurrentListenerForCameraActivity != null) {
                boolean closeSuccessful =
                        mCurrentListenerForCameraActivity.onCameraClosed(cameraId);
                if (closeSuccessful) {
            final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId);
            if (closeSuccessfulForAllListeners) {
                // Finish cleaning up.
                mCameraIdPackageBiMapping.removeCameraId(cameraId);
                    mCurrentListenerForCameraActivity = null;
            } else {
                // Not ready to process closure yet - the camera activity might be refreshing.
                // Try again later.
                rescheduleRemoveCameraActivity(cameraId);
            }
        }
    }

    /**
     * @return {@code false} if any listeners have reported issues processing the close.
     */
    private boolean notifyListenersCameraClosed(@NonNull String cameraId) {
        boolean closeSuccessfulForAllListeners = true;
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId);
        }

        return closeSuccessfulForAllListeners;
    }

    // TODO(b/335165310): verify that this works in multi instance and permission dialogs.
@@ -286,11 +282,9 @@ class CameraStateMonitor {
    interface CameraCompatStateListener {
        /**
         * Notifies the compat listener that an activity has opened camera.
         *
         * @return true if the treatment has been applied.
         */
        // TODO(b/336474959): try to decouple `cameraId` from the listeners.
        boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
        void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
        /**
         * Notifies the compat listener that camera is closed.
         *
+2 −4
Original line number Diff line number Diff line
@@ -298,7 +298,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
    }

    @Override
    public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
        mCameraTask = cameraActivity.getTask();
        // Checking whether an activity in fullscreen rather than the task as this camera
@@ -306,7 +306,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
        if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
            recomputeConfigurationForCameraCompatIfNeeded(cameraActivity);
            mDisplayContent.updateOrientation();
            return true;
            return;
        }
        // Checking that the whole app is in multi-window mode as we shouldn't show toast
        // for the activity embedding case.
@@ -320,7 +320,6 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
                        (String) packageManager.getApplicationLabel(
                                packageManager.getApplicationInfo(cameraActivity.packageName,
                                        /* flags */ 0)));
                return true;
            } catch (PackageManager.NameNotFoundException e) {
                ProtoLog.e(WM_DEBUG_ORIENTATION,
                        "DisplayRotationCompatPolicy: Multi-window toast not shown as "
@@ -328,7 +327,6 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
                        cameraActivity.packageName);
            }
        }
        return false;
    }

    @VisibleForTesting
+18 −54
Original line number Diff line number Diff line
@@ -43,10 +43,10 @@ import org.junit.runner.RunWith;
import java.util.concurrent.Executor;

/**
 * Tests for {@link DisplayRotationCompatPolicy}.
 * Tests for {@link CameraStateMonitor}.
 *
 * Build/Install/Run:
 *  atest WmTests:DisplayRotationCompatPolicyTests
 *  atest WmTests:CameraStateMonitorTests
 */
@SmallTest
@Presubmit
@@ -68,23 +68,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
    private ActivityRecord mActivity;
    private Task mTask;

    // Simulates a listener which will not react to the change on a particular activity.
    private final FakeCameraCompatStateListener mNotInterestedListener =
            new FakeCameraCompatStateListener(
                    /*onCameraOpenedReturnValue=*/ false,
                    /*simulateUnsuccessfulCloseOnce=*/ false);
    // 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 mInterestedListener =
            new FakeCameraCompatStateListener(
                    /*onCameraOpenedReturnValue=*/ true,
                    /*simulateUnsuccessfulCloseOnce=*/ false);
    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(
                    /*onCameraOpenedReturnValue=*/ true,
                    /*simulateUnsuccessfulCloseOnce=*/ true);
            new FakeCameraCompatStateListener(/* simulateUnsuccessfulCloseOnce= */ true);

    @Before
    public void setUp() throws Exception {
@@ -129,44 +120,31 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
    @After
    public void tearDown() {
        // Remove all listeners.
        mCameraStateMonitor.removeCameraStateListener(mNotInterestedListener);
        mCameraStateMonitor.removeCameraStateListener(mInterestedListener);
        mCameraStateMonitor.removeCameraStateListener(mListener);
        mCameraStateMonitor.removeCameraStateListener(mListenerCannotClose);

        // Reset the listener's state.
        mNotInterestedListener.resetCounters();
        mInterestedListener.resetCounters();
        mListener.resetCounters();
        mListenerCannotClose.resetCounters();
    }

    @Test
    public void testOnCameraOpened_listenerAdded_notifiesCameraOpened() {
        mCameraStateMonitor.addCameraStateListener(mNotInterestedListener);
        mCameraStateMonitor.addCameraStateListener(mListener);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        assertEquals(1, mNotInterestedListener.mOnCameraOpenedCounter);
        assertEquals(1, mListener.mOnCameraOpenedCounter);
    }

    @Test
    public void testOnCameraOpened_listenerReturnsFalse_doesNotNotifyCameraClosed() {
        mCameraStateMonitor.addCameraStateListener(mNotInterestedListener);
        // Listener returns false on `onCameraOpened`.
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);

        assertEquals(0, mNotInterestedListener.mOnCameraClosedCounter);
    }

    @Test
    public void testOnCameraOpened_listenerReturnsTrue_notifyCameraClosed() {
        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
    public void testOnCameraOpened_cameraClosed_notifyCameraClosed() {
        mCameraStateMonitor.addCameraStateListener(mListener);
        // Listener returns true on `onCameraOpened`.
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);

        assertEquals(1, mInterestedListener.mOnCameraClosedCounter);
        assertEquals(1, mListener.mOnCameraClosedCounter);
    }

    @Test
@@ -182,32 +160,22 @@ public final class CameraStateMonitorTests extends WindowTestsBase {

    @Test
    public void testReconnectedToDifferentCamera_notifiesListener() {
        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
        mCameraStateMonitor.addCameraStateListener(mListener);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);

        assertEquals(2, mInterestedListener.mOnCameraOpenedCounter);
        assertEquals(2, mListener.mOnCameraOpenedCounter);
    }

    @Test
    public void testDifferentAppConnectedToCamera_notifiesListener() {
        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
        mCameraStateMonitor.addCameraStateListener(mListener);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);

        assertEquals(2, mInterestedListener.mOnCameraOpenedCounter);
    }

    @Test
    public void testCameraAlreadyClosed_notifiesListenerOnce() {
        mCameraStateMonitor.addCameraStateListener(mInterestedListener);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);

        assertEquals(1, mInterestedListener.mOnCameraClosedCounter);
        assertEquals(2, mListener.mOnCameraOpenedCounter);
    }

    private void configureActivity(@NonNull String packageName) {
@@ -232,7 +200,6 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
        int mOnCameraOpenedCounter = 0;
        int mOnCameraClosedCounter = 0;

        boolean mOnCameraOpenedReturnValue = true;
        private boolean mOnCameraClosedReturnValue = true;

        /**
@@ -242,17 +209,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
         *                                      subsequent calls. This fake implementation tests the
         *                                      retry mechanism in {@link CameraStateMonitor}.
         */
        FakeCameraCompatStateListener(boolean onCameraOpenedReturnValue,
                boolean simulateUnsuccessfulCloseOnce) {
            mOnCameraOpenedReturnValue = onCameraOpenedReturnValue;
        FakeCameraCompatStateListener(boolean simulateUnsuccessfulCloseOnce) {
            mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce;
        }

        @Override
        public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity,
        public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
                @NonNull String cameraId) {
            mOnCameraOpenedCounter++;
            return mOnCameraOpenedReturnValue;
        }

        @Override