• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                 ImageView imageView = new ImageView(viewContext);
338                 imageView.setBackground(mIconDrawable);
339                 viewHost.setView(imageView, mIconSize, mIconSize);
340                 SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
341                 surfaceView.setChildSurfacePackage(surfacePackage);
342                 view.mSurfacePackage = surfacePackage;
343                 view.mSurfaceHost = viewHost;
344                 view.mSurfacePackageCopy = new SurfaceControlViewHost.SurfacePackage(
345                         surfacePackage);
346             } else {
347                 if (DEBUG) {
348                     Log.d(TAG, "Using copy of SurfacePackage in the client");
349                 }
350                 view.mSurfacePackage = mSurfacePackage;
351             }
352             if (mIconSize != 0) {
353                 LayoutParams lp = new FrameLayout.LayoutParams(mIconSize, mIconSize);
354                 lp.gravity = Gravity.CENTER;
355                 surfaceView.setLayoutParams(lp);
356                 if (DEBUG) {
357                     Log.d(TAG, "Icon size " + mIconSize);
358                 }
359             }
360 
361             // We ensure that we can blend the alpha of the surface view with the SplashScreenView
362             surfaceView.setUseAlpha();
363             surfaceView.setZOrderOnTop(true);
364             surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
365 
366             view.addView(surfaceView);
367             view.mSurfaceView = surfaceView;
368             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
369             return surfaceView;
370         }
371     }
372 
373     /** @hide */
SplashScreenView(Context context)374     public SplashScreenView(Context context) {
375         super(context);
376     }
377 
378     /** @hide */
SplashScreenView(Context context, AttributeSet attributeSet)379     public SplashScreenView(Context context, AttributeSet attributeSet) {
380         super(context, attributeSet);
381     }
382 
383     /**
384      * Declared this view is not copyable.
385      * @hide
386      */
setNotCopyable()387     public void setNotCopyable() {
388         mNotCopyable = true;
389     }
390 
391     /**
392      * Whether this view is copyable.
393      * @hide
394      */
isCopyable()395     public boolean isCopyable() {
396         return !mNotCopyable;
397     }
398 
399     /**
400      * Called when this {@link SplashScreenView} has been copied to be transferred to the client.
401      *
402      * @hide
403      */
onCopied()404     public void onCopied() {
405         mIsCopied = true;
406         if (mSurfaceView == null) {
407             return;
408         }
409         if (DEBUG) {
410             Log.d(TAG, "Setting SurfaceView's SurfacePackage to null.");
411         }
412         // If we don't release the surface package, the surface will be reparented to this
413         // surface view. So once it's copied into the client process, we release it.
414         mSurfacePackage.release();
415         mSurfacePackage = null;
416     }
417 
418     /** @hide **/
419     @Nullable
getSurfaceHost()420     public SurfaceControlViewHost getSurfaceHost() {
421         return mSurfaceHost;
422     }
423 
424     @Override
setAlpha(float alpha)425     public void setAlpha(float alpha) {
426         super.setAlpha(alpha);
427 
428         // The surface view's alpha is not multiplied with the containing view's alpha, so we
429         // manually do it here
430         if (mSurfaceView != null) {
431             mSurfaceView.setAlpha(mSurfaceView.getAlpha() * alpha);
432         }
433     }
434 
435     /**
436      * Returns the duration of the icon animation if icon is animatable.
437      *
438      * Note the return value can be null or 0 if the
439      * {@link android.R.attr#windowSplashScreenAnimatedIcon} is not
440      * {@link android.graphics.drawable.AnimationDrawable} or
441      * {@link android.graphics.drawable.AnimatedVectorDrawable}.
442      *
443      * @see android.R.attr#windowSplashScreenAnimatedIcon
444      * @see android.R.attr#windowSplashScreenAnimationDuration
445      */
446     @Nullable
getIconAnimationDuration()447     public Duration getIconAnimationDuration() {
448         return mIconAnimationDuration;
449     }
450 
451     /**
452      * If the replaced icon is animatable, return the animation start time based on system clock.
453      */
454     @Nullable
getIconAnimationStart()455     public Instant getIconAnimationStart() {
456         return mIconAnimationStart;
457     }
458 
459 
460     /**
461      * @hide
462      */
syncTransferSurfaceOnDraw()463     public void syncTransferSurfaceOnDraw() {
464         if (mSurfacePackage == null) {
465             return;
466         }
467         if (DEBUG) {
468             mSurfacePackage.getSurfaceControl().addOnReparentListener(
469                     (transaction, parent) -> Log.e(TAG,
470                             String.format("SurfacePackage'surface reparented to %s", parent)));
471             Log.d(TAG, "Transferring surface " + mSurfaceView.toString());
472         }
473 
474         mSurfaceView.setChildSurfacePackage(mSurfacePackage);
475     }
476 
initIconAnimation(Drawable iconDrawable)477     void initIconAnimation(Drawable iconDrawable) {
478         if (!(iconDrawable instanceof IconAnimateListener)) {
479             return;
480         }
481         IconAnimateListener aniDrawable = (IconAnimateListener) iconDrawable;
482         aniDrawable.prepareAnimate(this::animationStartCallback);
483         aniDrawable.setAnimationJankMonitoring(new AnimatorListenerAdapter() {
484             @Override
485             public void onAnimationCancel(Animator animation) {
486                 InteractionJankMonitor.getInstance().cancel(CUJ_SPLASHSCREEN_AVD);
487             }
488 
489             @Override
490             public void onAnimationEnd(Animator animation) {
491                 InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_AVD);
492             }
493 
494             @Override
495             public void onAnimationStart(Animator animation) {
496                 InteractionJankMonitor.getInstance().begin(
497                         SplashScreenView.this, CUJ_SPLASHSCREEN_AVD);
498             }
499         });
500     }
501 
animationStartCallback(long animDuration)502     private void animationStartCallback(long animDuration) {
503         mIconAnimationStart = Instant.now();
504         if (animDuration >= 0) {
505             mIconAnimationDuration = Duration.ofMillis(animDuration);
506         }
507     }
508 
509     /**
510      * <p>Remove this view and release its resource. </p>
511      * <p><strong>Do not</strong> invoke this method from a drawing method
512      * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
513      */
514     @UiThread
remove()515     public void remove() {
516         if (mHasRemoved) {
517             return;
518         }
519         setVisibility(GONE);
520         if (mParceledIconBitmap != null) {
521             if (mIconView instanceof ImageView) {
522                 ((ImageView) mIconView).setImageDrawable(null);
523             } else if (mIconView != null) {
524                 mIconView.setBackground(null);
525             }
526             mParceledIconBitmap.recycle();
527             mParceledIconBitmap = null;
528         }
529         if (mParceledBrandingBitmap != null) {
530             mBrandingImageView.setBackground(null);
531             mParceledBrandingBitmap.recycle();
532             mParceledBrandingBitmap = null;
533         }
534         if (mParceledIconBackgroundBitmap != null) {
535             if (mIconView != null) {
536                 mIconView.setBackground(null);
537             }
538             mParceledIconBackgroundBitmap.recycle();
539             mParceledIconBackgroundBitmap = null;
540         }
541         if (mWindow != null) {
542             final DecorView decorView = (DecorView) mWindow.peekDecorView();
543             if (DEBUG) {
544                 Log.d(TAG, "remove starting view");
545             }
546             if (decorView != null) {
547                 decorView.removeView(this);
548             }
549             mWindow = null;
550         }
551         mHasRemoved = true;
552     }
553 
554     /** @hide **/
555     @Override
onDetachedFromWindow()556     protected void onDetachedFromWindow() {
557         super.onDetachedFromWindow();
558         releaseAnimationSurfaceHost();
559     }
560 
561     @Override
onLayout(boolean changed, int l, int t, int r, int b)562     protected void onLayout(boolean changed, int l, int t, int r, int b) {
563         super.onLayout(changed, l, t, r, b);
564 
565         mBrandingImageView.getDrawingRect(mTmpRect);
566         final int brandingHeight = mTmpRect.height();
567         if (brandingHeight == 0 || mIconView == null) {
568             return;
569         }
570         final int visibility = mBrandingImageView.getVisibility();
571         if (visibility != VISIBLE) {
572             return;
573         }
574         final int currentHeight = b - t;
575 
576         mIconView.getLocationInWindow(mTmpPos);
577         mIconView.getDrawingRect(mTmpRect);
578         final int iconHeight = mTmpRect.height();
579 
580         final ViewGroup.MarginLayoutParams params =
581                 (ViewGroup.MarginLayoutParams) mBrandingImageView.getLayoutParams();
582         if (params == null) {
583             Log.e(TAG, "Unable to adjust branding image layout, layout changed?");
584             return;
585         }
586         final int marginBottom = params.bottomMargin;
587         final int remainingHeight = currentHeight - mTmpPos[1] - iconHeight;
588         final int remainingMaxMargin = remainingHeight - brandingHeight;
589         if (remainingHeight < brandingHeight) {
590             // unable to show the branding image, hide it
591             mBrandingImageView.setVisibility(GONE);
592         } else if (remainingMaxMargin < marginBottom) {
593             // shorter than original margin
594             params.bottomMargin = (int) Math.round(remainingMaxMargin / 2.0);
595             mBrandingImageView.setLayoutParams(params);
596         }
597         // nothing need to adjust
598     }
599 
releaseAnimationSurfaceHost()600     private void releaseAnimationSurfaceHost() {
601         if (mSurfaceHost != null && !mIsCopied) {
602             if (DEBUG) {
603                 Log.d(TAG,
604                         "Shell removed splash screen."
605                                 + " Releasing SurfaceControlViewHost on thread #"
606                                 + Thread.currentThread().getId());
607             }
608             releaseIconHost(mSurfaceHost);
609             mSurfaceHost = null;
610         } else if (mSurfacePackage != null && mSurfaceHost == null) {
611             mSurfacePackage = null;
612             mClientCallback.sendResult(null);
613         }
614     }
615 
616     /**
617      * Release the host which hold the SurfaceView of the icon.
618      * @hide
619      */
releaseIconHost(SurfaceControlViewHost host)620     public static void releaseIconHost(SurfaceControlViewHost host) {
621         final Drawable background = host.getView().getBackground();
622         if (background instanceof SplashScreenView.IconAnimateListener) {
623             ((SplashScreenView.IconAnimateListener) background).stopAnimation();
624         }
625         host.release();
626     }
627 
628     /**
629      * Called when this view is attached to a window of an activity.
630      *
631      * @hide
632      */
attachHostWindow(Window window)633     public void attachHostWindow(Window window) {
634         mWindow = window;
635     }
636 
637     /**
638      * Get the view containing the Splash Screen icon and its background.
639      * @see android.R.attr#windowSplashScreenAnimatedIcon
640      */
getIconView()641     public @Nullable View getIconView() {
642         return mIconView;
643     }
644 
645     /**
646      * Get the branding image view.
647      * @hide
648      */
649     @TestApi
getBrandingView()650     public @Nullable View getBrandingView() {
651         return mBrandingImageView;
652     }
653 
654     /**
655      * Get the initial background color of this view.
656      * @hide
657      */
getInitBackgroundColor()658     public @ColorInt int getInitBackgroundColor() {
659         return mInitBackgroundColor;
660     }
661 
662     /**
663      * An interface for an animatable drawable object to register a callback when animation start.
664      * @hide
665      */
666     public interface IconAnimateListener {
667         /**
668          * Prepare the animation if this drawable also be animatable.
669          * @param startListener The callback listener used to receive the start of the animation.
670          */
prepareAnimate(LongConsumer startListener)671         void prepareAnimate(LongConsumer startListener);
672 
673         /**
674          * Stop animation.
675          */
stopAnimation()676         void stopAnimation();
677 
678         /**
679          * Provides a chance to start interaction jank monitoring in avd animation.
680          * @param listener a listener to start jank monitoring
681          */
setAnimationJankMonitoring(AnimatorListenerAdapter listener)682         default void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {}
683     }
684 
685     /**
686      * Use to create {@link SplashScreenView} object across process.
687      * @hide
688      */
689     public static class SplashScreenViewParcelable implements Parcelable {
690         private int mIconSize;
691         private int mBackgroundColor;
692         private Bitmap mIconBackground;
693 
694         private Bitmap mIconBitmap = null;
695         private int mBrandingWidth;
696         private int mBrandingHeight;
697         private Bitmap mBrandingBitmap;
698 
699         private long mIconAnimationStartMillis;
700         private long mIconAnimationDurationMillis;
701 
702         private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
703         private RemoteCallback mClientCallback;
704 
SplashScreenViewParcelable(SplashScreenView view)705         public SplashScreenViewParcelable(SplashScreenView view) {
706             final View iconView = view.getIconView();
707             mIconSize = iconView != null ? iconView.getWidth() : 0;
708             mBackgroundColor = view.getInitBackgroundColor();
709             mIconBackground = iconView != null ? copyDrawable(iconView.getBackground()) : null;
710             mSurfacePackage = view.mSurfacePackageCopy;
711             if (mSurfacePackage == null) {
712                 // We only need to copy the drawable if we are not using a SurfaceView
713                 mIconBitmap = iconView != null
714                         ? copyDrawable(((ImageView) view.getIconView()).getDrawable()) : null;
715             }
716             mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground());
717 
718             ViewGroup.LayoutParams params = view.getBrandingView().getLayoutParams();
719             mBrandingWidth = params.width;
720             mBrandingHeight = params.height;
721 
722             if (view.getIconAnimationStart() != null) {
723                 mIconAnimationStartMillis = view.getIconAnimationStart().toEpochMilli();
724             }
725             if (view.getIconAnimationDuration() != null) {
726                 mIconAnimationDurationMillis = view.getIconAnimationDuration().toMillis();
727             }
728         }
729 
copyDrawable(Drawable drawable)730         private Bitmap copyDrawable(Drawable drawable) {
731             if (drawable != null) {
732                 final Rect initialBounds = drawable.copyBounds();
733                 final int width = initialBounds.width();
734                 final int height = initialBounds.height();
735                 if (width <= 0 || height <= 0) {
736                     return null;
737                 }
738 
739                 final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
740                 final Canvas bmpCanvas = new Canvas(snapshot);
741                 drawable.setBounds(0, 0, width, height);
742                 drawable.draw(bmpCanvas);
743                 final Bitmap copyBitmap = snapshot.createAshmemBitmap();
744                 snapshot.recycle();
745                 return copyBitmap;
746             }
747             return null;
748         }
749 
SplashScreenViewParcelable(@onNull Parcel source)750         private SplashScreenViewParcelable(@NonNull Parcel source) {
751             readParcel(source);
752         }
753 
readParcel(@onNull Parcel source)754         private void readParcel(@NonNull Parcel source) {
755             mIconSize = source.readInt();
756             mBackgroundColor = source.readInt();
757             mIconBitmap = source.readTypedObject(Bitmap.CREATOR);
758             mBrandingWidth = source.readInt();
759             mBrandingHeight = source.readInt();
760             mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR);
761             mIconAnimationStartMillis = source.readLong();
762             mIconAnimationDurationMillis = source.readLong();
763             mIconBackground = source.readTypedObject(Bitmap.CREATOR);
764             mSurfacePackage = source.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR);
765             mClientCallback = source.readTypedObject(RemoteCallback.CREATOR);
766         }
767 
768         @Override
describeContents()769         public int describeContents() {
770             return 0;
771         }
772 
773         @Override
writeToParcel(Parcel dest, int flags)774         public void writeToParcel(Parcel dest, int flags) {
775             dest.writeInt(mIconSize);
776             dest.writeInt(mBackgroundColor);
777             dest.writeTypedObject(mIconBitmap, flags);
778             dest.writeInt(mBrandingWidth);
779             dest.writeInt(mBrandingHeight);
780             dest.writeTypedObject(mBrandingBitmap, flags);
781             dest.writeLong(mIconAnimationStartMillis);
782             dest.writeLong(mIconAnimationDurationMillis);
783             dest.writeTypedObject(mIconBackground, flags);
784             dest.writeTypedObject(mSurfacePackage, flags);
785             dest.writeTypedObject(mClientCallback, flags);
786         }
787 
788         public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR =
789                 new Parcelable.Creator<SplashScreenViewParcelable>() {
790                     public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) {
791                         return new SplashScreenViewParcelable(source);
792                     }
793                     public SplashScreenViewParcelable[] newArray(int size) {
794                         return new SplashScreenViewParcelable[size];
795                     }
796                 };
797 
798         /**
799          * Release the bitmap if another process cannot handle it.
800          */
clearIfNeeded()801         public void clearIfNeeded() {
802             if (mIconBitmap != null) {
803                 mIconBitmap.recycle();
804                 mIconBitmap = null;
805             }
806             if (mBrandingBitmap != null) {
807                 mBrandingBitmap.recycle();
808                 mBrandingBitmap = null;
809             }
810         }
811 
getIconSize()812         int getIconSize() {
813             return mIconSize;
814         }
815 
getBackgroundColor()816         int getBackgroundColor() {
817             return mBackgroundColor;
818         }
819 
820         /**
821          * Sets the {@link RemoteCallback} that will be called by the client to notify the shell
822          * of the removal of the {@link SplashScreenView}.
823          */
setClientCallback(@onNull RemoteCallback clientCallback)824         public void setClientCallback(@NonNull RemoteCallback clientCallback) {
825             mClientCallback = clientCallback;
826         }
827     }
828 }
829