diff options
| author | 2023-03-07 17:26:06 +0000 | |
|---|---|---|
| committer | 2023-03-23 16:53:06 +0000 | |
| commit | 31b731c2de27b5ff2a9240c361a3e48114d5f59b (patch) | |
| tree | 7dd071742a964483c90d05d0edd556e4623e2739 | |
| parent | 106aa99da2f1122c54f042a53288a75087c241b2 (diff) | |
Prevent orientation request loop in override
MUST_SLEEP
Ignore any orientation requests if activity is not letterboxed for fixed
orientation and aspect ratio and activity already requests
orientation change more than twice. Logic is behind per-app
OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED.
Bug: 273509367
Test: atest WmTests:LetterboxUiControllerTest
Change-Id: I72e8c9faf191c0ae819b409a3906da28389e72e6
3 files changed, 164 insertions, 14 deletions
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 226278cbe44d..319f1298a87f 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1035,6 +1035,20 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { 254631730L; // buganizer id /** + * This change id enables compat policy that ignores app requested orientation in + * response to an app calling {@link android.app.Activity#setRequestedOrientation} more + * than twice in one second if an activity is not letterboxed for fixed orientation. + * See com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation + * for details. + * @hide + */ + @ChangeId + @Overridable + @Disabled + public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = + 273509367L; // buganizer id + + /** * This change id forces the packages it is applied to never have Display API sandboxing * applied for a letterbox or SCM activity. The Display APIs will continue to provide * DisplayArea bounds. diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 6bffc4c95e64..4f0f22732136 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FOR import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; @@ -125,6 +126,14 @@ final class LetterboxUiController { private static final float UNDEFINED_ASPECT_RATIO = 0f; + // Minimum value of mSetOrientationRequestCounter before qualifying as orientation request loop + @VisibleForTesting + static final int MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP = 2; + // Used to determine reset of mSetOrientationRequestCounter if next app requested + // orientation is after timeout value + @VisibleForTesting + static final int SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS = 1000; + private final Point mTmpPoint = new Point(); private final LetterboxConfiguration mLetterboxConfiguration; @@ -163,6 +172,8 @@ final class LetterboxUiController { // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled; + // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED + private final boolean mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled; // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS private final boolean mIsOverrideEnableCompatFakeFocusEnabled; @@ -187,12 +198,18 @@ final class LetterboxUiController { private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; + // Updated when ActivityRecord#setRequestedOrientation is called + private long mTimeMsLastSetOrientationRequest = 0; + @Configuration.Orientation private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; + // Counter for ActivityRecord#setRequestedOrientation + private int mSetOrientationRequestCounter = 0; + // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @@ -288,6 +305,9 @@ final class LetterboxUiController { mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION); + mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled = + isCompatChangeEnabled( + OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); mIsOverrideEnableCompatFakeFocusEnabled = isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS); @@ -354,35 +374,85 @@ final class LetterboxUiController { * <li>Opt-in component property or per-app override are enabled * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation} * call from an app or camera compat force rotation treatment is active for the activity. + * <li>Orientation request loop detected and is not letterboxed for fixed orientation * </ul> */ boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { - if (!shouldEnableWithOverrideAndProperty( + if (shouldEnableWithOverrideAndProperty( /* gatingCondition */ mLetterboxConfiguration ::isPolicyForIgnoringRequestedOrientationEnabled, mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled, mBooleanPropertyIgnoreRequestedOrientation)) { - return false; + if (mIsRelauchingAfterRequestedOrientationChanged) { + Slog.w(TAG, "Ignoring orientation update to " + + screenOrientationToString(requestedOrientation) + + " due to relaunching after setRequestedOrientation for " + + mActivityRecord); + return true; + } + DisplayContent displayContent = mActivityRecord.mDisplayContent; + if (displayContent == null) { + return false; + } + if (displayContent.mDisplayRotationCompatPolicy != null + && displayContent.mDisplayRotationCompatPolicy + .isTreatmentEnabledForActivity(mActivityRecord)) { + Slog.w(TAG, "Ignoring orientation update to " + + screenOrientationToString(requestedOrientation) + + " due to camera compat treatment for " + mActivityRecord); + return true; + } } - if (mIsRelauchingAfterRequestedOrientationChanged) { + + if (shouldIgnoreOrientationRequestLoop()) { Slog.w(TAG, "Ignoring orientation update to " + screenOrientationToString(requestedOrientation) - + " due to relaunching after setRequestedOrientation for " + mActivityRecord); + + " as orientation request loop was detected for " + + mActivityRecord); return true; } - DisplayContent displayContent = mActivityRecord.mDisplayContent; - if (displayContent == null) { + return false; + } + + /** + * Whether an app is calling {@link android.app.Activity#setRequestedOrientation} + * in a loop and orientation request should be ignored. + * + * <p>This should only be called once in response to + * {@link android.app.Activity#setRequestedOrientation}. See + * {@link #shouldIgnoreRequestedOrientation} for more details. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Per-app override is enabled + * <li>App has requested orientation more than 2 times within 1-second + * timer and activity is not letterboxed for fixed orientation + * </ul> + */ + @VisibleForTesting + boolean shouldIgnoreOrientationRequestLoop() { + if (!mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled) { return false; } - if (displayContent.mDisplayRotationCompatPolicy != null - && displayContent.mDisplayRotationCompatPolicy - .isTreatmentEnabledForActivity(mActivityRecord)) { - Slog.w(TAG, "Ignoring orientation update to " - + screenOrientationToString(requestedOrientation) - + " due to camera compat treatment for " + mActivityRecord); - return true; + + final long currTimeMs = System.currentTimeMillis(); + if (currTimeMs - mTimeMsLastSetOrientationRequest + < SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS) { + mSetOrientationRequestCounter += 1; + } else { + // Resets app setOrientationRequest counter if timed out + mSetOrientationRequestCounter = 0; } - return false; + // Update time last called + mTimeMsLastSetOrientationRequest = currTimeMs; + + return mSetOrientationRequestCounter >= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP + && !mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio(); + } + + @VisibleForTesting + int getSetOrientationRequestCounter() { + return mSetOrientationRequestCounter; } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 693f1ced78dd..23a3d520ea3e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -21,6 +21,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FOR import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; @@ -46,6 +47,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.LetterboxUiController.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; +import static com.android.server.wm.LetterboxUiController.SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -185,6 +188,69 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test + public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() { + doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + // Request 3 times to simulate orientation request loop + for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, + /* expectedCount */ 0); + } + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_isLetterboxed_returnsFalse() { + doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + // Request 3 times to simulate orientation request loop + for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, + /* expectedCount */ i); + } + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_noLoop_returnsFalse() { + doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + // No orientation request loop + assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, + /* expectedCount */ 0); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_timeout_returnsFalse() + throws InterruptedException { + doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + for (int i = MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i > 0; i--) { + assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, + /* expectedCount */ 0); + Thread.sleep(SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS); + } + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_returnsTrue() { + doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + for (int i = 0; i < MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, + /* expectedCount */ i); + } + assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ true, + /* expectedCount */ MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP); + } + + private void assertShouldIgnoreOrientationRequestLoop(boolean shouldIgnore, int expectedCount) { + if (shouldIgnore) { + assertTrue(mController.shouldIgnoreOrientationRequestLoop()); + } else { + assertFalse(mController.shouldIgnoreOrientationRequestLoop()); + } + assertEquals(expectedCount, mController.getSetOrientationRequestCounter()); + } + + @Test @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); |