Add a way to disable auto rotation for immersive apps
Provide a way to always show a rotation button instead of auto rotating for immersive applications when the following conditions are met: 1) Top activity requests to hide status and navigation bars 2) Top activity is fullscreen and in optimal orientation (without letterboxing) 3) Rotation will lead to letterboxing due to fixed orientation. 4) ignoreOrientationRequest is enabled and config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled is enabled This is needed because immersive apps, such as games, are often not optimized for all orientations and can have a poor UX when rotated. Additionally, some games relying on sensors for the gameplay so users can trigger such rotations accidentally when auto rotation is on. Test: atest LetterboxConfigurationDeviceConfigTests DisplayRotationCompatPolicyForImmersiveAppsTests Bug: 251404186 Change-Id: I0b5d8f666b8bec9bd9d6fb5303986d3c288e283f
This commit is contained in:
@@ -5245,6 +5245,14 @@
|
|||||||
having a separating hinge. -->
|
having a separating hinge. -->
|
||||||
<bool name="config_isDisplayHingeAlwaysSeparating">false</bool>
|
<bool name="config_isDisplayHingeAlwaysSeparating">false</bool>
|
||||||
|
|
||||||
|
<!-- Whether enabling rotation compat policy for immersive apps that prevents auto rotation
|
||||||
|
into non-optimal screen orientation while in fullscreen. This is needed because immersive
|
||||||
|
apps, such as games, are often not optimized for all orientations and can have a poor UX
|
||||||
|
when rotated. Additionally, some games rely on sensors for the gameplay so users can
|
||||||
|
trigger such rotations accidentally when auto rotation is on.
|
||||||
|
Applicable only if ignoreOrientationRequest is enabled. -->
|
||||||
|
<bool name="config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled">false</bool>
|
||||||
|
|
||||||
<!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored.
|
<!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored.
|
||||||
Note: Activity min/max aspect ratio restrictions will still be respected.
|
Note: Activity min/max aspect ratio restrictions will still be respected.
|
||||||
Therefore this override can control the maximum screen area that can be occupied by
|
Therefore this override can control the maximum screen area that can be occupied by
|
||||||
|
|||||||
@@ -4413,6 +4413,7 @@
|
|||||||
<java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
|
<java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
|
||||||
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
|
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
|
||||||
|
|
||||||
|
<java-symbol type="bool" name="config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled" />
|
||||||
<java-symbol type="dimen" name="config_fixedOrientationLetterboxAspectRatio" />
|
<java-symbol type="dimen" name="config_fixedOrientationLetterboxAspectRatio" />
|
||||||
<java-symbol type="dimen" name="config_letterboxBackgroundWallpaperBlurRadius" />
|
<java-symbol type="dimen" name="config_letterboxBackgroundWallpaperBlurRadius" />
|
||||||
<java-symbol type="integer" name="config_letterboxActivityCornersRadius" />
|
<java-symbol type="integer" name="config_letterboxActivityCornersRadius" />
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ public class RotationButtonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setRotationLockedAtAngle(int rotationSuggestion) {
|
public void setRotationLockedAtAngle(int rotationSuggestion) {
|
||||||
RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
|
RotationPolicy.setRotationLockAtAngle(mContext, /* enabled= */ isRotationLocked(),
|
||||||
|
/* rotation= */ rotationSuggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRotationLocked() {
|
public boolean isRotationLocked() {
|
||||||
|
|||||||
@@ -6024,6 +6024,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
ActivityRecord topRunningActivity() {
|
ActivityRecord topRunningActivity() {
|
||||||
return topRunningActivity(false /* considerKeyguardState */);
|
return topRunningActivity(false /* considerKeyguardState */);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ public class DisplayRotation {
|
|||||||
private final DisplayWindowSettings mDisplayWindowSettings;
|
private final DisplayWindowSettings mDisplayWindowSettings;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final Object mLock;
|
private final Object mLock;
|
||||||
|
@Nullable
|
||||||
|
private final DisplayRotationImmersiveAppCompatPolicy mCompatPolicyForImmersiveApps;
|
||||||
|
|
||||||
public final boolean isDefaultDisplay;
|
public final boolean isDefaultDisplay;
|
||||||
private final boolean mSupportAutoRotation;
|
private final boolean mSupportAutoRotation;
|
||||||
@@ -205,7 +207,7 @@ public class DisplayRotation {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag to indicate if the display rotation should be fixed to user specified rotation
|
* A flag to indicate if the display rotation should be fixed to user specified rotation
|
||||||
* regardless of all other states (including app requrested orientation). {@code true} the
|
* regardless of all other states (including app requested orientation). {@code true} the
|
||||||
* display rotation should be fixed to user specified rotation, {@code false} otherwise.
|
* display rotation should be fixed to user specified rotation, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
private int mFixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
|
private int mFixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
|
||||||
@@ -232,6 +234,7 @@ public class DisplayRotation {
|
|||||||
mContext = context;
|
mContext = context;
|
||||||
mLock = lock;
|
mLock = lock;
|
||||||
isDefaultDisplay = displayContent.isDefaultDisplay;
|
isDefaultDisplay = displayContent.isDefaultDisplay;
|
||||||
|
mCompatPolicyForImmersiveApps = initImmersiveAppCompatPolicy(service, displayContent);
|
||||||
|
|
||||||
mSupportAutoRotation =
|
mSupportAutoRotation =
|
||||||
mContext.getResources().getBoolean(R.bool.config_supportAutoRotation);
|
mContext.getResources().getBoolean(R.bool.config_supportAutoRotation);
|
||||||
@@ -255,6 +258,14 @@ public class DisplayRotation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
|
||||||
|
WindowManagerService service, DisplayContent displayContent) {
|
||||||
|
return DisplayRotationImmersiveAppCompatPolicy.createIfNeeded(
|
||||||
|
service.mLetterboxConfiguration, this, displayContent);
|
||||||
|
}
|
||||||
|
|
||||||
// Change the default value to the value specified in the sysprop
|
// Change the default value to the value specified in the sysprop
|
||||||
// ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
|
// ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
|
||||||
// ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
|
// ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
|
||||||
@@ -1305,11 +1316,11 @@ public class DisplayRotation {
|
|||||||
return mAllowAllRotations;
|
return mAllowAllRotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLandscapeOrSeascape(int rotation) {
|
boolean isLandscapeOrSeascape(@Surface.Rotation final int rotation) {
|
||||||
return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
|
return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAnyPortrait(int rotation) {
|
boolean isAnyPortrait(@Surface.Rotation final int rotation) {
|
||||||
return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
|
return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1348,9 +1359,16 @@ public class DisplayRotation {
|
|||||||
return mFoldController != null && mFoldController.overrideFrozenRotation();
|
return mFoldController != null && mFoldController.overrideFrozenRotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRotationChoicePossible(int orientation) {
|
private boolean isRotationChoiceAllowed(@Surface.Rotation final int proposedRotation) {
|
||||||
// Rotation choice is only shown when the user is in locked mode.
|
final boolean isRotationLockEnforced = mCompatPolicyForImmersiveApps != null
|
||||||
if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false;
|
&& mCompatPolicyForImmersiveApps.isRotationLockEnforced(proposedRotation);
|
||||||
|
|
||||||
|
// Don't show rotation choice button if
|
||||||
|
if (!isRotationLockEnforced // not enforcing locked rotation
|
||||||
|
// and the screen rotation is not locked by the user.
|
||||||
|
&& mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't show rotation choice if we are in tabletop or book modes.
|
// Don't show rotation choice if we are in tabletop or book modes.
|
||||||
if (isTabletopAutoRotateOverrideEnabled()) return false;
|
if (isTabletopAutoRotateOverrideEnabled()) return false;
|
||||||
@@ -1402,7 +1420,7 @@ public class DisplayRotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that some rotation choice is possible for the given orientation.
|
// Ensure that some rotation choice is possible for the given orientation.
|
||||||
switch (orientation) {
|
switch (mCurrentAppOrientation) {
|
||||||
case ActivityInfo.SCREEN_ORIENTATION_FULL_USER:
|
case ActivityInfo.SCREEN_ORIENTATION_FULL_USER:
|
||||||
case ActivityInfo.SCREEN_ORIENTATION_USER:
|
case ActivityInfo.SCREEN_ORIENTATION_USER:
|
||||||
case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
|
case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
|
||||||
@@ -1719,11 +1737,11 @@ public class DisplayRotation {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProposedRotationChanged(int rotation) {
|
public void onProposedRotationChanged(@Surface.Rotation int rotation) {
|
||||||
ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation);
|
ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation);
|
||||||
// Send interaction power boost to improve redraw performance.
|
// Send interaction power boost to improve redraw performance.
|
||||||
mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
|
mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
|
||||||
if (isRotationChoicePossible(mCurrentAppOrientation)) {
|
if (isRotationChoiceAllowed(rotation)) {
|
||||||
final boolean isValid = isValidRotationChoice(rotation);
|
final boolean isValid = isValidRotationChoice(rotation);
|
||||||
sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
|
sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.res.Configuration.Orientation;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.WindowInsets.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy to decide whether to enforce screen rotation lock for optimisation of the screen rotation
|
||||||
|
* user experience for immersive applications for compatibility when ignoring orientation request.
|
||||||
|
*
|
||||||
|
* <p>This is needed because immersive apps, such as games, are often not optimized for all
|
||||||
|
* orientations and can have a poor UX when rotated (e.g., state loss or entering size-compat mode).
|
||||||
|
* Additionally, some games rely on sensors for the gameplay so users can trigger such rotations
|
||||||
|
* accidentally when auto rotation is on.
|
||||||
|
*/
|
||||||
|
final class DisplayRotationImmersiveAppCompatPolicy {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static DisplayRotationImmersiveAppCompatPolicy createIfNeeded(
|
||||||
|
@NonNull final LetterboxConfiguration letterboxConfiguration,
|
||||||
|
@NonNull final DisplayRotation displayRotation,
|
||||||
|
@NonNull final DisplayContent displayContent) {
|
||||||
|
if (!letterboxConfiguration
|
||||||
|
.isDisplayRotationImmersiveAppCompatPolicyEnabled(/* checkDeviceConfig */ false)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DisplayRotationImmersiveAppCompatPolicy(
|
||||||
|
letterboxConfiguration, displayRotation, displayContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DisplayRotation mDisplayRotation;
|
||||||
|
private final LetterboxConfiguration mLetterboxConfiguration;
|
||||||
|
private final DisplayContent mDisplayContent;
|
||||||
|
|
||||||
|
private DisplayRotationImmersiveAppCompatPolicy(
|
||||||
|
@NonNull final LetterboxConfiguration letterboxConfiguration,
|
||||||
|
@NonNull final DisplayRotation displayRotation,
|
||||||
|
@NonNull final DisplayContent displayContent) {
|
||||||
|
mDisplayRotation = displayRotation;
|
||||||
|
mLetterboxConfiguration = letterboxConfiguration;
|
||||||
|
mDisplayContent = displayContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether it is necessary to lock screen rotation, preventing auto rotation, based on
|
||||||
|
* the top activity configuration and proposed screen rotation.
|
||||||
|
*
|
||||||
|
* <p>This is needed because immersive apps, such as games, are often not optimized for all
|
||||||
|
* orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
|
||||||
|
* for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
|
||||||
|
*
|
||||||
|
* <p>Screen rotation is locked when the following conditions are met:
|
||||||
|
* <ul>
|
||||||
|
* <li>Top activity requests to hide status and navigation bars
|
||||||
|
* <li>Top activity is fullscreen and in optimal orientation (without letterboxing)
|
||||||
|
* <li>Rotation will lead to letterboxing due to fixed orientation.
|
||||||
|
* <li>{@link DisplayContent#getIgnoreOrientationRequest} is {@code true}
|
||||||
|
* <li>This policy is enabled on the device, for details see
|
||||||
|
* {@link LetterboxConfiguration#isDisplayRotationImmersiveAppCompatPolicyEnabled}
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param proposedRotation new proposed {@link Surface.Rotation} for the screen.
|
||||||
|
* @return {@code true}, if there is a need to lock screen rotation, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
boolean isRotationLockEnforced(@Surface.Rotation final int proposedRotation) {
|
||||||
|
if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
|
||||||
|
/* checkDeviceConfig */ true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
synchronized (mDisplayContent.mWmService.mGlobalLock) {
|
||||||
|
return isRotationLockEnforcedLocked(proposedRotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRotationLockEnforcedLocked(@Surface.Rotation final int proposedRotation) {
|
||||||
|
if (!mDisplayContent.getIgnoreOrientationRequest()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ActivityRecord activityRecord = mDisplayContent.topRunningActivity();
|
||||||
|
if (activityRecord == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't lock screen rotation if an activity hasn't requested to hide system bars.
|
||||||
|
if (!hasRequestedToHideStatusAndNavBars(activityRecord)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't lock screen rotation if activity is not in fullscreen. Checking windowing mode
|
||||||
|
// for a task rather than an activity to exclude activity embedding scenario.
|
||||||
|
if (activityRecord.getTask() == null
|
||||||
|
|| activityRecord.getTask().getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't lock screen rotation if activity is letterboxed.
|
||||||
|
if (activityRecord.areBoundsLetterboxed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activityRecord.getRequestedConfigurationOrientation() == ORIENTATION_UNDEFINED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock screen rotation only if, after rotation the activity's orientation won't match
|
||||||
|
// the screen orientation, forcing the activity to enter letterbox mode after rotation.
|
||||||
|
return activityRecord.getRequestedConfigurationOrientation()
|
||||||
|
!= surfaceRotationToConfigurationOrientation(proposedRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether activity has requested to hide status and navigation bars.
|
||||||
|
*/
|
||||||
|
private boolean hasRequestedToHideStatusAndNavBars(@NonNull ActivityRecord activity) {
|
||||||
|
WindowState mainWindow = activity.findMainWindow();
|
||||||
|
if (mainWindow == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (mainWindow.getRequestedVisibleTypes()
|
||||||
|
& (Type.statusBars() | Type.navigationBars())) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Orientation
|
||||||
|
private int surfaceRotationToConfigurationOrientation(@Surface.Rotation final int rotation) {
|
||||||
|
if (mDisplayRotation.isAnyPortrait(rotation)) {
|
||||||
|
return ORIENTATION_PORTRAIT;
|
||||||
|
} else if (mDisplayRotation.isLandscapeOrSeascape(rotation)) {
|
||||||
|
return ORIENTATION_LANDSCAPE;
|
||||||
|
} else {
|
||||||
|
return ORIENTATION_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ package com.android.server.wm;
|
|||||||
|
|
||||||
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
|
||||||
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
|
||||||
|
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
@@ -227,23 +228,35 @@ final class LetterboxConfiguration {
|
|||||||
// LetterboxUiController#shouldIgnoreRequestedOrientation for details.
|
// LetterboxUiController#shouldIgnoreRequestedOrientation for details.
|
||||||
private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled;
|
private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled;
|
||||||
|
|
||||||
LetterboxConfiguration(Context systemUiContext) {
|
// Whether enabling rotation compat policy for immersive apps that prevents auto rotation
|
||||||
this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
|
// into non-optimal screen orientation while in fullscreen. This is needed because immersive
|
||||||
() -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
|
// apps, such as games, are often not optimized for all orientations and can have a poor UX
|
||||||
/* forBookMode */ false),
|
// when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
|
||||||
() -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
|
// such rotations accidentally when auto rotation is on.
|
||||||
/* forTabletopMode */ false),
|
private final boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
|
||||||
() -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
|
|
||||||
/* forBookMode */ true),
|
// Flags dynamically updated with {@link android.provider.DeviceConfig}.
|
||||||
() -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
|
@NonNull private final LetterboxConfigurationDeviceConfig mDeviceConfig;
|
||||||
/* forTabletopMode */ true)
|
|
||||||
));
|
LetterboxConfiguration(@NonNull final Context systemUiContext) {
|
||||||
|
this(systemUiContext,
|
||||||
|
new LetterboxConfigurationPersister(systemUiContext,
|
||||||
|
() -> readLetterboxHorizontalReachabilityPositionFromConfig(
|
||||||
|
systemUiContext, /* forBookMode */ false),
|
||||||
|
() -> readLetterboxVerticalReachabilityPositionFromConfig(
|
||||||
|
systemUiContext, /* forTabletopMode */ false),
|
||||||
|
() -> readLetterboxHorizontalReachabilityPositionFromConfig(
|
||||||
|
systemUiContext, /* forBookMode */ true),
|
||||||
|
() -> readLetterboxVerticalReachabilityPositionFromConfig(
|
||||||
|
systemUiContext, /* forTabletopMode */ true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
LetterboxConfiguration(Context systemUiContext,
|
LetterboxConfiguration(@NonNull final Context systemUiContext,
|
||||||
LetterboxConfigurationPersister letterboxConfigurationPersister) {
|
@NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) {
|
||||||
mContext = systemUiContext;
|
mContext = systemUiContext;
|
||||||
|
mDeviceConfig = new LetterboxConfigurationDeviceConfig(systemUiContext.getMainExecutor());
|
||||||
|
|
||||||
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
|
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
|
||||||
R.dimen.config_fixedOrientationLetterboxAspectRatio);
|
R.dimen.config_fixedOrientationLetterboxAspectRatio);
|
||||||
mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
|
mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
|
||||||
@@ -284,6 +297,12 @@ final class LetterboxConfiguration {
|
|||||||
mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
|
mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
|
||||||
R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
|
R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
|
||||||
|
|
||||||
|
mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
|
||||||
|
R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
|
||||||
|
mDeviceConfig.updateFlagActiveStatus(
|
||||||
|
/* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
|
||||||
|
/* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
|
||||||
|
|
||||||
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
|
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
|
||||||
mLetterboxConfigurationPersister.start();
|
mLetterboxConfigurationPersister.start();
|
||||||
}
|
}
|
||||||
@@ -1105,4 +1124,20 @@ final class LetterboxConfiguration {
|
|||||||
mIsCameraCompatRefreshCycleThroughStopEnabled = true;
|
mIsCameraCompatRefreshCycleThroughStopEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether rotation compat policy for immersive apps that prevents auto rotation
|
||||||
|
* into non-optimal screen orientation while in fullscreen is enabled.
|
||||||
|
*
|
||||||
|
* <p>This is needed because immersive apps, such as games, are often not optimized for all
|
||||||
|
* orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
|
||||||
|
* for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
|
||||||
|
*
|
||||||
|
* @param checkDeviceConfig whether should check both static config and a dynamic property
|
||||||
|
* from {@link DeviceConfig} or only static value.
|
||||||
|
*/
|
||||||
|
boolean isDisplayRotationImmersiveAppCompatPolicyEnabled(final boolean checkDeviceConfig) {
|
||||||
|
return mIsDisplayRotationImmersiveAppCompatPolicyEnabled && (!checkDeviceConfig
|
||||||
|
|| mDeviceConfig.getFlag(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.annotation.NonNull;
|
||||||
|
import android.provider.DeviceConfig;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class that caches {@link DeviceConfig} flags for app compat features and listens
|
||||||
|
* to updates by implementing {@link DeviceConfig.OnPropertiesChangedListener}.
|
||||||
|
*/
|
||||||
|
final class LetterboxConfigurationDeviceConfig
|
||||||
|
implements DeviceConfig.OnPropertiesChangedListener {
|
||||||
|
|
||||||
|
static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
|
||||||
|
"enable_display_rotation_immersive_app_compat_policy";
|
||||||
|
private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
|
||||||
|
true;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
|
||||||
|
KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
|
||||||
|
DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY
|
||||||
|
);
|
||||||
|
|
||||||
|
// Whether enabling rotation compat policy for immersive apps that prevents auto rotation
|
||||||
|
// into non-optimal screen orientation while in fullscreen. This is needed because immersive
|
||||||
|
// apps, such as games, are often not optimized for all orientations and can have a poor UX
|
||||||
|
// when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
|
||||||
|
// such rotations accidentally when auto rotation is on.
|
||||||
|
private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
|
||||||
|
DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
|
||||||
|
|
||||||
|
// Set of active device configs that need to be updated in
|
||||||
|
// DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
|
||||||
|
private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
|
||||||
|
|
||||||
|
LetterboxConfigurationDeviceConfig(@NonNull final Executor executor) {
|
||||||
|
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
|
||||||
|
executor, /* onPropertiesChangedListener */ this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPropertiesChanged(@NonNull final DeviceConfig.Properties properties) {
|
||||||
|
for (int i = mActiveDeviceConfigsSet.size() - 1; i >= 0; i--) {
|
||||||
|
String key = mActiveDeviceConfigsSet.valueAt(i);
|
||||||
|
// Reads the new configuration, if the device config properties contain the key.
|
||||||
|
if (properties.getKeyset().contains(key)) {
|
||||||
|
readAndSaveValueFromDeviceConfig(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds {@code key} to a set of flags that can be updated from the server if
|
||||||
|
* {@code isActive} is {@code true} and read it's current value from {@link DeviceConfig}.
|
||||||
|
*/
|
||||||
|
void updateFlagActiveStatus(boolean isActive, String key) {
|
||||||
|
if (!isActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mActiveDeviceConfigsSet.add(key);
|
||||||
|
readAndSaveValueFromDeviceConfig(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns values of the {@code key} flag.
|
||||||
|
*
|
||||||
|
* @throws AssertionError {@code key} isn't recognised.
|
||||||
|
*/
|
||||||
|
boolean getFlag(String key) {
|
||||||
|
switch (key) {
|
||||||
|
case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
|
||||||
|
return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unexpected flag name: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readAndSaveValueFromDeviceConfig(String key) {
|
||||||
|
Boolean defaultValue = sKeyToDefaultValueMap.get(key);
|
||||||
|
if (defaultValue == null) {
|
||||||
|
throw new AssertionError("Haven't found default value for flag: " + key);
|
||||||
|
}
|
||||||
|
switch (key) {
|
||||||
|
case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
|
||||||
|
mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
|
||||||
|
getDeviceConfig(key, defaultValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unexpected flag name: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean getDeviceConfig(String key, boolean defaultValue) {
|
||||||
|
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
|
||||||
|
key, defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,6 +68,10 @@ android_test {
|
|||||||
"android.test.runner",
|
"android.test.runner",
|
||||||
],
|
],
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"modules-utils-testable-device-config-defaults",
|
||||||
|
],
|
||||||
|
|
||||||
// These are not normally accessible from apps so they must be explicitly included.
|
// These are not normally accessible from apps so they must be explicitly included.
|
||||||
jni_libs: [
|
jni_libs: [
|
||||||
"libdexmakerjvmtiagent",
|
"libdexmakerjvmtiagent",
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||||
|
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||||
|
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||||
|
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
||||||
|
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
|
||||||
|
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.WindowInsets.Type;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for {@link DisplayRotationImmersiveAppCompatPolicy}.
|
||||||
|
*
|
||||||
|
* Build/Install/Run:
|
||||||
|
* atest WmTests:DisplayRotationImmersiveAppCompatPolicyTests
|
||||||
|
*/
|
||||||
|
@SmallTest
|
||||||
|
@Presubmit
|
||||||
|
@RunWith(WindowTestRunner.class)
|
||||||
|
public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBase {
|
||||||
|
|
||||||
|
private DisplayRotationImmersiveAppCompatPolicy mPolicy;
|
||||||
|
|
||||||
|
private LetterboxConfiguration mMockLetterboxConfiguration;
|
||||||
|
private ActivityRecord mMockActivityRecord;
|
||||||
|
private Task mMockTask;
|
||||||
|
private WindowState mMockWindowState;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
mMockActivityRecord = mock(ActivityRecord.class);
|
||||||
|
mMockTask = mock(Task.class);
|
||||||
|
when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_FULLSCREEN);
|
||||||
|
when(mMockActivityRecord.getTask()).thenReturn(mMockTask);
|
||||||
|
when(mMockActivityRecord.areBoundsLetterboxed()).thenReturn(false);
|
||||||
|
when(mMockActivityRecord.getRequestedConfigurationOrientation()).thenReturn(
|
||||||
|
ORIENTATION_LANDSCAPE);
|
||||||
|
mMockWindowState = mock(WindowState.class);
|
||||||
|
when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(0);
|
||||||
|
when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState);
|
||||||
|
|
||||||
|
spy(mDisplayContent);
|
||||||
|
doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity();
|
||||||
|
when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);
|
||||||
|
|
||||||
|
mMockLetterboxConfiguration = mock(LetterboxConfiguration.class);
|
||||||
|
when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
|
||||||
|
/* checkDeviceConfig */ anyBoolean())).thenReturn(true);
|
||||||
|
|
||||||
|
mPolicy = DisplayRotationImmersiveAppCompatPolicy.createIfNeeded(
|
||||||
|
mMockLetterboxConfiguration, createDisplayRotationMock(),
|
||||||
|
mDisplayContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DisplayRotation createDisplayRotationMock() {
|
||||||
|
DisplayRotation mockDisplayRotation = mock(DisplayRotation.class);
|
||||||
|
|
||||||
|
when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_0)).thenReturn(true);
|
||||||
|
when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_90)).thenReturn(false);
|
||||||
|
when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_180)).thenReturn(true);
|
||||||
|
when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_270)).thenReturn(false);
|
||||||
|
when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_0)).thenReturn(false);
|
||||||
|
when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_90)).thenReturn(true);
|
||||||
|
when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_180)).thenReturn(false);
|
||||||
|
when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_270)).thenReturn(true);
|
||||||
|
|
||||||
|
return mockDisplayRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_landscapeActivity_lockedWhenRotatingToPortrait() {
|
||||||
|
// Base case: App is optimal in Landscape.
|
||||||
|
|
||||||
|
// ROTATION_* is the target display orientation counted from the natural display
|
||||||
|
// orientation. Outside of test environment, ROTATION_0 means that proposed display
|
||||||
|
// rotation is the natural device orientation.
|
||||||
|
// DisplayRotationImmersiveAppCompatPolicy assesses whether the proposed target
|
||||||
|
// orientation ROTATION_* is optimal for the top fullscreen activity or not.
|
||||||
|
// For instance, ROTATION_0 means portrait screen orientation (see
|
||||||
|
// createDisplayRotationMock) which isn't optimal for a landscape-only activity so
|
||||||
|
// we should show a rotation suggestion button instead of rotating directly.
|
||||||
|
|
||||||
|
// Rotation to portrait
|
||||||
|
assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_0));
|
||||||
|
// Rotation to landscape
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_90));
|
||||||
|
// Rotation to portrait
|
||||||
|
assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_180));
|
||||||
|
// Rotation to landscape
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_270));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_portraitActivity_lockedWhenRotatingToLandscape() {
|
||||||
|
when(mMockActivityRecord.getRequestedConfigurationOrientation()).thenReturn(
|
||||||
|
ORIENTATION_PORTRAIT);
|
||||||
|
|
||||||
|
// Rotation to portrait
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_0));
|
||||||
|
// Rotation to landscape
|
||||||
|
assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_90));
|
||||||
|
// Rotation to portrait
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_180));
|
||||||
|
// Rotation to landscape
|
||||||
|
assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_270));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_responsiveActivity_lockNotEnforced() {
|
||||||
|
// Do not fix screen orientation
|
||||||
|
when(mMockActivityRecord.getRequestedConfigurationOrientation()).thenReturn(
|
||||||
|
ORIENTATION_UNDEFINED);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_statusBarVisible_lockNotEnforced() {
|
||||||
|
// Some system bars are visible
|
||||||
|
when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(Type.statusBars());
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_navBarVisible_lockNotEnforced() {
|
||||||
|
// Some system bars are visible
|
||||||
|
when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(Type.navigationBars());
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_activityIsLetterboxed_lockNotEnforced() {
|
||||||
|
// Activity is letterboxed
|
||||||
|
when(mMockActivityRecord.areBoundsLetterboxed()).thenReturn(true);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_notFullscreen_lockNotEnforced() {
|
||||||
|
when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_MULTI_WINDOW);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
|
||||||
|
when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_PINNED);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
|
||||||
|
when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_FREEFORM);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_ignoreOrientationRequestDisabled_lockNotEnforced() {
|
||||||
|
when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(false);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRotationChoiceEnforcedOnly_nullTopRunningActivity_lockNotEnforced() {
|
||||||
|
when(mDisplayContent.topRunningActivity()).thenReturn(null);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRotationChoiceEnforcedOnly_featureFlagDisabled_lockNotEnforced() {
|
||||||
|
when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
|
||||||
|
/* checkDeviceConfig */ true)).thenReturn(false);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsRotationLockEnforcedReturnsFalseForAllRotations() {
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_0));
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_90));
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_180));
|
||||||
|
assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_270));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1096,8 +1096,16 @@ public class DisplayRotationTests {
|
|||||||
mMockDisplayAddress = mock(DisplayAddress.class);
|
mMockDisplayAddress = mock(DisplayAddress.class);
|
||||||
|
|
||||||
mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
|
mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
|
||||||
|
|
||||||
mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
|
mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
|
||||||
mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object());
|
mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object()) {
|
||||||
|
@Override
|
||||||
|
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
|
||||||
|
WindowManagerService service, DisplayContent displayContent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
reset(sMockWm);
|
reset(sMockWm);
|
||||||
|
|
||||||
captureObservers();
|
captureObservers();
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.server.wm.LetterboxConfigurationDeviceConfig.sKeyToDefaultValueMap;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
import android.provider.DeviceConfig;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import com.android.modules.utils.testing.TestableDeviceConfig;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for {@link LetterboxConfigurationDeviceConfig}.
|
||||||
|
*
|
||||||
|
* atest WmTests:LetterboxConfigurationDeviceConfigTests
|
||||||
|
*/
|
||||||
|
@SmallTest
|
||||||
|
@Presubmit
|
||||||
|
public class LetterboxConfigurationDeviceConfigTests {
|
||||||
|
|
||||||
|
private LetterboxConfigurationDeviceConfig mDeviceConfig;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestableDeviceConfig.TestableDeviceConfigRule
|
||||||
|
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mDeviceConfig = new LetterboxConfigurationDeviceConfig(/* executor */ Runnable::run);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFlag_flagIsActive_flagChanges() throws Throwable {
|
||||||
|
for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
|
||||||
|
testGetFlagForKey_flagIsActive_flagChanges(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGetFlagForKey_flagIsActive_flagChanges(final String key, boolean defaultValue)
|
||||||
|
throws InterruptedException {
|
||||||
|
mDeviceConfig.updateFlagActiveStatus(/* isActive */ true, key);
|
||||||
|
|
||||||
|
assertEquals("Unexpected default value for " + key,
|
||||||
|
mDeviceConfig.getFlag(key), defaultValue);
|
||||||
|
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
|
||||||
|
/* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
|
||||||
|
|
||||||
|
assertTrue("Flag " + key + "is not true after change", mDeviceConfig.getFlag(key));
|
||||||
|
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
|
||||||
|
/* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
|
||||||
|
|
||||||
|
assertFalse("Flag " + key + "is not false after change", mDeviceConfig.getFlag(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFlag_flagIsNotActive_alwaysReturnDefaultValue() throws Throwable {
|
||||||
|
for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
|
||||||
|
testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(
|
||||||
|
entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(final String key,
|
||||||
|
boolean defaultValue) throws InterruptedException {
|
||||||
|
assertEquals("Unexpected default value for " + key,
|
||||||
|
mDeviceConfig.getFlag(key), defaultValue);
|
||||||
|
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
|
||||||
|
/* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
|
||||||
|
|
||||||
|
assertEquals("Flag " + key + "is not set to default after change",
|
||||||
|
mDeviceConfig.getFlag(key), defaultValue);
|
||||||
|
|
||||||
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
|
||||||
|
/* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
|
||||||
|
|
||||||
|
assertEquals("Flag " + key + "is not set to default after change",
|
||||||
|
mDeviceConfig.getFlag(key), defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user