Merge "Allow rotation when display is half-folded" into tm-qpr-dev
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 */);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user