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