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.pm.ActivityInfo.CONFIG_SCREEN_SIZE; 20 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; 21 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE; 22 import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS; 23 import static android.server.wm.ShellCommandHelper.executeShellCommand; 24 import static android.server.wm.UiDeviceUtils.pressSleepButton; 25 import static android.view.Display.INVALID_DISPLAY; 26 27 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 28 29 import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents; 30 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 31 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent; 32 import static com.android.cts.mockime.ImeEventStreamTestUtils.withDescription; 33 34 import static org.junit.Assert.assertEquals; 35 import static org.junit.Assert.assertNotEquals; 36 37 import android.app.WallpaperManager; 38 import android.content.ComponentName; 39 import android.content.Context; 40 import android.content.pm.PackageManager; 41 import android.content.res.Configuration; 42 import android.inputmethodservice.InputMethodService; 43 import android.os.Bundle; 44 import android.server.wm.CommandSession.ActivitySession; 45 import android.server.wm.CommandSession.ActivitySessionClient; 46 import android.server.wm.WindowManagerState.DisplayContent; 47 import android.util.Pair; 48 49 import androidx.annotation.Nullable; 50 51 import com.android.cts.mockime.ImeEvent; 52 import com.android.cts.mockime.ImeEventStream; 53 import com.android.cts.mockime.ImeEventStreamTestUtils; 54 55 import org.junit.Before; 56 import org.junit.ClassRule; 57 58 import java.util.List; 59 import java.util.concurrent.TimeUnit; 60 import java.util.function.Consumer; 61 import java.util.function.Predicate; 62 63 /** 64 * Base class for ActivityManager display tests. 65 * 66 * @see android.server.wm.display.DisplayTests 67 * @see android.server.wm.display.AppConfigurationTests 68 * @see android.server.wm.multidisplay.MultiDisplayKeyguardTests 69 * @see android.server.wm.multidisplay.MultiDisplayLockedKeyguardTests 70 */ 71 public class MultiDisplayTestBase extends ActivityManagerTestBase { 72 73 public static final int CUSTOM_DENSITY_DPI = 222; 74 protected Context mTargetContext; 75 76 @ClassRule 77 public static DisableImmersiveModeConfirmationRule mDisableImmersiveModeConfirmationRule = 78 new DisableImmersiveModeConfirmationRule(); 79 80 @Before 81 @Override setUp()82 public void setUp() throws Exception { 83 super.setUp(); 84 mTargetContext = getInstrumentation().getTargetContext(); 85 } 86 isAutomotive()87 protected boolean isAutomotive() { 88 return mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE); 89 } 90 supportsInstallableIme()91 protected boolean supportsInstallableIme() { 92 return mContext.getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS); 93 } 94 95 public static class LetterboxAspectRatioSession extends IgnoreOrientationRequestSession { 96 private static final String WM_SET_LETTERBOX_STYLE_ASPECT_RATIO = 97 "wm set-letterbox-style --aspectRatio "; 98 private static final String WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO = 99 "wm reset-letterbox-style aspectRatio"; 100 LetterboxAspectRatioSession(float aspectRatio)101 LetterboxAspectRatioSession(float aspectRatio) { 102 super(true); 103 executeShellCommand(WM_SET_LETTERBOX_STYLE_ASPECT_RATIO + aspectRatio); 104 } 105 106 @Override close()107 public void close() { 108 super.close(); 109 executeShellCommand(WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO); 110 } 111 } 112 113 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedLetterboxAspectRatioSession( float aspectRatio)114 protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession( 115 float aspectRatio) { 116 return mObjectTracker.manage(new LetterboxAspectRatioSession(aspectRatio)); 117 } 118 119 // TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated. 120 public class VirtualDisplayLauncher extends VirtualDisplaySession { 121 private final ActivitySessionClient mActivitySessionClient = createActivitySessionClient(); 122 launchActivityOnDisplay(ComponentName activityName, DisplayContent display)123 public ActivitySession launchActivityOnDisplay(ComponentName activityName, 124 DisplayContent display) { 125 return launchActivityOnDisplay(activityName, display, null /* extrasConsumer */, 126 true /* withShellPermission */, true /* waitForLaunch */); 127 } 128 launchActivityOnDisplay(ComponentName activityName, DisplayContent display, Consumer<Bundle> extrasConsumer, boolean withShellPermission, boolean waitForLaunch)129 public ActivitySession launchActivityOnDisplay(ComponentName activityName, 130 DisplayContent display, Consumer<Bundle> extrasConsumer, 131 boolean withShellPermission, boolean waitForLaunch) { 132 return launchActivity(builder -> builder 133 // VirtualDisplayActivity is in different package. If the display is not public, 134 // it requires shell permission to launch activity ({@see com.android.server.wm. 135 // ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay}). 136 .setWithShellPermission(withShellPermission) 137 .setWaitForLaunched(waitForLaunch) 138 .setIntentExtra(extrasConsumer) 139 .setTargetActivity(activityName) 140 .setDisplayId(display.mId)); 141 } 142 launchActivity(Consumer<LaunchActivityBuilder> setupBuilder)143 public ActivitySession launchActivity(Consumer<LaunchActivityBuilder> setupBuilder) { 144 final LaunchActivityBuilder builder = getLaunchActivityBuilder() 145 .setUseInstrumentation(); 146 setupBuilder.accept(builder); 147 return mActivitySessionClient.startActivity(builder); 148 } 149 150 @Override close()151 public void close() { 152 super.close(); 153 mActivitySessionClient.close(); 154 } 155 } 156 157 /** A clearer alias of {@link Pair#create(Object, Object)}. */ pair(K k, V v)158 protected <K, V> Pair<K, V> pair(K k, V v) { 159 return new Pair<>(k, v); 160 } 161 assertBothDisplaysHaveResumedActivities( Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair)162 protected void assertBothDisplaysHaveResumedActivities( 163 Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair) { 164 assertNotEquals("Displays must be different. First display id: " 165 + firstPair.first, firstPair.first, secondPair.first); 166 mWmState.assertResumedActivities("Both displays must have resumed activities", 167 mapping -> { 168 mapping.put(firstPair.first, firstPair.second); 169 mapping.put(secondPair.first, secondPair.second); 170 }); 171 } 172 173 /** Checks if the device supports multi-display. */ supportsMultiDisplay()174 protected boolean supportsMultiDisplay() { 175 return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS); 176 } 177 178 /** Checks if the device supports live wallpaper for multi-display. */ supportsLiveWallpaper()179 protected boolean supportsLiveWallpaper() { 180 return hasDeviceFeature(PackageManager.FEATURE_LIVE_WALLPAPER); 181 } 182 183 /** Checks if the device supports wallpaper. */ supportsWallpaper()184 protected boolean supportsWallpaper() { 185 return WallpaperManager.getInstance(mContext).isWallpaperSupported(); 186 } 187 188 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedExternalDisplaySession()189 protected ExternalDisplaySession createManagedExternalDisplaySession() { 190 return mObjectTracker.manage(new ExternalDisplaySession()); 191 } 192 193 @SafeVarargs waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId, Predicate<ImeEvent>... conditions)194 protected final void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, 195 int displayId, 196 Predicate<ImeEvent>... conditions) throws Exception { 197 for (var condition : conditions) { 198 expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */); 199 } 200 // Assert the IME is shown on the expected display. 201 mWmState.waitAndAssertImeWindowShownOnDisplay(displayId); 202 } 203 waitAndAssertImeNoScreenSizeChanged(ImeEventStream stream)204 protected void waitAndAssertImeNoScreenSizeChanged(ImeEventStream stream) { 205 notExpectEvent(stream, withDescription("onConfigurationChanged(SCREEN_SIZE | ..)", 206 event -> "onConfigurationChanged".equals(event.getEventName()) 207 && (event.getArguments().getInt("ConfigUpdates") & CONFIG_SCREEN_SIZE) 208 != 0), TimeUnit.SECONDS.toMillis(1) /* eventTimeout */); 209 } 210 211 /** 212 * Clears all {@link InputMethodService#onConfigurationChanged(Configuration)} events from the 213 * given {@code stream} and returns a forked {@link ImeEventStream}. 214 * 215 * @see ImeEventStreamTestUtils#clearAllEvents(ImeEventStream, String) 216 */ clearOnConfigurationChangedFromStream(ImeEventStream stream)217 protected ImeEventStream clearOnConfigurationChangedFromStream(ImeEventStream stream) { 218 return clearAllEvents(stream, "onConfigurationChanged"); 219 } 220 221 /** 222 * This class is used when you need to test virtual display created by a privileged app. 223 * 224 * If you need to test virtual display created by a non-privileged app or when you need to test 225 * on simulated display, please use {@link VirtualDisplaySession} instead. 226 */ 227 public class ExternalDisplaySession implements AutoCloseable { 228 229 private boolean mCanShowWithInsecureKeyguard = false; 230 private boolean mPublicDisplay = false; 231 private boolean mShowSystemDecorations = false; 232 233 private int mDisplayId = INVALID_DISPLAY; 234 235 @Nullable 236 private VirtualDisplayHelper mExternalDisplayHelper; 237 setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)238 public ExternalDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) { 239 mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard; 240 return this; 241 } 242 setPublicDisplay(boolean publicDisplay)243 public ExternalDisplaySession setPublicDisplay(boolean publicDisplay) { 244 mPublicDisplay = publicDisplay; 245 return this; 246 } 247 248 /** 249 * @deprecated untrusted virtual display won't have system decorations even it has the flag. 250 * Only use this method to verify that. To test secondary display with system decorations, 251 * please use simulated display. 252 */ 253 @Deprecated setShowSystemDecorations(boolean showSystemDecorations)254 public ExternalDisplaySession setShowSystemDecorations(boolean showSystemDecorations) { 255 mShowSystemDecorations = showSystemDecorations; 256 return this; 257 } 258 259 /** 260 * Creates a private virtual display with insecure keyguard flags set. 261 */ createVirtualDisplay()262 public DisplayContent createVirtualDisplay() { 263 final List<DisplayContent> originalDS = getDisplaysStates(); 264 final int originalDisplayCount = originalDS.size(); 265 266 mExternalDisplayHelper = new VirtualDisplayHelper(); 267 mExternalDisplayHelper 268 .setPublicDisplay(mPublicDisplay) 269 .setCanShowWithInsecureKeyguard(mCanShowWithInsecureKeyguard) 270 .setShowSystemDecorations(mShowSystemDecorations) 271 .createAndWaitForDisplay(); 272 273 // Wait for the virtual display to be created and get configurations. 274 final List<DisplayContent> ds = getDisplayStateAfterChange(originalDisplayCount + 1); 275 assertEquals("New virtual display must be created", originalDisplayCount + 1, 276 ds.size()); 277 278 // Find the newly added display. 279 final DisplayContent newDisplay = findNewDisplayStates(originalDS, ds).get(0); 280 mDisplayId = newDisplay.mId; 281 return newDisplay; 282 } 283 turnDisplayOff()284 public void turnDisplayOff() { 285 if (mExternalDisplayHelper == null) { 286 throw new RuntimeException("No external display created"); 287 } 288 mExternalDisplayHelper.turnDisplayOff(); 289 } 290 turnDisplayOn()291 public void turnDisplayOn() { 292 if (mExternalDisplayHelper == null) { 293 throw new RuntimeException("No external display created"); 294 } 295 mExternalDisplayHelper.turnDisplayOn(); 296 } 297 298 @Override close()299 public void close() { 300 if (mExternalDisplayHelper != null) { 301 mExternalDisplayHelper.releaseDisplay(); 302 mExternalDisplayHelper = null; 303 304 waitForDisplayGone(d -> d.mId == mDisplayId); 305 mDisplayId = INVALID_DISPLAY; 306 } 307 } 308 } 309 310 public class PrimaryDisplayStateSession implements AutoCloseable { 311 turnScreenOff()312 public void turnScreenOff() { 313 setPrimaryDisplayState(false); 314 } 315 316 @Override close()317 public void close() { 318 setPrimaryDisplayState(true); 319 } 320 321 /** Turns the primary display on/off by pressing the power key */ setPrimaryDisplayState(boolean wantOn)322 private void setPrimaryDisplayState(boolean wantOn) { 323 if (wantOn) { 324 UiDeviceUtils.wakeUpAndUnlock(mContext); 325 } else { 326 pressSleepButton(); 327 } 328 VirtualDisplayHelper.waitForDefaultDisplayState(wantOn); 329 } 330 } 331 } 332