| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.window; |
| |
| import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; |
| |
| import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_AVD; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.annotation.ColorInt; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.TestApi; |
| import android.annotation.UiThread; |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Build; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.os.RemoteCallback; |
| import android.os.Trace; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.AttachedSurfaceControl; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.SurfaceControlViewHost; |
| import android.view.SurfaceView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.view.WindowManager; |
| import android.widget.FrameLayout; |
| import android.widget.ImageView; |
| |
| import com.android.internal.R; |
| import com.android.internal.jank.InteractionJankMonitor; |
| import com.android.internal.policy.DecorView; |
| |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.time.Duration; |
| import java.time.Instant; |
| import java.util.function.Consumer; |
| import java.util.function.LongConsumer; |
| |
| /** |
| * <p>The view which allows an activity to customize its splash screen exit animation.</p> |
| * |
| * <p>Activities will receive this view as a parameter of |
| * {@link SplashScreen.OnExitAnimationListener#onSplashScreenExit} if |
| * they set {@link SplashScreen#setOnExitAnimationListener}. |
| * When this callback is called, this view will be on top of the activity.</p> |
| * |
| * <p>This view is composed of a view containing the splashscreen icon (see |
| * windowSplashscreenAnimatedIcon) and a background. |
| * Developers can use {@link #getIconView} to get this view and replace the drawable or |
| * add animation to it. The background of this view is filled with a single color, which can be |
| * edited during the animation by {@link View#setBackground} or {@link View#setBackgroundColor}.</p> |
| * |
| * @see SplashScreen |
| */ |
| public final class SplashScreenView extends FrameLayout { |
| private static final String TAG = SplashScreenView.class.getSimpleName(); |
| private static final boolean DEBUG = Build.IS_DEBUGGABLE; |
| |
| private boolean mNotCopyable; |
| private boolean mIsCopied; |
| private int mInitBackgroundColor; |
| private View mIconView; |
| private Bitmap mParceledIconBitmap; |
| private View mBrandingImageView; |
| private Bitmap mParceledBrandingBitmap; |
| private Bitmap mParceledIconBackgroundBitmap; |
| private Duration mIconAnimationDuration; |
| private Instant mIconAnimationStart; |
| |
| private final Rect mTmpRect = new Rect(); |
| private final int[] mTmpPos = new int[2]; |
| |
| @Nullable |
| private SurfaceControlViewHost.SurfacePackage mSurfacePackageCopy; |
| @Nullable |
| private SurfaceControlViewHost.SurfacePackage mSurfacePackage; |
| @Nullable |
| private SurfaceView mSurfaceView; |
| @Nullable |
| private SurfaceControlViewHost mSurfaceHost; |
| @Nullable |
| private RemoteCallback mClientCallback; |
| |
| // cache original window and status |
| private Window mWindow; |
| private boolean mHasRemoved; |
| |
| /** |
| * Internal builder to create a SplashScreenView object. |
| * @hide |
| */ |
| public static class Builder { |
| private final Context mContext; |
| private int mIconSize; |
| private @ColorInt int mBackgroundColor; |
| private Bitmap mParceledIconBitmap; |
| private Bitmap mParceledIconBackgroundBitmap; |
| private Drawable mIconDrawable; |
| // It is only set for legacy splash screen which won't be sent across processes. |
| private Drawable mOverlayDrawable; |
| private Drawable mIconBackground; |
| private SurfaceControlViewHost.SurfacePackage mSurfacePackage; |
| private RemoteCallback mClientCallback; |
| private int mBrandingImageWidth; |
| private int mBrandingImageHeight; |
| private Drawable mBrandingDrawable; |
| private Bitmap mParceledBrandingBitmap; |
| private Instant mIconAnimationStart; |
| private Duration mIconAnimationDuration; |
| private Consumer<Runnable> mUiThreadInitTask; |
| private boolean mAllowHandleSolidColor = true; |
| |
| public Builder(@NonNull Context context) { |
| mContext = context; |
| } |
| |
| /** |
| * When create from {@link SplashScreenViewParcelable}, all the materials were be settled so |
| * you do not need to call other set methods. |
| */ |
| public Builder createFromParcel(SplashScreenViewParcelable parcelable) { |
| mIconSize = parcelable.getIconSize(); |
| mBackgroundColor = parcelable.getBackgroundColor(); |
| mSurfacePackage = parcelable.mSurfacePackage; |
| if (mSurfacePackage == null && parcelable.mIconBitmap != null) { |
| // We only create a Bitmap copies of immobile icons since animated icon are using |
| // a surface view |
| mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap); |
| mParceledIconBitmap = parcelable.mIconBitmap; |
| } |
| if (parcelable.mIconBackground != null) { |
| mIconBackground = new BitmapDrawable(mContext.getResources(), |
| parcelable.mIconBackground); |
| mParceledIconBackgroundBitmap = parcelable.mIconBackground; |
| } |
| if (parcelable.mBrandingBitmap != null) { |
| setBrandingDrawable(new BitmapDrawable(mContext.getResources(), |
| parcelable.mBrandingBitmap), parcelable.mBrandingWidth, |
| parcelable.mBrandingHeight); |
| mParceledBrandingBitmap = parcelable.mBrandingBitmap; |
| } |
| mIconAnimationStart = Instant.ofEpochMilli(parcelable.mIconAnimationStartMillis); |
| mIconAnimationDuration = Duration.ofMillis(parcelable.mIconAnimationDurationMillis); |
| mClientCallback = parcelable.mClientCallback; |
| if (DEBUG) { |
| Log.d(TAG, String.format("Building from parcel drawable: %s", mIconDrawable)); |
| } |
| return this; |
| } |
| |
| /** |
| * Set the rectangle size for the center view. |
| */ |
| public Builder setIconSize(int iconSize) { |
| mIconSize = iconSize; |
| return this; |
| } |
| |
| /** |
| * Set the background color for the view. |
| */ |
| public Builder setBackgroundColor(@ColorInt int backgroundColor) { |
| mBackgroundColor = backgroundColor; |
| return this; |
| } |
| |
| /** |
| * Set the Drawable object to fill entire view |
| */ |
| public Builder setOverlayDrawable(@Nullable Drawable drawable) { |
| mOverlayDrawable = drawable; |
| return this; |
| } |
| |
| /** |
| * Set the Drawable object to fill the center view. |
| */ |
| public Builder setCenterViewDrawable(@Nullable Drawable drawable) { |
| mIconDrawable = drawable; |
| return this; |
| } |
| |
| /** |
| * Set the background color for the icon. |
| */ |
| public Builder setIconBackground(Drawable iconBackground) { |
| mIconBackground = iconBackground; |
| return this; |
| } |
| |
| /** |
| * Set the Runnable that can receive the task which should be executed on UI thread. |
| */ |
| public Builder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) { |
| mUiThreadInitTask = uiThreadInitTask; |
| return this; |
| } |
| |
| /** |
| * Set the Drawable object and size for the branding view. |
| */ |
| public Builder setBrandingDrawable(@Nullable Drawable branding, int width, int height) { |
| mBrandingDrawable = branding; |
| mBrandingImageWidth = width; |
| mBrandingImageHeight = height; |
| return this; |
| } |
| |
| /** |
| * Sets whether this view can be copied and transferred to the client if the view is |
| * empty style splash screen. |
| */ |
| public Builder setAllowHandleSolidColor(boolean allowHandleSolidColor) { |
| mAllowHandleSolidColor = allowHandleSolidColor; |
| return this; |
| } |
| |
| /** |
| * Create SplashScreenWindowView object from materials. |
| */ |
| public SplashScreenView build() { |
| Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#build"); |
| final LayoutInflater layoutInflater = LayoutInflater.from(mContext); |
| final SplashScreenView view = (SplashScreenView) |
| layoutInflater.inflate(R.layout.splash_screen_view, null, false); |
| view.mInitBackgroundColor = mBackgroundColor; |
| if (mOverlayDrawable != null) { |
| view.setBackground(mOverlayDrawable); |
| } else { |
| view.setBackgroundColor(mBackgroundColor); |
| } |
| view.mClientCallback = mClientCallback; |
| |
| view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view); |
| |
| boolean hasIcon = false; |
| // center icon |
| if (mIconDrawable instanceof SplashScreenView.IconAnimateListener |
| || mSurfacePackage != null) { |
| hasIcon = true; |
| if (mUiThreadInitTask != null) { |
| mUiThreadInitTask.accept(() -> view.mIconView = createSurfaceView(view)); |
| } else { |
| view.mIconView = createSurfaceView(view); |
| } |
| view.initIconAnimation(mIconDrawable); |
| view.mIconAnimationStart = mIconAnimationStart; |
| view.mIconAnimationDuration = mIconAnimationDuration; |
| } else if (mIconSize != 0) { |
| ImageView imageView = view.findViewById(R.id.splashscreen_icon_view); |
| assert imageView != null; |
| |
| final ViewGroup.LayoutParams params = imageView.getLayoutParams(); |
| params.width = mIconSize; |
| params.height = mIconSize; |
| imageView.setLayoutParams(params); |
| if (mIconDrawable != null) { |
| imageView.setImageDrawable(mIconDrawable); |
| } |
| if (mIconBackground != null) { |
| imageView.setBackground(mIconBackground); |
| } |
| hasIcon = true; |
| view.mIconView = imageView; |
| } |
| if (mOverlayDrawable != null || (!hasIcon && !mAllowHandleSolidColor)) { |
| view.setNotCopyable(); |
| } |
| |
| view.mParceledIconBackgroundBitmap = mParceledIconBackgroundBitmap; |
| view.mParceledIconBitmap = mParceledIconBitmap; |
| |
| // branding image |
| if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0 && mBrandingDrawable != null) { |
| final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams(); |
| params.width = mBrandingImageWidth; |
| params.height = mBrandingImageHeight; |
| view.mBrandingImageView.setLayoutParams(params); |
| view.mBrandingImageView.setBackground(mBrandingDrawable); |
| } else { |
| view.mBrandingImageView.setVisibility(GONE); |
| } |
| if (mParceledBrandingBitmap != null) { |
| view.mParceledBrandingBitmap = mParceledBrandingBitmap; |
| } |
| if (DEBUG) { |
| Log.d(TAG, "Build " + view |
| + "\nIcon: view: " + view.mIconView + " drawable: " |
| + mIconDrawable + " size: " + mIconSize |
| + "\nBranding: view: " + view.mBrandingImageView + " drawable: " |
| + mBrandingDrawable + " size w: " + mBrandingImageWidth + " h: " |
| + mBrandingImageHeight); |
| } |
| Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); |
| return view; |
| } |
| |
| private SurfaceView createSurfaceView(@NonNull SplashScreenView view) { |
| Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#createSurfaceView"); |
| final Context viewContext = view.getContext(); |
| final SurfaceView surfaceView = new SurfaceView(viewContext); |
| surfaceView.setPadding(0, 0, 0, 0); |
| surfaceView.setBackground(mIconBackground); |
| if (mSurfacePackage == null) { |
| if (DEBUG) { |
| Log.d(TAG, |
| "SurfaceControlViewHost created on thread " |
| + Thread.currentThread().getId()); |
| } |
| |
| AttachedSurfaceControl attachedSurfaceControl = surfaceView.getRootSurfaceControl(); |
| SurfaceControlViewHost viewHost = new SurfaceControlViewHost(viewContext, |
| viewContext.getDisplay(), |
| attachedSurfaceControl == null ? null |
| : attachedSurfaceControl.getInputTransferToken(), |
| "SplashScreenView"); |
| ImageView imageView = new ImageView(viewContext); |
| imageView.setBackground(mIconDrawable); |
| final int windowFlag = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
| | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; |
| final WindowManager.LayoutParams lp = |
| new WindowManager.LayoutParams(mIconSize, mIconSize, |
| WindowManager.LayoutParams.TYPE_APPLICATION, windowFlag, |
| PixelFormat.TRANSPARENT); |
| viewHost.setView(imageView, lp); |
| SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage(); |
| surfaceView.setChildSurfacePackage(surfacePackage); |
| view.mSurfacePackage = surfacePackage; |
| view.mSurfaceHost = viewHost; |
| view.mSurfacePackageCopy = new SurfaceControlViewHost.SurfacePackage( |
| surfacePackage); |
| } else { |
| if (DEBUG) { |
| Log.d(TAG, "Using copy of SurfacePackage in the client"); |
| } |
| view.mSurfacePackage = mSurfacePackage; |
| } |
| if (mIconSize != 0) { |
| LayoutParams lp = new FrameLayout.LayoutParams(mIconSize, mIconSize); |
| lp.gravity = Gravity.CENTER; |
| surfaceView.setLayoutParams(lp); |
| if (DEBUG) { |
| Log.d(TAG, "Icon size " + mIconSize); |
| } |
| } |
| |
| // We ensure that we can blend the alpha of the surface view with the SplashScreenView |
| surfaceView.setUseAlpha(); |
| surfaceView.setZOrderOnTop(true); |
| surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); |
| |
| view.addView(surfaceView); |
| view.mSurfaceView = surfaceView; |
| Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); |
| return surfaceView; |
| } |
| } |
| |
| /** @hide */ |
| public SplashScreenView(Context context) { |
| super(context); |
| } |
| |
| /** @hide */ |
| public SplashScreenView(Context context, AttributeSet attributeSet) { |
| super(context, attributeSet); |
| } |
| |
| /** |
| * Declared this view is not copyable. |
| * @hide |
| */ |
| public void setNotCopyable() { |
| mNotCopyable = true; |
| } |
| |
| /** |
| * Whether this view is copyable. |
| * @hide |
| */ |
| public boolean isCopyable() { |
| return !mNotCopyable; |
| } |
| |
| /** |
| * Called when this {@link SplashScreenView} has been copied to be transferred to the client. |
| * |
| * @hide |
| */ |
| public void onCopied() { |
| mIsCopied = true; |
| if (mSurfaceView == null) { |
| return; |
| } |
| if (DEBUG) { |
| Log.d(TAG, "Setting SurfaceView's SurfacePackage to null."); |
| } |
| // If we don't release the surface package, the surface will be reparented to this |
| // surface view. So once it's copied into the client process, we release it. |
| mSurfacePackage.release(); |
| mSurfacePackage = null; |
| } |
| |
| /** @hide **/ |
| @Nullable |
| public SurfaceControlViewHost getSurfaceHost() { |
| return mSurfaceHost; |
| } |
| |
| @Override |
| public void setAlpha(float alpha) { |
| super.setAlpha(alpha); |
| |
| // The surface view's alpha is not multiplied with the containing view's alpha, so we |
| // manually do it here |
| if (mSurfaceView != null) { |
| mSurfaceView.setAlpha(mSurfaceView.getAlpha() * alpha); |
| } |
| } |
| |
| /** |
| * Returns the duration of the icon animation if icon is animatable. |
| * |
| * Note the return value can be null or 0 if the |
| * {@link android.R.attr#windowSplashScreenAnimatedIcon} is not |
| * {@link android.graphics.drawable.AnimationDrawable} or |
| * {@link android.graphics.drawable.AnimatedVectorDrawable}. |
| * |
| * @see android.R.attr#windowSplashScreenAnimatedIcon |
| * @see android.R.attr#windowSplashScreenAnimationDuration |
| */ |
| @Nullable |
| public Duration getIconAnimationDuration() { |
| return mIconAnimationDuration; |
| } |
| |
| /** |
| * If the replaced icon is animatable, return the animation start time based on system clock. |
| */ |
| @Nullable |
| public Instant getIconAnimationStart() { |
| return mIconAnimationStart; |
| } |
| |
| |
| /** |
| * @hide |
| */ |
| public void syncTransferSurfaceOnDraw() { |
| if (mSurfacePackage == null) { |
| return; |
| } |
| if (DEBUG) { |
| mSurfacePackage.getSurfaceControl().addOnReparentListener( |
| (transaction, parent) -> Log.e(TAG, |
| String.format("SurfacePackage'surface reparented to %s", parent))); |
| Log.d(TAG, "Transferring surface " + mSurfaceView.toString()); |
| } |
| |
| mSurfaceView.setChildSurfacePackage(mSurfacePackage); |
| } |
| |
| void initIconAnimation(Drawable iconDrawable) { |
| if (!(iconDrawable instanceof IconAnimateListener)) { |
| return; |
| } |
| IconAnimateListener aniDrawable = (IconAnimateListener) iconDrawable; |
| aniDrawable.prepareAnimate(this::animationStartCallback); |
| aniDrawable.setAnimationJankMonitoring(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationCancel(Animator animation) { |
| InteractionJankMonitor.getInstance().cancel(CUJ_SPLASHSCREEN_AVD); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_AVD); |
| } |
| |
| @Override |
| public void onAnimationStart(Animator animation) { |
| InteractionJankMonitor.getInstance().begin( |
| SplashScreenView.this, CUJ_SPLASHSCREEN_AVD); |
| } |
| }); |
| } |
| |
| private void animationStartCallback(long animDuration) { |
| mIconAnimationStart = Instant.now(); |
| if (animDuration >= 0) { |
| mIconAnimationDuration = Duration.ofMillis(animDuration); |
| } |
| } |
| |
| /** |
| * <p>Remove this view and release its resource. </p> |
| * <p><strong>Do not</strong> invoke this method from a drawing method |
| * ({@link #onDraw(android.graphics.Canvas)} for instance).</p> |
| */ |
| @UiThread |
| public void remove() { |
| if (mHasRemoved) { |
| return; |
| } |
| setVisibility(GONE); |
| if (mParceledIconBitmap != null) { |
| if (mIconView instanceof ImageView) { |
| ((ImageView) mIconView).setImageDrawable(null); |
| } else if (mIconView != null) { |
| mIconView.setBackground(null); |
| } |
| mParceledIconBitmap.recycle(); |
| mParceledIconBitmap = null; |
| } |
| if (mParceledBrandingBitmap != null) { |
| mBrandingImageView.setBackground(null); |
| mParceledBrandingBitmap.recycle(); |
| mParceledBrandingBitmap = null; |
| } |
| if (mParceledIconBackgroundBitmap != null) { |
| if (mIconView != null) { |
| mIconView.setBackground(null); |
| } |
| mParceledIconBackgroundBitmap.recycle(); |
| mParceledIconBackgroundBitmap = null; |
| } |
| if (mWindow != null) { |
| final DecorView decorView = (DecorView) mWindow.peekDecorView(); |
| if (DEBUG) { |
| Log.d(TAG, "remove starting view"); |
| } |
| if (decorView != null) { |
| decorView.removeView(this); |
| } |
| mWindow = null; |
| } |
| mHasRemoved = true; |
| } |
| |
| /** @hide **/ |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| releaseAnimationSurfaceHost(); |
| if (mIconView instanceof ImageView imageView |
| && imageView.getDrawable() instanceof Closeable closeableDrawable) { |
| try { |
| closeableDrawable.close(); |
| } catch (IOException ignore) { } |
| } |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| super.onLayout(changed, l, t, r, b); |
| |
| mBrandingImageView.getDrawingRect(mTmpRect); |
| final int brandingHeight = mTmpRect.height(); |
| if (brandingHeight == 0 || mIconView == null) { |
| return; |
| } |
| final int visibility = mBrandingImageView.getVisibility(); |
| if (visibility != VISIBLE) { |
| return; |
| } |
| final int currentHeight = b - t; |
| |
| mIconView.getLocationInWindow(mTmpPos); |
| mIconView.getDrawingRect(mTmpRect); |
| final int iconHeight = mTmpRect.height(); |
| |
| final ViewGroup.MarginLayoutParams params = |
| (ViewGroup.MarginLayoutParams) mBrandingImageView.getLayoutParams(); |
| if (params == null) { |
| Log.e(TAG, "Unable to adjust branding image layout, layout changed?"); |
| return; |
| } |
| final int marginBottom = params.bottomMargin; |
| final int remainingHeight = currentHeight - mTmpPos[1] - iconHeight; |
| final int remainingMaxMargin = remainingHeight - brandingHeight; |
| if (remainingHeight < brandingHeight) { |
| // unable to show the branding image, hide it |
| mBrandingImageView.setVisibility(GONE); |
| } else if (remainingMaxMargin < marginBottom) { |
| // shorter than original margin |
| params.bottomMargin = (int) Math.round(remainingMaxMargin / 2.0); |
| mBrandingImageView.setLayoutParams(params); |
| } |
| // nothing need to adjust |
| } |
| |
| private void releaseAnimationSurfaceHost() { |
| if (mSurfaceHost != null && !mIsCopied) { |
| if (DEBUG) { |
| Log.d(TAG, |
| "Shell removed splash screen." |
| + " Releasing SurfaceControlViewHost on thread #" |
| + Thread.currentThread().getId()); |
| } |
| releaseIconHost(mSurfaceHost); |
| mSurfaceHost = null; |
| } else if (mSurfacePackage != null && mSurfaceHost == null) { |
| mSurfacePackage = null; |
| mClientCallback.sendResult(null); |
| } |
| } |
| |
| /** |
| * Release the host which hold the SurfaceView of the icon. |
| * @hide |
| */ |
| public static void releaseIconHost(SurfaceControlViewHost host) { |
| final Drawable background = host.getView().getBackground(); |
| if (background instanceof SplashScreenView.IconAnimateListener) { |
| ((SplashScreenView.IconAnimateListener) background).stopAnimation(); |
| } |
| host.release(); |
| } |
| |
| /** |
| * Called when this view is attached to a window of an activity. |
| * |
| * @hide |
| */ |
| public void attachHostWindow(Window window) { |
| mWindow = window; |
| } |
| |
| /** |
| * Get the view containing the Splash Screen icon and its background. |
| * @see android.R.attr#windowSplashScreenAnimatedIcon |
| */ |
| public @Nullable View getIconView() { |
| return mIconView; |
| } |
| |
| /** |
| * Get the branding image view. |
| * @hide |
| */ |
| @TestApi |
| public @Nullable View getBrandingView() { |
| return mBrandingImageView; |
| } |
| |
| /** |
| * Get the initial background color of this view. |
| * @hide |
| */ |
| public @ColorInt int getInitBackgroundColor() { |
| return mInitBackgroundColor; |
| } |
| |
| /** |
| * An interface for an animatable drawable object to register a callback when animation start. |
| * @hide |
| */ |
| public interface IconAnimateListener { |
| /** |
| * Prepare the animation if this drawable also be animatable. |
| * @param startListener The callback listener used to receive the start of the animation. |
| */ |
| void prepareAnimate(LongConsumer startListener); |
| |
| /** |
| * Stop animation. |
| */ |
| void stopAnimation(); |
| |
| /** |
| * Provides a chance to start interaction jank monitoring in avd animation. |
| * @param listener a listener to start jank monitoring |
| */ |
| default void setAnimationJankMonitoring(AnimatorListenerAdapter listener) {} |
| } |
| |
| /** |
| * Use to create {@link SplashScreenView} object across process. |
| * @hide |
| */ |
| public static class SplashScreenViewParcelable implements Parcelable { |
| private int mIconSize; |
| private int mBackgroundColor; |
| private Bitmap mIconBackground; |
| |
| private Bitmap mIconBitmap = null; |
| private int mBrandingWidth; |
| private int mBrandingHeight; |
| private Bitmap mBrandingBitmap; |
| |
| private long mIconAnimationStartMillis; |
| private long mIconAnimationDurationMillis; |
| |
| private SurfaceControlViewHost.SurfacePackage mSurfacePackage; |
| private RemoteCallback mClientCallback; |
| |
| public SplashScreenViewParcelable(SplashScreenView view) { |
| final View iconView = view.getIconView(); |
| mIconSize = iconView != null ? iconView.getWidth() : 0; |
| mBackgroundColor = view.getInitBackgroundColor(); |
| mIconBackground = iconView != null ? copyDrawable(iconView.getBackground()) : null; |
| mSurfacePackage = view.mSurfacePackageCopy; |
| if (mSurfacePackage == null) { |
| // We only need to copy the drawable if we are not using a SurfaceView |
| mIconBitmap = iconView != null |
| ? copyDrawable(((ImageView) view.getIconView()).getDrawable()) : null; |
| } |
| mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground()); |
| |
| ViewGroup.LayoutParams params = view.getBrandingView().getLayoutParams(); |
| mBrandingWidth = params.width; |
| mBrandingHeight = params.height; |
| |
| if (view.getIconAnimationStart() != null) { |
| mIconAnimationStartMillis = view.getIconAnimationStart().toEpochMilli(); |
| } |
| if (view.getIconAnimationDuration() != null) { |
| mIconAnimationDurationMillis = view.getIconAnimationDuration().toMillis(); |
| } |
| } |
| |
| private Bitmap copyDrawable(Drawable drawable) { |
| if (drawable != null) { |
| final Rect initialBounds = drawable.copyBounds(); |
| final int width = initialBounds.width(); |
| final int height = initialBounds.height(); |
| if (width <= 0 || height <= 0) { |
| return null; |
| } |
| |
| final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
| final Canvas bmpCanvas = new Canvas(snapshot); |
| drawable.setBounds(0, 0, width, height); |
| drawable.draw(bmpCanvas); |
| final Bitmap copyBitmap = snapshot.createAshmemBitmap(); |
| snapshot.recycle(); |
| return copyBitmap; |
| } |
| return null; |
| } |
| |
| private SplashScreenViewParcelable(@NonNull Parcel source) { |
| readParcel(source); |
| } |
| |
| private void readParcel(@NonNull Parcel source) { |
| mIconSize = source.readInt(); |
| mBackgroundColor = source.readInt(); |
| mIconBitmap = source.readTypedObject(Bitmap.CREATOR); |
| mBrandingWidth = source.readInt(); |
| mBrandingHeight = source.readInt(); |
| mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR); |
| mIconAnimationStartMillis = source.readLong(); |
| mIconAnimationDurationMillis = source.readLong(); |
| mIconBackground = source.readTypedObject(Bitmap.CREATOR); |
| mSurfacePackage = source.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR); |
| mClientCallback = source.readTypedObject(RemoteCallback.CREATOR); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mIconSize); |
| dest.writeInt(mBackgroundColor); |
| dest.writeTypedObject(mIconBitmap, flags); |
| dest.writeInt(mBrandingWidth); |
| dest.writeInt(mBrandingHeight); |
| dest.writeTypedObject(mBrandingBitmap, flags); |
| dest.writeLong(mIconAnimationStartMillis); |
| dest.writeLong(mIconAnimationDurationMillis); |
| dest.writeTypedObject(mIconBackground, flags); |
| dest.writeTypedObject(mSurfacePackage, flags); |
| dest.writeTypedObject(mClientCallback, flags); |
| } |
| |
| public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR = |
| new Parcelable.Creator<SplashScreenViewParcelable>() { |
| public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) { |
| return new SplashScreenViewParcelable(source); |
| } |
| public SplashScreenViewParcelable[] newArray(int size) { |
| return new SplashScreenViewParcelable[size]; |
| } |
| }; |
| |
| /** |
| * Release the bitmap if another process cannot handle it. |
| */ |
| public void clearIfNeeded() { |
| if (mIconBitmap != null) { |
| mIconBitmap.recycle(); |
| mIconBitmap = null; |
| } |
| if (mBrandingBitmap != null) { |
| mBrandingBitmap.recycle(); |
| mBrandingBitmap = null; |
| } |
| } |
| |
| int getIconSize() { |
| return mIconSize; |
| } |
| |
| int getBackgroundColor() { |
| return mBackgroundColor; |
| } |
| |
| /** |
| * Sets the {@link RemoteCallback} that will be called by the client to notify the shell |
| * of the removal of the {@link SplashScreenView}. |
| */ |
| public void setClientCallback(@NonNull RemoteCallback clientCallback) { |
| mClientCallback = clientCallback; |
| } |
| } |
| } |