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