summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Hongwei Wang <hwwang@google.com> 2023-02-23 14:50:47 -0800
committer Hongwei Wang <hwwang@google.com> 2023-03-06 16:54:40 -0800
commit6cf33c6d0b0034ed872bfaa0c1a3044c9ce3ed7e (patch)
tree44ca266c4ef1d83e53ba170d1db36196d6f4c9ae
parenta88017c32fa5c257c5f541436e1cc53c776d1b44 (diff)
Add TabletopModeController in WMShell
Populate the resource config that defines the horizontal fold and together with the device posture to determine if the foldable device is in tabletop mode or not. Bug: 260871991 Test: atest WMShellUnitTests:TabletopModeControllerTest Change-Id: I7878ec9ef95be39c2f1dc7ada7e7c871a018fa6e
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java208
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java270
5 files changed, 496 insertions, 0 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 22587f4c6456..8b4ac1a8dc79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -39,6 +39,9 @@ import java.util.List;
*
* Note that most of the implementation here inherits from
* {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ *
+ * Use the {@link TabletopModeController} if you are interested in tabletop mode change only,
+ * which is more common.
*/
public class DevicePostureController {
@IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
new file mode 100644
index 000000000000..bf226283ae54
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.ArraySet;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Wrapper class to track the tabletop (aka. flex) mode change on Fold-ables.
+ * See also <a
+ * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables
+ * #foldable_postures">Foldable states and postures</a> for reference.
+ *
+ * Use the {@link DevicePostureController} for more detailed posture changes.
+ */
+public class TabletopModeController implements
+ DevicePostureController.OnDevicePostureChangedListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+
+ private final Context mContext;
+
+ private final DevicePostureController mDevicePostureController;
+
+ private final DisplayController mDisplayController;
+
+ private final ShellExecutor mMainExecutor;
+
+ private final Set<Integer> mTabletopModeRotations = new ArraySet<>();
+
+ private final List<OnTabletopModeChangedListener> mListeners = new ArrayList<>();
+
+ @VisibleForTesting
+ final Runnable mOnEnterTabletopModeCallback = () -> {
+ if (isInTabletopMode()) {
+ // We are still in tabletop mode, go ahead.
+ mayBroadcastOnTabletopModeChange(true /* isInTabletopMode */);
+ }
+ };
+
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ @Surface.Rotation
+ private int mDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+ /**
+ * Track the last callback value for {@link OnTabletopModeChangedListener}.
+ * This is to avoid duplicated {@code false} callback to {@link #mListeners}.
+ */
+ private Boolean mLastIsInTabletopModeForCallback;
+
+ public TabletopModeController(Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mDevicePostureController = postureController;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @VisibleForTesting
+ void onInit() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ // Aligns with what's in {@link com.android.server.wm.DisplayRotation}.
+ final int[] deviceTabletopRotations = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_deviceTabletopRotations);
+ if (deviceTabletopRotations == null || deviceTabletopRotations.length == 0) {
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "No valid config_deviceTabletopRotations, can not tell"
+ + " tabletop mode in WMShell");
+ return;
+ }
+ for (int angle : deviceTabletopRotations) {
+ switch (angle) {
+ case 0:
+ mTabletopModeRotations.add(Surface.ROTATION_0);
+ break;
+ case 90:
+ mTabletopModeRotations.add(Surface.ROTATION_90);
+ break;
+ case 180:
+ mTabletopModeRotations.add(Surface.ROTATION_180);
+ break;
+ case 270:
+ mTabletopModeRotations.add(Surface.ROTATION_270);
+ break;
+ default:
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "Invalid surface rotation angle in "
+ + "config_deviceTabletopRotations: %d",
+ angle);
+ break;
+ }
+ }
+ }
+
+ /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
+ public void registerOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ if (listener == null || mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onTabletopModeChanged(isInTabletopMode());
+ }
+
+ /** Unregister {@link OnTabletopModeChangedListener} for tabletop mode change. */
+ public void unregisterOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mDevicePosture != posture) {
+ onDevicePostureOrDisplayRotationChanged(posture, mDisplayRotation);
+ }
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ final int newDisplayRotation = newConfig.windowConfiguration.getDisplayRotation();
+ if (displayId == DEFAULT_DISPLAY && newDisplayRotation != mDisplayRotation) {
+ onDevicePostureOrDisplayRotationChanged(mDevicePosture, newDisplayRotation);
+ }
+ }
+
+ private void onDevicePostureOrDisplayRotationChanged(
+ @DevicePostureController.DevicePostureInt int newPosture,
+ @Surface.Rotation int newDisplayRotation) {
+ final boolean wasInTabletopMode = isInTabletopMode();
+ mDevicePosture = newPosture;
+ mDisplayRotation = newDisplayRotation;
+ final boolean couldBeInTabletopMode = isInTabletopMode();
+ mMainExecutor.removeCallbacks(mOnEnterTabletopModeCallback);
+ if (!wasInTabletopMode && couldBeInTabletopMode) {
+ // May enter tabletop mode, but we need to wait for additional time since this
+ // could be an intermediate state.
+ mMainExecutor.executeDelayed(mOnEnterTabletopModeCallback, TABLETOP_MODE_DELAY_MILLIS);
+ } else {
+ // Cancel entering tabletop mode if any condition's changed.
+ mayBroadcastOnTabletopModeChange(false /* isInTabletopMode */);
+ }
+ }
+
+ private boolean isHalfOpened(@DevicePostureController.DevicePostureInt int posture) {
+ return posture == DEVICE_POSTURE_HALF_OPENED;
+ }
+
+ private boolean isInTabletopMode() {
+ return isHalfOpened(mDevicePosture) && mTabletopModeRotations.contains(mDisplayRotation);
+ }
+
+ private void mayBroadcastOnTabletopModeChange(boolean isInTabletopMode) {
+ if (mLastIsInTabletopModeForCallback == null
+ || mLastIsInTabletopModeForCallback != isInTabletopMode) {
+ mListeners.forEach(l -> l.onTabletopModeChanged(isInTabletopMode));
+ mLastIsInTabletopModeForCallback = isInTabletopMode;
+ }
+ }
+
+ /**
+ * Listener interface for tabletop mode change.
+ */
+ public interface OnTabletopModeChangedListener {
+ /**
+ * Callback when tabletop mode changes. Expect duplicated callbacks with {@code false}.
+ * @param isInTabletopMode {@code true} if enters tabletop mode, {@code false} otherwise.
+ */
+ void onTabletopModeChanged(boolean isInTabletopMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 72dc771ee08c..ef21c7e9ec0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -51,6 +51,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
@@ -171,6 +172,18 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static TabletopModeController provideTabletopModeController(
+ Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new TabletopModeController(
+ context, shellInit, postureController, displayController, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DragAndDropController provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 75f9a4c33af9..c9b3a1af6507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -50,6 +50,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
new file mode 100644
index 000000000000..96d202ce3a85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_OPENED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link TabletopModeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class TabletopModeControllerTest extends ShellTestCase {
+ // It's considered tabletop mode if the display rotation angle matches what's in this array.
+ // It's defined as com.android.internal.R.array.config_deviceTabletopRotations on real devices.
+ private static final int[] TABLETOP_MODE_ROTATIONS = new int[] {
+ 90 /* Surface.ROTATION_90 */,
+ 270 /* Surface.ROTATION_270 */
+ };
+
+ private TestShellExecutor mMainExecutor;
+
+ private Configuration mConfiguration;
+
+ private TabletopModeController mPipTabletopController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private Resources mResources;
+
+ @Mock
+ private DevicePostureController mDevicePostureController;
+
+ @Mock
+ private DisplayController mDisplayController;
+
+ @Mock
+ private TabletopModeController.OnTabletopModeChangedListener mOnTabletopModeChangedListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mResources.getIntArray(com.android.internal.R.array.config_deviceTabletopRotations))
+ .thenReturn(TABLETOP_MODE_ROTATIONS);
+ when(mContext.getResources()).thenReturn(mResources);
+ mMainExecutor = new TestShellExecutor();
+ mConfiguration = new Configuration();
+ mPipTabletopController = new TabletopModeController(mContext, mShellInit,
+ mDevicePostureController, mDisplayController, mMainExecutor);
+ mPipTabletopController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipTabletopController));
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopMode_callbackFalse() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(false);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_inTabletopMode_callbackTrue() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(true);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopModeTwice_callbackOnce() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+ clearInvocations(mOnTabletopModeChangedListener);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ verifyZeroInteractions(mOnTabletopModeChangedListener);
+ }
+
+ // Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
+ @Test
+ public void foldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ // Test cases starting from unfolded state (DEVICE_POSTURE_OPENED)
+ @Test
+ public void unfoldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+}