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
This commit is contained in:
@@ -70,6 +70,7 @@ public class SplashscreenContentDrawer {
|
||||
private int mIconNormalExitDistance;
|
||||
private int mIconEarlyExitDistance;
|
||||
private final TransactionPool mTransactionPool;
|
||||
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
|
||||
|
||||
SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
|
||||
int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
|
||||
@@ -109,49 +110,79 @@ public class SplashscreenContentDrawer {
|
||||
return new ColorDrawable(getSystemBGColor());
|
||||
}
|
||||
|
||||
SplashScreenView makeSplashScreenContentView(Context context, int iconRes,
|
||||
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();
|
||||
@ColorInt int peekWindowBGColor(Context context) {
|
||||
final Drawable themeBGDrawable;
|
||||
|
||||
if (attrs.mWindowBgColor != 0) {
|
||||
themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
|
||||
} else if (attrs.mWindowBgResId != 0) {
|
||||
themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
|
||||
if (mTmpAttrs.mWindowBgColor != 0) {
|
||||
themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
|
||||
} else if (mTmpAttrs.mWindowBgResId != 0) {
|
||||
themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
|
||||
} else {
|
||||
Slog.w(TAG, "Window background not exist!");
|
||||
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 Drawable iconDrawable;
|
||||
if (attrs.mReplaceIcon != null) {
|
||||
iconDrawable = attrs.mReplaceIcon;
|
||||
if (mTmpAttrs.mReplaceIcon != null) {
|
||||
iconDrawable = mTmpAttrs.mReplaceIcon;
|
||||
animationDuration = Math.max(0,
|
||||
Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration));
|
||||
Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
|
||||
} else {
|
||||
iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
|
||||
: context.getPackageManager().getDefaultActivityIcon();
|
||||
animationDuration = 0;
|
||||
}
|
||||
final int themeBGColor = peekWindowBGColor(context);
|
||||
// TODO (b/173975965) Tracking the performance on improved splash screen.
|
||||
return builder
|
||||
.setContext(context)
|
||||
.setThemeDrawable(themeBGDrawable)
|
||||
.setWindowBGColor(themeBGColor)
|
||||
.setIconDrawable(iconDrawable)
|
||||
.setIconAnimationDuration(animationDuration)
|
||||
.setBrandingDrawable(attrs.mBrandingImage)
|
||||
.setIconBackground(attrs.mIconBgColor).build();
|
||||
.setBrandingDrawable(mTmpAttrs.mBrandingImage)
|
||||
.setIconBackground(mTmpAttrs.mIconBgColor).build();
|
||||
}
|
||||
|
||||
private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(
|
||||
com.android.internal.R.styleable.Window);
|
||||
attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
|
||||
attrs.mWindowBgColor = typedArray.getColor(
|
||||
R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
|
||||
attrs.mReplaceIcon = typedArray.getDrawable(
|
||||
R.styleable.Window_windowSplashScreenAnimatedIcon);
|
||||
attrs.mAnimationDuration = typedArray.getInt(
|
||||
R.styleable.Window_windowSplashScreenAnimationDuration, 0);
|
||||
attrs.mBrandingImage = typedArray.getDrawable(
|
||||
R.styleable.Window_windowSplashScreenBrandingImage);
|
||||
attrs.mIconBgColor = typedArray.getColor(
|
||||
R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
|
||||
typedArray.recycle();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "window attributes color: "
|
||||
+ Integer.toHexString(attrs.mWindowBgColor)
|
||||
+ " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
|
||||
+ " brandImage " + attrs.mBrandingImage);
|
||||
}
|
||||
}
|
||||
|
||||
static class SplashScreenWindowAttrs {
|
||||
@@ -161,35 +192,9 @@ public class SplashscreenContentDrawer {
|
||||
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(
|
||||
com.android.internal.R.styleable.Window);
|
||||
attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
|
||||
attrs.mWindowBgColor = typedArray.getColor(
|
||||
R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
|
||||
attrs.mReplaceIcon = typedArray.getDrawable(
|
||||
R.styleable.Window_windowSplashScreenAnimatedIcon);
|
||||
attrs.mAnimationDuration = typedArray.getInt(
|
||||
R.styleable.Window_windowSplashScreenAnimationDuration, 0);
|
||||
attrs.mBrandingImage = typedArray.getDrawable(
|
||||
R.styleable.Window_windowSplashScreenBrandingImage);
|
||||
attrs.mIconBgColor = typedArray.getColor(
|
||||
R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
|
||||
typedArray.recycle();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "window attributes color: "
|
||||
+ Integer.toHexString(attrs.mWindowBgColor)
|
||||
+ " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
|
||||
+ " brandImage " + attrs.mBrandingImage);
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
}
|
||||
|
||||
private class StartingWindowViewBuilder {
|
||||
private Drawable mThemeBGDrawable;
|
||||
private Drawable mIconDrawable;
|
||||
private int mIconAnimationDuration;
|
||||
private Context mContext;
|
||||
@@ -203,8 +208,8 @@ public class SplashscreenContentDrawer {
|
||||
private Drawable mFinalIconDrawable;
|
||||
private float mScale = 1f;
|
||||
|
||||
StartingWindowViewBuilder setThemeDrawable(Drawable background) {
|
||||
mThemeBGDrawable = background;
|
||||
StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
|
||||
mThemeColor = background;
|
||||
mBuildComplete = false;
|
||||
return this;
|
||||
}
|
||||
@@ -247,18 +252,14 @@ public class SplashscreenContentDrawer {
|
||||
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
|
||||
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 (DEBUG) {
|
||||
Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
|
||||
}
|
||||
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
|
||||
mIconBackground != Color.TRANSPARENT
|
||||
? mIconBackground : mThemeColor, mIconDrawable);
|
||||
? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
|
||||
}
|
||||
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
|
||||
mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
|
||||
@@ -266,22 +267,10 @@ public class SplashscreenContentDrawer {
|
||||
return mCachedResult;
|
||||
}
|
||||
|
||||
private void createIconDrawable(Drawable iconDrawable) {
|
||||
private void createIconDrawable(Drawable iconDrawable, int iconSize) {
|
||||
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
|
||||
mIconBackground != Color.TRANSPARENT
|
||||
? mIconBackground : mThemeColor, iconDrawable);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
? mIconBackground : mThemeColor, iconDrawable, iconSize);
|
||||
}
|
||||
|
||||
private boolean processAdaptiveIcon() {
|
||||
@@ -325,23 +314,20 @@ public class SplashscreenContentDrawer {
|
||||
if (DEBUG) {
|
||||
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
|
||||
// should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
|
||||
if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
|
||||
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 {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
|
||||
}
|
||||
if (mIconBackground != Color.TRANSPARENT) {
|
||||
createIconDrawable(adaptiveIconDrawable);
|
||||
} else {
|
||||
mFinalIconDrawable = adaptiveIconDrawable;
|
||||
}
|
||||
createIconDrawable(adaptiveIconDrawable, mIconSize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -395,7 +381,7 @@ public class SplashscreenContentDrawer {
|
||||
return root < 0.1;
|
||||
}
|
||||
|
||||
private static SplashScreenView makeSplashscreenContentDrawable(Context ctx,
|
||||
static SplashScreenView makeSplashscreenContent(Context ctx,
|
||||
int splashscreenContentResId) {
|
||||
// doesn't support windowSplashscreenContent after S
|
||||
// TODO add an allowlist to skip some packages if needed
|
||||
|
||||
@@ -16,11 +16,15 @@
|
||||
|
||||
package com.android.wm.shell.startingsurface;
|
||||
|
||||
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
@@ -28,11 +32,13 @@ import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.util.PathParser;
|
||||
import android.window.SplashScreenView;
|
||||
|
||||
@@ -47,12 +53,57 @@ import java.util.function.Consumer;
|
||||
public class SplashscreenIconDrawableFactory {
|
||||
|
||||
static Drawable makeIconDrawable(@ColorInt int backgroundColor,
|
||||
@NonNull Drawable foregroundDrawable) {
|
||||
@NonNull Drawable foregroundDrawable, int iconSize) {
|
||||
if (foregroundDrawable instanceof Animatable) {
|
||||
return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
|
||||
} else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
|
||||
return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
|
||||
} else {
|
||||
// TODO make a light weight drawable instead of AdaptiveIconDrawable
|
||||
return new AdaptiveIconDrawable(new ColorDrawable(backgroundColor), foregroundDrawable);
|
||||
return new ImmobileIconDrawable(new AdaptiveIconDrawable(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,21 +267,31 @@ public class StartingSurfaceDrawer {
|
||||
final int taskId = taskInfo.taskId;
|
||||
SplashScreenView sView = null;
|
||||
try {
|
||||
sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes,
|
||||
splashscreenContentResId[0]);
|
||||
final View view = win.getDecorView();
|
||||
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);
|
||||
sView.cacheRootWindow(win);
|
||||
}
|
||||
postAddWindow(taskId, appToken, view, wm, params);
|
||||
} catch (RuntimeException e) {
|
||||
// don't crash if something else bad happens, for example a
|
||||
// failure loading resources because we are loading from an app
|
||||
// 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 {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
boolean shouldSaveView = true;
|
||||
try {
|
||||
@@ -349,7 +359,6 @@ public class StartingSurfaceDrawer {
|
||||
removeWindowNoAnimate(taskId);
|
||||
saveSplashScreenRecord(taskId, view);
|
||||
}
|
||||
return shouldSaveView;
|
||||
}
|
||||
|
||||
private void saveSplashScreenRecord(int taskId, View view) {
|
||||
@@ -358,13 +367,6 @@ public class StartingSurfaceDrawer {
|
||||
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) {
|
||||
removeWindowSynced(taskId, null, null, false);
|
||||
}
|
||||
|
||||
@@ -83,12 +83,11 @@ public class StartingSurfaceDrawerTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean postAddWindow(int taskId, IBinder appToken,
|
||||
protected void postAddWindow(int taskId, IBinder appToken,
|
||||
View view, WindowManager wm, WindowManager.LayoutParams params) {
|
||||
// listen for addView
|
||||
mAddWindowForTask = taskId;
|
||||
mViewThemeResId = view.getContext().getThemeResId();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user