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

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

Merge "Mark camera as closed before notifying listeners." into main

parents d16c8288 011e1e69
Loading
Loading
Loading
Loading
+43 −11
Original line number Diff line number Diff line
@@ -65,6 +65,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
    @NonNull
    private final CameraStateMonitor mCameraStateMonitor;

    // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
    // all current camera activities, especially when the camera access is switching from one app to
    // another.
    @Nullable
    private Task mCameraTask;

@@ -123,8 +126,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
    }

    @Override
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
        // Do not check orientation outside of the config recompute, as the app's orientation intent
        // might be obscured by a fullscreen override. Especially for apps which have a camera
        // functionality which is not the main focus of the app: while most of the app might work
@@ -136,18 +138,15 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
            return;
        }

        cameraActivity.recomputeConfiguration();
        cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true);
        cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false);
        mCameraTask = cameraActivity.getTask();
        updateAndDispatchCameraConfiguration();
    }

    @Override
    public boolean onCameraClosed(@NonNull String cameraId) {
    public boolean canCameraBeClosed(@NonNull String cameraId) {
        // Top activity in the same task as the camera activity, or `null` if the task is
        // closed.
        final ActivityRecord topActivity = mCameraTask != null
                ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
                : null;
        final ActivityRecord topActivity = getTopActivityFromCameraTask();
        if (topActivity != null) {
            if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
                ProtoLog.v(WmProtoLogGroups.WM_DEBUG_STATES,
@@ -157,10 +156,36 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
                return false;
            }
        }
        mCameraTask = null;
        return true;
    }

    @Override
    public void onCameraClosed() {
        // Top activity in the same task as the camera activity, or `null` if the task is
        // closed.
        final ActivityRecord topActivity = getTopActivityFromCameraTask();
        // Only clean up if the camera is not running - this close signal could be from switching
        // cameras (e.g. back to front camera, and vice versa).
        if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
            updateAndDispatchCameraConfiguration();
            mCameraTask = null;
        }
    }

    private void updateAndDispatchCameraConfiguration() {
        if (mCameraTask == null) {
            return;
        }
        final ActivityRecord activity = getTopActivityFromCameraTask();
        if (activity != null) {
            activity.recomputeConfiguration();
            mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
            activity.ensureActivityConfiguration(/* ignoreVisibility= */ true);
        } else {
            mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
        }
    }

    boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
        return isCameraRunningAndWindowingModeEligible(activity);
    }
@@ -262,10 +287,17 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
                && !activity.isEmbedded();
    }

    @Nullable
    private ActivityRecord getTopActivityFromCameraTask() {
        return mCameraTask != null
                ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
                : null;
    }

    private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity,
            @NonNull String cameraId) {
        if (!isTreatmentEnabledForActivity(topActivity, /* checkOrientation= */ true)
                || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
                || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
            return false;
        }
        return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+30 −17
Original line number Diff line number Diff line
@@ -67,6 +67,10 @@ class CameraStateMonitor {
    // when camera connection is closed and we need to clean up our records.
    private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping =
            new CameraIdPackageNameBiMapping();
    // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to
    // keep track of camera-closed signals when apps are switching camera access, so that the policy
    // can restore app configuration when an app closes camera (e.g. loses camera access due to
    // another app).
    private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();

    // TODO(b/336474959): should/can this go in the compat listeners?
@@ -163,15 +167,14 @@ class CameraStateMonitor {
            if (cameraActivity == null || cameraActivity.getTask() == null) {
                return;
            }
            notifyListenersCameraOpened(cameraActivity, cameraId);
            notifyListenersCameraOpened(cameraActivity);
        }
    }

    private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
    private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity) {
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            CameraCompatStateListener listener = mCameraStateListeners.get(i);
            listener.onCameraOpened(cameraActivity, cameraId);
            listener.onCameraOpened(cameraActivity);
        }
    }

@@ -224,11 +227,11 @@ class CameraStateMonitor {
                // Already reconnected to this camera, no need to clean up.
                return;
            }

            final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId);
            if (closeSuccessfulForAllListeners) {
            final boolean canClose = checkCanCloseForAllListeners(cameraId);
            if (canClose) {
                // Finish cleaning up.
                mCameraIdPackageBiMapping.removeCameraId(cameraId);
                notifyListenersCameraClosed();
            } else {
                // Not ready to process closure yet - the camera activity might be refreshing.
                // Try again later.
@@ -238,15 +241,21 @@ class CameraStateMonitor {
    }

    /**
     * @return {@code false} if any listeners have reported issues processing the close.
     * @return {@code false} if any listener has reported that they cannot process camera close now.
     */
    private boolean notifyListenersCameraClosed(@NonNull String cameraId) {
        boolean closeSuccessfulForAllListeners = true;
    private boolean checkCanCloseForAllListeners(@NonNull String cameraId) {
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId);
            if (!mCameraStateListeners.get(i).canCameraBeClosed(cameraId)) {
                return false;
            }
        }
        return true;
    }

        return closeSuccessfulForAllListeners;
    private void notifyListenersCameraClosed() {
        for (int i = 0; i < mCameraStateListeners.size(); i++) {
            mCameraStateListeners.get(i).onCameraClosed();
        }
    }

    // TODO(b/335165310): verify that this works in multi instance and permission dialogs.
@@ -297,14 +306,18 @@ class CameraStateMonitor {
        /**
         * Notifies the compat listener that an activity has opened camera.
         */
        // TODO(b/336474959): try to decouple `cameraId` from the listeners.
        void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
        void onCameraOpened(@NonNull ActivityRecord cameraActivity);
        /**
         * Notifies the compat listener that camera is closed.
         * Checks whether a listener is ready to do a cleanup when camera is closed.
         *
         * @return true if cleanup has been successful - the notifier might try again if false.
         * <p>The notifier might try again if false is returned.
         */
        // TODO(b/336474959): try to decouple `cameraId` from the listeners.
        boolean onCameraClosed(@NonNull String cameraId);
        boolean canCameraBeClosed(@NonNull String cameraId);

        /**
         * Notifies the compat listener that camera is closed.
         */
        void onCameraClosed();
    }
}
+34 −13
Original line number Diff line number Diff line
@@ -71,6 +71,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
    @NonNull
    private final ActivityRefresher mActivityRefresher;

    // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
    // all current camera activities, especially when the camera access is switching from one app to
    // another.
    @Nullable
    private Task mCameraTask;

@@ -327,8 +330,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
    }

    @Override
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
    public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
        mCameraTask = cameraActivity.getTask();
        // Checking whether an activity in fullscreen rather than the task as this camera
        // compat treatment doesn't cover activity embedding.
@@ -374,16 +376,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
    }

    @Override
    public boolean onCameraClosed(@NonNull String cameraId) {
        final ActivityRecord topActivity;
        if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
            topActivity = mCameraTask != null ? mCameraTask.getTopActivity(
                    /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
        } else {
            topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
        }
    public boolean canCameraBeClosed(@NonNull String cameraId) {
        final ActivityRecord topActivity = getTopActivity();

        mCameraTask = null;
        if (topActivity == null) {
            return true;
        }
@@ -399,6 +394,23 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
                return false;
            }
        }
        return true;
    }

    @Override
    public void onCameraClosed() {
        final ActivityRecord topActivity = getTopActivity();

        // Only clean up if the camera is not running - this close signal could be from switching
        // cameras (e.g. back to front camera, and vice versa).
        if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
            // Call after getTopActivity(), as that method might use the activity from mCameraTask.
            mCameraTask = null;
        }

        if (topActivity == null) {
            return;
        }

        ProtoLog.v(WM_DEBUG_ORIENTATION,
                "Display id=%d is notified that Camera is closed, updating rotation.",
@@ -406,11 +418,10 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
        // Checking whether an activity in fullscreen rather than the task as this camera compat
        // treatment doesn't cover activity embedding.
        if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
            return true;
            return;
        }
        recomputeConfigurationForCameraCompatIfNeeded(topActivity);
        mDisplayContent.updateOrientation();
        return true;
    }

    // TODO(b/336474959): Do we need cameraId here?
@@ -430,6 +441,16 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
        }
    }

    @Nullable
    private ActivityRecord getTopActivity() {
        if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
            return mCameraTask != null ? mCameraTask.getTopActivity(
                    /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
        } else {
            return mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
        }
    }

    /**
     * @return {@code true} if the configuration needs to be recomputed after a camera state update.
     */
+45 −13
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ 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;
@@ -136,6 +138,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
        assertEquals(1, mListener.mOnCameraOpenedCounter);
    }

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

        assertTrue(mListener.mIsCameraOpened);
    }

    @Test
    public void testOnCameraOpened_cameraClosed_notifyCameraClosed() {
        mCameraStateMonitor.addCameraStateListener(mListener);
@@ -144,9 +154,20 @@ public final class CameraStateMonitorTests extends WindowTestsBase {

        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);

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

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

        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
        assertFalse(mListener.mIsCameraOpened);
    }

    @Test
    public void testOnCameraOpened_listenerCannotCloseYet_notifyCameraClosedAgain() {
        mCameraStateMonitor.addCameraStateListener(mListenerCannotClose);
@@ -155,7 +176,8 @@ public final class CameraStateMonitorTests extends WindowTestsBase {

        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);

        assertEquals(2, mListenerCannotClose.mOnCameraClosedCounter);
        assertEquals(2, mListenerCannotClose.mCheckCanCloseCounter);
        assertEquals(1, mListenerCannotClose.mOnCameraClosedCounter);
    }

    @Test
@@ -197,39 +219,49 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
            CameraStateMonitor.CameraCompatStateListener {

        int mOnCameraOpenedCounter = 0;
        int mCheckCanCloseCounter = 0;
        int mOnCameraClosedCounter = 0;

        private boolean mOnCameraClosedReturnValue = true;
        boolean mIsCameraOpened;

        private boolean mCheckCanCloseReturnValue = true;

        /**
         * @param simulateUnsuccessfulCloseOnce When false, returns `true` on every
         *                                      `onCameraClosed`. When true, returns `false` on the
         *                                      first `onCameraClosed` callback, and `true on the
         * @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 simulateUnsuccessfulCloseOnce) {
            mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce;
        FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) {
            mCheckCanCloseReturnValue = !simulateCannotCloseOnce;
        }

        @Override
        public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
                @NonNull String cameraId) {
        public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
            mOnCameraOpenedCounter++;
            mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(cameraActivity);
        }

        @Override
        public boolean onCameraClosed(@NonNull String cameraId) {
            mOnCameraClosedCounter++;
            boolean returnValue = mOnCameraClosedReturnValue;
        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.
            mOnCameraClosedReturnValue = true;
            mCheckCanCloseReturnValue = true;
            return returnValue;
        }

        @Override
        public void onCameraClosed() {
            mOnCameraClosedCounter++;
            mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(mActivity);
        }

        void resetCounters() {
            mOnCameraOpenedCounter = 0;
            mCheckCanCloseCounter = 0;
            mOnCameraClosedCounter = 0;
        }
    }