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