Merge "Cache window and icon color of splash screen starting window" into sc-dev
This commit is contained in:
@@ -22,7 +22,10 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
|||||||
import android.annotation.ColorInt;
|
import android.annotation.ColorInt;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
@@ -34,15 +37,19 @@ import android.graphics.drawable.AdaptiveIconDrawable;
|
|||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Trace;
|
import android.os.Trace;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.util.ArrayMap;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.window.SplashScreenView;
|
import android.window.SplashScreenView;
|
||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.graphics.palette.Palette;
|
import com.android.internal.graphics.palette.Palette;
|
||||||
import com.android.internal.graphics.palette.Quantizer;
|
import com.android.internal.graphics.palette.Quantizer;
|
||||||
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
|
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
|
||||||
@@ -51,6 +58,8 @@ import com.android.wm.shell.common.TransactionPool;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Util class to create the view for a splash screen content.
|
* Util class to create the view for a splash screen content.
|
||||||
@@ -76,9 +85,12 @@ public class SplashscreenContentDrawer {
|
|||||||
private int mBrandingImageWidth;
|
private int mBrandingImageWidth;
|
||||||
private int mBrandingImageHeight;
|
private int mBrandingImageHeight;
|
||||||
private int mMainWindowShiftLength;
|
private int mMainWindowShiftLength;
|
||||||
|
private int mLastPackageContextConfigHash;
|
||||||
private final TransactionPool mTransactionPool;
|
private final TransactionPool mTransactionPool;
|
||||||
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
|
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
|
||||||
private final Handler mSplashscreenWorkerHandler;
|
private final Handler mSplashscreenWorkerHandler;
|
||||||
|
@VisibleForTesting
|
||||||
|
final ColorCache mColorCache;
|
||||||
|
|
||||||
SplashscreenContentDrawer(Context context, TransactionPool pool) {
|
SplashscreenContentDrawer(Context context, TransactionPool pool) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -92,6 +104,7 @@ public class SplashscreenContentDrawer {
|
|||||||
new HandlerThread("wmshell.splashworker", THREAD_PRIORITY_TOP_APP_BOOST);
|
new HandlerThread("wmshell.splashworker", THREAD_PRIORITY_TOP_APP_BOOST);
|
||||||
shellSplashscreenWorkerThread.start();
|
shellSplashscreenWorkerThread.start();
|
||||||
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
|
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
|
||||||
|
mColorCache = new ColorCache(mContext, mSplashscreenWorkerHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,14 +196,14 @@ public class SplashscreenContentDrawer {
|
|||||||
updateDensity();
|
updateDensity();
|
||||||
|
|
||||||
getWindowAttrs(context, mTmpAttrs);
|
getWindowAttrs(context, mTmpAttrs);
|
||||||
final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
|
mLastPackageContextConfigHash = context.getResources().getConfiguration().hashCode();
|
||||||
final int themeBGColor = peekWindowBGColor(context, this.mTmpAttrs);
|
final int themeBGColor = mColorCache.getWindowColor(ai.packageName,
|
||||||
|
mLastPackageContextConfigHash, mTmpAttrs.mWindowBgColor, mTmpAttrs.mWindowBgResId,
|
||||||
|
() -> peekWindowBGColor(context, mTmpAttrs)).mBgColor;
|
||||||
// TODO (b/173975965) Tracking the performance on improved splash screen.
|
// TODO (b/173975965) Tracking the performance on improved splash screen.
|
||||||
return builder
|
return new StartingWindowViewBuilder(context, ai)
|
||||||
.setContext(context)
|
|
||||||
.setWindowBGColor(themeBGColor)
|
.setWindowBGColor(themeBGColor)
|
||||||
.makeEmptyView(emptyView)
|
.makeEmptyView(emptyView)
|
||||||
.setActivityInfo(ai)
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,50 +245,30 @@ public class SplashscreenContentDrawer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class StartingWindowViewBuilder {
|
private class StartingWindowViewBuilder {
|
||||||
private ActivityInfo mActivityInfo;
|
private final Context mContext;
|
||||||
private Context mContext;
|
private final ActivityInfo mActivityInfo;
|
||||||
private boolean mEmptyView;
|
|
||||||
|
|
||||||
// result
|
private boolean mEmptyView;
|
||||||
private boolean mBuildComplete = false;
|
|
||||||
private SplashScreenView mCachedResult;
|
|
||||||
private int mThemeColor;
|
private int mThemeColor;
|
||||||
private Drawable mFinalIconDrawable;
|
private Drawable mFinalIconDrawable;
|
||||||
private int mFinalIconSize = mIconSize;
|
private int mFinalIconSize = mIconSize;
|
||||||
|
|
||||||
|
StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
|
||||||
|
mContext = context;
|
||||||
|
mActivityInfo = aInfo;
|
||||||
|
}
|
||||||
|
|
||||||
StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
|
StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
|
||||||
mThemeColor = background;
|
mThemeColor = background;
|
||||||
mBuildComplete = false;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartingWindowViewBuilder makeEmptyView(boolean empty) {
|
StartingWindowViewBuilder makeEmptyView(boolean empty) {
|
||||||
mEmptyView = empty;
|
mEmptyView = empty;
|
||||||
mBuildComplete = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
StartingWindowViewBuilder setActivityInfo(ActivityInfo ai) {
|
|
||||||
mActivityInfo = ai;
|
|
||||||
mBuildComplete = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
StartingWindowViewBuilder setContext(Context context) {
|
|
||||||
mContext = context;
|
|
||||||
mBuildComplete = false;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
SplashScreenView build() {
|
SplashScreenView build() {
|
||||||
if (mBuildComplete) {
|
|
||||||
return mCachedResult;
|
|
||||||
}
|
|
||||||
if (mContext == null || mActivityInfo == null) {
|
|
||||||
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawable iconDrawable;
|
Drawable iconDrawable;
|
||||||
final int animationDuration;
|
final int animationDuration;
|
||||||
if (mEmptyView) {
|
if (mEmptyView) {
|
||||||
@@ -292,7 +285,9 @@ public class SplashscreenContentDrawer {
|
|||||||
final int densityDpi = mContext.getResources().getDisplayMetrics().densityDpi;
|
final int densityDpi = mContext.getResources().getDisplayMetrics().densityDpi;
|
||||||
final int scaledIconDpi =
|
final int scaledIconDpi =
|
||||||
(int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
|
(int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
|
||||||
|
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
|
||||||
iconDrawable = mIconProvider.getIcon(mActivityInfo, scaledIconDpi);
|
iconDrawable = mIconProvider.getIcon(mActivityInfo, scaledIconDpi);
|
||||||
|
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||||
if (iconDrawable == null) {
|
if (iconDrawable == null) {
|
||||||
iconDrawable = mContext.getPackageManager().getDefaultActivityIcon();
|
iconDrawable = mContext.getPackageManager().getDefaultActivityIcon();
|
||||||
}
|
}
|
||||||
@@ -306,9 +301,7 @@ public class SplashscreenContentDrawer {
|
|||||||
animationDuration = 0;
|
animationDuration = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
mCachedResult = fillViewWithIcon(mFinalIconSize, mFinalIconDrawable, animationDuration);
|
return fillViewWithIcon(mFinalIconSize, mFinalIconDrawable, animationDuration);
|
||||||
mBuildComplete = true;
|
|
||||||
return mCachedResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createIconDrawable(Drawable iconDrawable, boolean legacy) {
|
private void createIconDrawable(Drawable iconDrawable, boolean legacy) {
|
||||||
@@ -331,28 +324,20 @@ public class SplashscreenContentDrawer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
|
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
|
||||||
final AdaptiveIconDrawable adaptiveIconDrawable =
|
final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) iconDrawable;
|
||||||
(AdaptiveIconDrawable) iconDrawable;
|
|
||||||
final DrawableColorTester backIconTester =
|
|
||||||
new DrawableColorTester(adaptiveIconDrawable.getBackground());
|
|
||||||
|
|
||||||
final Drawable iconForeground = adaptiveIconDrawable.getForeground();
|
final Drawable iconForeground = adaptiveIconDrawable.getForeground();
|
||||||
final DrawableColorTester foreIconTester =
|
final ColorCache.IconColor iconColor = mColorCache.getIconColor(
|
||||||
new DrawableColorTester(iconForeground, true /* filterTransparent */);
|
mActivityInfo.packageName, mActivityInfo.getIconResource(),
|
||||||
|
mLastPackageContextConfigHash,
|
||||||
final boolean foreComplex = foreIconTester.isComplexColor();
|
() -> new DrawableColorTester(iconForeground, true /* filterTransparent */),
|
||||||
final int foreMainColor = foreIconTester.getDominateColor();
|
() -> new DrawableColorTester(adaptiveIconDrawable.getBackground()));
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "foreground complex color? " + foreComplex + " main color: "
|
Slog.d(TAG, "FgMainColor=" + Integer.toHexString(iconColor.mFgColor)
|
||||||
+ Integer.toHexString(foreMainColor));
|
+ " BgMainColor=" + Integer.toHexString(iconColor.mBgColor)
|
||||||
}
|
+ " IsBgComplex=" + iconColor.mIsBgComplex
|
||||||
final boolean backComplex = backIconTester.isComplexColor();
|
+ " FromCache=" + (iconColor.mReuseCount > 0)
|
||||||
final int backMainColor = backIconTester.getDominateColor();
|
+ " ThemeColor=" + Integer.toHexString(mThemeColor));
|
||||||
if (DEBUG) {
|
|
||||||
Slog.d(TAG, "background complex color? " + backComplex + " main color: "
|
|
||||||
+ Integer.toHexString(backMainColor));
|
|
||||||
Slog.d(TAG, "theme color? " + Integer.toHexString(mThemeColor));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only draw the foreground of AdaptiveIcon to the splash screen if below condition
|
// Only draw the foreground of AdaptiveIcon to the splash screen if below condition
|
||||||
@@ -363,17 +348,17 @@ public class SplashscreenContentDrawer {
|
|||||||
// C. The background of the adaptive icon is grayscale, and the foreground of the
|
// C. The background of the adaptive icon is grayscale, and the foreground of the
|
||||||
// adaptive icon forms a certain contrast with the theme color.
|
// adaptive icon forms a certain contrast with the theme color.
|
||||||
// D. Didn't specify icon background color.
|
// D. Didn't specify icon background color.
|
||||||
if (!backComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
|
if (!iconColor.mIsBgComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
|
||||||
&& (isRgbSimilarInHsv(mThemeColor, backMainColor)
|
&& (isRgbSimilarInHsv(mThemeColor, iconColor.mBgColor)
|
||||||
|| (backIconTester.isGrayscale()
|
|| (iconColor.mIsBgGrayscale
|
||||||
&& !isRgbSimilarInHsv(mThemeColor, foreMainColor)))) {
|
&& !isRgbSimilarInHsv(mThemeColor, iconColor.mFgColor)))) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
|
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
|
||||||
}
|
}
|
||||||
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
|
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
|
||||||
// scale by 192/160 if we only draw adaptiveIcon's foreground.
|
// scale by 192/160 if we only draw adaptiveIcon's foreground.
|
||||||
final float noBgScale =
|
final float noBgScale =
|
||||||
foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD
|
iconColor.mFgNonTransparentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
|
||||||
? NO_BACKGROUND_SCALE : 1f;
|
? NO_BACKGROUND_SCALE : 1f;
|
||||||
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
|
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
|
||||||
// current settings.
|
// current settings.
|
||||||
@@ -689,6 +674,150 @@ public class SplashscreenContentDrawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Cache the result of {@link DrawableColorTester} to reduce expensive calculation. */
|
||||||
|
@VisibleForTesting
|
||||||
|
static class ColorCache extends BroadcastReceiver {
|
||||||
|
/**
|
||||||
|
* The color may be different according to resource id and configuration (e.g. night mode),
|
||||||
|
* so this allows to cache more than one color per package.
|
||||||
|
*/
|
||||||
|
private static final int CACHE_SIZE = 2;
|
||||||
|
|
||||||
|
/** The computed colors of packages. */
|
||||||
|
private final ArrayMap<String, Colors> mColorMap = new ArrayMap<>();
|
||||||
|
|
||||||
|
private static class Colors {
|
||||||
|
final WindowColor[] mWindowColors = new WindowColor[CACHE_SIZE];
|
||||||
|
final IconColor[] mIconColors = new IconColor[CACHE_SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Cache {
|
||||||
|
/** The hash used to check whether this cache is hit. */
|
||||||
|
final int mHash;
|
||||||
|
|
||||||
|
/** The number of times this cache has been reused. */
|
||||||
|
int mReuseCount;
|
||||||
|
|
||||||
|
Cache(int hash) {
|
||||||
|
mHash = hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class WindowColor extends Cache {
|
||||||
|
final int mBgColor;
|
||||||
|
|
||||||
|
WindowColor(int hash, int bgColor) {
|
||||||
|
super(hash);
|
||||||
|
mBgColor = bgColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class IconColor extends Cache {
|
||||||
|
final int mFgColor;
|
||||||
|
final int mBgColor;
|
||||||
|
final boolean mIsBgComplex;
|
||||||
|
final boolean mIsBgGrayscale;
|
||||||
|
final float mFgNonTransparentRatio;
|
||||||
|
|
||||||
|
IconColor(int hash, int fgColor, int bgColor, boolean isBgComplex,
|
||||||
|
boolean isBgGrayscale, float fgNonTransparentRatio) {
|
||||||
|
super(hash);
|
||||||
|
mFgColor = fgColor;
|
||||||
|
mBgColor = bgColor;
|
||||||
|
mIsBgComplex = isBgComplex;
|
||||||
|
mIsBgGrayscale = isBgGrayscale;
|
||||||
|
mFgNonTransparentRatio = fgNonTransparentRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorCache(Context context, Handler handler) {
|
||||||
|
// This includes reinstall and uninstall.
|
||||||
|
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
|
filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
|
||||||
|
context.registerReceiverAsUser(this, UserHandle.ALL, filter,
|
||||||
|
null /* broadcastPermission */, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
final Uri packageUri = intent.getData();
|
||||||
|
if (packageUri != null) {
|
||||||
|
mColorMap.remove(packageUri.getEncodedSchemeSpecificPart());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the existing cache if the hash matches. If null is returned, the caller can use
|
||||||
|
* outLeastUsedIndex to put the new cache.
|
||||||
|
*/
|
||||||
|
private static <T extends Cache> T getCache(T[] caches, int hash, int[] outLeastUsedIndex) {
|
||||||
|
int minReuseCount = Integer.MAX_VALUE;
|
||||||
|
for (int i = 0; i < CACHE_SIZE; i++) {
|
||||||
|
final T cache = caches[i];
|
||||||
|
if (cache == null) {
|
||||||
|
// Empty slot has the highest priority to put new cache.
|
||||||
|
minReuseCount = -1;
|
||||||
|
outLeastUsedIndex[0] = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cache.mHash == hash) {
|
||||||
|
cache.mReuseCount++;
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
if (cache.mReuseCount < minReuseCount) {
|
||||||
|
minReuseCount = cache.mReuseCount;
|
||||||
|
outLeastUsedIndex[0] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull WindowColor getWindowColor(String packageName, int configHash, int windowBgColor,
|
||||||
|
int windowBgResId, IntSupplier windowBgColorSupplier) {
|
||||||
|
Colors colors = mColorMap.get(packageName);
|
||||||
|
int hash = 31 * configHash + windowBgColor;
|
||||||
|
hash = 31 * hash + windowBgResId;
|
||||||
|
final int[] leastUsedIndex = { 0 };
|
||||||
|
if (colors != null) {
|
||||||
|
final WindowColor windowColor = getCache(colors.mWindowColors, hash,
|
||||||
|
leastUsedIndex);
|
||||||
|
if (windowColor != null) {
|
||||||
|
return windowColor;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
colors = new Colors();
|
||||||
|
mColorMap.put(packageName, colors);
|
||||||
|
}
|
||||||
|
final WindowColor windowColor = new WindowColor(hash, windowBgColorSupplier.getAsInt());
|
||||||
|
colors.mWindowColors[leastUsedIndex[0]] = windowColor;
|
||||||
|
return windowColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull IconColor getIconColor(String packageName, int configHash, int iconResId,
|
||||||
|
Supplier<DrawableColorTester> fgColorTesterSupplier,
|
||||||
|
Supplier<DrawableColorTester> bgColorTesterSupplier) {
|
||||||
|
Colors colors = mColorMap.get(packageName);
|
||||||
|
final int hash = configHash * 31 + iconResId;
|
||||||
|
final int[] leastUsedIndex = { 0 };
|
||||||
|
if (colors != null) {
|
||||||
|
final IconColor iconColor = getCache(colors.mIconColors, hash, leastUsedIndex);
|
||||||
|
if (iconColor != null) {
|
||||||
|
return iconColor;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
colors = new Colors();
|
||||||
|
mColorMap.put(packageName, colors);
|
||||||
|
}
|
||||||
|
final DrawableColorTester fgTester = fgColorTesterSupplier.get();
|
||||||
|
final DrawableColorTester bgTester = bgColorTesterSupplier.get();
|
||||||
|
final IconColor iconColor = new IconColor(hash, fgTester.getDominateColor(),
|
||||||
|
bgTester.getDominateColor(), bgTester.isComplexColor(), bgTester.isGrayscale(),
|
||||||
|
fgTester.nonTransparentRatio());
|
||||||
|
colors.mIconColors[leastUsedIndex[0]] = iconColor;
|
||||||
|
return iconColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and play the default exit animation for splash screen view.
|
* Create and play the default exit animation for splash screen view.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.wm.shell.startingsurface;
|
package com.android.wm.shell.startingsurface;
|
||||||
|
|
||||||
import static android.content.Context.CONTEXT_RESTRICTED;
|
import static android.content.Context.CONTEXT_RESTRICTED;
|
||||||
|
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
||||||
import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
|
import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
|
||||||
import static android.view.Display.DEFAULT_DISPLAY;
|
import static android.view.Display.DEFAULT_DISPLAY;
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ import android.graphics.Rect;
|
|||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.SystemProperties;
|
import android.os.SystemProperties;
|
||||||
|
import android.os.Trace;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
@@ -49,8 +51,10 @@ import android.window.StartingWindowInfo;
|
|||||||
import android.window.TaskSnapshot;
|
import android.window.TaskSnapshot;
|
||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.wm.shell.common.ShellExecutor;
|
import com.android.wm.shell.common.ShellExecutor;
|
||||||
import com.android.wm.shell.common.TransactionPool;
|
import com.android.wm.shell.common.TransactionPool;
|
||||||
|
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@@ -90,6 +94,7 @@ import java.util.function.Supplier;
|
|||||||
* => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
|
* => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
|
||||||
* directly).
|
* directly).
|
||||||
*/
|
*/
|
||||||
|
@ShellSplashscreenThread
|
||||||
public class StartingSurfaceDrawer {
|
public class StartingSurfaceDrawer {
|
||||||
static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
|
static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
|
||||||
static final boolean DEBUG_SPLASH_SCREEN = StartingWindowController.DEBUG_SPLASH_SCREEN;
|
static final boolean DEBUG_SPLASH_SCREEN = StartingWindowController.DEBUG_SPLASH_SCREEN;
|
||||||
@@ -98,7 +103,8 @@ public class StartingSurfaceDrawer {
|
|||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final DisplayManager mDisplayManager;
|
private final DisplayManager mDisplayManager;
|
||||||
private final ShellExecutor mSplashScreenExecutor;
|
private final ShellExecutor mSplashScreenExecutor;
|
||||||
private final SplashscreenContentDrawer mSplashscreenContentDrawer;
|
@VisibleForTesting
|
||||||
|
final SplashscreenContentDrawer mSplashscreenContentDrawer;
|
||||||
private Choreographer mChoreographer;
|
private Choreographer mChoreographer;
|
||||||
|
|
||||||
private static final boolean DEBUG_ENABLE_REVEAL_ANIMATION =
|
private static final boolean DEBUG_ENABLE_REVEAL_ANIMATION =
|
||||||
@@ -276,6 +282,7 @@ public class StartingSurfaceDrawer {
|
|||||||
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
|
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
|
||||||
final FrameLayout rootLayout = new FrameLayout(context);
|
final FrameLayout rootLayout = new FrameLayout(context);
|
||||||
final Runnable setViewSynchronized = () -> {
|
final Runnable setViewSynchronized = () -> {
|
||||||
|
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
|
||||||
// waiting for setContentView before relayoutWindow
|
// waiting for setContentView before relayoutWindow
|
||||||
SplashScreenView contentView = viewSupplier.get();
|
SplashScreenView contentView = viewSupplier.get();
|
||||||
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
|
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
|
||||||
@@ -294,13 +301,14 @@ public class StartingSurfaceDrawer {
|
|||||||
}
|
}
|
||||||
record.setSplashScreenView(contentView);
|
record.setSplashScreenView(contentView);
|
||||||
}
|
}
|
||||||
|
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||||
};
|
};
|
||||||
mSplashscreenContentDrawer.createContentView(context, emptyView, activityInfo, taskId,
|
mSplashscreenContentDrawer.createContentView(context, emptyView, activityInfo, taskId,
|
||||||
viewSupplier::setView);
|
viewSupplier::setView);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final WindowManager wm = context.getSystemService(WindowManager.class);
|
final WindowManager wm = context.getSystemService(WindowManager.class);
|
||||||
if (postAddWindow(taskId, appToken, rootLayout, wm, params)) {
|
if (addWindow(taskId, appToken, rootLayout, wm, params)) {
|
||||||
// We use the splash screen worker thread to create SplashScreenView while adding
|
// We use the splash screen worker thread to create SplashScreenView while adding
|
||||||
// the window, as otherwise Choreographer#doFrame might be delayed on this thread.
|
// the window, as otherwise Choreographer#doFrame might be delayed on this thread.
|
||||||
// And since Choreographer#doFrame won't happen immediately after adding the window,
|
// And since Choreographer#doFrame won't happen immediately after adding the window,
|
||||||
@@ -393,10 +401,11 @@ 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 boolean addWindow(int taskId, IBinder appToken, View view, WindowManager wm,
|
||||||
WindowManager.LayoutParams params) {
|
WindowManager.LayoutParams params) {
|
||||||
boolean shouldSaveView = true;
|
boolean shouldSaveView = true;
|
||||||
try {
|
try {
|
||||||
|
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
|
||||||
wm.addView(view, params);
|
wm.addView(view, params);
|
||||||
} catch (WindowManager.BadTokenException e) {
|
} catch (WindowManager.BadTokenException e) {
|
||||||
// ignore
|
// ignore
|
||||||
@@ -404,6 +413,7 @@ public class StartingSurfaceDrawer {
|
|||||||
+ e.getMessage());
|
+ e.getMessage());
|
||||||
shouldSaveView = false;
|
shouldSaveView = false;
|
||||||
} finally {
|
} finally {
|
||||||
|
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||||
if (view != null && view.getParent() == null) {
|
if (view != null && view.getParent() == null) {
|
||||||
Slog.w(TAG, "view not successfully added to wm, removing view");
|
Slog.w(TAG, "view not successfully added to wm, removing view");
|
||||||
wm.removeViewImmediate(view);
|
wm.removeViewImmediate(view);
|
||||||
|
|||||||
@@ -18,25 +18,27 @@ package com.android.wm.shell.startingsurface;
|
|||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
|
||||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.testing.TestableContext;
|
import android.testing.TestableContext;
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -58,6 +60,8 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the starting surface drawer.
|
* Tests for the starting surface drawer.
|
||||||
*/
|
*/
|
||||||
@@ -71,6 +75,9 @@ public class StartingSurfaceDrawerTests {
|
|||||||
@Mock
|
@Mock
|
||||||
private TransactionPool mTransactionPool;
|
private TransactionPool mTransactionPool;
|
||||||
|
|
||||||
|
private final Handler mTestHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private final TestableContext mTestContext = new TestContext(
|
||||||
|
InstrumentationRegistry.getInstrumentation().getTargetContext());
|
||||||
TestStartingSurfaceDrawer mStartingSurfaceDrawer;
|
TestStartingSurfaceDrawer mStartingSurfaceDrawer;
|
||||||
|
|
||||||
static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
|
static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
|
||||||
@@ -83,7 +90,7 @@ public class StartingSurfaceDrawerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean postAddWindow(int taskId, IBinder appToken,
|
protected boolean addWindow(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;
|
||||||
@@ -101,45 +108,50 @@ public class StartingSurfaceDrawerTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TestContext extends TestableContext {
|
||||||
|
TestContext(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
|
||||||
|
IntentFilter filter, String broadcastPermission, Handler scheduler) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
final TestableContext context = new TestableContext(
|
final WindowManager realWindowManager = mTestContext.getSystemService(WindowManager.class);
|
||||||
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
|
|
||||||
final WindowManager realWindowManager = context.getSystemService(WindowManager.class);
|
|
||||||
final WindowMetrics metrics = realWindowManager.getMaximumWindowMetrics();
|
final WindowMetrics metrics = realWindowManager.getMaximumWindowMetrics();
|
||||||
context.addMockSystemService(WindowManager.class, mMockWindowManager);
|
mTestContext.addMockSystemService(WindowManager.class, mMockWindowManager);
|
||||||
|
|
||||||
spyOn(context);
|
|
||||||
spyOn(realWindowManager);
|
|
||||||
try {
|
|
||||||
doReturn(context).when(context)
|
|
||||||
.createPackageContextAsUser(anyString(), anyInt(), any());
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
|
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
|
||||||
doNothing().when(mMockWindowManager).addView(any(), any());
|
doNothing().when(mMockWindowManager).addView(any(), any());
|
||||||
|
|
||||||
final HandlerExecutor testExecutor =
|
mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext,
|
||||||
new HandlerExecutor(new Handler(Looper.getMainLooper()));
|
new HandlerExecutor(mTestHandler), mTransactionPool));
|
||||||
mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context, testExecutor,
|
|
||||||
mTransactionPool));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddSplashScreenSurface() {
|
public void testAddSplashScreenSurface() {
|
||||||
final int taskId = 1;
|
final int taskId = 1;
|
||||||
final Handler mainLoop = new Handler(Looper.getMainLooper());
|
|
||||||
final StartingWindowInfo windowInfo =
|
final StartingWindowInfo windowInfo =
|
||||||
createWindowInfo(taskId, android.R.style.Theme);
|
createWindowInfo(taskId, android.R.style.Theme);
|
||||||
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, false);
|
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, false);
|
||||||
waitHandlerIdle(mainLoop);
|
waitHandlerIdle(mTestHandler);
|
||||||
verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
|
verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any());
|
||||||
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
|
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
|
||||||
|
|
||||||
mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false);
|
mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false);
|
||||||
waitHandlerIdle(mainLoop);
|
waitHandlerIdle(mTestHandler);
|
||||||
verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false));
|
verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false));
|
||||||
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
|
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
|
||||||
}
|
}
|
||||||
@@ -147,15 +159,45 @@ public class StartingSurfaceDrawerTests {
|
|||||||
@Test
|
@Test
|
||||||
public void testFallbackDefaultTheme() {
|
public void testFallbackDefaultTheme() {
|
||||||
final int taskId = 1;
|
final int taskId = 1;
|
||||||
final Handler mainLoop = new Handler(Looper.getMainLooper());
|
|
||||||
final StartingWindowInfo windowInfo =
|
final StartingWindowInfo windowInfo =
|
||||||
createWindowInfo(taskId, 0);
|
createWindowInfo(taskId, 0);
|
||||||
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, false);
|
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, false);
|
||||||
waitHandlerIdle(mainLoop);
|
waitHandlerIdle(mTestHandler);
|
||||||
verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
|
verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any());
|
||||||
assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
|
assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testColorCache() {
|
||||||
|
final String packageName = mTestContext.getPackageName();
|
||||||
|
final int configHash = 1;
|
||||||
|
final int windowBgColor = 0xff000000;
|
||||||
|
final int windowBgResId = 1;
|
||||||
|
final IntSupplier windowBgColorSupplier = () -> windowBgColor;
|
||||||
|
final SplashscreenContentDrawer.ColorCache colorCache =
|
||||||
|
mStartingSurfaceDrawer.mSplashscreenContentDrawer.mColorCache;
|
||||||
|
final SplashscreenContentDrawer.ColorCache.WindowColor windowColor1 =
|
||||||
|
colorCache.getWindowColor(packageName, configHash, windowBgColor, windowBgResId,
|
||||||
|
windowBgColorSupplier);
|
||||||
|
assertEquals(windowBgColor, windowColor1.mBgColor);
|
||||||
|
assertEquals(0, windowColor1.mReuseCount);
|
||||||
|
|
||||||
|
final SplashscreenContentDrawer.ColorCache.WindowColor windowColor2 =
|
||||||
|
colorCache.getWindowColor(packageName, configHash, windowBgColor, windowBgResId,
|
||||||
|
windowBgColorSupplier);
|
||||||
|
assertEquals(windowColor1, windowColor2);
|
||||||
|
assertEquals(1, windowColor1.mReuseCount);
|
||||||
|
|
||||||
|
final Intent packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
|
packageRemoved.setData(Uri.parse("package:" + packageName));
|
||||||
|
colorCache.onReceive(mTestContext, packageRemoved);
|
||||||
|
|
||||||
|
final SplashscreenContentDrawer.ColorCache.WindowColor windowColor3 =
|
||||||
|
colorCache.getWindowColor(packageName, configHash, windowBgColor, windowBgResId,
|
||||||
|
windowBgColorSupplier);
|
||||||
|
assertEquals(0, windowColor3.mReuseCount);
|
||||||
|
}
|
||||||
|
|
||||||
private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
|
private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
|
||||||
StartingWindowInfo windowInfo = new StartingWindowInfo();
|
StartingWindowInfo windowInfo = new StartingWindowInfo();
|
||||||
final ActivityInfo info = new ActivityInfo();
|
final ActivityInfo info = new ActivityInfo();
|
||||||
|
|||||||
Reference in New Issue
Block a user