1 /*
2  * Copyright (C) 2020 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 package android.window;
17 
18 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
19 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
20 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
21 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
22 
23 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_AVD;
24 
25 import android.animation.Animator;
26 import android.animation.AnimatorListenerAdapter;
27 import android.annotation.ColorInt;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.TestApi;
31 import android.annotation.UiThread;
32 import android.app.Activity;
33 import android.content.Context;
34 import android.graphics.Bitmap;
35 import android.graphics.Canvas;
36 import android.graphics.Color;
37 import android.graphics.PixelFormat;
38 import android.graphics.Rect;
39 import android.graphics.drawable.BitmapDrawable;
40 import android.graphics.drawable.Drawable;
41 import android.os.Build;
42 import android.os.Parcel;
43 import android.os.Parcelable;
44 import android.os.RemoteCallback;
45 import android.os.Trace;
46 import android.util.AttributeSet;
47 import android.util.Log;
48 import android.view.Gravity;
49 import android.view.LayoutInflater;
50 import android.view.SurfaceControlViewHost;
51 import android.view.SurfaceView;
52 import android.view.View;
53 import android.view.ViewGroup;
54 import android.view.Window;
55 import android.view.WindowInsetsController;
56 import android.view.WindowManager;
57 import android.widget.FrameLayout;
58 import android.widget.ImageView;
59 
60 import com.android.internal.R;
61 import com.android.internal.jank.InteractionJankMonitor;
62 import com.android.internal.policy.DecorView;
63 import com.android.internal.util.ContrastColorUtil;
64 
65 import java.time.Duration;
66 import java.time.Instant;
67 import java.util.function.Consumer;
68 
69 /**
70  * <p>The view which allows an activity to customize its splash screen exit animation.</p>
71  *
72  * <p>Activities will receive this view as a parameter of
73  * {@link SplashScreen.OnExitAnimationListener#onSplashScreenExit} if
74  * they set {@link SplashScreen#setOnExitAnimationListener}.
75  * When this callback is called, this view will be on top of the activity.</p>
76  *
77  * <p>This view is composed of a view containing the splashscreen icon (see
78  * windowSplashscreenAnimatedIcon) and a background.
79  * Developers can use {@link #getIconView} to get this view and replace the drawable or
80  * add animation to it. The background of this view is filled with a single color, which can be
81  * edited during the animation by {@link View#setBackground} or {@link View#setBackgroundColor}.</p>
82  *
83  * @see SplashScreen
84  */
85 public final class SplashScreenView extends FrameLayout {
86     private static final String TAG = SplashScreenView.class.getSimpleName();
87     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
88 
89     private static final int LIGHT_BARS_MASK =
90             WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
91                     | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
92     private static final int WINDOW_FLAG_MASK = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
93                     | FLAG_TRANSLUCENT_NAVIGATION | FLAG_TRANSLUCENT_STATUS;
94 
95     private boolean mNotCopyable;
96     private boolean mIsCopied;
97     private int mInitBackgroundColor;
98     private View mIconView;
99     private Bitmap mParceledIconBitmap;
100     private View mBrandingImageView;
101     private Bitmap mParceledBrandingBitmap;
102     private Bitmap mParceledIconBackgroundBitmap;
103     private Duration mIconAnimationDuration;
104     private Instant mIconAnimationStart;
105 
106     // The host activity when transfer view to it.
107     private Activity mHostActivity;
108 
109     @Nullable
110     private SurfaceControlViewHost.SurfacePackage mSurfacePackageCopy;
111     @Nullable
112     private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
113     @Nullable
114     private SurfaceView mSurfaceView;
115     @Nullable
116     private SurfaceControlViewHost mSurfaceHost;
117     @Nullable
118     private RemoteCallback mClientCallback;
119 
120     // cache original window and status
121     private Window mWindow;
122     private int mAppWindowFlags;
123     private int mStatusBarColor;
124     private int mNavigationBarColor;
125     private int mSystemBarsAppearance;
126     private boolean mHasRemoved;
127     private boolean mNavigationContrastEnforced;
128     private boolean mStatusContrastEnforced;
129     private boolean mDecorFitsSystemWindows;
130 
131     /**
132      * Internal builder to create a SplashScreenView object.
133      * @hide
134      */
135     public static class Builder {
136         private final Context mContext;
137         private int mIconSize;
138         private @ColorInt int mBackgroundColor;
139         private Bitmap mParceledIconBitmap;
140         private Bitmap mParceledIconBackgroundBitmap;
141         private Drawable mIconDrawable;
142         // It is only set for legacy splash screen which won't be sent across processes.
143         private Drawable mOverlayDrawable;
144         private Drawable mIconBackground;
145         private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
146         private RemoteCallback mClientCallback;
147         private int mBrandingImageWidth;
148         private int mBrandingImageHeight;
149         private Drawable mBrandingDrawable;
150         private Bitmap mParceledBrandingBitmap;
151         private Instant mIconAnimationStart;
152         private Duration mIconAnimationDuration;
153         private Consumer<Runnable> mUiThreadInitTask;
154 
Builder(@onNull Context context)155         public Builder(@NonNull Context context) {
156             mContext = context;
157         }
158 
159         /**
160          * When create from {@link SplashScreenViewParcelable}, all the materials were be settled so
161          * you do not need to call other set methods.
162          */
createFromParcel(SplashScreenViewParcelable parcelable)163         public Builder createFromParcel(SplashScreenViewParcelable parcelable) {
164             mIconSize = parcelable.getIconSize();
165             mBackgroundColor = parcelable.getBackgroundColor();
166             mSurfacePackage = parcelable.mSurfacePackage;
167             if (mSurfacePackage == null && parcelable.mIconBitmap != null) {
168                 // We only create a Bitmap copies of immobile icons since animated icon are using
169                 // a surface view
170                 mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap);
171                 mParceledIconBitmap = parcelable.mIconBitmap;
172             }
173             if (parcelable.mIconBackground != null) {
174                 mIconBackground = new BitmapDrawable(mContext.getResources(),
175                         parcelable.mIconBackground);
176                 mParceledIconBackgroundBitmap = parcelable.mIconBackground;
177             }
178             if (parcelable.mBrandingBitmap != null) {
179                 setBrandingDrawable(new BitmapDrawable(mContext.getResources(),
180                                 parcelable.mBrandingBitmap), parcelable.mBrandingWidth,
181                         parcelable.mBrandingHeight);
182                 mParceledBrandingBitmap = parcelable.mBrandingBitmap;
183             }
184             mIconAnimationStart = Instant.ofEpochMilli(parcelable.mIconAnimationStartMillis);
185             mIconAnimationDuration = Duration.ofMillis(parcelable.mIconAnimationDurationMillis);
186             mClientCallback = parcelable.mClientCallback;
187             if (DEBUG) {
188                 Log.d(TAG, String.format("Building from parcel drawable: %s", mIconDrawable));
189             }
190             return this;
191         }
192 
193         /**
194          * Set the rectangle size for the center view.
195          */
setIconSize(int iconSize)196         public Builder setIconSize(int iconSize) {
197             mIconSize = iconSize;
198             return this;
199         }
200 
201         /**
202          * Set the background color for the view.
203          */
setBackgroundColor(@olorInt int backgroundColor)204         public Builder setBackgroundColor(@ColorInt int backgroundColor) {
205             mBackgroundColor = backgroundColor;
206             return this;
207         }
208 
209         /**
210          * Set the Drawable object to fill entire view
211          */
setOverlayDrawable(@ullable Drawable drawable)212         public Builder setOverlayDrawable(@Nullable Drawable drawable) {
213             mOverlayDrawable = drawable;
214             return this;
215         }
216 
217         /**
218          * Set the Drawable object to fill the center view.
219          */
setCenterViewDrawable(@ullable Drawable drawable)220         public Builder setCenterViewDrawable(@Nullable Drawable drawable) {
221             mIconDrawable = drawable;
222             return this;
223         }
224 
225         /**
226          * Set the background color for the icon.
227          */
setIconBackground(Drawable iconBackground)228         public Builder setIconBackground(Drawable iconBackground) {
229             mIconBackground = iconBackground;
230             return this;
231         }
232 
233         /**
234          * Set the animation duration if icon is animatable.
235          */
setAnimationDurationMillis(int duration)236         public Builder setAnimationDurationMillis(int duration) {
237             mIconAnimationDuration = Duration.ofMillis(duration);
238             return this;
239         }
240 
241         /**
242          * Set the Runnable that can receive the task which should be executed on UI thread.
243          * @param uiThreadInitTask
244          */
setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask)245         public Builder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
246             mUiThreadInitTask = uiThreadInitTask;
247             return this;
248         }
249 
250         /**
251          * Set the Drawable object and size for the branding view.
252          */
setBrandingDrawable(@ullable Drawable branding, int width, int height)253         public Builder setBrandingDrawable(@Nullable Drawable branding, int width, int height) {
254             mBrandingDrawable = branding;
255             mBrandingImageWidth = width;
256             mBrandingImageHeight = height;
257             return this;
258         }
259 
260         /**
261          * Create SplashScreenWindowView object from materials.
262          */
build()263         public SplashScreenView build() {
264             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#build");
265             final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
266             final SplashScreenView view = (SplashScreenView)
267                     layoutInflater.inflate(R.layout.splash_screen_view, null, false);
268             view.mInitBackgroundColor = mBackgroundColor;
269             if (mOverlayDrawable != null) {
270                 view.setBackground(mOverlayDrawable);
271             } else {
272                 view.setBackgroundColor(mBackgroundColor);
273             }
274             view.mClientCallback = mClientCallback;
275 
276             view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
277 
278             // center icon
279             if (mIconDrawable instanceof SplashScreenView.IconAnimateListener
280                     || mSurfacePackage != null) {
281                 if (mUiThreadInitTask != null) {
282                     mUiThreadInitTask.accept(() -> view.mIconView = createSurfaceView(view));
283                 } else {
284                     view.mIconView = createSurfaceView(view);
285                 }
286                 view.initIconAnimation(mIconDrawable,
287                         mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0);
288                 view.mIconAnimationStart = mIconAnimationStart;
289                 view.mIconAnimationDuration = mIconAnimationDuration;
290             } else if (mIconSize != 0) {
291                 ImageView imageView = view.findViewById(R.id.splashscreen_icon_view);
292                 assert imageView != null;
293 
294                 final ViewGroup.LayoutParams params = imageView.getLayoutParams();
295                 params.width = mIconSize;
296                 params.height = mIconSize;
297                 imageView.setLayoutParams(params);
298                 if (mIconDrawable != null) {
299                     imageView.setImageDrawable(mIconDrawable);
300                 }
301                 if (mIconBackground != null) {
302                     imageView.setBackground(mIconBackground);
303                 }
304                 view.mIconView = imageView;
305             }
306             if (mOverlayDrawable != null || mIconDrawable == null) {
307                 view.setNotCopyable();
308             }
309 
310             view.mParceledIconBackgroundBitmap = mParceledIconBackgroundBitmap;
311             view.mParceledIconBitmap = mParceledIconBitmap;
312 
313             // branding image
314             if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0 && mBrandingDrawable != null) {
315                 final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
316                 params.width = mBrandingImageWidth;
317                 params.height = mBrandingImageHeight;
318                 view.mBrandingImageView.setLayoutParams(params);
319                 view.mBrandingImageView.setBackground(mBrandingDrawable);
320             } else {
321                 view.mBrandingImageView.setVisibility(GONE);
322             }
323             if (mParceledBrandingBitmap != null) {
324                 view.mParceledBrandingBitmap = mParceledBrandingBitmap;
325             }
326             if (DEBUG) {
327                 Log.d(TAG, "Build " + view
328                         + "\nIcon: view: " + view.mIconView + " drawable: "
329                         + mIconDrawable + " size: " + mIconSize
330                         + "\nBranding: view: " + view.mBrandingImageView + " drawable: "
331                         + mBrandingDrawable + " size w: " + mBrandingImageWidth + " h: "
332                         + mBrandingImageHeight);
333             }
334             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
335             return view;
336         }
337 
createSurfaceView(@onNull SplashScreenView view)338         private SurfaceView createSurfaceView(@NonNull SplashScreenView view) {
339             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#createSurfaceView");
340             final Context viewContext = view.getContext();
341             final SurfaceView surfaceView = new SurfaceView(viewContext);
342             surfaceView.setPadding(0, 0, 0, 0);
343             surfaceView.setBackground(mIconBackground);
344             if (mSurfacePackage == null) {
345                 if (DEBUG) {
346                     Log.d(TAG,
347                             "SurfaceControlViewHost created on thread "
348                                     + Thread.currentThread().getId());
349                 }
350 
351                 SurfaceControlViewHost viewHost = new SurfaceControlViewHost(viewContext,
352                         viewContext.getDisplay(),
353                         surfaceView.getHostToken());
354                 ImageView imageView = new ImageView(viewContext);
355                 imageView.setBackground(mIconDrawable);
356                 viewHost.setView(imageView, mIconSize, mIconSize);
357                 SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
358                 surfaceView.setChildSurfacePackage(surfacePackage);
359                 view.mSurfacePackage = surfacePackage;
360                 view.mSurfaceHost = viewHost;
361                 view.mSurfacePackageCopy = new SurfaceControlViewHost.SurfacePackage(
362                         surfacePackage);
363             } else {
364                 if (DEBUG) {
365                     Log.d(TAG, "Using copy of SurfacePackage in the client");
366                 }
367                 view.mSurfacePackage = mSurfacePackage;
368             }
369             if (mIconSize != 0) {
370                 LayoutParams lp = new FrameLayout.LayoutParams(mIconSize, mIconSize);
371                 lp.gravity = Gravity.CENTER;
372                 surfaceView.setLayoutParams(lp);
373                 if (DEBUG) {
374                     Log.d(TAG, "Icon size " + mIconSize);
375                 }
376             }
377 
378             // We ensure that we can blend the alpha of the surface view with the SplashScreenView
379             surfaceView.setUseAlpha();
380             surfaceView.setZOrderOnTop(true);
381             surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
382 
383             view.addView(surfaceView);
384             view.mSurfaceView = surfaceView;
385             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
386             return surfaceView;
387         }
388     }
389 
390     /** @hide */
SplashScreenView(Context context)391     public SplashScreenView(Context context) {
392         super(context);
393     }
394 
395     /** @hide */
SplashScreenView(Context context, AttributeSet attributeSet)396     public SplashScreenView(Context context, AttributeSet attributeSet) {
397         super(context, attributeSet);
398     }
399 
400     /**
401      * Declared this view is not copyable.
402      * @hide
403      */
setNotCopyable()404     public void setNotCopyable() {
405         mNotCopyable = true;
406     }
407 
408     /**
409      * Whether this view is copyable.
410      * @hide
411      */
isCopyable()412     public boolean isCopyable() {
413         return !mNotCopyable;
414     }
415 
416     /**
417      * Called when this {@link SplashScreenView} has been copied to be transferred to the client.
418      *
419      * @hide
420      */
onCopied()421     public void onCopied() {
422         mIsCopied = true;
423         if (mSurfaceView == null) {
424             return;
425         }
426         if (DEBUG) {
427             Log.d(TAG, "Setting SurfaceView's SurfacePackage to null.");
428         }
429         // If we don't release the surface package, the surface will be reparented to this
430         // surface view. So once it's copied into the client process, we release it.
431         mSurfacePackage.release();
432         mSurfacePackage = null;
433     }
434 
435     /** @hide **/
436     @Nullable
getSurfaceHost()437     public SurfaceControlViewHost getSurfaceHost() {
438         return mSurfaceHost;
439     }
440 
441     @Override
setAlpha(float alpha)442     public void setAlpha(float alpha) {
443         super.setAlpha(alpha);
444 
445         // The surface view's alpha is not multiplied with the containing view's alpha, so we
446         // manually do it here
447         if (mSurfaceView != null) {
448             mSurfaceView.setAlpha(mSurfaceView.getAlpha() * alpha);
449         }
450     }
451 
452     /**
453      * Returns the duration of the icon animation if icon is animatable.
454      *
455      * @see android.R.attr#windowSplashScreenAnimatedIcon
456      * @see android.R.attr#windowSplashScreenAnimationDuration
457      */
458     @Nullable
getIconAnimationDuration()459     public Duration getIconAnimationDuration() {
460         return mIconAnimationDuration;
461     }
462 
463     /**
464      * If the replaced icon is animatable, return the animation start time based on system clock.
465      */
466     @Nullable
getIconAnimationStart()467     public Instant getIconAnimationStart() {
468         return mIconAnimationStart;
469     }
470 
471 
472     /**
473      * @hide
474      */
syncTransferSurfaceOnDraw()475     public void syncTransferSurfaceOnDraw() {
476         if (mSurfacePackage == null) {
477             return;
478         }
479         if (DEBUG) {
480             mSurfacePackage.getSurfaceControl().addOnReparentListener(
481                     (transaction, parent) -> Log.e(TAG,
482                             String.format("SurfacePackage'surface reparented to %s", parent)));
483             Log.d(TAG, "Transferring surface " + mSurfaceView.toString());
484         }
485 
486         mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage);
487     }
488 
initIconAnimation(Drawable iconDrawable, long duration)489     void initIconAnimation(Drawable iconDrawable, long duration) {
490         if (!(iconDrawable instanceof IconAnimateListener)) {
491             return;
492         }
493         IconAnimateListener aniDrawable = (IconAnimateListener) iconDrawable;
494         aniDrawable.prepareAnimate(duration, this::animationStartCallback);
495         aniDrawable.setAnimationJankMonitoring(new AnimatorListenerAdapter() {
496             @Override
497             public void onAnimationCancel(Animator animation) {
498                 InteractionJankMonitor.getInstance().cancel(CUJ_SPLASHSCREEN_AVD);
499             }
500 
501             @Override
502             public void onAnimationEnd(Animator animation) {
503                 InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_AVD);
504             }
505 
506             @Override
507             public void onAnimationStart(Animator animation) {
508                 InteractionJankMonitor.getInstance().begin(
509                         SplashScreenView.this, CUJ_SPLASHSCREEN_AVD);
510             }
511         });
512     }
513 
animationStartCallback()514     private void animationStartCallback() {
515         mIconAnimationStart = Instant.now();
516     }
517 
518     /**
519      * <p>Remove this view and release its resource. </p>
520      * <p><strong>Do not</strong> invoke this method from a drawing method
521      * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
522      */
523     @UiThread
remove()524     public void remove() {
525         if (mHasRemoved) {
526             return;
527         }
528         setVisibility(GONE);
529         if (mParceledIconBitmap != null) {
530             if (mIconView instanceof ImageView) {
531                 ((ImageView) mIconView).setImageDrawable(null);
532             } else if (mIconView != null) {
533                 mIconView.setBackground(null);
534             }
535             mParceledIconBitmap.recycle();
536             mParceledIconBitmap = null;
537         }
538         if (mParceledBrandingBitmap != null) {
539             mBrandingImageView.setBackground(null);
540             mParceledBrandingBitmap.recycle();
541             mParceledBrandingBitmap = null;
542         }
543         if (mParceledIconBackgroundBitmap != null) {
544             if (mIconView != null) {
545                 mIconView.setBackground(null);
546             }
547             mParceledIconBackgroundBitmap.recycle();
548             mParceledIconBackgroundBitmap = null;
549         }
550         if (mWindow != null) {
551             final DecorView decorView = (DecorView) mWindow.peekDecorView();
552             if (DEBUG) {
553                 Log.d(TAG, "remove starting view");
554             }
555             if (decorView != null) {
556                 decorView.removeView(this);
557             }
558             restoreSystemUIColors();
559             mWindow = null;
560         }
561         mHasRemoved = true;
562     }
563 
564     /** @hide **/
565     @Override
onDetachedFromWindow()566     protected void onDetachedFromWindow() {
567         super.onDetachedFromWindow();
568         releaseAnimationSurfaceHost();
569     }
570 
releaseAnimationSurfaceHost()571     private void releaseAnimationSurfaceHost() {
572         if (mSurfaceHost != null && !mIsCopied) {
573             if (DEBUG) {
574                 Log.d(TAG,
575                         "Shell removed splash screen."
576                                 + " Releasing SurfaceControlViewHost on thread #"
577                                 + Thread.currentThread().getId());
578             }
579             releaseIconHost(mSurfaceHost);
580             mSurfaceHost = null;
581         } else if (mSurfacePackage != null && mSurfaceHost == null) {
582             mSurfacePackage = null;
583             mClientCallback.sendResult(null);
584         }
585     }
586 
587     /**
588      * Release the host which hold the SurfaceView of the icon.
589      * @hide
590      */
releaseIconHost(SurfaceControlViewHost host)591     public static void releaseIconHost(SurfaceControlViewHost host) {
592         final Drawable background = host.getView().getBackground();
593         if (background instanceof SplashScreenView.IconAnimateListener) {
594             ((SplashScreenView.IconAnimateListener) background).stopAnimation();
595         }
596         host.release();
597     }
598 
599     /**
600      * Called when this view is attached to an activity. This also makes SystemUI colors
601      * transparent so the content of splash screen view can draw fully.
602      *
603      * @hide
604      */
attachHostActivityAndSetSystemUIColors(Activity activity, Window window)605     public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) {
606         mHostActivity = activity;
607         mWindow = window;
608         final WindowManager.LayoutParams attr = window.getAttributes();
609         mAppWindowFlags = attr.flags;
610         mStatusBarColor = window.getStatusBarColor();
611         mNavigationBarColor = window.getNavigationBarColor();
612         mSystemBarsAppearance = window.getInsetsController().getSystemBarsAppearance();
613         mNavigationContrastEnforced = window.isNavigationBarContrastEnforced();
614         mStatusContrastEnforced = window.isStatusBarContrastEnforced();
615         mDecorFitsSystemWindows = window.decorFitsSystemWindows();
616 
617         applySystemBarsContrastColor(window.getInsetsController(), mInitBackgroundColor);
618         // Let app draw the background of bars.
619         window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
620         // Use specified bar colors instead of window background.
621         window.clearFlags(FLAG_TRANSLUCENT_STATUS | FLAG_TRANSLUCENT_NAVIGATION);
622         window.setStatusBarColor(Color.TRANSPARENT);
623         window.setNavigationBarColor(Color.TRANSPARENT);
624         window.setDecorFitsSystemWindows(false);
625         window.setStatusBarContrastEnforced(false);
626         window.setNavigationBarContrastEnforced(false);
627     }
628 
629     /** Called when this view is removed from the host activity. */
restoreSystemUIColors()630     private void restoreSystemUIColors() {
631         mWindow.setFlags(mAppWindowFlags, WINDOW_FLAG_MASK);
632         mWindow.setStatusBarColor(mStatusBarColor);
633         mWindow.setNavigationBarColor(mNavigationBarColor);
634         mWindow.getInsetsController().setSystemBarsAppearance(mSystemBarsAppearance,
635                 LIGHT_BARS_MASK);
636         mWindow.setDecorFitsSystemWindows(mDecorFitsSystemWindows);
637         mWindow.setStatusBarContrastEnforced(mStatusContrastEnforced);
638         mWindow.setNavigationBarContrastEnforced(mNavigationContrastEnforced);
639     }
640 
641     /**
642      * Makes the icon color of system bars contrast.
643      * @hide
644      */
applySystemBarsContrastColor(WindowInsetsController windowInsetsController, int backgroundColor)645     public static void applySystemBarsContrastColor(WindowInsetsController windowInsetsController,
646             int backgroundColor) {
647         final int lightBarAppearance = ContrastColorUtil.isColorLight(backgroundColor)
648                 ? LIGHT_BARS_MASK : 0;
649         windowInsetsController.setSystemBarsAppearance(lightBarAppearance, LIGHT_BARS_MASK);
650     }
651 
652     /**
653      * Get the view containing the Splash Screen icon and its background.
654      * @see android.R.attr#windowSplashScreenAnimatedIcon
655      */
getIconView()656     public @Nullable View getIconView() {
657         return mIconView;
658     }
659 
660     /**
661      * Get the branding image view.
662      * @hide
663      */
664     @TestApi
getBrandingView()665     public @Nullable View getBrandingView() {
666         return mBrandingImageView;
667     }
668 
669     /**
670      * Get the initial background color of this view.
671      * @hide
672      */
getInitBackgroundColor()673     public @ColorInt int getInitBackgroundColor() {
674         return mInitBackgroundColor;
675     }
676 
677     /**
678      * An interface for an animatable drawable object to register a callback when animation start.
679      * @hide
680      */
681     public interface IconAnimateListener {
682         /**
683          * Prepare the animation if this drawable also be animatable.
684          * @param duration The animation duration.
685          * @param startListener The callback listener used to receive the start of the animation.
686          * @return true if this drawable object can also be animated and it can be played now.
687          */
prepareAnimate(long duration, Runnable startListener)688         boolean prepareAnimate(long duration, Runnable startListener);
689 
690         /**
691          * Stop animation.
692          */
stopAnimation()693         void stopAnimation();
694 
695         /**
696          * Provides a chance to start interaction jank monitoring in avd animation.
697          * @param listener a listener to start jank monitoring
698          */
setAnimationJankMonitoring(AnimatorListenerAdapter listener)699         default void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {}
700     }
701 
702     /**
703      * Use to create {@link SplashScreenView} object across process.
704      * @hide
705      */
706     public static class SplashScreenViewParcelable implements Parcelable {
707         private int mIconSize;
708         private int mBackgroundColor;
709         private Bitmap mIconBackground;
710 
711         private Bitmap mIconBitmap = null;
712         private int mBrandingWidth;
713         private int mBrandingHeight;
714         private Bitmap mBrandingBitmap;
715 
716         private long mIconAnimationStartMillis;
717         private long mIconAnimationDurationMillis;
718 
719         private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
720         private RemoteCallback mClientCallback;
721 
SplashScreenViewParcelable(SplashScreenView view)722         public SplashScreenViewParcelable(SplashScreenView view) {
723             mIconSize = view.mIconView.getWidth();
724             mBackgroundColor = view.getInitBackgroundColor();
725             mIconBackground = copyDrawable(view.getIconView().getBackground());
726             mSurfacePackage = view.mSurfacePackageCopy;
727             if (mSurfacePackage == null) {
728                 // We only need to copy the drawable if we are not using a SurfaceView
729                 mIconBitmap = copyDrawable(((ImageView) view.getIconView()).getDrawable());
730             }
731             mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground());
732 
733             ViewGroup.LayoutParams params = view.getBrandingView().getLayoutParams();
734             mBrandingWidth = params.width;
735             mBrandingHeight = params.height;
736 
737             if (view.getIconAnimationStart() != null) {
738                 mIconAnimationStartMillis = view.getIconAnimationStart().toEpochMilli();
739             }
740             if (view.getIconAnimationDuration() != null) {
741                 mIconAnimationDurationMillis = view.getIconAnimationDuration().toMillis();
742             }
743         }
744 
copyDrawable(Drawable drawable)745         private Bitmap copyDrawable(Drawable drawable) {
746             if (drawable != null) {
747                 final Rect initialBounds = drawable.copyBounds();
748                 final int width = initialBounds.width();
749                 final int height = initialBounds.height();
750 
751                 final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
752                 final Canvas bmpCanvas = new Canvas(snapshot);
753                 drawable.setBounds(0, 0, width, height);
754                 drawable.draw(bmpCanvas);
755                 final Bitmap copyBitmap = snapshot.createAshmemBitmap();
756                 snapshot.recycle();
757                 return copyBitmap;
758             }
759             return null;
760         }
761 
SplashScreenViewParcelable(@onNull Parcel source)762         private SplashScreenViewParcelable(@NonNull Parcel source) {
763             readParcel(source);
764         }
765 
readParcel(@onNull Parcel source)766         private void readParcel(@NonNull Parcel source) {
767             mIconSize = source.readInt();
768             mBackgroundColor = source.readInt();
769             mIconBitmap = source.readTypedObject(Bitmap.CREATOR);
770             mBrandingWidth = source.readInt();
771             mBrandingHeight = source.readInt();
772             mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR);
773             mIconAnimationStartMillis = source.readLong();
774             mIconAnimationDurationMillis = source.readLong();
775             mIconBackground = source.readTypedObject(Bitmap.CREATOR);
776             mSurfacePackage = source.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR);
777             mClientCallback = source.readTypedObject(RemoteCallback.CREATOR);
778         }
779 
780         @Override
describeContents()781         public int describeContents() {
782             return 0;
783         }
784 
785         @Override
writeToParcel(Parcel dest, int flags)786         public void writeToParcel(Parcel dest, int flags) {
787             dest.writeInt(mIconSize);
788             dest.writeInt(mBackgroundColor);
789             dest.writeTypedObject(mIconBitmap, flags);
790             dest.writeInt(mBrandingWidth);
791             dest.writeInt(mBrandingHeight);
792             dest.writeTypedObject(mBrandingBitmap, flags);
793             dest.writeLong(mIconAnimationStartMillis);
794             dest.writeLong(mIconAnimationDurationMillis);
795             dest.writeTypedObject(mIconBackground, flags);
796             dest.writeTypedObject(mSurfacePackage, flags);
797             dest.writeTypedObject(mClientCallback, flags);
798         }
799 
800         public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR =
801                 new Parcelable.Creator<SplashScreenViewParcelable>() {
802                     public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) {
803                         return new SplashScreenViewParcelable(source);
804                     }
805                     public SplashScreenViewParcelable[] newArray(int size) {
806                         return new SplashScreenViewParcelable[size];
807                     }
808                 };
809 
810         /**
811          * Release the bitmap if another process cannot handle it.
812          */
clearIfNeeded()813         public void clearIfNeeded() {
814             if (mIconBitmap != null) {
815                 mIconBitmap.recycle();
816                 mIconBitmap = null;
817             }
818             if (mBrandingBitmap != null) {
819                 mBrandingBitmap.recycle();
820                 mBrandingBitmap = null;
821             }
822         }
823 
getIconSize()824         int getIconSize() {
825             return mIconSize;
826         }
827 
getBackgroundColor()828         int getBackgroundColor() {
829             return mBackgroundColor;
830         }
831 
832         /**
833          * Sets the {@link RemoteCallback} that will be called by the client to notify the shell
834          * of the removal of the {@link SplashScreenView}.
835          */
setClientCallback(@onNull RemoteCallback clientCallback)836         public void setClientCallback(@NonNull RemoteCallback clientCallback) {
837             mClientCallback = clientCallback;
838         }
839     }
840 }
841