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