/* * 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 android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; 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.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; 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.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.WindowInsetsController; 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 com.android.internal.util.ContrastColorUtil; import java.time.Duration; import java.time.Instant; import java.util.function.Consumer; /** *
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 static final int LIGHT_BARS_MASK = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; private static final int WINDOW_FLAG_MASK = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS | FLAG_TRANSLUCENT_NAVIGATION | FLAG_TRANSLUCENT_STATUS; 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; // The host activity when transfer view to it. private Activity mHostActivity; @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 int mAppWindowFlags; private int mStatusBarColor; private int mNavigationBarColor; private int mSystemBarsAppearance; private boolean mHasRemoved; private boolean mNavigationContrastEnforced; private boolean mStatusContrastEnforced; private boolean mDecorFitsSystemWindows; /** * 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); } restoreSystemUIColors(); mWindow = null; } mHasRemoved = true; } /** @hide **/ @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); releaseAnimationSurfaceHost(); } 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 an activity. This also makes SystemUI colors * transparent so the content of splash screen view can draw fully. * * @hide */ public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) { mHostActivity = activity; mWindow = window; final WindowManager.LayoutParams attr = window.getAttributes(); mAppWindowFlags = attr.flags; mStatusBarColor = window.getStatusBarColor(); mNavigationBarColor = window.getNavigationBarColor(); mSystemBarsAppearance = window.getInsetsController().getSystemBarsAppearance(); mNavigationContrastEnforced = window.isNavigationBarContrastEnforced(); mStatusContrastEnforced = window.isStatusBarContrastEnforced(); mDecorFitsSystemWindows = window.decorFitsSystemWindows(); applySystemBarsContrastColor(window.getInsetsController(), mInitBackgroundColor); // Let app draw the background of bars. window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); // Use specified bar colors instead of window background. window.clearFlags(FLAG_TRANSLUCENT_STATUS | FLAG_TRANSLUCENT_NAVIGATION); window.setStatusBarColor(Color.TRANSPARENT); window.setNavigationBarColor(Color.TRANSPARENT); window.setDecorFitsSystemWindows(false); window.setStatusBarContrastEnforced(false); window.setNavigationBarContrastEnforced(false); } /** Called when this view is removed from the host activity. */ private void restoreSystemUIColors() { mWindow.setFlags(mAppWindowFlags, WINDOW_FLAG_MASK); mWindow.setStatusBarColor(mStatusBarColor); mWindow.setNavigationBarColor(mNavigationBarColor); mWindow.getInsetsController().setSystemBarsAppearance(mSystemBarsAppearance, LIGHT_BARS_MASK); mWindow.setDecorFitsSystemWindows(mDecorFitsSystemWindows); mWindow.setStatusBarContrastEnforced(mStatusContrastEnforced); mWindow.setNavigationBarContrastEnforced(mNavigationContrastEnforced); } /** * Makes the icon color of system bars contrast. * @hide */ public static void applySystemBarsContrastColor(WindowInsetsController windowInsetsController, int backgroundColor) { final int lightBarAppearance = ContrastColorUtil.isColorLight(backgroundColor) ? LIGHT_BARS_MASK : 0; windowInsetsController.setSystemBarsAppearance(lightBarAppearance, LIGHT_BARS_MASK); } /** * 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 duration The animation duration. * @param startListener The callback listener used to receive the start of the animation. * @return true if this drawable object can also be animated and it can be played now. */ boolean prepareAnimate(long duration, Runnable 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) { mIconSize = view.mIconView.getWidth(); mBackgroundColor = view.getInitBackgroundColor(); mIconBackground = copyDrawable(view.getIconView().getBackground()); mSurfacePackage = view.mSurfacePackageCopy; if (mSurfacePackage == null) { // We only need to copy the drawable if we are not using a SurfaceView mIconBitmap = copyDrawable(((ImageView) view.getIconView()).getDrawable()); } 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(); 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