• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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