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