1 /* 2 * Copyright (C) 2017 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.app.UiModeManager.MODE_NIGHT_AUTO; 20 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; 21 import static android.app.UiModeManager.MODE_NIGHT_NO; 22 import static android.app.UiModeManager.MODE_NIGHT_YES; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 24 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 25 import static android.content.Intent.ACTION_MAIN; 26 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 27 import static android.server.wm.CliIntentExtra.extraBool; 28 import static android.server.wm.CliIntentExtra.extraString; 29 import static android.server.wm.WindowManagerState.STATE_RESUMED; 30 import static android.server.wm.app.Components.HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY; 31 import static android.server.wm.app.Components.SPLASHSCREEN_ACTIVITY; 32 import static android.server.wm.app.Components.SPLASH_SCREEN_REPLACE_ICON_ACTIVITY; 33 import static android.server.wm.app.Components.SPLASH_SCREEN_REPLACE_THEME_ACTIVITY; 34 import static android.server.wm.app.Components.SPLASH_SCREEN_STYLE_THEME_ACTIVITY; 35 import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES; 36 import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITY; 37 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT; 38 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS; 39 import static android.server.wm.app.Components.TestActivity.EXTRA_OPTION; 40 import static android.server.wm.app.Components.TestStartingWindowKeys.CANCEL_HANDLE_EXIT; 41 import static android.server.wm.app.Components.TestStartingWindowKeys.CENTER_VIEW_IS_SURFACE_VIEW; 42 import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_BRANDING_VIEW; 43 import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_CENTER_VIEW; 44 import static android.server.wm.app.Components.TestStartingWindowKeys.DELAY_RESUME; 45 import static android.server.wm.app.Components.TestStartingWindowKeys.GET_NIGHT_MODE_ACTIVITY_CHANGED; 46 import static android.server.wm.app.Components.TestStartingWindowKeys.HANDLE_SPLASH_SCREEN_EXIT; 47 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_DURATION; 48 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_START; 49 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_BACKGROUND_COLOR; 50 import static android.server.wm.app.Components.TestStartingWindowKeys.OVERRIDE_THEME_COLOR; 51 import static android.server.wm.app.Components.TestStartingWindowKeys.OVERRIDE_THEME_COMPONENT; 52 import static android.server.wm.app.Components.TestStartingWindowKeys.OVERRIDE_THEME_ENABLED; 53 import static android.server.wm.app.Components.TestStartingWindowKeys.RECEIVE_SPLASH_SCREEN_EXIT; 54 import static android.server.wm.app.Components.TestStartingWindowKeys.REPLACE_ICON_EXIT; 55 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_CREATE; 56 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_RESUME; 57 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_SET_NIGHT_MODE_ON_CREATE; 58 import static android.server.wm.app.Components.TestStartingWindowKeys.STYLE_THEME_COMPONENT; 59 import static android.view.Display.DEFAULT_DISPLAY; 60 import static android.view.WindowInsets.Type.captionBar; 61 import static android.view.WindowInsets.Type.systemBars; 62 63 import static org.hamcrest.MatcherAssert.assertThat; 64 import static org.hamcrest.Matchers.greaterThanOrEqualTo; 65 import static org.hamcrest.Matchers.lessThanOrEqualTo; 66 import static org.junit.Assert.assertEquals; 67 import static org.junit.Assert.assertFalse; 68 import static org.junit.Assert.assertTrue; 69 import static org.junit.Assert.fail; 70 import static org.junit.Assume.assumeFalse; 71 import static org.junit.Assume.assumeTrue; 72 73 import android.app.ActivityOptions; 74 import android.app.UiModeManager; 75 import android.content.ComponentName; 76 import android.content.Intent; 77 import android.content.pm.LauncherApps; 78 import android.content.pm.ShortcutInfo; 79 import android.content.pm.ShortcutManager; 80 import android.content.res.Configuration; 81 import android.graphics.Bitmap; 82 import android.graphics.Color; 83 import android.graphics.Rect; 84 import android.os.Bundle; 85 import android.platform.test.annotations.Presubmit; 86 import android.view.WindowManager; 87 import android.view.WindowMetrics; 88 import android.window.SplashScreen; 89 90 import androidx.core.graphics.ColorUtils; 91 92 import com.android.compatibility.common.util.TestUtils; 93 94 import org.junit.After; 95 import org.junit.Before; 96 import org.junit.Rule; 97 import org.junit.Test; 98 99 import java.util.Collections; 100 import java.util.function.Consumer; 101 102 /** 103 * Build/Install/Run: 104 * atest CtsWindowManagerDeviceTestCases:SplashscreenTests 105 */ 106 @Presubmit 107 @android.server.wm.annotation.Group1 108 public class SplashscreenTests extends ActivityManagerTestBase { 109 110 private static final int CENTER_ICON_SIZE = 192; 111 private static final int BRANDING_HEIGHT = 80; 112 private static final int BRANDING_DEFAULT_MARGIN = 60; 113 114 @Rule 115 public final DumpOnFailure dumpOnFailure = new DumpOnFailure(); 116 117 @Before setUp()118 public void setUp() throws Exception { 119 super.setUp(); 120 mWmState.setSanityCheckWithFocusedWindow(false); 121 mWmState.waitForDisplayUnfrozen(); 122 } 123 124 @After tearDown()125 public void tearDown() { 126 mWmState.setSanityCheckWithFocusedWindow(true); 127 } 128 129 /** 130 * @return The starter activity session to start the test activity 131 */ prepareTestStarter()132 private CommandSession.ActivitySession prepareTestStarter() { 133 return createManagedActivityClientSession() 134 .startActivity(getLaunchActivityBuilder().setUseInstrumentation()); 135 } 136 startActivitiesFromStarter(CommandSession.ActivitySession starter, Intent[] intents, ActivityOptions options)137 private void startActivitiesFromStarter(CommandSession.ActivitySession starter, 138 Intent[] intents, ActivityOptions options) { 139 140 final Bundle data = new Bundle(); 141 data.putParcelableArray(EXTRA_INTENTS, intents); 142 if (options != null) { 143 data.putParcelable(EXTRA_OPTION, options.toBundle()); 144 } 145 starter.sendCommand(COMMAND_START_ACTIVITIES, data); 146 } 147 startActivityFromStarter(CommandSession.ActivitySession starter, ComponentName componentName, Consumer<Intent> fillExtra, ActivityOptions options)148 private void startActivityFromStarter(CommandSession.ActivitySession starter, 149 ComponentName componentName, Consumer<Intent> fillExtra, ActivityOptions options) { 150 151 final Bundle data = new Bundle(); 152 final Intent startIntent = new Intent(); 153 startIntent.setComponent(componentName); 154 startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 155 fillExtra.accept(startIntent); 156 data.putParcelable(EXTRA_INTENT, startIntent); 157 if (options != null) { 158 data.putParcelable(EXTRA_OPTION, options.toBundle()); 159 } 160 starter.sendCommand(COMMAND_START_ACTIVITY, data); 161 } 162 163 @Test testSplashscreenContent()164 public void testSplashscreenContent() { 165 // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly 166 // applied insets by system bars in AAOS. 167 assumeFalse(isCar()); 168 assumeFalse(isLeanBack()); 169 170 final CommandSession.ActivitySession starter = prepareTestStarter(); 171 final ActivityOptions noIconOptions = ActivityOptions.makeBasic() 172 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); 173 noIconOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); 174 175 // launch from app with no-icon options 176 startActivityFromStarter(starter, SPLASHSCREEN_ACTIVITY, 177 intent -> {}, noIconOptions); 178 // The windowSplashScreenContent attribute is set to RED. We check that it is ignored. 179 testSplashScreenColor(SPLASHSCREEN_ACTIVITY, Color.BLUE, Color.WHITE); 180 } 181 182 @Test testSplashscreenContent_FreeformWindow()183 public void testSplashscreenContent_FreeformWindow() { 184 // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly 185 // applied insets by system bars in AAOS. 186 assumeFalse(isCar()); 187 assumeTrue(supportsFreeform()); 188 189 final CommandSession.ActivitySession starter = prepareTestStarter(); 190 final ActivityOptions noIconOptions = ActivityOptions.makeBasic() 191 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); 192 noIconOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); 193 // launch from app with no-icon options 194 startActivityFromStarter(starter, SPLASHSCREEN_ACTIVITY, 195 intent -> {}, noIconOptions); 196 // The windowSplashScreenContent attribute is set to RED. We check that it is ignored. 197 testSplashScreenColor(SPLASHSCREEN_ACTIVITY, Color.BLUE, Color.WHITE); 198 } 199 testSplashScreenColor(ComponentName name, int primaryColor, int secondaryColor)200 private void testSplashScreenColor(ComponentName name, int primaryColor, int secondaryColor) { 201 // Activity may not be launched yet even if app transition is in idle state. 202 mWmState.waitForActivityState(name, STATE_RESUMED); 203 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 204 205 final Bitmap image = takeScreenshot(); 206 final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics(); 207 final Rect stableBounds = new Rect(windowMetrics.getBounds()); 208 stableBounds.inset(windowMetrics.getWindowInsets().getInsetsIgnoringVisibility( 209 systemBars() & ~captionBar())); 210 WindowManagerState.WindowState startingWindow = mWmState.findFirstWindowWithType( 211 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); 212 213 Rect startingWindowBounds = startingWindow.getBounds(); 214 final Rect appBounds; 215 if (startingWindowBounds != null) { 216 appBounds = new Rect(startingWindowBounds); 217 } else { 218 appBounds = new Rect(startingWindow.getFrame()); 219 } 220 221 insetGivenFrame(startingWindow, WindowManagerState.InsetsSource::isCaptionBar, 222 appBounds); 223 224 assertFalse("Couldn't find splash screen bounds. Impossible to assert the colors", 225 appBounds.isEmpty()); 226 227 // Use ratios to flexibly accommodate circular or not quite rectangular displays 228 // Note: Color.BLACK is the pixel color outside of the display region 229 230 int px = WindowManagerState.dpToPx(CENTER_ICON_SIZE, 231 mContext.getResources().getConfiguration().densityDpi); 232 Rect ignoreRect = new Rect(0, 0, px, px); 233 ignoreRect.offsetTo(appBounds.centerX() - ignoreRect.width() / 2, 234 appBounds.centerY() - ignoreRect.height() / 2); 235 236 appBounds.intersect(stableBounds); 237 assertColors(image, appBounds, primaryColor, 0.99f, secondaryColor, 0.02f, ignoreRect); 238 } 239 240 // For real devices, gamma correction might be applied on hardware driver, so the colors may 241 // not exactly match. isSimilarColor(int a, int b)242 private static boolean isSimilarColor(int a, int b) { 243 if (a == b) { 244 return true; 245 } 246 return Math.abs(Color.alpha(a) - Color.alpha(b)) + 247 Math.abs(Color.red(a) - Color.red(b)) + 248 Math.abs(Color.green(a) - Color.green(b)) + 249 Math.abs(Color.blue(a) - Color.blue(b)) < 10; 250 } 251 assertColors(Bitmap img, Rect bounds, int primaryColor, float expectedPrimaryRatio, int secondaryColor, float acceptableWrongRatio, Rect ignoreRect)252 private void assertColors(Bitmap img, Rect bounds, int primaryColor, float expectedPrimaryRatio, 253 int secondaryColor, float acceptableWrongRatio, Rect ignoreRect) { 254 255 int primaryPixels = 0; 256 int secondaryPixels = 0; 257 int wrongPixels = 0; 258 259 assertThat(bounds.top, greaterThanOrEqualTo(0)); 260 assertThat(bounds.left, greaterThanOrEqualTo(0)); 261 assertThat(bounds.right, lessThanOrEqualTo(img.getWidth())); 262 assertThat(bounds.bottom, lessThanOrEqualTo(img.getHeight())); 263 264 for (int x = bounds.left; x < bounds.right; x++) { 265 for (int y = bounds.top; y < bounds.bottom; y++) { 266 if (ignoreRect != null && ignoreRect.contains(x, y)) { 267 continue; 268 } 269 final int color = img.getPixel(x, y); 270 if (isSimilarColor(primaryColor, color)) { 271 primaryPixels++; 272 } else if (isSimilarColor(secondaryColor, color)) { 273 secondaryPixels++; 274 } else { 275 wrongPixels++; 276 } 277 } 278 } 279 280 int totalPixels = bounds.width() * bounds.height(); 281 if (ignoreRect != null) { 282 totalPixels -= ignoreRect.width() * ignoreRect.height(); 283 } 284 285 final float primaryRatio = (float) primaryPixels / totalPixels; 286 if (primaryRatio < expectedPrimaryRatio) { 287 generateFailureImage(img, bounds, primaryColor, secondaryColor, ignoreRect); 288 fail("Less than " + (expectedPrimaryRatio * 100.0f) 289 + "% of pixels have non-primary color primaryPixels=" + primaryPixels 290 + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels); 291 } 292 // Some pixels might be covered by screen shape decorations, like rounded corners. 293 // On circular displays, there is an antialiased edge. 294 final float wrongRatio = (float) wrongPixels / totalPixels; 295 if (wrongRatio > acceptableWrongRatio) { 296 generateFailureImage(img, bounds, primaryColor, secondaryColor, ignoreRect); 297 fail("More than " + (acceptableWrongRatio * 100.0f) 298 + "% of pixels have wrong color primaryPixels=" + primaryPixels 299 + " secondaryPixels=" + secondaryPixels + " wrongPixels=" 300 + wrongPixels); 301 } 302 } 303 generateFailureImage(Bitmap img, Rect bounds, int primaryColor, int secondaryColor, Rect ignoreRect)304 private void generateFailureImage(Bitmap img, Rect bounds, int primaryColor, 305 int secondaryColor, Rect ignoreRect) { 306 307 // Create a bitmap with on the left the original image and on the right the result of the 308 // test. The pixel marked in green have the right color, the transparent black one are 309 // ignored and the wrong pixels have the original color. 310 final int ignoredDebugColor = 0xEE000000; 311 final int validDebugColor = 0x6600FF00; 312 Bitmap result = Bitmap.createBitmap(img.getWidth() * 2, img.getHeight(), 313 Bitmap.Config.ARGB_8888); 314 315 // Execute the exact same logic applied in assertColor() to avoid bugs between the assertion 316 // method and the failure method 317 for (int x = bounds.left; x < bounds.right; x++) { 318 for (int y = bounds.top; y < bounds.bottom; y++) { 319 final int pixel = img.getPixel(x, y); 320 if (ignoreRect != null && ignoreRect.contains(x, y)) { 321 markDebugPixel(pixel, result, x, y, ignoredDebugColor, 0.95f); 322 continue; 323 } 324 if (isSimilarColor(primaryColor, pixel)) { 325 markDebugPixel(pixel, result, x, y, validDebugColor, 0.8f); 326 } else if (isSimilarColor(secondaryColor, pixel)) { 327 markDebugPixel(pixel, result, x, y, validDebugColor, 0.8f); 328 } else { 329 markDebugPixel(pixel, result, x, y, Color.TRANSPARENT, 0.0f); 330 } 331 } 332 } 333 334 // Mark the pixels outside the bounds as ignored 335 for (int x = 0; x < img.getWidth(); x++) { 336 for (int y = 0; y < img.getHeight(); y++) { 337 if (bounds.contains(x, y)) { 338 continue; 339 } 340 markDebugPixel(img.getPixel(x, y), result, x, y, ignoredDebugColor, 0.95f); 341 } 342 } 343 dumpOnFailure.dumpOnFailure("splashscreen-color-check", result); 344 } 345 markDebugPixel(int pixel, Bitmap result, int x, int y, int color, float ratio)346 private void markDebugPixel(int pixel, Bitmap result, int x, int y, int color, float ratio) { 347 int debugPixel = ColorUtils.blendARGB(pixel, color, ratio); 348 result.setPixel(x, y, pixel); 349 int debugOffsetX = result.getWidth() / 2; 350 result.setPixel(x + debugOffsetX, y, debugPixel); 351 } 352 353 // Roughly check whether the height of the window is high enough to display the brand image. canShowBranding()354 private boolean canShowBranding() { 355 final int iconHeight = WindowManagerState.dpToPx(CENTER_ICON_SIZE, 356 mContext.getResources().getConfiguration().densityDpi); 357 final int brandingHeight = WindowManagerState.dpToPx(BRANDING_HEIGHT, 358 mContext.getResources().getConfiguration().densityDpi); 359 final int brandingDefaultMargin = WindowManagerState.dpToPx(BRANDING_DEFAULT_MARGIN, 360 mContext.getResources().getConfiguration().densityDpi); 361 final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics(); 362 final Rect drawableBounds = new Rect(windowMetrics.getBounds()); 363 final int leftHeight = (drawableBounds.height() - iconHeight) / 2; 364 return leftHeight > brandingHeight + brandingDefaultMargin; 365 } 366 @Test testHandleExitAnimationOnCreate()367 public void testHandleExitAnimationOnCreate() throws Exception { 368 assumeFalse(isLeanBack()); 369 launchRuntimeHandleExitAnimationActivity(true, false, false, true); 370 } 371 372 @Test testHandleExitAnimationOnResume()373 public void testHandleExitAnimationOnResume() throws Exception { 374 assumeFalse(isLeanBack()); 375 launchRuntimeHandleExitAnimationActivity(false, true, false, true); 376 } 377 378 @Test testHandleExitAnimationCancel()379 public void testHandleExitAnimationCancel() throws Exception { 380 assumeFalse(isLeanBack()); 381 launchRuntimeHandleExitAnimationActivity(true, false, true, false); 382 } 383 launchRuntimeHandleExitAnimationActivity(boolean extraOnCreate, boolean extraOnResume, boolean extraCancel, boolean expectResult)384 private void launchRuntimeHandleExitAnimationActivity(boolean extraOnCreate, 385 boolean extraOnResume, boolean extraCancel, boolean expectResult) throws Exception { 386 TestJournalProvider.TestJournalContainer.start(); 387 388 launchActivityNoWait(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, 389 extraBool(REQUEST_HANDLE_EXIT_ON_CREATE, extraOnCreate), 390 extraBool(REQUEST_HANDLE_EXIT_ON_RESUME, extraOnResume), 391 extraBool(CANCEL_HANDLE_EXIT, extraCancel)); 392 393 mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY); 394 mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true); 395 if (expectResult) { 396 assertHandleExit(HANDLE_SPLASH_SCREEN_EXIT, true /* containsIcon */, 397 true /* containsBranding */, false /* iconAnimatable */); 398 } 399 } 400 401 @Test testSetApplicationNightMode()402 public void testSetApplicationNightMode() throws Exception { 403 final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); 404 assumeTrue(uiModeManager != null); 405 final int systemNightMode = uiModeManager.getNightMode(); 406 final int testNightMode = (systemNightMode == MODE_NIGHT_AUTO 407 || systemNightMode == MODE_NIGHT_CUSTOM) ? MODE_NIGHT_YES 408 : systemNightMode == MODE_NIGHT_YES ? MODE_NIGHT_NO : MODE_NIGHT_YES; 409 final int testConfigNightMode = testNightMode == MODE_NIGHT_YES 410 ? Configuration.UI_MODE_NIGHT_YES 411 : Configuration.UI_MODE_NIGHT_NO; 412 final String nightModeNo = String.valueOf(testNightMode); 413 414 TestJournalProvider.TestJournalContainer.start(); 415 launchActivity(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, 416 extraString(REQUEST_SET_NIGHT_MODE_ON_CREATE, nightModeNo)); 417 mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY); 418 mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true); 419 final TestJournalProvider.TestJournal journal = 420 TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT); 421 TestUtils.waitUntil("Waiting for night mode changed", 5 /* timeoutSecond */, () -> 422 testConfigNightMode == journal.extras.getInt(GET_NIGHT_MODE_ACTIVITY_CHANGED)); 423 assertEquals(testConfigNightMode, 424 journal.extras.getInt(GET_NIGHT_MODE_ACTIVITY_CHANGED)); 425 } 426 427 @Test testSetBackgroundColorActivity()428 public void testSetBackgroundColorActivity() { 429 // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly 430 // applied insets by system bars in AAOS. 431 assumeFalse(isCar()); 432 assumeFalse(isLeanBack()); 433 434 final CommandSession.ActivitySession starter = prepareTestStarter(); 435 final ActivityOptions noIconOptions = ActivityOptions.makeBasic() 436 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); 437 noIconOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); 438 439 // launch from app with no-icon options 440 startActivityFromStarter(starter, SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, 441 intent -> intent.putExtra(DELAY_RESUME, true), noIconOptions); 442 443 testSplashScreenColor(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, Color.BLUE, Color.WHITE); 444 } 445 446 @Test testSetBackgroundColorActivity_FreeformWindow()447 public void testSetBackgroundColorActivity_FreeformWindow() { 448 // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly 449 // applied insets by system bars in AAOS. 450 assumeFalse(isCar()); 451 assumeTrue(supportsFreeform()); 452 453 final CommandSession.ActivitySession starter = prepareTestStarter(); 454 final ActivityOptions noIconOptions = ActivityOptions.makeBasic() 455 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); 456 noIconOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); 457 458 // launch from app with no-icon options 459 startActivityFromStarter(starter, SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, 460 intent -> intent.putExtra(DELAY_RESUME, true), noIconOptions); 461 462 testSplashScreenColor(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, Color.BLUE, Color.WHITE); 463 } 464 465 @Test testHandleExitIconAnimatingActivity()466 public void testHandleExitIconAnimatingActivity() throws Exception { 467 assumeFalse(isLeanBack()); 468 469 TestJournalProvider.TestJournalContainer.start(); 470 launchActivityNoWait(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, 471 extraBool(REQUEST_HANDLE_EXIT_ON_CREATE, true)); 472 mWmState.computeState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY); 473 mWmState.assertVisibility(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, true); 474 475 assertHandleExit(REPLACE_ICON_EXIT, true /* containsIcon */, false /* containsBranding */, 476 true /* iconAnimatable */); 477 } 478 479 @Test testCancelHandleExitIconAnimatingActivity()480 public void testCancelHandleExitIconAnimatingActivity() { 481 assumeFalse(isLeanBack()); 482 483 TestJournalProvider.TestJournalContainer.start(); 484 launchActivityNoWait(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, 485 extraBool(REQUEST_HANDLE_EXIT_ON_CREATE, true), 486 extraBool(CANCEL_HANDLE_EXIT, true)); 487 488 mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, STATE_RESUMED); 489 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 490 491 final TestJournalProvider.TestJournal journal = 492 TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT); 493 assertFalse(journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT)); 494 } 495 496 @Test testShortcutChangeTheme()497 public void testShortcutChangeTheme() { 498 // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly 499 // applied insets by system bars in AAOS. 500 assumeFalse(isCar()); 501 assumeFalse(isLeanBack()); 502 503 final LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); 504 final ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class); 505 assumeTrue(launcherApps != null && shortcutManager != null); 506 507 final String shortCutId = "shortcut1"; 508 final ShortcutInfo.Builder b = new ShortcutInfo.Builder( 509 mContext, shortCutId); 510 final Intent i = new Intent(ACTION_MAIN) 511 .setComponent(SPLASHSCREEN_ACTIVITY); 512 final ShortcutInfo shortcut = b.setShortLabel("label") 513 .setLongLabel("long label") 514 .setIntent(i) 515 .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen) 516 .build(); 517 try { 518 shortcutManager.addDynamicShortcuts(Collections.singletonList(shortcut)); 519 runWithShellPermission(() -> launcherApps.startShortcut(shortcut, null, null)); 520 testSplashScreenColor(SPLASHSCREEN_ACTIVITY, Color.BLACK, Color.WHITE); 521 } finally { 522 shortcutManager.removeDynamicShortcuts(Collections.singletonList(shortCutId)); 523 } 524 } 525 waitAndAssertOverrideThemeColor(int expectedColor)526 private void waitAndAssertOverrideThemeColor(int expectedColor) { 527 waitAndAssertForSelfFinishActivity(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY, 528 OVERRIDE_THEME_COMPONENT, OVERRIDE_THEME_COLOR, result -> { 529 if (expectedColor > 0) { 530 assertEquals("Override theme color must match", 531 Integer.toHexString(expectedColor), 532 Integer.toHexString(result.getInt(OVERRIDE_THEME_COLOR))); 533 } 534 }); 535 } 536 537 @Test testLaunchWithSolidColorOptions()538 public void testLaunchWithSolidColorOptions() throws Exception { 539 assumeFalse(isLeanBack()); 540 final CommandSession.ActivitySession starter = prepareTestStarter(); 541 TestJournalProvider.TestJournalContainer.start(); 542 final ActivityOptions noIconOptions = ActivityOptions.makeBasic() 543 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); 544 startActivityFromStarter(starter, SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, intent -> 545 intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true), noIconOptions); 546 mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, STATE_RESUMED); 547 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 548 549 assertHandleExit(REPLACE_ICON_EXIT, false /* containsIcon */, false /* containsBranding */, 550 false /* iconAnimatable */); 551 } 552 553 @Test testLaunchAppWithIconOptions()554 public void testLaunchAppWithIconOptions() throws Exception { 555 assumeFalse(isLeanBack()); 556 final Bundle bundle = ActivityOptions.makeBasic() 557 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON).toBundle(); 558 TestJournalProvider.TestJournalContainer.start(); 559 final Intent intent = new Intent(Intent.ACTION_VIEW) 560 .setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY) 561 .setFlags(FLAG_ACTIVITY_NEW_TASK); 562 intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true); 563 mContext.startActivity(intent, bundle); 564 565 mWmState.waitForActivityState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, STATE_RESUMED); 566 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 567 568 assertHandleExit(HANDLE_SPLASH_SCREEN_EXIT, true /* containsIcon */, 569 true /* containsBranding */, false /* iconAnimatable */); 570 } 571 launchActivitiesFromStarterWithOptions(Intent[] intents, ActivityOptions options, ComponentName waitResumeComponent)572 private void launchActivitiesFromStarterWithOptions(Intent[] intents, 573 ActivityOptions options, ComponentName waitResumeComponent) { 574 assumeFalse(isLeanBack()); 575 final CommandSession.ActivitySession starter = prepareTestStarter(); 576 TestJournalProvider.TestJournalContainer.start(); 577 578 startActivitiesFromStarter(starter, intents, options); 579 580 mWmState.waitForActivityState(waitResumeComponent, STATE_RESUMED); 581 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 582 } 583 584 @Test testLaunchActivitiesWithIconOptions()585 public void testLaunchActivitiesWithIconOptions() throws Exception { 586 final ActivityOptions options = ActivityOptions.makeBasic() 587 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); 588 final Intent[] intents = new Intent[] { 589 new Intent().setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY) 590 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 591 new Intent().setComponent(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY) 592 .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true) 593 }; 594 launchActivitiesFromStarterWithOptions(intents, options, 595 SPLASH_SCREEN_REPLACE_ICON_ACTIVITY); 596 assertHandleExit(REPLACE_ICON_EXIT, true /* containsIcon */, false /* containsBranding */, 597 true /* iconAnimatable */); 598 } 599 600 @Test testLaunchActivitiesWithSolidColorOptions()601 public void testLaunchActivitiesWithSolidColorOptions() throws Exception { 602 final ActivityOptions options = ActivityOptions.makeBasic() 603 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); 604 605 final Intent[] intents = new Intent[] { 606 new Intent().setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY) 607 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 608 .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true), 609 new Intent().setComponent(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY) 610 .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true) 611 }; 612 launchActivitiesFromStarterWithOptions(intents, options, 613 SPLASH_SCREEN_REPLACE_ICON_ACTIVITY); 614 assertHandleExit(REPLACE_ICON_EXIT, false /* containsIcon */, false /* containsBranding */, 615 false /* iconAnimatable */); 616 } 617 assertHandleExit(String journalOwner, boolean containsIcon, boolean containsBranding, boolean iconAnimatable)618 private void assertHandleExit(String journalOwner, 619 boolean containsIcon, boolean containsBranding, boolean iconAnimatable) 620 throws Exception { 621 final TestJournalProvider.TestJournal journal = TestJournalProvider.TestJournalContainer 622 .get(journalOwner); 623 TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */, 624 () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT)); 625 assertTrue("No entry for CONTAINS_CENTER_VIEW", 626 journal.extras.containsKey(CONTAINS_CENTER_VIEW)); 627 assertTrue("No entry for CONTAINS_BRANDING_VIEW", 628 journal.extras.containsKey(CONTAINS_BRANDING_VIEW)); 629 630 final long iconAnimationStart = journal.extras.getLong(ICON_ANIMATION_START); 631 final long iconAnimationDuration = journal.extras.getLong(ICON_ANIMATION_DURATION); 632 assertEquals(containsIcon, journal.extras.getBoolean(CONTAINS_CENTER_VIEW)); 633 assertEquals(iconAnimatable, journal.extras.getBoolean(CENTER_VIEW_IS_SURFACE_VIEW)); 634 assertEquals(iconAnimatable, (iconAnimationStart != 0)); 635 assertEquals(iconAnimatable ? 500 : 0, iconAnimationDuration); 636 if (containsBranding && canShowBranding()) { 637 assertEquals(containsBranding, journal.extras.getBoolean(CONTAINS_BRANDING_VIEW)); 638 } 639 if (containsIcon && !iconAnimatable) { 640 assertEquals(Color.BLUE, journal.extras.getInt(ICON_BACKGROUND_COLOR, Color.YELLOW)); 641 } else { 642 assertEquals(Color.TRANSPARENT, 643 journal.extras.getInt(ICON_BACKGROUND_COLOR, Color.TRANSPARENT)); 644 } 645 } 646 647 @Test testOverrideSplashscreenTheme()648 public void testOverrideSplashscreenTheme() { 649 assumeFalse(isLeanBack()); 650 // Pre-launch the activity to ensure status is cleared on the device 651 launchActivityNoWait(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY); 652 mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY, STATE_RESUMED); 653 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 654 waitAndAssertOverrideThemeColor(0 /* ignore */); 655 656 // Launch the activity a first time, check that the splashscreen use the default theme, 657 // and override the theme for the next launch 658 launchActivityNoWait(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY, 659 extraBool(OVERRIDE_THEME_ENABLED, true)); 660 mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY, STATE_RESUMED); 661 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 662 waitAndAssertOverrideThemeColor(Color.BLUE); 663 664 // Launch the activity a second time, check that the theme has been overridden and reset 665 // to the default theme 666 launchActivityNoWait(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY); 667 mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY, STATE_RESUMED); 668 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 669 waitAndAssertOverrideThemeColor(Color.RED); 670 671 // Launch the activity a third time just to check that the theme has indeed been reset. 672 launchActivityNoWait(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY); 673 mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_THEME_ACTIVITY, STATE_RESUMED); 674 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 675 waitAndAssertOverrideThemeColor(Color.BLUE); 676 } 677 waitAndAssertForSelfFinishActivity(ComponentName activity, String component, String validateKey, Consumer<Bundle> assertConsumer)678 private void waitAndAssertForSelfFinishActivity(ComponentName activity, String component, 679 String validateKey, Consumer<Bundle> assertConsumer) { 680 final Bundle resultExtras = Condition.waitForResult( 681 new Condition<Bundle>("splash screen of " + activity) 682 .setResultSupplier(() -> TestJournalProvider.TestJournalContainer.get( 683 component).extras) 684 .setResultValidator(extras -> extras.containsKey(validateKey))); 685 if (resultExtras == null) { 686 fail("No reported validate key from " + activity); 687 } 688 assertConsumer.accept(resultExtras); 689 mWmState.waitForActivityRemoved(activity); 690 separateTestJournal(); 691 } 692 waitAndAssertStyleThemeIcon(boolean expectContainIcon)693 private void waitAndAssertStyleThemeIcon(boolean expectContainIcon) { 694 waitAndAssertForSelfFinishActivity(SPLASH_SCREEN_STYLE_THEME_ACTIVITY, 695 STYLE_THEME_COMPONENT, CONTAINS_CENTER_VIEW, 696 result -> assertEquals("Splash screen style must match", 697 expectContainIcon, result.getBoolean(CONTAINS_CENTER_VIEW))); 698 } 699 700 @Test testDefineSplashScreenStyleFromTheme()701 public void testDefineSplashScreenStyleFromTheme() { 702 assumeFalse(isLeanBack()); 703 final CommandSession.ActivitySession starter = prepareTestStarter(); 704 final ActivityOptions noIconOptions = ActivityOptions.makeBasic() 705 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); 706 707 // launch from app with sold color options 708 startActivityFromStarter(starter, SPLASH_SCREEN_STYLE_THEME_ACTIVITY, 709 intent -> {}, noIconOptions); 710 waitAndAssertStyleThemeIcon(false); 711 712 // launch from app with icon options 713 final ActivityOptions iconOptions = ActivityOptions.makeBasic() 714 .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); 715 startActivityFromStarter(starter, SPLASH_SCREEN_STYLE_THEME_ACTIVITY, 716 intent -> {}, iconOptions); 717 waitAndAssertStyleThemeIcon(true); 718 719 // launch from app without activity options 720 startActivityFromStarter(starter, SPLASH_SCREEN_STYLE_THEME_ACTIVITY, 721 intent -> {}, null /* options */); 722 waitAndAssertStyleThemeIcon(true); 723 } 724 } 725