1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.server.wm; 18 19 import static android.content.res.Configuration.UI_MODE_TYPE_DESK; 20 import static android.content.res.Configuration.UI_MODE_TYPE_MASK; 21 import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL; 22 import static android.server.wm.app.Components.TEST_ACTIVITY; 23 import static android.server.wm.deskresources.Components.DESK_RESOURCES_ACTIVITY; 24 25 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 26 27 import static org.junit.Assume.assumeTrue; 28 29 import android.content.ComponentName; 30 import android.content.Intent; 31 import android.content.res.Configuration; 32 import android.content.res.Resources; 33 import android.platform.test.annotations.Presubmit; 34 import android.view.Surface; 35 36 import org.junit.Test; 37 38 /** 39 * Build/Install/Run: 40 * atest CtsWindowManagerDeviceTestCases:DockConfigChangeTests 41 */ 42 @Presubmit 43 public class DockConfigChangeTests extends ActivityManagerTestBase { 44 45 @Test testDeskMode_noConfigChange()46 public void testDeskMode_noConfigChange() { 47 // Test only applies to behavior when the config_skipActivityRelaunchWhenDocking flag is 48 // enabled. 49 assumeTrue(getConfigSkipRelaunchOnDock()); 50 51 RotationSession rotationSession = createManagedRotationSession(); 52 53 launchActivity(TEST_ACTIVITY); 54 waitAndAssertResumedActivity(TEST_ACTIVITY, "Activity must be resumed"); 55 56 // Set rotation to the same rotation as the device would be rotated to after docking. This 57 // prevents an extraneous config change from the device rotating when docked/undocked. 58 rotateToDockRotation(rotationSession); 59 waitAndAssertResumedActivity(TEST_ACTIVITY, "Activity must be resumed"); 60 separateTestJournal(); 61 62 final DockTestSession dockTestSession = mObjectTracker.manage(new DockTestSession()); 63 64 // Dock the device. 65 dockTestSession.dock(); 66 dockTestSession.waitForDeskUiMode(TEST_ACTIVITY); 67 68 // The activity receives a configuration change instead of relaunching. 69 assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */, 1 /* numConfigChange */); 70 71 // Undock the device. 72 separateTestJournal(); 73 dockTestSession.undock(); 74 dockTestSession.waitForNormalUiMode(TEST_ACTIVITY); 75 76 // The activity receives another configuration change. 77 assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */, 1 /* numConfigChange */); 78 } 79 80 @Test testDeskMode_hasDeskResources_relaunches()81 public void testDeskMode_hasDeskResources_relaunches() { 82 // Test only applies to behavior when the config_skipActivityRelaunchWhenDocking flag is 83 // enabled. 84 assumeTrue(getConfigSkipRelaunchOnDock()); 85 86 RotationSession rotationSession = createManagedRotationSession(); 87 88 launchActivity(DESK_RESOURCES_ACTIVITY); 89 waitAndAssertResumedActivity(DESK_RESOURCES_ACTIVITY, "Activity must be resumed"); 90 91 // Set rotation to the same rotation as the device would be rotated to after docking. This 92 // prevents an extraneous config change from the device rotating when docked/undocked. 93 rotateToDockRotation(rotationSession); 94 waitAndAssertResumedActivity(DESK_RESOURCES_ACTIVITY, "Activity must be resumed"); 95 separateTestJournal(); 96 97 final DockTestSession dockTestSession = mObjectTracker.manage(new DockTestSession()); 98 99 // Dock the device. 100 dockTestSession.dock(); 101 dockTestSession.waitForDeskUiMode(DESK_RESOURCES_ACTIVITY); 102 103 // The activity is relaunched since the app has -desk resources. 104 assertRelaunchOrConfigChanged(DESK_RESOURCES_ACTIVITY, 1 /* numRelaunch */, 105 0 /* numConfigChange */); 106 107 // Undock the device. 108 separateTestJournal(); 109 dockTestSession.undock(); 110 dockTestSession.waitForNormalUiMode(DESK_RESOURCES_ACTIVITY); 111 112 // The activity is relaunched again. 113 assertRelaunchOrConfigChanged(DESK_RESOURCES_ACTIVITY, 1 /* numRelaunch */, 114 0 /* numConfigChange */); 115 } 116 getConfigSkipRelaunchOnDock()117 boolean getConfigSkipRelaunchOnDock() { 118 return mContext.getResources().getBoolean( 119 Resources.getSystem().getIdentifier("config_skipActivityRelaunchWhenDocking", 120 "bool", "android")); 121 } 122 123 /** 124 * Rotates the device to the same rotation as it would rotate to when docked. 125 * 126 * Dock rotation is read from config_deskDockRotation. 127 */ rotateToDockRotation(RotationSession rotationSession)128 void rotateToDockRotation(RotationSession rotationSession) { 129 int rotation = rotationDegreesToConst(mContext.getResources().getInteger( 130 Resources.getSystem().getIdentifier("config_deskDockRotation", 131 "integer", "android"))); 132 if (rotation == -1) { 133 // -1 could come from the const itself, which means no rotation on dock, or from 134 // rotationDegreesToConst, which means we got an unexpected value from the resource. 135 return; 136 } 137 rotationSession.set(rotation); 138 } 139 140 /** 141 * Converts from a rotation in degrees to a {@link Surface.Rotation} constant. 142 * 143 * Returns -1 if a value that doesn't match a {@link Surface.Rotation} constant is provided. 144 */ rotationDegreesToConst(int rotationDegrees)145 private int rotationDegreesToConst(int rotationDegrees) { 146 switch (rotationDegrees) { 147 case 0: 148 return Surface.ROTATION_0; 149 case 90: 150 return Surface.ROTATION_90; 151 case 180: 152 return Surface.ROTATION_180; 153 case 270: 154 return Surface.ROTATION_270; 155 } 156 return -1; 157 } 158 159 160 private class DockTestSession implements AutoCloseable { dock()161 private void dock() { 162 runShellCommand("dumpsys DockObserver set state " 163 + Intent.EXTRA_DOCK_STATE_HE_DESK); 164 } 165 undock()166 private void undock() { 167 runShellCommand("dumpsys DockObserver set state " 168 + Intent.EXTRA_DOCK_STATE_UNDOCKED); 169 } 170 waitForDeskUiMode(ComponentName activity)171 private void waitForDeskUiMode(ComponentName activity) { 172 waitForUiMode(activity, UI_MODE_TYPE_DESK); 173 } 174 waitForNormalUiMode(ComponentName activity)175 private void waitForNormalUiMode(ComponentName activity) { 176 waitForUiMode(activity, UI_MODE_TYPE_NORMAL); 177 } 178 179 /** 180 * Waits until the activity has received the expected uiMode in a configuration change. 181 */ waitForUiMode(ComponentName activity, int expectedUiMode)182 private void waitForUiMode(ComponentName activity, int expectedUiMode) { 183 mWmState.waitFor(state -> { 184 int actualUiMode = state.getActivity(activity).getUiMode(); 185 return (actualUiMode & UI_MODE_TYPE_MASK) == actualUiMode; 186 }, "Didn't enter expected uiMode in time: " + expectedUiMode); 187 } 188 189 @Override close()190 public void close() throws Exception { 191 runShellCommand("dumpsys DockObserver reset"); 192 } 193 } 194 } 195