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