Merge "Allow rotation when display is half-folded" into tm-qpr-dev

This commit is contained in:
Will Osborn
2022-10-13 08:47:42 +00:00
committed by Android (Google) Code Review
10 changed files with 470 additions and 35 deletions

View File

@@ -658,6 +658,20 @@
-->
</integer-array>
<!-- The device states (supplied by DeviceStateManager) that should be treated as half-folded by
the display fold controller. Default is empty. -->
<integer-array name="config_halfFoldedDeviceStates">
<!-- Example:
<item>0</item>
<item>1</item>
<item>2</item>
-->
</integer-array>
<!-- Indicates whether the window manager reacts to half-fold device states by overriding
rotation. -->
<bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
<!-- When a device enters any of these states, it should be woken up. States are defined in
device_state_configuration.xml. -->
<integer-array name="config_deviceStatesOnWhichToWakeUp">

View File

@@ -4005,6 +4005,8 @@
<!-- For Foldables -->
<java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="array" name="config_halfFoldedDeviceStates" />
<java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
<java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" />
<java-symbol type="array" name="config_deviceStatesOnWhichToSleep" />
<java-symbol type="string" name="config_foldedArea" />

View File

@@ -1087,6 +1087,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/WindowState.java"
},
"-1043981272": {
"message": "Reverting orientation. Rotating to %s from %s rather than %s.",
"level": "VERBOSE",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
"-1042574499": {
"message": "Attempted to add Accessibility overlay window with unknown token %s. Aborting.",
"level": "WARN",
@@ -4285,6 +4291,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"2066210760": {
"message": "foldStateChanged: displayId %d, halfFoldStateChanged %s, saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, mLastOrientation: %d, mRotation: %d",
"level": "VERBOSE",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
"2070726247": {
"message": "InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
"level": "DEBUG",

View File

@@ -132,8 +132,11 @@ public class RotationButtonController {
mMainThreadHandler.postAtFrontOfQueue(() -> {
// If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions.
if (isRotationLocked()) {
if (shouldOverrideUserLockPrefs(rotation)) {
boolean rotationLocked = isRotationLocked();
// The isVisible check makes the rotation button disappear when we are not locked
// (e.g. for tabletop auto-rotate).
if (rotationLocked || mRotationButton.isVisible()) {
if (shouldOverrideUserLockPrefs(rotation) && rotationLocked) {
setRotationLockedAtAngle(rotation);
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);

View File

@@ -0,0 +1,96 @@
/*
* 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.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.os.HandlerExecutor;
import com.android.internal.util.ArrayUtils;
import java.util.function.Consumer;
/**
* Class that registers callbacks with the {@link DeviceStateManager} and
* responds to fold state changes by forwarding such events to a delegate.
*/
final class DeviceStateController {
private final DeviceStateManager mDeviceStateManager;
private final Context mContext;
private FoldStateListener mDeviceStateListener;
public enum FoldState {
UNKNOWN, OPEN, FOLDED, HALF_FOLDED
}
DeviceStateController(Context context, Handler handler, Consumer<FoldState> delegate) {
mContext = context;
mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class);
if (mDeviceStateManager != null) {
mDeviceStateListener = new FoldStateListener(mContext, delegate);
mDeviceStateManager
.registerCallback(new HandlerExecutor(handler),
mDeviceStateListener);
}
}
void unregisterFromDeviceStateManager() {
if (mDeviceStateListener != null) {
mDeviceStateManager.unregisterCallback(mDeviceStateListener);
}
}
/**
* A listener for half-fold device state events that dispatches state changes to a delegate.
*/
static final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
private final int[] mHalfFoldedDeviceStates;
private final int[] mFoldedDeviceStates;
@Nullable
private FoldState mLastResult;
private final Consumer<FoldState> mDelegate;
FoldStateListener(Context context, Consumer<FoldState> delegate) {
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
mHalfFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_halfFoldedDeviceStates);
mDelegate = delegate;
}
@Override
public void onStateChanged(int state) {
final boolean halfFolded = ArrayUtils.contains(mHalfFoldedDeviceStates, state);
FoldState result;
if (halfFolded) {
result = FoldState.HALF_FOLDED;
} else {
final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
result = folded ? FoldState.FOLDED : FoldState.OPEN;
}
if (mLastResult == null || !mLastResult.equals(result)) {
mLastResult = result;
mDelegate.accept(result);
}
}
}
}

View File

@@ -564,6 +564,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
private final DeviceStateController mDeviceStateController;
private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
final RemoteDisplayChangeController mRemoteDisplayChangeController;
@@ -1119,6 +1120,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDisplayPolicy = new DisplayPolicy(mWmService, this);
mDisplayRotation = new DisplayRotation(mWmService, this);
mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH,
newFoldState -> {
mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState);
mDisplayRotation.foldStateChanged(newFoldState);
});
mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
R.dimen.config_closeToSquareDisplayMaxAspectRatio);
if (isDefaultDisplay) {
@@ -3218,7 +3226,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
mWmService.stopFreezingDisplayLocked();
mDisplaySwitchTransitionLauncher.destroy();
mDeviceStateController.unregisterFromDeviceStateManager();
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();

View File

@@ -40,6 +40,7 @@ import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_D
import android.annotation.AnimRes;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -108,6 +109,8 @@ public class DisplayRotation {
private OrientationListener mOrientationListener;
private StatusBarManagerInternal mStatusBarManagerInternal;
private SettingsObserver mSettingsObserver;
@Nullable
private FoldController mFoldController;
@ScreenOrientation
private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -238,6 +241,10 @@ public class DisplayRotation {
mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
if (mSupportAutoRotation && mContext.getResources().getBoolean(
R.bool.config_windowManagerHalfFoldAutoRotateOverride)) {
mFoldController = new FoldController();
}
}
}
@@ -436,7 +443,17 @@ public class DisplayRotation {
final int oldRotation = mRotation;
final int lastOrientation = mLastOrientation;
final int rotation = rotationForOrientation(lastOrientation, oldRotation);
int rotation = rotationForOrientation(lastOrientation, oldRotation);
// Use the saved rotation for tabletop mode, if set.
if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
int prevRotation = rotation;
rotation = mFoldController.revertOverriddenRotation();
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Reverting orientation. Rotating to %s from %s rather than %s.",
Surface.rotationToString(rotation),
Surface.rotationToString(oldRotation),
Surface.rotationToString(prevRotation));
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ "oldRotation=%s (%d)",
@@ -1138,7 +1155,8 @@ public class DisplayRotation {
// If we don't support auto-rotation then bail out here and ignore
// the sensor and any rotation lock settings.
preferredRotation = -1;
} else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
} else if (((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
|| isTabletopAutoRotateOverrideEnabled())
&& (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
|| orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|| orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
@@ -1292,10 +1310,17 @@ public class DisplayRotation {
return false;
}
private boolean isTabletopAutoRotateOverrideEnabled() {
return mFoldController != null && mFoldController.overrideFrozenRotation();
}
private boolean isRotationChoicePossible(int orientation) {
// Rotation choice is only shown when the user is in locked mode.
if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false;
// Don't show rotation choice if we are in tabletop or book modes.
if (isTabletopAutoRotateOverrideEnabled()) return false;
// We should only enable rotation choice if the rotation isn't forced by the lid, dock,
// demo, hdmi, vr, etc mode.
@@ -1496,6 +1521,74 @@ public class DisplayRotation {
proto.end(token);
}
/**
* Called by the DeviceStateManager callback when the device state changes.
*/
void foldStateChanged(DeviceStateController.FoldState foldState) {
if (mFoldController != null) {
synchronized (mLock) {
mFoldController.foldStateChanged(foldState);
}
}
}
private class FoldController {
@Surface.Rotation
private int mHalfFoldSavedRotation = -1; // No saved rotation
private DeviceStateController.FoldState mFoldState =
DeviceStateController.FoldState.UNKNOWN;
boolean overrideFrozenRotation() {
return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
}
boolean shouldRevertOverriddenRotation() {
return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
&& mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
&& mUserRotationMode
== WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
}
int revertOverriddenRotation() {
int savedRotation = mHalfFoldSavedRotation;
mHalfFoldSavedRotation = -1;
return savedRotation;
}
void foldStateChanged(DeviceStateController.FoldState newState) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"foldStateChanged: displayId %d, halfFoldStateChanged %s, "
+ "saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, "
+ "mLastOrientation: %d, mRotation: %d",
mDisplayContent.getDisplayId(), newState.name(), mHalfFoldSavedRotation,
mUserRotation, mLastSensorRotation, mLastOrientation, mRotation);
if (mFoldState == DeviceStateController.FoldState.UNKNOWN) {
mFoldState = newState;
return;
}
if (newState == DeviceStateController.FoldState.HALF_FOLDED
&& mFoldState != DeviceStateController.FoldState.HALF_FOLDED) {
// The device has transitioned to HALF_FOLDED state: save the current rotation and
// update the device rotation.
mHalfFoldSavedRotation = mRotation;
mFoldState = newState;
// Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
// return true, so rotation is unlocked.
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
} else {
// Revert the rotation to our saved value if we transition from HALF_FOLDED.
mRotation = mHalfFoldSavedRotation;
// Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
// so we will override USER_ROTATION_LOCKED and allow a rotation).
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
// Once we are rotated, set mFoldstate, effectively removing the lock override.
mFoldState = newState;
}
}
}
private class OrientationListener extends WindowOrientationListener implements Runnable {
transient boolean mEnabled;

View File

@@ -24,10 +24,7 @@ import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.HandlerExecutor;
import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
@@ -36,11 +33,8 @@ public class PhysicalDisplaySwitchTransitionLauncher {
private final DisplayContent mDisplayContent;
private final WindowManagerService mService;
private final DeviceStateManager mDeviceStateManager;
private final TransitionController mTransitionController;
private DeviceStateListener mDeviceStateListener;
/**
* If on a foldable device represents whether the device is folded or not
*/
@@ -52,21 +46,15 @@ public class PhysicalDisplaySwitchTransitionLauncher {
mDisplayContent = displayContent;
mService = displayContent.mWmService;
mTransitionController = transitionController;
mDeviceStateManager = mService.mContext.getSystemService(DeviceStateManager.class);
if (mDeviceStateManager != null) {
mDeviceStateListener = new DeviceStateListener(mService.mContext);
mDeviceStateManager
.registerCallback(new HandlerExecutor(mDisplayContent.mWmService.mH),
mDeviceStateListener);
}
}
public void destroy() {
if (mDeviceStateManager != null) {
mDeviceStateManager.unregisterCallback(mDeviceStateListener);
}
/**
* Called by the DeviceStateManager callback when the state changes.
*/
void foldStateChanged(DeviceStateController.FoldState newFoldState) {
// Ignore transitions to/from half-folded.
if (newFoldState == DeviceStateController.FoldState.HALF_FOLDED) return;
mIsFolded = newFoldState == DeviceStateController.FoldState.FOLDED;
}
/**
@@ -143,10 +131,4 @@ public class PhysicalDisplaySwitchTransitionLauncher {
mTransition = null;
}
class DeviceStateListener extends DeviceStateManager.FoldStateListener {
DeviceStateListener(Context context) {
super(context, newIsFolded -> mIsFolded = newIsFolded);
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.util.function.Consumer;
/**
* Test class for {@link DeviceStateController}.
*
* Build/Install/Run:
* atest WmTests:DeviceStateControllerTests
*/
@SmallTest
@Presubmit
public class DeviceStateControllerTests {
private DeviceStateController.FoldStateListener mFoldStateListener;
private DeviceStateController mTarget;
private DeviceStateControllerBuilder mBuilder;
private Context mMockContext;
private Handler mMockHandler;
private Resources mMockRes;
private DeviceStateManager mMockDeviceStateManager;
private Consumer<DeviceStateController.FoldState> mDelegate;
private DeviceStateController.FoldState mCurrentState = DeviceStateController.FoldState.UNKNOWN;
@Before
public void setUp() {
mBuilder = new DeviceStateControllerBuilder();
mCurrentState = DeviceStateController.FoldState.UNKNOWN;
}
private void initialize(boolean supportFold, boolean supportHalfFold) throws Exception {
mBuilder.setSupportFold(supportFold, supportHalfFold);
mDelegate = (newFoldState) -> {
mCurrentState = newFoldState;
};
mBuilder.setDelegate(mDelegate);
mBuilder.build();
verifyFoldStateListenerRegistration(1);
}
@Test
public void testInitialization() throws Exception {
initialize(true /* supportFold */, true /* supportHalfFolded */);
mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
}
@Test
public void testInitializationWithNoFoldSupport() throws Exception {
initialize(false /* supportFold */, false /* supportHalfFolded */);
mFoldStateListener.onStateChanged(mFoldedStates[0]);
// Note that the folded state is ignored.
assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
}
@Test
public void testWithFoldSupported() throws Exception {
initialize(true /* supportFold */, false /* supportHalfFolded */);
mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
mFoldStateListener.onStateChanged(mFoldedStates[0]);
assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); // Ignored
}
@Test
public void testWithHalfFoldSupported() throws Exception {
initialize(true /* supportFold */, true /* supportHalfFolded */);
mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
mFoldStateListener.onStateChanged(mFoldedStates[0]);
assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
assertEquals(mCurrentState, DeviceStateController.FoldState.HALF_FOLDED);
}
private final int[] mFoldedStates = {0};
private final int[] mUnfoldedStates = {1};
private final int[] mHalfFoldedStates = {2};
private void verifyFoldStateListenerRegistration(int numOfInvocation) {
final ArgumentCaptor<DeviceStateController.FoldStateListener> listenerCaptor =
ArgumentCaptor.forClass(DeviceStateController.FoldStateListener.class);
verify(mMockDeviceStateManager, times(numOfInvocation)).registerCallback(
any(),
listenerCaptor.capture());
if (numOfInvocation > 0) {
mFoldStateListener = listenerCaptor.getValue();
}
}
private class DeviceStateControllerBuilder {
private boolean mSupportFold = false;
private boolean mSupportHalfFold = false;
private Consumer<DeviceStateController.FoldState> mDelegate;
DeviceStateControllerBuilder setSupportFold(
boolean supportFold, boolean supportHalfFold) {
mSupportFold = supportFold;
mSupportHalfFold = supportHalfFold;
return this;
}
DeviceStateControllerBuilder setDelegate(
Consumer<DeviceStateController.FoldState> delegate) {
mDelegate = delegate;
return this;
}
private void mockFold(boolean enableFold, boolean enableHalfFold) {
if (enableFold) {
when(mMockContext.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates))
.thenReturn(mFoldedStates);
}
if (enableHalfFold) {
when(mMockContext.getResources().getIntArray(
com.android.internal.R.array.config_halfFoldedDeviceStates))
.thenReturn(mHalfFoldedStates);
}
}
private void build() throws Exception {
mMockContext = mock(Context.class);
mMockRes = mock(Resources.class);
when(mMockContext.getResources()).thenReturn((mMockRes));
mMockDeviceStateManager = mock(DeviceStateManager.class);
when(mMockContext.getSystemService(DeviceStateManager.class))
.thenReturn(mMockDeviceStateManager);
mockFold(mSupportFold, mSupportHalfFold);
mMockHandler = mock(Handler.class);
mTarget = new DeviceStateController(mMockContext, mMockHandler, mDelegate);
}
}
}

View File

@@ -28,6 +28,7 @@ import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atMost;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
@@ -103,7 +104,7 @@ public class DisplayRotationTests {
private Context mMockContext;
private Resources mMockRes;
private SensorManager mMockSensorManager;
private Sensor mFakeSensor;
private Sensor mFakeOrientationSensor;
private DisplayWindowSettings mMockDisplayWindowSettings;
private ContentResolver mMockResolver;
private FakeSettingsProvider mFakeSettingsProvider;
@@ -323,7 +324,7 @@ public class DisplayRotationTests {
waitForUiHandler();
verify(mMockSensorManager, times(numOfInvocation)).registerListener(
listenerCaptor.capture(),
same(mFakeSensor),
same(mFakeOrientationSensor),
anyInt(),
any());
if (numOfInvocation > 0) {
@@ -460,7 +461,7 @@ public class DisplayRotationTests {
SensorEvent.class.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
final SensorEvent event = constructor.newInstance(1);
event.sensor = mFakeSensor;
event.sensor = mFakeOrientationSensor;
event.values[0] = rotation;
event.timestamp = SystemClock.elapsedRealtimeNanos();
return event;
@@ -691,6 +692,43 @@ public class DisplayRotationTests {
SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
}
// ====================================================
// Tests for half-fold auto-rotate override of rotation
// ====================================================
@Test
public void testUpdatesRotationWhenSensorUpdates_RotationLocked_HalfFolded() throws Exception {
mBuilder.setSupportHalfFoldAutoRotateOverride(true);
mBuilder.build();
configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
enableOrientationSensor();
mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
freezeRotation(Surface.ROTATION_270);
mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0));
assertTrue(waitForUiHandler());
// No rotation...
assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
// ... until half-fold
mTarget.foldStateChanged(DeviceStateController.FoldState.HALF_FOLDED);
assertTrue(waitForUiHandler());
verify(sMockWm).updateRotation(false, false);
assertTrue(waitForUiHandler());
assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
// ... then transition back to flat
mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
assertTrue(waitForUiHandler());
verify(sMockWm, atLeast(1)).updateRotation(false, false);
assertTrue(waitForUiHandler());
assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
}
// =================================
// Tests for Policy based Rotation
// =================================
@@ -884,6 +922,7 @@ public class DisplayRotationTests {
private class DisplayRotationBuilder {
private boolean mIsDefaultDisplay = true;
private boolean mSupportAutoRotation = true;
private boolean mSupportHalfFoldAutoRotateOverride = false;
private int mLidOpenRotation = WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
private int mCarDockRotation;
@@ -920,6 +959,12 @@ public class DisplayRotationTests {
return this;
}
private DisplayRotationBuilder setSupportHalfFoldAutoRotateOverride(
boolean supportHalfFoldAutoRotateOverride) {
mSupportHalfFoldAutoRotateOverride = supportHalfFoldAutoRotateOverride;
return this;
}
private void captureObservers() {
ArgumentCaptor<ContentObserver> captor = ArgumentCaptor.forClass(
ContentObserver.class);
@@ -1032,9 +1077,13 @@ public class DisplayRotationTests {
mMockSensorManager = mock(SensorManager.class);
when(mMockContext.getSystemService(Context.SENSOR_SERVICE))
.thenReturn(mMockSensorManager);
mFakeSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
mFakeOrientationSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
when(mMockSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION)).thenReturn(
Collections.singletonList(mFakeSensor));
Collections.singletonList(mFakeOrientationSensor));
when(mMockContext.getResources().getBoolean(
com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride))
.thenReturn(mSupportHalfFoldAutoRotateOverride);
mMockResolver = mock(ContentResolver.class);
when(mMockContext.getContentResolver()).thenReturn(mMockResolver);