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