summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Will Osborn <willosborn@google.com> 2022-10-13 08:47:42 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-10-13 08:47:42 +0000
commit6d1c1b57b45cfd3a38c5ae632ef80cd8caa0051d (patch)
treeab5caa2faf465b512f8c3e55548dc6fbd17c0feb
parent7e64429d94a480a845b4c01cc192b03ae3b43e97 (diff)
parent9723cfbc38ba5dc47208584def2711924184a77e (diff)
Merge "Allow rotation when display is half-folded" into tm-qpr-dev
-rw-r--r--core/res/res/values/config.xml14
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java7
-rw-r--r--services/core/java/com/android/server/wm/DeviceStateController.java96
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java10
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java97
-rw-r--r--services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java176
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java59
10 files changed, 470 insertions, 35 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3408f75f0f72..84280605862f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -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">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 16798b8b0a24..97105d80bf79 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -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" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b1ecb43f9a23..31e2abe0bb85 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -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",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 22bffda33918..6087655615a6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -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 */);
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
new file mode 100644
index 000000000000..a6f855755192
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -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);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 71c80fb9a97c..38f6a53982c4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -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();
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 97609a7dd8ba..a8d13c57ffa2 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -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;
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index a89894db4b4b..30bdc3477edf 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -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);
- }
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
new file mode 100644
index 000000000000..86732c9a3f46
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
@@ -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);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 89f71110f3c2..b45c37f9da0c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -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);