1 /* 2 * Copyright (C) 2019 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.Intent.FLAG_ACTIVITY_NEW_TASK; 20 import static android.server.wm.CommandSession.ActivityCallback.ON_CONFIGURATION_CHANGED; 21 import static android.server.wm.CommandSession.ActivityCallback.ON_RESUME; 22 import static android.view.Display.DEFAULT_DISPLAY; 23 import static android.view.Display.INVALID_DISPLAY; 24 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; 25 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; 26 27 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 28 29 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand; 30 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 31 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertTrue; 34 import static org.junit.Assume.assumeTrue; 35 36 import android.app.Activity; 37 import android.app.ActivityOptions; 38 import android.content.ComponentName; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.os.Bundle; 42 import android.platform.test.annotations.Presubmit; 43 import android.server.wm.WindowManagerState.DisplayContent; 44 import android.view.Display; 45 import android.view.View; 46 import android.view.WindowManager; 47 import android.view.inputmethod.InputMethodManager; 48 import android.widget.EditText; 49 import android.widget.LinearLayout; 50 51 import androidx.test.filters.MediumTest; 52 import androidx.test.rule.ActivityTestRule; 53 54 import com.android.cts.mockime.ImeEventStream; 55 import com.android.cts.mockime.MockImeSession; 56 57 import org.junit.Before; 58 import org.junit.Test; 59 60 import java.util.concurrent.TimeUnit; 61 62 /** 63 * Build/Install/Run: 64 * atest CtsWindowManagerDeviceTestCases:MultiDisplayClientTests 65 */ 66 @Presubmit 67 @MediumTest 68 @android.server.wm.annotation.Group3 69 public class MultiDisplayClientTests extends MultiDisplayTestBase { 70 71 private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); // 10 seconds 72 private static final String EXTRA_SHOW_IME = "show_ime"; 73 74 @Before 75 @Override setUp()76 public void setUp() throws Exception { 77 super.setUp(); 78 assumeTrue(supportsMultiDisplay()); 79 } 80 81 @Test testDisplayIdUpdateOnMove_RelaunchActivity()82 public void testDisplayIdUpdateOnMove_RelaunchActivity() throws Exception { 83 testDisplayIdUpdateOnMove(ClientTestActivity.class, false /* handlesConfigChange */); 84 } 85 86 @Test testDisplayIdUpdateOnMove_NoRelaunchActivity()87 public void testDisplayIdUpdateOnMove_NoRelaunchActivity() throws Exception { 88 testDisplayIdUpdateOnMove(NoRelaunchActivity.class, true /* handlesConfigChange */); 89 } 90 testDisplayIdUpdateOnMove(Class<T> activityClass, boolean handlesConfigChange)91 private <T extends Activity> void testDisplayIdUpdateOnMove(Class<T> activityClass, 92 boolean handlesConfigChange) throws Exception { 93 final ActivityTestRule<T> activityTestRule = new ActivityTestRule<>( 94 activityClass, true /* initialTouchMode */, false /* launchActivity */); 95 96 // Launch activity display. 97 separateTestJournal(); 98 Activity activity = activityTestRule.launchActivity(new Intent()); 99 final ComponentName activityName = activity.getComponentName(); 100 waitAndAssertResume(activityName); 101 102 // Create new simulated display 103 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 104 .setSimulateDisplay(true) 105 .createDisplay(); 106 107 // Move the activity to the new secondary display. 108 separateTestJournal(); 109 final ActivityOptions launchOptions = ActivityOptions.makeBasic(); 110 final int displayId = newDisplay.mId; 111 launchOptions.setLaunchDisplayId(displayId); 112 final Intent newDisplayIntent = new Intent(mContext, activityClass); 113 newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK); 114 getInstrumentation().getTargetContext().startActivity(newDisplayIntent, 115 launchOptions.toBundle()); 116 waitAndAssertTopResumedActivity(activityName, displayId, 117 "Activity moved to secondary display must be focused"); 118 119 if (handlesConfigChange) { 120 // Wait for activity to receive the configuration change after move 121 waitAndAssertConfigurationChange(activityName); 122 } else { 123 // Activity will be re-created, wait for resumed state 124 waitAndAssertResume(activityName); 125 activity = activityTestRule.getActivity(); 126 } 127 128 final String suffix = " must be updated."; 129 assertEquals("Activity#getDisplayId()" + suffix, displayId, activity.getDisplayId()); 130 assertEquals("Activity#getDisplay" + suffix, 131 displayId, activity.getDisplay().getDisplayId()); 132 133 final WindowManager wm = activity.getWindowManager(); 134 assertEquals("WM#getDefaultDisplay()" + suffix, 135 displayId, wm.getDefaultDisplay().getDisplayId()); 136 137 final View view = activity.getWindow().getDecorView(); 138 assertEquals("View#getDisplay()" + suffix, 139 displayId, view.getDisplay().getDisplayId()); 140 } 141 142 @Test testDisplayIdUpdateWhenImeMove_RelaunchActivity()143 public void testDisplayIdUpdateWhenImeMove_RelaunchActivity() throws Exception { 144 testDisplayIdUpdateWhenImeMove(ClientTestActivity.class); 145 } 146 147 @Test testDisplayIdUpdateWhenImeMove_NoRelaunchActivity()148 public void testDisplayIdUpdateWhenImeMove_NoRelaunchActivity() throws Exception { 149 testDisplayIdUpdateWhenImeMove(NoRelaunchActivity.class); 150 } 151 testDisplayIdUpdateWhenImeMove(Class<? extends ImeTestActivity> activityClass)152 private void testDisplayIdUpdateWhenImeMove(Class<? extends ImeTestActivity> activityClass) 153 throws Exception { 154 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 155 156 final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession(); 157 final MockImeSession mockImeSession = MockImeHelper.createManagedMockImeSession(this); 158 159 assertImeShownAndMatchesDisplayId( 160 activityClass, mockImeSession, DEFAULT_DISPLAY); 161 162 final DisplayContent newDisplay = virtualDisplaySession 163 .setSimulateDisplay(true) 164 .setShowSystemDecorations(true) 165 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 166 .createDisplay(); 167 168 // Launch activity on the secondary display and make IME show. 169 assertImeShownAndMatchesDisplayId( 170 activityClass, mockImeSession, newDisplay.mId); 171 } 172 assertImeShownAndMatchesDisplayId(Class<? extends ImeTestActivity> activityClass, MockImeSession imeSession, int targetDisplayId)173 private void assertImeShownAndMatchesDisplayId(Class<? extends ImeTestActivity> activityClass, 174 MockImeSession imeSession, int targetDisplayId) throws Exception { 175 final ImeEventStream stream = imeSession.openEventStream(); 176 177 final Intent intent = new Intent(mContext, activityClass) 178 .putExtra(EXTRA_SHOW_IME, true).setFlags(FLAG_ACTIVITY_NEW_TASK); 179 separateTestJournal(); 180 final ActivityOptions launchOptions = ActivityOptions.makeBasic(); 181 launchOptions.setLaunchDisplayId(targetDisplayId); 182 getInstrumentation().getTargetContext().startActivity(intent, launchOptions.toBundle()); 183 184 185 // Verify if IME is showed on the target display. 186 expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT); 187 mWmState.waitAndAssertImeWindowShownOnDisplay(targetDisplayId); 188 189 final int imeDisplayId = expectCommand(stream, imeSession.callGetDisplayId(), TIMEOUT) 190 .getReturnIntegerValue(); 191 assertEquals("IME#getDisplayId() must match when IME move.", 192 targetDisplayId, imeDisplayId); 193 } 194 195 @Test testInputMethodManagerDisplayId()196 public void testInputMethodManagerDisplayId() { 197 // Create a simulated display. 198 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 199 .setSimulateDisplay(true) 200 .createDisplay(); 201 202 final Display display = mDm.getDisplay(newDisplay.mId); 203 final Context newDisplayContext = mContext.createDisplayContext(display); 204 final InputMethodManager imm = newDisplayContext.getSystemService(InputMethodManager.class); 205 206 assertEquals("IMM#getDisplayId() must match.", newDisplay.mId, imm.getDisplayId()); 207 } 208 209 @Test testViewGetDisplayOnPrimaryDisplay()210 public void testViewGetDisplayOnPrimaryDisplay() { 211 testViewGetDisplay(true /* isPrimary */); 212 } 213 214 @Test testViewGetDisplayOnSecondaryDisplay()215 public void testViewGetDisplayOnSecondaryDisplay() { 216 testViewGetDisplay(false /* isPrimary */); 217 } 218 testViewGetDisplay(boolean isPrimary)219 private void testViewGetDisplay(boolean isPrimary) { 220 final TestActivitySession<ClientTestActivity> activitySession = 221 createManagedTestActivitySession(); 222 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 223 .setSimulateDisplay(true) 224 .createDisplay(); 225 final int displayId = isPrimary ? DEFAULT_DISPLAY : newDisplay.mId; 226 227 separateTestJournal(); 228 activitySession.launchTestActivityOnDisplaySync(ClientTestActivity.class, displayId); 229 230 final Activity activity = activitySession.getActivity(); 231 final ComponentName activityName = activity.getComponentName(); 232 233 waitAndAssertTopResumedActivity(activityName, displayId, 234 "Activity launched on display:" + displayId + " must be focused"); 235 236 // Test View#getdisplay() from activity 237 final View view = activity.getWindow().getDecorView(); 238 assertEquals("View#getDisplay() must match.", displayId, view.getDisplay().getDisplayId()); 239 240 final int[] resultDisplayId = { INVALID_DISPLAY }; 241 activitySession.runOnMainAndAssertWithTimeout( 242 () -> { 243 // Test View#getdisplay() from WM#addView() 244 final WindowManager wm = activity.getWindowManager(); 245 final View addedView = new View(activity); 246 wm.addView(addedView, new WindowManager.LayoutParams()); 247 248 // Get display ID from callback in case the added view has not be attached. 249 addedView.addOnAttachStateChangeListener( 250 new View.OnAttachStateChangeListener() { 251 @Override 252 public void onViewAttachedToWindow(View view) { 253 resultDisplayId[0] = view.getDisplay().getDisplayId(); 254 } 255 256 @Override 257 public void onViewDetachedFromWindow(View view) {} 258 }); 259 260 return displayId == resultDisplayId[0]; 261 }, TIMEOUT, "Display from added view must match. " 262 + "Should be display:" + displayId 263 + ", but was display:" + resultDisplayId[0] 264 ); 265 } 266 waitAndAssertConfigurationChange(ComponentName activityName)267 private void waitAndAssertConfigurationChange(ComponentName activityName) { 268 assertTrue("Must receive a single configuration change", 269 mWmState.waitForWithAmState( 270 state -> getCallbackCount(activityName, ON_CONFIGURATION_CHANGED) == 1, 271 activityName + " receives configuration change")); 272 } 273 waitAndAssertResume(ComponentName activityName)274 private void waitAndAssertResume(ComponentName activityName) { 275 assertTrue("Must be resumed once", 276 mWmState.waitForWithAmState( 277 state -> getCallbackCount(activityName, ON_RESUME) == 1, 278 activityName + " performs resume")); 279 } 280 getCallbackCount(ComponentName activityName, CommandSession.ActivityCallback callback)281 private static int getCallbackCount(ComponentName activityName, 282 CommandSession.ActivityCallback callback) { 283 final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName); 284 return lifecycles.getCount(callback); 285 } 286 287 public static class ClientTestActivity extends ImeTestActivity { } 288 289 public static class NoRelaunchActivity extends ImeTestActivity { } 290 291 public static class ImeTestActivity extends CommandSession.BasicTestActivity { 292 private EditText mEditText; 293 private boolean mShouldShowIme; 294 295 @Override onCreate(Bundle icicle)296 protected void onCreate(Bundle icicle) { 297 super.onCreate(icicle); 298 mShouldShowIme = getIntent().hasExtra(EXTRA_SHOW_IME); 299 if (mShouldShowIme) { 300 mEditText = new EditText(this); 301 final LinearLayout layout = new LinearLayout(this); 302 layout.setOrientation(LinearLayout.VERTICAL); 303 layout.addView(mEditText); 304 setContentView(layout); 305 } 306 } 307 308 @Override onResume()309 protected void onResume() { 310 super.onResume(); 311 if (mShouldShowIme) { 312 getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE); 313 mEditText.requestFocus(); 314 } 315 } 316 } 317 } 318