Merge "Add a way to disable auto rotation for immersive apps" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
53cf7e47f0
@@ -5213,6 +5213,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
|
||||||
|
|||||||
@@ -4441,6 +4441,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() {
|
||||||
|
|||||||
@@ -5948,6 +5948,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,161 @@
|
|||||||
|
/*
|
||||||
|
* 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 static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
|
||||||
|
import static android.view.InsetsState.ITYPE_STATUS_BAR;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.res.Configuration.Orientation;
|
||||||
|
import android.view.InsetsVisibilities;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
InsetsVisibilities insetsVisibilities = mainWindow.getRequestedVisibilities();
|
||||||
|
return !insetsVisibilities.getVisibility(ITYPE_STATUS_BAR)
|
||||||
|
&& !insetsVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,225 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.view.InsetsState.ITYPE_NAVIGATION_BAR;
|
||||||
|
import static android.view.InsetsState.ITYPE_STATUS_BAR;
|
||||||
|
|
||||||
|
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.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.InsetsVisibilities;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
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 InsetsVisibilities mMockInsetsVisibilities;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
WindowState mockWindowState = mock(WindowState.class);
|
||||||
|
mMockInsetsVisibilities = mock(InsetsVisibilities.class);
|
||||||
|
when(mMockInsetsVisibilities.getVisibility(eq(ITYPE_STATUS_BAR))).thenReturn(false);
|
||||||
|
when(mMockInsetsVisibilities.getVisibility(eq(ITYPE_NAVIGATION_BAR))).thenReturn(false);
|
||||||
|
when(mockWindowState.getRequestedVisibilities()).thenReturn(mMockInsetsVisibilities);
|
||||||
|
when(mMockActivityRecord.findMainWindow()).thenReturn(mockWindowState);
|
||||||
|
|
||||||
|
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(mMockInsetsVisibilities.getVisibility(eq(ITYPE_STATUS_BAR))).thenReturn(true);
|
||||||
|
|
||||||
|
assertIsRotationLockEnforcedReturnsFalseForAllRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsRotationLockEnforced_navBarVisible_lockNotEnforced() {
|
||||||
|
// Some system bars are visible
|
||||||
|
when(mMockInsetsVisibilities.getVisibility(eq(ITYPE_NAVIGATION_BAR))).thenReturn(true);
|
||||||
|
|
||||||
|
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