diff options
5 files changed, 91 insertions, 91 deletions
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 68a417289c55..2755a80ea705 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -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)) { - 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; + 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; } diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java index a54141ce5230..068fc001ae2c 100644 --- a/services/core/java/com/android/server/wm/CameraStateMonitor.java +++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java @@ -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,25 +139,30 @@ 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( - () -> { - synchronized (mWmService.mGlobalLock) { - if (!mScheduledCompatModeUpdateCameraIdSet.remove(cameraId)) { - // Camera compat mode update has happened already or was cancelled - // because camera was closed. - return; - } - mCameraIdPackageBiMapping.put(packageName, cameraId); - mCameraActivity = findCameraActivity(packageName); - if (mCameraActivity == null || mCameraActivity.getTask() == null) { - return; - } - notifyListenersCameraOpened(mCameraActivity, cameraId); - } - }, + 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 + // because camera was closed. + return; + } + mCameraIdPackageBiMapping.put(packageName, cameraId); + // 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(cameraActivity, cameraId); + } + } + private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { for (int i = 0; i < mCameraStateListeners.size(); i++) { @@ -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); } } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 9998e1a016d0..1a0124a1d4de 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -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( diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index 564c29f09bc9..11e6d90a5f50 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -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; @@ -176,22 +175,9 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { 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(); @@ -199,20 +185,6 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @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); diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java index e468fd8ee495..1c8dc05c6787 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java @@ -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 |