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