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.content.Intent.ACTION_MAIN;
24 import static android.content.Intent.CATEGORY_HOME;
25 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
26 import static android.server.wm.CliIntentExtra.extraBool;
27 import static android.server.wm.CliIntentExtra.extraString;
28 import static android.server.wm.WindowManagerState.STATE_RESUMED;
29 import static android.server.wm.app.Components.HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY;
30 import static android.server.wm.app.Components.HOME_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.TestActivity.COMMAND_START_ACTIVITY;
35 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT;
36 import static android.server.wm.app.Components.TestStartingWindowKeys.CANCEL_HANDLE_EXIT;
37 import static android.server.wm.app.Components.TestStartingWindowKeys.CENTER_VIEW_IS_SURFACE_VIEW;
38 import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_BRANDING_VIEW;
39 import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_CENTER_VIEW;
40 import static android.server.wm.app.Components.TestStartingWindowKeys.DELAY_RESUME;
41 import static android.server.wm.app.Components.TestStartingWindowKeys.GET_NIGHT_MODE_ACTIVITY_CHANGED;
42 import static android.server.wm.app.Components.TestStartingWindowKeys.HANDLE_SPLASH_SCREEN_EXIT;
43 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_DURATION;
44 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_START;
45 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_BACKGROUND_COLOR;
46 import static android.server.wm.app.Components.TestStartingWindowKeys.OVERRIDE_THEME_COLOR;
47 import static android.server.wm.app.Components.TestStartingWindowKeys.OVERRIDE_THEME_COMPONENT;
48 import static android.server.wm.app.Components.TestStartingWindowKeys.OVERRIDE_THEME_ENABLED;
49 import static android.server.wm.app.Components.TestStartingWindowKeys.RECEIVE_SPLASH_SCREEN_EXIT;
50 import static android.server.wm.app.Components.TestStartingWindowKeys.REPLACE_ICON_EXIT;
51 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_CREATE;
52 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_RESUME;
53 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_SET_NIGHT_MODE_ON_CREATE;
54 import static android.view.Display.DEFAULT_DISPLAY;
55 import static android.view.WindowInsets.Type.captionBar;
56 import static android.view.WindowInsets.Type.systemBars;
57 
58 import static org.hamcrest.MatcherAssert.assertThat;
59 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
60 import static org.hamcrest.Matchers.lessThanOrEqualTo;
61 import static org.junit.Assert.assertEquals;
62 import static org.junit.Assert.assertFalse;
63 import static org.junit.Assert.assertTrue;
64 import static org.junit.Assert.fail;
65 import static org.junit.Assume.assumeFalse;
66 import static org.junit.Assume.assumeTrue;
67 
68 import android.app.UiModeManager;
69 import android.content.ComponentName;
70 import android.content.Intent;
71 import android.content.pm.LauncherApps;
72 import android.content.pm.ShortcutInfo;
73 import android.content.pm.ShortcutManager;
74 import android.content.res.Configuration;
75 import android.graphics.Bitmap;
76 import android.graphics.Color;
77 import android.graphics.Insets;
78 import android.graphics.Rect;
79 import android.os.Bundle;
80 import android.platform.test.annotations.Presubmit;
81 import android.view.WindowManager;
82 import android.view.WindowMetrics;
83 
84 import androidx.core.graphics.ColorUtils;
85 
86 import com.android.compatibility.common.util.TestUtils;
87 
88 import org.junit.After;
89 import org.junit.Before;
90 import org.junit.Rule;
91 import org.junit.Test;
92 
93 import java.util.Collections;
94 import java.util.function.Consumer;
95 
96 /**
97  * Build/Install/Run:
98  * atest CtsWindowManagerDeviceTestCases:SplashscreenTests
99  */
100 @Presubmit
101 @android.server.wm.annotation.Group1
102 public class SplashscreenTests extends ActivityManagerTestBase {
103 
104     private static final int CENTER_ICON_SIZE = 192;
105 
106     @Rule
107     public final DumpOnFailure dumpOnFailure = new DumpOnFailure();
108 
109     @Before
setUp()110     public void setUp() throws Exception {
111         super.setUp();
112         mWmState.setSanityCheckWithFocusedWindow(false);
113     }
114 
115     @After
tearDown()116     public void tearDown() {
117         mWmState.setSanityCheckWithFocusedWindow(true);
118     }
119 
prepareTestLauncher()120     private CommandSession.ActivitySession prepareTestLauncher() {
121         createManagedHomeActivitySession(HOME_ACTIVITY);
122         return createManagedActivityClientSession()
123                 .startActivity(new Intent(ACTION_MAIN)
124                         .addCategory(CATEGORY_HOME)
125                         .addFlags(FLAG_ACTIVITY_NEW_TASK)
126                         .setComponent(HOME_ACTIVITY));
127     }
128 
startActivityFromTestLauncher(CommandSession.ActivitySession homeActivity, ComponentName componentName, Consumer<Intent> fillExtra)129     private void startActivityFromTestLauncher(CommandSession.ActivitySession homeActivity,
130             ComponentName componentName, Consumer<Intent> fillExtra) {
131 
132         final Bundle data = new Bundle();
133         final Intent startIntent = new Intent();
134         startIntent.setComponent(componentName);
135         startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
136         fillExtra.accept(startIntent);
137         data.putParcelable(EXTRA_INTENT, startIntent);
138         homeActivity.sendCommand(COMMAND_START_ACTIVITY, data);
139     }
140 
141     @Test
testSplashscreenContent()142     public void testSplashscreenContent() {
143         // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly
144         // applied insets by system bars in AAOS.
145         assumeFalse(isCar());
146 
147         launchActivityNoWait(SPLASHSCREEN_ACTIVITY);
148         // The windowSplashScreenContent attribute is set to RED. We check that it is ignored.
149         testSplashScreenColor(SPLASHSCREEN_ACTIVITY, Color.BLUE, Color.WHITE);
150     }
151 
testSplashScreenColor(ComponentName name, int primaryColor, int secondaryColor)152     private void testSplashScreenColor(ComponentName name, int primaryColor, int secondaryColor) {
153         // Activity may not be launched yet even if app transition is in idle state.
154         mWmState.waitForActivityState(name, STATE_RESUMED);
155         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
156         final Bitmap image = takeScreenshot();
157         final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics();
158         final Rect stableBounds = new Rect(windowMetrics.getBounds());
159         Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
160                 systemBars() & ~captionBar());
161         stableBounds.inset(insets);
162         WindowManagerState.WindowState startingWindow = mWmState.findFirstWindowWithType(
163                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
164 
165         Rect startingWindowBounds = startingWindow.getBounds();
166         final Rect appBounds;
167         if (startingWindowBounds != null) {
168             appBounds = new Rect(startingWindowBounds);
169         } else {
170             appBounds = new Rect(startingWindow.getFrame());
171         }
172 
173         Rect topInsetsBounds = new Rect(insets.left, 0, appBounds.right - insets.right, insets.top);
174         Rect bottomInsetsBounds = new Rect(insets.left, appBounds.bottom - insets.bottom,
175                 appBounds.right - insets.right, appBounds.bottom);
176         assertFalse("Top insets bounds rect is empty", topInsetsBounds.isEmpty());
177         assertFalse("Bottom insets bounds rect is empty", bottomInsetsBounds.isEmpty());
178 
179         if (appBounds.isEmpty()) {
180             fail("Couldn't find splash screen bounds. Impossible to assert the colors");
181         }
182 
183         // Use ratios to flexibly accommodate circular or not quite rectangular displays
184         // Note: Color.BLACK is the pixel color outside of the display region
185 
186         int px = WindowManagerState.dpToPx(CENTER_ICON_SIZE,
187                 mContext.getResources().getConfiguration().densityDpi);
188         Rect ignoreRect = new Rect(0, 0, px, px);
189         ignoreRect.offsetTo(appBounds.centerX() - ignoreRect.width() / 2,
190                 appBounds.centerY() - ignoreRect.height() / 2);
191 
192         appBounds.intersect(stableBounds);
193         assertColors(image, appBounds, primaryColor, 0.99f, secondaryColor, 0.02f, ignoreRect);
194         assertColors(image, topInsetsBounds, primaryColor, 0.80f, secondaryColor, 0.10f, null);
195         assertColors(image, bottomInsetsBounds, primaryColor, 0.80f, secondaryColor, 0.10f, null);
196     }
197 
198     // For real devices, gamma correction might be applied on hardware driver, so the colors may
199     // not exactly match.
isSimilarColor(int a, int b)200     private static boolean isSimilarColor(int a, int b) {
201         if (a == b) {
202             return true;
203         }
204         return Math.abs(Color.alpha(a) - Color.alpha(b)) +
205                 Math.abs(Color.red(a) - Color.red(b)) +
206                 Math.abs(Color.green(a) - Color.green(b)) +
207                 Math.abs(Color.blue(a) - Color.blue(b)) < 10;
208     }
209 
assertColors(Bitmap img, Rect bounds, int primaryColor, float expectedPrimaryRatio, int secondaryColor, float acceptableWrongRatio, Rect ignoreRect)210     private void assertColors(Bitmap img, Rect bounds, int primaryColor, float expectedPrimaryRatio,
211             int secondaryColor, float acceptableWrongRatio, Rect ignoreRect) {
212 
213         int primaryPixels = 0;
214         int secondaryPixels = 0;
215         int wrongPixels = 0;
216 
217         assertThat(bounds.top, greaterThanOrEqualTo(0));
218         assertThat(bounds.left, greaterThanOrEqualTo(0));
219         assertThat(bounds.right, lessThanOrEqualTo(img.getWidth()));
220         assertThat(bounds.bottom, lessThanOrEqualTo(img.getHeight()));
221 
222         for (int x = bounds.left; x < bounds.right; x++) {
223             for (int y = bounds.top; y < bounds.bottom; y++) {
224                 if (ignoreRect != null && ignoreRect.contains(x, y)) {
225                     continue;
226                 }
227                 final int color = img.getPixel(x, y);
228                 if (isSimilarColor(primaryColor, color)) {
229                     primaryPixels++;
230                 } else if (isSimilarColor(secondaryColor, color)) {
231                     secondaryPixels++;
232                 } else {
233                     wrongPixels++;
234                 }
235             }
236         }
237 
238         int totalPixels = bounds.width() * bounds.height();
239         if (ignoreRect != null) {
240             totalPixels -= ignoreRect.width() * ignoreRect.height();
241         }
242 
243         final float primaryRatio = (float) primaryPixels / totalPixels;
244         if (primaryRatio < expectedPrimaryRatio) {
245             generateFailureImage(img, bounds, primaryColor, secondaryColor, ignoreRect);
246             fail("Less than " + (expectedPrimaryRatio * 100.0f)
247                     + "% of pixels have non-primary color primaryPixels=" + primaryPixels
248                     + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
249         }
250         // Some pixels might be covered by screen shape decorations, like rounded corners.
251         // On circular displays, there is an antialiased edge.
252         final float wrongRatio = (float) wrongPixels / totalPixels;
253         if (wrongRatio > acceptableWrongRatio) {
254             generateFailureImage(img, bounds, primaryColor, secondaryColor, ignoreRect);
255             fail("More than " + (acceptableWrongRatio * 100.0f)
256                     + "% of pixels have wrong color primaryPixels=" + primaryPixels
257                     + " secondaryPixels=" + secondaryPixels + " wrongPixels="
258                     + wrongPixels);
259         }
260     }
261 
generateFailureImage(Bitmap img, Rect bounds, int primaryColor, int secondaryColor, Rect ignoreRect)262     private void generateFailureImage(Bitmap img, Rect bounds, int primaryColor,
263             int secondaryColor, Rect ignoreRect) {
264 
265         // Create a bitmap with on the left the original image and on the right the result of the
266         // test. The pixel marked in green have the right color, the transparent black one are
267         // ignored and the wrong pixels have the original color.
268         final int ignoredDebugColor = 0xEE000000;
269         final int validDebugColor = 0x6600FF00;
270         Bitmap result = Bitmap.createBitmap(img.getWidth() * 2, img.getHeight(),
271                 Bitmap.Config.ARGB_8888);
272 
273         // Execute the exact same logic applied in assertColor() to avoid bugs between the assertion
274         // method and the failure method
275         for (int x = bounds.left; x < bounds.right; x++) {
276             for (int y = bounds.top; y < bounds.bottom; y++) {
277                 final int pixel = img.getPixel(x, y);
278                 if (ignoreRect != null && ignoreRect.contains(x, y)) {
279                     markDebugPixel(pixel, result, x, y, ignoredDebugColor, 0.95f);
280                     continue;
281                 }
282                 if (isSimilarColor(primaryColor, pixel)) {
283                     markDebugPixel(pixel, result, x, y, validDebugColor, 0.8f);
284                 } else if (isSimilarColor(secondaryColor, pixel)) {
285                     markDebugPixel(pixel, result, x, y, validDebugColor, 0.8f);
286                 } else {
287                     markDebugPixel(pixel, result, x, y, Color.TRANSPARENT, 0.0f);
288                 }
289             }
290         }
291 
292         // Mark the pixels outside the bounds as ignored
293         for (int x = 0; x < img.getWidth(); x++) {
294             for (int y = 0; y < img.getHeight(); y++) {
295                 if (bounds.contains(x, y)) {
296                     continue;
297                 }
298                 markDebugPixel(img.getPixel(x, y), result, x, y, ignoredDebugColor, 0.95f);
299             }
300         }
301         dumpOnFailure.dumpOnFailure("splashscreen-color-check", result);
302     }
303 
markDebugPixel(int pixel, Bitmap result, int x, int y, int color, float ratio)304     private void markDebugPixel(int pixel, Bitmap result, int x, int y, int color, float ratio) {
305         int debugPixel = ColorUtils.blendARGB(pixel, color, ratio);
306         result.setPixel(x, y, pixel);
307         int debugOffsetX = result.getWidth() / 2;
308         result.setPixel(x + debugOffsetX, y, debugPixel);
309     }
310 
311     @Test
testHandleExitAnimationOnCreate()312     public void testHandleExitAnimationOnCreate() throws Exception {
313         assumeFalse(isLeanBack());
314         launchRuntimeHandleExitAnimationActivity(true, false, false, true);
315     }
316 
317     @Test
testHandleExitAnimationOnResume()318     public void testHandleExitAnimationOnResume() throws Exception {
319         assumeFalse(isLeanBack());
320         launchRuntimeHandleExitAnimationActivity(false, true, false, true);
321     }
322 
323     @Test
testHandleExitAnimationCancel()324     public void testHandleExitAnimationCancel() throws Exception {
325         assumeFalse(isLeanBack());
326         launchRuntimeHandleExitAnimationActivity(true, false, true, false);
327     }
328 
launchRuntimeHandleExitAnimationActivity(boolean extraOnCreate, boolean extraOnResume, boolean extraCancel, boolean expectResult)329     private void launchRuntimeHandleExitAnimationActivity(boolean extraOnCreate,
330             boolean extraOnResume, boolean extraCancel, boolean expectResult) throws Exception {
331         TestJournalProvider.TestJournalContainer.start();
332         final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
333         startActivityFromTestLauncher(homeActivity, HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, intent -> {
334             intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, extraOnCreate);
335             intent.putExtra(REQUEST_HANDLE_EXIT_ON_RESUME, extraOnResume);
336             intent.putExtra(CANCEL_HANDLE_EXIT, extraCancel);
337         });
338 
339         mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY);
340         mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true);
341         final TestJournalProvider.TestJournal journal =
342                 TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT);
343         if (expectResult) {
344             TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
345                     () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
346             assertTrue("No entry for CONTAINS_CENTER_VIEW",
347                     journal.extras.containsKey(CONTAINS_CENTER_VIEW));
348             assertTrue("No entry for CONTAINS_BRANDING_VIEW",
349                     journal.extras.containsKey(CONTAINS_BRANDING_VIEW));
350             assertTrue("Center View shouldn't be null", journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
351             assertTrue(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
352             assertEquals(Color.BLUE, journal.extras.getInt(ICON_BACKGROUND_COLOR, Color.YELLOW));
353         }
354     }
355 
356     @Test
testSetApplicationNightMode()357     public void testSetApplicationNightMode() throws Exception {
358         final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
359         assumeTrue(uiModeManager != null);
360         final int systemNightMode = uiModeManager.getNightMode();
361         final int testNightMode = (systemNightMode == MODE_NIGHT_AUTO
362                 || systemNightMode == MODE_NIGHT_CUSTOM) ? MODE_NIGHT_YES
363                 : systemNightMode == MODE_NIGHT_YES ? MODE_NIGHT_NO : MODE_NIGHT_YES;
364         final int testConfigNightMode = testNightMode == MODE_NIGHT_YES
365                 ? Configuration.UI_MODE_NIGHT_YES
366                 : Configuration.UI_MODE_NIGHT_NO;
367         final String nightModeNo = String.valueOf(testNightMode);
368 
369         TestJournalProvider.TestJournalContainer.start();
370         launchActivity(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY,
371                 extraString(REQUEST_SET_NIGHT_MODE_ON_CREATE, nightModeNo));
372         mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY);
373         mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true);
374         final TestJournalProvider.TestJournal journal =
375                 TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT);
376         TestUtils.waitUntil("Waiting for night mode changed", 5 /* timeoutSecond */, () ->
377                 testConfigNightMode == journal.extras.getInt(GET_NIGHT_MODE_ACTIVITY_CHANGED));
378         assertEquals(testConfigNightMode,
379                 journal.extras.getInt(GET_NIGHT_MODE_ACTIVITY_CHANGED));
380     }
381 
382     @Test
testSetBackgroundColorActivity()383     public void testSetBackgroundColorActivity() {
384         // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly
385         // applied insets by system bars in AAOS.
386         assumeFalse(isCar());
387 
388         launchActivityNoWait(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, extraBool(DELAY_RESUME, true));
389         testSplashScreenColor(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, Color.BLUE, Color.WHITE);
390     }
391 
392     @Test
testHandleExitIconAnimatingActivity()393     public void testHandleExitIconAnimatingActivity() throws Exception {
394         assumeFalse(isLeanBack());
395         final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
396         TestJournalProvider.TestJournalContainer.start();
397 
398         startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, intent -> {
399             intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true);
400         });
401         mWmState.computeState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
402         mWmState.assertVisibility(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, true);
403         final TestJournalProvider.TestJournal journal =
404                 TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
405         TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
406                 () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
407         assertTrue(journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
408         final long iconAnimationStart = journal.extras.getLong(ICON_ANIMATION_START);
409         final long iconAnimationDuration = journal.extras.getLong(ICON_ANIMATION_DURATION);
410         assertTrue(iconAnimationStart != 0);
411         assertEquals(iconAnimationDuration, 500);
412         assertFalse(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
413         assertTrue(journal.extras.getBoolean(CENTER_VIEW_IS_SURFACE_VIEW));
414     }
415 
416     @Test
testCancelHandleExitIconAnimatingActivity()417     public void testCancelHandleExitIconAnimatingActivity() {
418         assumeFalse(isLeanBack());
419         final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
420         TestJournalProvider.TestJournalContainer.start();
421         startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, intent -> {
422             intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true);
423             intent.putExtra(CANCEL_HANDLE_EXIT, true);
424         });
425         mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, STATE_RESUMED);
426         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
427 
428         final TestJournalProvider.TestJournal journal =
429                 TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
430         assertFalse(journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
431     }
432 
433     @Test
testShortcutChangeTheme()434     public void testShortcutChangeTheme() {
435         // TODO(b/192431448): Allow Automotive to skip this test until Splash Screen is properly
436         // applied insets by system bars in AAOS.
437         assumeFalse(isCar());
438 
439         final LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
440         final ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
441         assumeTrue(launcherApps != null && shortcutManager != null);
442 
443         final String shortCutId = "shortcut1";
444         final ShortcutInfo.Builder b = new ShortcutInfo.Builder(
445                 mContext, shortCutId);
446         final Intent i = new Intent(ACTION_MAIN)
447                 .setComponent(SPLASHSCREEN_ACTIVITY);
448         final ShortcutInfo shortcut = b.setShortLabel("label")
449                 .setLongLabel("long label")
450                 .setIntent(i)
451                 .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
452                 .build();
453         try {
454             shortcutManager.addDynamicShortcuts(Collections.singletonList(shortcut));
455             runWithShellPermission(() -> launcherApps.startShortcut(shortcut, null, null));
456             testSplashScreenColor(SPLASHSCREEN_ACTIVITY, Color.BLACK, Color.WHITE);
457         } finally {
458             shortcutManager.removeDynamicShortcuts(Collections.singletonList(shortCutId));
459         }
460     }
461 
waitAndAssertOverrideThemeColor(int expectedColor)462     private void waitAndAssertOverrideThemeColor(int expectedColor) {
463         final ComponentName activity = SPLASH_SCREEN_REPLACE_THEME_ACTIVITY;
464         final Bundle resultExtras = Condition.waitForResult(
465                 new Condition<Bundle>("splash screen theme color of " + activity)
466                         .setResultSupplier(() -> TestJournalProvider.TestJournalContainer.get(
467                                 OVERRIDE_THEME_COMPONENT).extras)
468                         .setResultValidator(extras -> extras.containsKey(OVERRIDE_THEME_COLOR)));
469         if (resultExtras == null) {
470             fail("No reported override theme color from " + activity);
471         }
472         if (expectedColor > 0) {
473             assertEquals("Override theme color must match",
474                     Integer.toHexString(expectedColor),
475                     Integer.toHexString(resultExtras.getInt(OVERRIDE_THEME_COLOR)));
476         }
477         mWmState.waitForActivityRemoved(activity);
478         separateTestJournal();
479     }
480 
481     @Test
testOverrideSplashscreenTheme()482     public void testOverrideSplashscreenTheme() {
483         assumeFalse(isLeanBack());
484         final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
485 
486         // Pre-launch the activity to ensure status is cleared on the device
487         startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_THEME_ACTIVITY,
488                 intent -> {});
489         waitAndAssertOverrideThemeColor(0 /* ignore */);
490 
491         // Launch the activity a first time, check that the splashscreen use the default theme,
492         // and override the theme for the next launch
493         startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_THEME_ACTIVITY,
494                 intent -> intent.putExtra(OVERRIDE_THEME_ENABLED, true));
495         waitAndAssertOverrideThemeColor(Color.BLUE);
496 
497         // Launch the activity a second time, check that the theme has been overridden and reset
498         // to the default theme
499         startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_THEME_ACTIVITY,
500                 intent -> {});
501         waitAndAssertOverrideThemeColor(Color.RED);
502 
503         // Launch the activity a third time just to check that the theme has indeed been reset.
504         startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_THEME_ACTIVITY,
505                 intent -> {});
506         waitAndAssertOverrideThemeColor(Color.BLUE);
507     }
508 }
509