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