Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d1ce22f authored by wilsonshih's avatar wilsonshih
Browse files

Pre-draw AdaptiveIconDrawable and cache it before doFrame

The AdaptiveIconDrawable can spend much time which will impact the
time for first window drawn. But usually there is a small time
between setContentView to doFrame, we can use this gap to pre-draw
the AdaptiveIconDrawable and cache it since we already know the size
and it won't change anymore.

Bug: 173975965
Test: atest StartingSurfaceDrawerTests SplashscreenTests
Change-Id: I759de2e1b4025dd150532f3fd5c67d1b404f56a0
parent f505c5d2
Loading
Loading
Loading
Loading
+68 −82
Original line number Original line Diff line number Diff line
@@ -70,6 +70,7 @@ public class SplashscreenContentDrawer {
    private int mIconNormalExitDistance;
    private int mIconNormalExitDistance;
    private int mIconEarlyExitDistance;
    private int mIconEarlyExitDistance;
    private final TransactionPool mTransactionPool;
    private final TransactionPool mTransactionPool;
    private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();


    SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
    SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
            int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
            int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
@@ -109,61 +110,59 @@ public class SplashscreenContentDrawer {
        return new ColorDrawable(getSystemBGColor());
        return new ColorDrawable(getSystemBGColor());
    }
    }


    SplashScreenView makeSplashScreenContentView(Context context, int iconRes,
    @ColorInt int peekWindowBGColor(Context context) {
            int splashscreenContentResId) {
        updateDensity();
        // splash screen content will be deprecated after S.
        final SplashScreenView ssc =
                makeSplashscreenContentDrawable(context, splashscreenContentResId);

        if (ssc != null) {
            return ssc;
        }

        final SplashScreenWindowAttrs attrs =
                SplashScreenWindowAttrs.createWindowAttrs(context);
        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
        final Drawable themeBGDrawable;
        final Drawable themeBGDrawable;

        if (mTmpAttrs.mWindowBgColor != 0) {
        if (attrs.mWindowBgColor != 0) {
            themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
            themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
        } else if (mTmpAttrs.mWindowBgResId != 0) {
        } else if (attrs.mWindowBgResId != 0) {
            themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
            themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
        } else {
        } else {
            Slog.w(TAG, "Window background not exist!");
            Slog.w(TAG, "Window background not exist!");
            themeBGDrawable = createDefaultBackgroundDrawable();
            themeBGDrawable = createDefaultBackgroundDrawable();
        }
        }
        return estimateWindowBGColor(themeBGDrawable);
    }

    private int estimateWindowBGColor(Drawable themeBGDrawable) {
        final DrawableColorTester themeBGTester =
                new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
        if (themeBGTester.nonTransparentRatio() == 0) {
            // the window background is transparent, unable to draw
            Slog.w(TAG, "Window background is transparent, fill background with black color");
            return getSystemBGColor();
        } else {
            return themeBGTester.getDominateColor();
        }
    }

    SplashScreenView makeSplashScreenContentView(Context context, int iconRes) {
        updateDensity();

        getWindowAttrs(context, mTmpAttrs);
        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
        final int animationDuration;
        final int animationDuration;
        final Drawable iconDrawable;
        final Drawable iconDrawable;
        if (attrs.mReplaceIcon != null) {
        if (mTmpAttrs.mReplaceIcon != null) {
            iconDrawable = attrs.mReplaceIcon;
            iconDrawable = mTmpAttrs.mReplaceIcon;
            animationDuration = Math.max(0,
            animationDuration = Math.max(0,
                    Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration));
                    Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
        } else {
        } else {
            iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
            iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
                    : context.getPackageManager().getDefaultActivityIcon();
                    : context.getPackageManager().getDefaultActivityIcon();
            animationDuration = 0;
            animationDuration = 0;
        }
        }
        final int themeBGColor = peekWindowBGColor(context);
        // TODO (b/173975965) Tracking the performance on improved splash screen.
        // TODO (b/173975965) Tracking the performance on improved splash screen.
        return builder
        return builder
                .setContext(context)
                .setContext(context)
                .setThemeDrawable(themeBGDrawable)
                .setWindowBGColor(themeBGColor)
                .setIconDrawable(iconDrawable)
                .setIconDrawable(iconDrawable)
                .setIconAnimationDuration(animationDuration)
                .setIconAnimationDuration(animationDuration)
                .setBrandingDrawable(attrs.mBrandingImage)
                .setBrandingDrawable(mTmpAttrs.mBrandingImage)
                .setIconBackground(attrs.mIconBgColor).build();
                .setIconBackground(mTmpAttrs.mIconBgColor).build();
    }
    }


    static class SplashScreenWindowAttrs {
    private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
        private int mWindowBgResId = 0;
        private int mWindowBgColor = Color.TRANSPARENT;
        private Drawable mReplaceIcon = null;
        private Drawable mBrandingImage = null;
        private int mIconBgColor = Color.TRANSPARENT;
        private int mAnimationDuration = 0;

        static SplashScreenWindowAttrs createWindowAttrs(Context context) {
            final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
        final TypedArray typedArray = context.obtainStyledAttributes(
        final TypedArray typedArray = context.obtainStyledAttributes(
                com.android.internal.R.styleable.Window);
                com.android.internal.R.styleable.Window);
        attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
        attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
@@ -184,12 +183,18 @@ public class SplashscreenContentDrawer {
                    + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
                    + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
                    + " brandImage " + attrs.mBrandingImage);
                    + " brandImage " + attrs.mBrandingImage);
        }
        }
            return attrs;
    }
    }

    static class SplashScreenWindowAttrs {
        private int mWindowBgResId = 0;
        private int mWindowBgColor = Color.TRANSPARENT;
        private Drawable mReplaceIcon = null;
        private Drawable mBrandingImage = null;
        private int mIconBgColor = Color.TRANSPARENT;
        private int mAnimationDuration = 0;
    }
    }


    private class StartingWindowViewBuilder {
    private class StartingWindowViewBuilder {
        private Drawable mThemeBGDrawable;
        private Drawable mIconDrawable;
        private Drawable mIconDrawable;
        private int mIconAnimationDuration;
        private int mIconAnimationDuration;
        private Context mContext;
        private Context mContext;
@@ -203,8 +208,8 @@ public class SplashscreenContentDrawer {
        private Drawable mFinalIconDrawable;
        private Drawable mFinalIconDrawable;
        private float mScale = 1f;
        private float mScale = 1f;


        StartingWindowViewBuilder setThemeDrawable(Drawable background) {
        StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
            mThemeBGDrawable = background;
            mThemeColor = background;
            mBuildComplete = false;
            mBuildComplete = false;
            return this;
            return this;
        }
        }
@@ -247,18 +252,14 @@ public class SplashscreenContentDrawer {
                Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
                Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
                return null;
                return null;
            }
            }
            if (mThemeBGDrawable == null) {

                Slog.w(TAG, "Theme Background Drawable is null, forget to set Theme Drawable?");
                mThemeBGDrawable = createDefaultBackgroundDrawable();
            }
            processThemeColor();
            if (!processAdaptiveIcon() && mIconDrawable != null) {
            if (!processAdaptiveIcon() && mIconDrawable != null) {
                if (DEBUG) {
                if (DEBUG) {
                    Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
                    Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
                }
                }
                mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
                mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
                        mIconBackground != Color.TRANSPARENT
                        mIconBackground != Color.TRANSPARENT
                        ? mIconBackground : mThemeColor, mIconDrawable);
                        ? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
            }
            }
            final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
            final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
            mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
            mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
@@ -266,22 +267,10 @@ public class SplashscreenContentDrawer {
            return mCachedResult;
            return mCachedResult;
        }
        }


        private void createIconDrawable(Drawable iconDrawable) {
        private void createIconDrawable(Drawable iconDrawable, int iconSize) {
            mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
            mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
                    mIconBackground != Color.TRANSPARENT
                    mIconBackground != Color.TRANSPARENT
                    ? mIconBackground : mThemeColor, iconDrawable);
                    ? mIconBackground : mThemeColor, iconDrawable, iconSize);
        }

        private void processThemeColor() {
            final DrawableColorTester themeBGTester =
                    new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */);
            if (themeBGTester.nonTransparentRatio() == 0) {
                // the window background is transparent, unable to draw
                Slog.w(TAG, "Window background is transparent, fill background with black color");
                mThemeColor = getSystemBGColor();
            } else {
                mThemeColor = themeBGTester.getDominateColor();
            }
        }
        }


        private boolean processAdaptiveIcon() {
        private boolean processAdaptiveIcon() {
@@ -325,23 +314,20 @@ public class SplashscreenContentDrawer {
                if (DEBUG) {
                if (DEBUG) {
                    Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
                    Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
                }
                }
                // Using AdaptiveIconDrawable here can help keep the shape consistent with the
                // current settings.
                createIconDrawable(iconForeground);
                // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
                // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
                // should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
                // should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
                if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
                if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
                    mScale = 1.5f;
                    mScale = 1.5f;
                }
                }
                // Using AdaptiveIconDrawable here can help keep the shape consistent with the
                // current settings.
                final int iconSize = (int) (0.5f + mIconSize * mScale);
                createIconDrawable(iconForeground, iconSize);
            } else {
            } else {
                if (DEBUG) {
                if (DEBUG) {
                    Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
                    Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
                }
                }
                if (mIconBackground != Color.TRANSPARENT) {
                createIconDrawable(adaptiveIconDrawable, mIconSize);
                    createIconDrawable(adaptiveIconDrawable);
                } else {
                    mFinalIconDrawable = adaptiveIconDrawable;
                }
            }
            }
            return true;
            return true;
        }
        }
@@ -395,7 +381,7 @@ public class SplashscreenContentDrawer {
        return root < 0.1;
        return root < 0.1;
    }
    }


    private static SplashScreenView makeSplashscreenContentDrawable(Context ctx,
    static SplashScreenView makeSplashscreenContent(Context ctx,
            int splashscreenContentResId) {
            int splashscreenContentResId) {
        // doesn't support windowSplashscreenContent after S
        // doesn't support windowSplashscreenContent after S
        // TODO add an allowlist to skip some packages if needed
        // TODO add an allowlist to skip some packages if needed
+54 −3
Original line number Original line Diff line number Diff line
@@ -16,11 +16,15 @@


package com.android.wm.shell.startingsurface;
package com.android.wm.shell.startingsurface;


import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;

import android.animation.Animator;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.content.res.Resources;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Matrix;
@@ -28,11 +32,13 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.Trace;
import android.util.PathParser;
import android.util.PathParser;
import android.window.SplashScreenView;
import android.window.SplashScreenView;


@@ -47,12 +53,57 @@ import java.util.function.Consumer;
public class SplashscreenIconDrawableFactory {
public class SplashscreenIconDrawableFactory {


    static Drawable makeIconDrawable(@ColorInt int backgroundColor,
    static Drawable makeIconDrawable(@ColorInt int backgroundColor,
            @NonNull Drawable foregroundDrawable) {
            @NonNull Drawable foregroundDrawable, int iconSize) {
        if (foregroundDrawable instanceof Animatable) {
        if (foregroundDrawable instanceof Animatable) {
            return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
            return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
        } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
            return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
        } else {
        } else {
            // TODO make a light weight drawable instead of AdaptiveIconDrawable
            return new ImmobileIconDrawable(new AdaptiveIconDrawable(
            return new AdaptiveIconDrawable(new ColorDrawable(backgroundColor), foregroundDrawable);
                    new ColorDrawable(backgroundColor), foregroundDrawable), iconSize);
        }
    }

    private static class ImmobileIconDrawable extends Drawable {
        private Shader mLayersShader;
        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
                | Paint.FILTER_BITMAP_FLAG);

        ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) {
            cachePaint(drawable, iconSize, iconSize);
        }

        private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
            final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            final Canvas canvas = new Canvas(layersBitmap);
            drawable.setBounds(0, 0, width, height);
            drawable.draw(canvas);
            mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
                    Shader.TileMode.CLAMP);
            mPaint.setShader(mLayersShader);
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }

        @Override
        public void draw(Canvas canvas) {
            final Rect bounds = getBounds();
            canvas.drawRect(bounds, mPaint);
        }

        @Override
        public void setAlpha(int alpha) {

        }

        @Override
        public void setColorFilter(ColorFilter colorFilter) {

        }

        @Override
        public int getOpacity() {
            return 1;
        }
        }
    }
    }


+16 −14
Original line number Original line Diff line number Diff line
@@ -267,21 +267,31 @@ public class StartingSurfaceDrawer {
        final int taskId = taskInfo.taskId;
        final int taskId = taskInfo.taskId;
        SplashScreenView sView = null;
        SplashScreenView sView = null;
        try {
        try {
            sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes,
                            splashscreenContentResId[0]);
            final View view = win.getDecorView();
            final View view = win.getDecorView();
            final WindowManager wm = mContext.getSystemService(WindowManager.class);
            final WindowManager wm = mContext.getSystemService(WindowManager.class);
            if (postAddWindow(taskId, appToken, view, wm, params)) {
            // splash screen content will be deprecated after S.
            sView = SplashscreenContentDrawer.makeSplashscreenContent(
                    context, splashscreenContentResId[0]);
            final boolean splashscreenContentCompatible = sView != null;
            if (splashscreenContentCompatible) {
                win.setContentView(sView);
            } else {
                sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes);
                win.setContentView(sView);
                win.setContentView(sView);
                sView.cacheRootWindow(win);
                sView.cacheRootWindow(win);
            }
            }
            postAddWindow(taskId, appToken, view, wm, params);
        } catch (RuntimeException e) {
        } catch (RuntimeException e) {
            // don't crash if something else bad happens, for example a
            // don't crash if something else bad happens, for example a
            // failure loading resources because we are loading from an app
            // failure loading resources because we are loading from an app
            // on external storage that has been unmounted.
            // on external storage that has been unmounted.
            Slog.w(TAG, " failed creating starting window", e);
            Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e);
            sView = null;
        } finally {
        } finally {
            setSplashScreenRecord(taskId, sView);
            final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
            if (record != null) {
                record.setSplashScreenView(sView);
            }
        }
        }
    }
    }


@@ -328,7 +338,7 @@ public class StartingSurfaceDrawer {
        ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
        ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
    }
    }


    protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
    protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
            WindowManager.LayoutParams params) {
            WindowManager.LayoutParams params) {
        boolean shouldSaveView = true;
        boolean shouldSaveView = true;
        try {
        try {
@@ -349,7 +359,6 @@ public class StartingSurfaceDrawer {
            removeWindowNoAnimate(taskId);
            removeWindowNoAnimate(taskId);
            saveSplashScreenRecord(taskId, view);
            saveSplashScreenRecord(taskId, view);
        }
        }
        return shouldSaveView;
    }
    }


    private void saveSplashScreenRecord(int taskId, View view) {
    private void saveSplashScreenRecord(int taskId, View view) {
@@ -358,13 +367,6 @@ public class StartingSurfaceDrawer {
        mStartingWindowRecords.put(taskId, tView);
        mStartingWindowRecords.put(taskId, tView);
    }
    }


    private void setSplashScreenRecord(int taskId, SplashScreenView splashScreenView) {
        final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
        if (record != null) {
            record.setSplashScreenView(splashScreenView);
        }
    }

    private void removeWindowNoAnimate(int taskId) {
    private void removeWindowNoAnimate(int taskId) {
        removeWindowSynced(taskId, null, null, false);
        removeWindowSynced(taskId, null, null, false);
    }
    }
+1 −2
Original line number Original line Diff line number Diff line
@@ -83,12 +83,11 @@ public class StartingSurfaceDrawerTests {
        }
        }


        @Override
        @Override
        protected boolean postAddWindow(int taskId, IBinder appToken,
        protected void postAddWindow(int taskId, IBinder appToken,
                View view, WindowManager wm, WindowManager.LayoutParams params) {
                View view, WindowManager wm, WindowManager.LayoutParams params) {
            // listen for addView
            // listen for addView
            mAddWindowForTask = taskId;
            mAddWindowForTask = taskId;
            mViewThemeResId = view.getContext().getThemeResId();
            mViewThemeResId = view.getContext().getThemeResId();
            return true;
        }
        }


        @Override
        @Override