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