/* * 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; /** *
The view which allows an activity to customize its splash screen exit animation.
* *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.
* *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}.
* * @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 ConsumerRemove this view and release its resource.
*Do not invoke this method from a drawing method * ({@link #onDraw(android.graphics.Canvas)} for instance).
*/ @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