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

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

Merge "Do not adjust camera compat treatment for activity after closing camera." into main

parents d2be75eb d4272eb4
Loading
Loading
Loading
Loading
+24 −16
Original line number Diff line number Diff line
@@ -26,13 +26,14 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.CameraCompatTaskInfo;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.window.flags.Flags;

/**
@@ -56,6 +57,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa

    private boolean mIsCameraCompatTreatmentPending = false;

    @Nullable
    private Task mCameraTask;

    CameraCompatFreeformPolicy(@NonNull DisplayContent displayContent,
            @NonNull CameraStateMonitor cameraStateMonitor,
            @NonNull ActivityRefresher activityRefresher) {
@@ -116,6 +120,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
        final int newCameraCompatMode = getCameraCompatMode(cameraActivity);
        if (newCameraCompatMode != existingCameraCompatMode) {
            mIsCameraCompatTreatmentPending = true;
            mCameraTask = cameraActivity.getTask();
            cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
                    .setFreeformCameraCompatMode(newCameraCompatMode);
            forceUpdateActivityAndTask(cameraActivity);
@@ -127,18 +132,22 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
    }

    @Override
    public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
        if (isActivityForCameraIdRefreshing(cameraId)) {
    public boolean onCameraClosed(@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;
        if (topActivity != null) {
            if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
                ProtoLog.v(ProtoLogGroup.WM_DEBUG_STATES,
                        "Display id=%d is notified that Camera %s is closed but activity is"
                                + " still refreshing. Rescheduling an update.",
                        mDisplayContent.mDisplayId, cameraId);
                return false;
            }
        cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
                .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE);
        forceUpdateActivityAndTask(cameraActivity);
        }
        mCameraTask = null;
        mIsCameraCompatTreatmentPending = false;
        return true;
    }
@@ -186,10 +195,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
                && !activity.isEmbedded();
    }

    private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                /* considerKeyguardState= */ true);
        if (topActivity == null || !isTreatmentEnabledForActivity(topActivity)
    private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity,
            @NonNull String cameraId) {
        if (!isTreatmentEnabledForActivity(topActivity)
                || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
            return false;
        }
+44 −29
Original line number Diff line number Diff line
@@ -61,9 +61,6 @@ class CameraStateMonitor {
    @NonNull
    private final Handler mHandler;

    @Nullable
    private ActivityRecord mCameraActivity;

    // Bi-directional map between package names and active camera IDs since we need to 1) get a
    // camera id by a package name when resizing the window; 2) get a package name by a camera id
    // when camera connection is closed and we need to clean up our records.
@@ -91,13 +88,13 @@ class CameraStateMonitor {
                @Override
                public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
                    synchronized (mWmService.mGlobalLock) {
                        notifyCameraOpened(cameraId, packageId);
                        notifyCameraOpenedWithDelay(cameraId, packageId);
                    }
                }
                @Override
                public void onCameraClosed(@NonNull String cameraId) {
                    synchronized (mWmService.mGlobalLock) {
                        notifyCameraClosed(cameraId);
                        notifyCameraClosedWithDelay(cameraId);
                    }
                }
            };
@@ -131,8 +128,8 @@ class CameraStateMonitor {
        mCameraStateListeners.remove(listener);
    }

    private void notifyCameraOpened(
            @NonNull String cameraId, @NonNull String packageName) {
    private void notifyCameraOpenedWithDelay(@NonNull String cameraId,
            @NonNull String packageName) {
        // If an activity is restarting or camera is flipping, the camera connection can be
        // quickly closed and reopened.
        mScheduledToBeRemovedCameraIdSet.remove(cameraId);
@@ -142,8 +139,11 @@ class CameraStateMonitor {
        // Some apps can’t handle configuration changes coming at the same time with Camera setup so
        // delaying orientation update to accommodate for that.
        mScheduledCompatModeUpdateCameraIdSet.add(cameraId);
        mHandler.postDelayed(
                () -> {
        mHandler.postDelayed(() -> notifyCameraOpenedInternal(cameraId, packageName),
                CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS);
    }

    private void notifyCameraOpenedInternal(@NonNull String cameraId, @NonNull String packageName) {
        synchronized (mWmService.mGlobalLock) {
            if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) {
                // Camera compat mode update has happened already or was cancelled
@@ -151,14 +151,16 @@ class CameraStateMonitor {
                return;
            }
            mCameraIdPackageBiMapping.put(packageName, cameraId);
                        mCameraActivity = findCameraActivity(packageName);
                        if (mCameraActivity == null || mCameraActivity.getTask() == null) {
            // If there are multiple activities of the same package name and none of
            // them are the top running activity, we do not apply treatment (rather than
            // guessing and applying it to the wrong activity).
            final ActivityRecord cameraActivity =
                    findUniqueActivityWithPackageName(packageName);
            if (cameraActivity == null || cameraActivity.getTask() == null) {
                return;
            }
                        notifyListenersCameraOpened(mCameraActivity, cameraId);
            notifyListenersCameraOpened(cameraActivity, cameraId);
        }
                },
                CAMERA_OPENED_LETTERBOX_UPDATE_DELAY_MS);
    }

    private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity,
@@ -174,7 +176,13 @@ class CameraStateMonitor {
        }
    }

    private void notifyCameraClosed(@NonNull String cameraId) {
    /**
     * Processes camera closed, and schedules notifying listeners.
     *
     * <p>The delay is introduced to avoid flickering when switching between front and back camera,
     * and when an activity is refreshed due to camera compat treatment.
     */
    private void notifyCameraClosedWithDelay(@NonNull String cameraId) {
        ProtoLog.v(WM_DEBUG_STATES,
                "Display id=%d is notified that Camera %s is closed.",
                mDisplayContent.mDisplayId, cameraId);
@@ -217,9 +225,10 @@ class CameraStateMonitor {
                // Already reconnected to this camera, no need to clean up.
                return;
            }
            if (mCameraActivity != null && mCurrentListenerForCameraActivity != null) {

            if (mCurrentListenerForCameraActivity != null) {
                boolean closeSuccessful =
                        mCurrentListenerForCameraActivity.onCameraClosed(mCameraActivity, cameraId);
                        mCurrentListenerForCameraActivity.onCameraClosed(cameraId);
                if (closeSuccessful) {
                    mCameraIdPackageBiMapping.removeCameraId(cameraId);
                    mCurrentListenerForCameraActivity = null;
@@ -231,8 +240,14 @@ class CameraStateMonitor {
    }

    // TODO(b/335165310): verify that this works in multi instance and permission dialogs.
    /**
     * Finds a visible activity with the given package name.
     *
     * <p>If there are multiple visible activities with a given package name, and none of them are
     * the `topRunningActivity`, returns null.
     */
    @Nullable
    private ActivityRecord findCameraActivity(@NonNull String packageName) {
    private ActivityRecord findUniqueActivityWithPackageName(@NonNull String packageName) {
        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                /* considerKeyguardState= */ true);
        if (topActivity != null && topActivity.packageName.equals(packageName)) {
@@ -277,11 +292,11 @@ class CameraStateMonitor {
        // TODO(b/336474959): try to decouple `cameraId` from the listeners.
        boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
        /**
         * Notifies the compat listener that an activity has closed the camera.
         * Notifies the compat listener that camera is closed.
         *
         * @return true if cleanup has been successful - the notifier might try again if false.
         */
        // TODO(b/336474959): try to decouple `cameraId` from the listeners.
        boolean onCameraClosed(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
        boolean onCameraClosed(@NonNull String cameraId);
    }
}
+21 −15
Original line number Diff line number Diff line
@@ -342,12 +342,19 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
    }

    @Override
    public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
            @NonNull String cameraId) {
    public boolean onCameraClosed(@NonNull String cameraId) {
        // Top activity in the same task as the camera activity, or `null` if the task is
        // closed.
        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                /* considerKeyguardState= */ true);
        if (topActivity == null) {
            return true;
        }

        synchronized (this) {
            // TODO(b/336474959): Once refresh is implemented in `CameraCompatFreeformPolicy`,
            // consider checking this in CameraStateMonitor before notifying the listeners (this).
            if (isActivityForCameraIdRefreshing(cameraId)) {
            if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
                ProtoLog.v(WM_DEBUG_ORIENTATION,
                        "Display id=%d is notified that camera is closed but activity is"
                                + " still refreshing. Rescheduling an update.",
@@ -355,15 +362,15 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
                return false;
            }
        }

        ProtoLog.v(WM_DEBUG_ORIENTATION,
                "Display id=%d is notified that Camera is closed, updating rotation.",
                mDisplayContent.mDisplayId);
        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                /* considerKeyguardState= */ true);
        if (topActivity == null
                // Checking whether an activity in fullscreen rather than the task as this
                // camera compat treatment doesn't cover activity embedding.
                || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
        // Checking whether an activity in fullscreen rather than the task as this camera compat
        // treatment doesn't cover activity embedding.
        // TODO(b/350495350): Consider checking whether this activity is the camera activity, or
        // whether the top activity has the same task as the one which opened camera.
        if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
            return true;
        }
        recomputeConfigurationForCameraCompatIfNeeded(topActivity);
@@ -372,14 +379,13 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
    }

    // TODO(b/336474959): Do we need cameraId here?
    private boolean isActivityForCameraIdRefreshing(@NonNull String cameraId) {
        final ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                /* considerKeyguardState= */ true);
        if (!isTreatmentEnabledForActivity(topActivity)
                || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
    private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord activity,
            @NonNull String cameraId) {
        if (!isTreatmentEnabledForActivity(activity)
                || !mCameraStateMonitor.isCameraWithIdRunningForActivity(activity, cameraId)) {
            return false;
        }
        return mActivityRefresher.isActivityRefreshing(topActivity);
        return mActivityRefresher.isActivityRefreshing(activity);
    }

    private void recomputeConfigurationForCameraCompatIfNeeded(
+1 −29
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -175,43 +174,16 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
    public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);

        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        callOnActivityConfigurationChanging(mActivity);

        assertInCameraCompatMode();
        assertActivityRefreshRequested(/* refreshRequested */ true);
    }

    @Test
    public void testReconnectedToDifferentCamera_activatesCameraCompatModeAndRefresh()
            throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);

        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        callOnActivityConfigurationChanging(mActivity);

        assertInCameraCompatMode();
        assertActivityRefreshRequested(/* refreshRequested */ true);
    }

    @Test
    public void testCameraDisconnected_deactivatesCameraCompatMode() {
        configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE,
                WINDOWING_MODE_FREEFORM);
        // Open camera and test for compat treatment
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        assertInCameraCompatMode();

        // Close camera and test for revert
        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);

        assertNotInCameraCompatMode();
    }

    @Test
    public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+1 −2
Original line number Diff line number Diff line
@@ -256,8 +256,7 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
        }

        @Override
        public boolean onCameraClosed(@NonNull ActivityRecord cameraActivity,
                @NonNull String cameraId) {
        public boolean onCameraClosed(@NonNull String cameraId) {
            mOnCameraClosedCounter++;
            boolean returnValue = mOnCameraClosedReturnValue;
            // If false, return false only the first time, so it doesn't fall in the infinite retry