From 46dc79bb44b17ac594a538ce1a57d009e7809133 Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Wed, 28 Dec 2022 21:07:31 +0000 Subject: [PATCH] Per-app compat treatment for setRequestedOrientation loops. Per-package controls introduced in this change: - OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION for device manufactures to opt in a package into the treatment. - android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION for app developers to opt in/out from the treatment via a component property. When enabled for the package and on the devices, treatment ignores app requested orientation in response to an app calling Activity#setRequestedOrientation if app is currently relaunching or has an active Camera Compat treatment. This is needed to avoid getting into Activity#setRequestedOrientation loop when ignoreOrientationRequest is enabled. For example, the loop can look like this: 1) App sets default orientation to "unspecified" at runtime 2) App requests to "portrait" after checking some condition (e.g. display rotation). 3) (2) leads to fullscreen -> letterboxed bounds change and activity relaunch because app can't handle the corresponding config changes. 4) Loop goes back to (1) Fix: 254631730 Test: atest WmTests:LetterboxUiControllerTest Change-Id: I3dc88db4193d08215c36d9a69ef2cc53155b60c7 --- .../java/android/content/pm/ActivityInfo.java | 13 ++ core/java/android/view/WindowManager.java | 39 ++++ core/res/res/values/config.xml | 5 + core/res/res/values/symbols.xml | 1 + .../com/android/server/wm/ActivityRecord.java | 25 ++- .../wm/DisplayRotationCompatPolicy.java | 2 +- .../server/wm/LetterboxConfiguration.java | 21 ++- .../server/wm/LetterboxUiController.java | 97 ++++++++++ .../server/wm/LetterboxUiControllerTest.java | 173 ++++++++++++++++++ 9 files changed, 368 insertions(+), 8 deletions(-) create mode 100644 services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 06399b9581599..9e5e8deda84b2 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1021,6 +1021,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface SizeChangesSupportMode {} + /** + * This change id enables compat policy that ignores app requested orientation in + * response to an app calling {@link android.app.Activity#setRequestedOrientation}. See + * com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation for + * details. + * @hide + */ + @ChangeId + @Overridable + @Disabled + public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION = + 254631730L; // 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 diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 2f779010be0e8..ed9cb00db290d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -813,6 +813,45 @@ public interface WindowManager extends ViewManager { int SCREENSHOT_VENDOR_GESTURE = 6; } + /** + * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the activity can be opted-in or opted-out + * from the compatibility treatment that avoids {@link + * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by + * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural + * orientation of the device. + * + *

The treatment is disabled by default but device manufacturers can enable the treatment + * using their discretion to improve display compatibility. + * + *

With this property set to {@code true}, the system could ignore {@link + * android.app.Activity#setRequestedOrientation} call from an app if one of the following + * conditions are true: + *

+ * + *

Setting this property to {@code false} informs the system that the activity must be + * opted-out from the compatibility treatment even if the device manufacturer has opted the app + * into the treatment. + * + *

Syntax: + *

+     * <activity>
+     *   <property
+     *     android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"
+     *     android:value="true|false"/>
+     * </activity>
+     * 
+ * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = + "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; + /** * @hide */ diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a8c2246ef9660..f7187c466799b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5328,6 +5328,11 @@ If given value is outside of this range, the option 0 (top) is assummed. --> 0 + + false + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5c3e1d69c6b51..368ef960f9383 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4457,6 +4457,7 @@ + diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c712167c4cd1a..9215cabad25a3 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1456,8 +1456,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updatePictureInPictureMode(null, false); } else { mLastReportedMultiWindowMode = inMultiWindowMode; - ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS, - false /* ignoreVisibility */); + ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS); } } } @@ -3988,6 +3987,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void finishRelaunching() { + mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(false); mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this); if (mPendingRelaunchCount > 0) { @@ -7745,13 +7745,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void setRequestedOrientation(int requestedOrientation) { + if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) { + return; + } setOrientation(requestedOrientation, this); // Push the new configuration to the requested app in case where it's not pushed, e.g. when // the request is handled at task level with letterbox. if (!getMergedOverrideConfiguration().equals( mLastReportedConfiguration.getMergedConfiguration())) { - ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); + ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */, + false /* ignoreVisibility */, true /* isRequestedOrientationChanged */); } mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( @@ -9052,7 +9056,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) { return ensureActivityConfiguration(globalChanges, preserveWindow, - false /* ignoreVisibility */); + false /* ignoreVisibility */, false /* isRequestedOrientationChanged */); + } + + boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow, + boolean ignoreVisibility) { + return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility, + false /* isRequestedOrientationChanged */); } /** @@ -9066,11 +9076,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * (stopped state). This is useful for the case where we know the * activity will be visible soon and we want to ensure its configuration * before we make it visible. + * @param isRequestedOrientationChanged whether this is triggered in response to an app calling + * {@link android.app.Activity#setRequestedOrientation}. * @return False if the activity was relaunched and true if it wasn't relaunched because we * can't or the app handles the specific configuration that is changing. */ boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow, - boolean ignoreVisibility) { + boolean ignoreVisibility, boolean isRequestedOrientationChanged) { final Task rootTask = getRootTask(); if (rootTask.mConfigWillChange) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check " @@ -9194,6 +9206,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { mRelaunchReason = RELAUNCH_REASON_NONE; } + if (isRequestedOrientationChanged) { + mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(true); + } if (mState == PAUSING) { // A little annoying: we are waiting for this activity to finish pausing. Let's not // do anything now, but just flag that it needs to be restarted when done pausing. diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 7266d2194779e..ba0413df63252 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -287,7 +287,7 @@ final class DisplayRotationCompatPolicy { *
  • The activity has fixed orientation but not "locked" or "nosensor" one. * */ - private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) { + boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) { return activity != null && !activity.inMultiWindowMode() && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED // "locked" and "nosensor" values are often used by camera apps that can't diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 9b84233272157..642732652ce9a 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -222,6 +222,11 @@ final class LetterboxConfiguration { // See RefreshCallbackItem for context. private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true; + // Whether should ignore app requested orientation in response to an app + // calling Activity#setRequestedOrientation. See + // LetterboxUiController#shouldIgnoreRequestedOrientation for details. + private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled; + LetterboxConfiguration(Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext, () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext, @@ -274,10 +279,13 @@ final class LetterboxConfiguration { R.bool.config_letterboxIsEnabledForTranslucentActivities); mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean( R.bool.config_isWindowManagerCameraCompatTreatmentEnabled); + mIsCompatFakeFocusEnabled = mContext.getResources().getBoolean( + R.bool.config_isCompatFakeFocusEnabled); + mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean( + R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled); + mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); - mIsCompatFakeFocusEnabled = mContext.getResources() - .getBoolean(R.bool.config_isCompatFakeFocusEnabled); } /** @@ -1034,6 +1042,15 @@ final class LetterboxConfiguration { mIsCompatFakeFocusEnabled = enabled; } + /** + * Whether should ignore app requested orientation in response to an app calling + * {@link android.app.Activity#setRequestedOrientation}. See {@link + * LetterboxUiController#shouldIgnoreRequestedOrientation} for details. + */ + boolean isPolicyForIgnoringRequestedOrientationEnabled() { + return mIsPolicyForIgnoringRequestedOrientationEnabled; + } + /** Whether camera compatibility treatment is enabled. */ boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) { return mIsCameraCompatTreatmentEnabled diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index fd7e082beed4a..0c8a6453e6fb6 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,10 +17,13 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER; @@ -55,6 +58,8 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; +import android.content.pm.ActivityInfo.ScreenOrientation; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; @@ -74,6 +79,7 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.PrintWriter; +import java.util.function.BooleanSupplier; /** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ // TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in @@ -131,12 +137,37 @@ final class LetterboxUiController { // DisplayRotationCompatPolicy. private boolean mIsRefreshAfterRotationRequested; + @Nullable + private final Boolean mBooleanPropertyIgnoreRequestedOrientation; + + private boolean mIsRelauchingAfterRequestedOrientationChanged; + LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { mLetterboxConfiguration = wmService.mLetterboxConfiguration; // Given activityRecord may not be fully constructed since LetterboxUiController // is created in its constructor. It shouldn't be used in this constructor but it's safe // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; + + PackageManager packageManager = wmService.mContext.getPackageManager(); + mBooleanPropertyIgnoreRequestedOrientation = + readComponentProperty(packageManager, mActivityRecord.packageName, + mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, + PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + } + + @Nullable + private static Boolean readComponentProperty(PackageManager packageManager, String packageName, + BooleanSupplier gatingCondition, String propertyName) { + if (!gatingCondition.getAsBoolean()) { + return null; + } + try { + return packageManager.getProperty(propertyName, packageName).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + // No such property name. + } + return null; } /** Cleans up {@link Letterbox} if it exists.*/ @@ -153,6 +184,72 @@ final class LetterboxUiController { } } + /** + * Whether should ignore app requested orientation in response to an app + * calling {@link android.app.Activity#setRequestedOrientation}. + * + *

    This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation} + * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has + * landscape natural orientation which app developers don't expect. For example, the loop can + * look like this: + *

      + *
    1. App sets default orientation to "unspecified" at runtime + *
    2. App requests to "portrait" after checking some condition (e.g. display rotation). + *
    3. (2) leads to fullscreen -> letterboxed bounds change and activity relaunch because + * app can't handle the corresponding config changes. + *
    4. Loop goes back to (1) + *
    + * + *

    This treatment is enabled when the following conditions are met: + *

      + *
    • Flag gating the treatment is enabled + *
    • Opt-out component property isn't enabled + *
    • Opt-in component property or per-app override are enabled + *
    • Activity is relaunched after {@link android.app.Activity#setRequestedOrientation} + * call from an app or camera compat force rotation treatment is active for the activity. + *
    + */ + boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { + if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) { + return false; + } + if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) { + return false; + } + if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation) + && !mActivityRecord.info.isChangeEnabled( + OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) { + 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; + } + return false; + } + + /** + * Sets whether an activity is relaunching after the app has called {@link + * android.app.Activity#setRequestedOrientation}. + */ + void setRelauchingAfterRequestedOrientationChanged(boolean isRelaunching) { + mIsRelauchingAfterRequestedOrientationChanged = isRelaunching; + } + /** * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked} * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}. diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java new file mode 100644 index 0000000000000..6d778afee88cc --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; + +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.spyOn; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; + +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.Property; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + + /** + * Test class for {@link LetterboxUiControllerTest}. + * + * Build/Install/Run: + * atest WmTests:LetterboxUiControllerTest + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class LetterboxUiControllerTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + private ActivityRecord mActivity; + private DisplayContent mDisplayContent; + private LetterboxUiController mController; + private LetterboxConfiguration mLetterboxConfiguration; + + @Before + public void setUp() throws Exception { + mActivity = setUpActivityWithComponent(); + + mLetterboxConfiguration = mWm.mLetterboxConfiguration; + spyOn(mLetterboxConfiguration); + + mController = new LetterboxUiController(mWm, mActivity); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() { + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() { + doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean()); + + // Recreate DisplayContent with DisplayRotationCompatPolicy + mActivity = setUpActivityWithComponent(); + mController = new LetterboxUiController(mWm, mActivity); + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + mController.setRelauchingAfterRequestedOrientationChanged(false); + + spyOn(mDisplayContent.mDisplayRotationCompatPolicy); + doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) + .isTreatmentEnabledForActivity(eq(mActivity)); + + assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() { + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ true); + mController = new LetterboxUiController(mWm, mActivity); + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + + assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { + prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); + doReturn(false).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + + assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); + } + + private void mockThatProperty(String propertyName, boolean value) throws Exception { + Property property = new Property(propertyName, /* value */ value, /* packageName */ "", + /* className */ ""); + PackageManager pm = mWm.mContext.getPackageManager(); + spyOn(pm); + doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); + } + + private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + mController.setRelauchingAfterRequestedOrientationChanged(true); + } + + private ActivityRecord setUpActivityWithComponent() { + mDisplayContent = new TestDisplayContent + .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); + Task task = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setOnTop(true) + .setTask(task) + // Set the component to be that of the test class in order to enable compat changes + .setComponent(ComponentName.createRelative(mContext, + com.android.server.wm.LetterboxUiControllerTest.class.getName())) + .build(); + return activity; + } +}