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