Merge "Improve app launch animation (1/N)"
This commit is contained in:
committed by
Android (Google) Code Review
commit
df8d239a99
@@ -17,4 +17,6 @@
|
||||
<resources>
|
||||
<!-- Bubbles -->
|
||||
<color name="bubbles_icon_tint">@color/GM2_grey_200</color>
|
||||
<!-- Splash screen-->
|
||||
<color name="splash_window_background_default">@color/splash_screen_bg_dark</color>
|
||||
</resources>
|
||||
@@ -35,4 +35,9 @@
|
||||
<color name="GM2_grey_200">#E8EAED</color>
|
||||
<color name="GM2_grey_700">#5F6368</color>
|
||||
<color name="GM2_grey_800">#3C4043</color>
|
||||
|
||||
<!-- Splash screen -->
|
||||
<color name="splash_screen_bg_light">#FFFFFF</color>
|
||||
<color name="splash_screen_bg_dark">#000000</color>
|
||||
<color name="splash_window_background_default">@color/splash_screen_bg_light</color>
|
||||
</resources>
|
||||
@@ -167,4 +167,7 @@
|
||||
<!-- Size of padding for the user education cling, this should at minimum be larger than
|
||||
individual_bubble_size + some padding. -->
|
||||
<dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
|
||||
|
||||
<!-- The width/height of the icon view on staring surface. -->
|
||||
<dimen name="starting_surface_icon_size">108dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
* 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 com.android.wm.shell.startingsurface;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityThread;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Build;
|
||||
import android.util.Slog;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.graphics.palette.Palette;
|
||||
import com.android.internal.graphics.palette.Quantizer;
|
||||
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
|
||||
import com.android.internal.policy.PhoneWindow;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Util class to create the view for a splash screen content.
|
||||
*/
|
||||
class SplashscreenContentDrawer {
|
||||
private static final String TAG = StartingSurfaceDrawer.TAG;
|
||||
private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_SPLASH_SCREEN;
|
||||
|
||||
// The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
|
||||
// icon which it's non-transparent foreground area is similar to it's background area, then
|
||||
// do not enlarge the foreground drawable.
|
||||
// For example, an icon with the foreground 108*108 opaque pixels and it's background
|
||||
// also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
|
||||
private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
|
||||
private final Context mContext;
|
||||
private int mIconSize;
|
||||
|
||||
SplashscreenContentDrawer(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
private void updateDensity() {
|
||||
mIconSize = mContext.getResources().getDimensionPixelSize(
|
||||
com.android.wm.shell.R.dimen.starting_surface_icon_size);
|
||||
}
|
||||
|
||||
private int getSystemBGColor() {
|
||||
final Context systemContext = ActivityThread.currentApplication();
|
||||
if (systemContext == null) {
|
||||
Slog.e(TAG, "System context does not exist!");
|
||||
return Color.BLACK;
|
||||
}
|
||||
final Resources res = systemContext.getResources();
|
||||
return res.getColor(com.android.wm.shell.R.color.splash_window_background_default);
|
||||
}
|
||||
|
||||
private Drawable createDefaultBackgroundDrawable() {
|
||||
return new ColorDrawable(getSystemBGColor());
|
||||
}
|
||||
|
||||
View makeSplashScreenContentView(PhoneWindow win, Context context, int iconRes,
|
||||
int splashscreenContentResId) {
|
||||
updateDensity();
|
||||
win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
// splash screen content will be deprecated after S.
|
||||
final View ssc = makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
|
||||
if (ssc != null) {
|
||||
return ssc;
|
||||
}
|
||||
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(
|
||||
com.android.internal.R.styleable.Window);
|
||||
final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
|
||||
typedArray.recycle();
|
||||
final Drawable themeBGDrawable;
|
||||
if (resId == 0) {
|
||||
Slog.w(TAG, "Window background not exist!");
|
||||
themeBGDrawable = createDefaultBackgroundDrawable();
|
||||
} else {
|
||||
themeBGDrawable = context.getDrawable(resId);
|
||||
}
|
||||
final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
|
||||
: context.getPackageManager().getDefaultActivityIcon();
|
||||
// TODO (b/173975965) Tracking the performance on improved splash screen.
|
||||
final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
|
||||
return builder
|
||||
.setPhoneWindow(win)
|
||||
.setContext(context)
|
||||
.setThemeDrawable(themeBGDrawable)
|
||||
.setIconDrawable(iconDrawable).build();
|
||||
}
|
||||
|
||||
private class StartingWindowViewBuilder {
|
||||
// materials
|
||||
private Drawable mThemeBGDrawable;
|
||||
private Drawable mIconDrawable;
|
||||
private PhoneWindow mPhoneWindow;
|
||||
private Context mContext;
|
||||
|
||||
// result
|
||||
private boolean mBuildComplete = false;
|
||||
private View mCachedResult;
|
||||
private int mThemeColor;
|
||||
private Drawable mFinalIconDrawable;
|
||||
private float mScale = 1f;
|
||||
|
||||
StartingWindowViewBuilder setThemeDrawable(Drawable background) {
|
||||
mThemeBGDrawable = background;
|
||||
mBuildComplete = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
StartingWindowViewBuilder setIconDrawable(Drawable iconDrawable) {
|
||||
mIconDrawable = iconDrawable;
|
||||
mBuildComplete = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
StartingWindowViewBuilder setPhoneWindow(PhoneWindow window) {
|
||||
mPhoneWindow = window;
|
||||
mBuildComplete = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
StartingWindowViewBuilder setContext(Context context) {
|
||||
mContext = context;
|
||||
mBuildComplete = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
View build() {
|
||||
if (mBuildComplete) {
|
||||
return mCachedResult;
|
||||
}
|
||||
if (mPhoneWindow == null || mContext == null) {
|
||||
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 = mIconDrawable;
|
||||
}
|
||||
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
|
||||
mCachedResult = fillViewWithIcon(mPhoneWindow, mContext, iconSize, mFinalIconDrawable);
|
||||
mBuildComplete = true;
|
||||
return mCachedResult;
|
||||
}
|
||||
|
||||
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() {
|
||||
if (!(mIconDrawable instanceof AdaptiveIconDrawable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) mIconDrawable;
|
||||
final DrawableColorTester backIconTester =
|
||||
new DrawableColorTester(adaptiveIconDrawable.getBackground());
|
||||
|
||||
final Drawable iconForeground = adaptiveIconDrawable.getForeground();
|
||||
final DrawableColorTester foreIconTester =
|
||||
new DrawableColorTester(iconForeground, true /* filterTransparent */);
|
||||
|
||||
final boolean foreComplex = foreIconTester.isComplexColor();
|
||||
final int foreMainColor = foreIconTester.getDominateColor();
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "foreground complex color? " + foreComplex + " main color: "
|
||||
+ Integer.toHexString(foreMainColor));
|
||||
}
|
||||
final boolean backComplex = backIconTester.isComplexColor();
|
||||
final int backMainColor = backIconTester.getDominateColor();
|
||||
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
|
||||
// meet:
|
||||
// A. The background of the adaptive icon is not complicated. If it is complicated,
|
||||
// it may contain some information, and
|
||||
// B. The background of the adaptive icon is similar to the theme color, or
|
||||
// C. The background of the adaptive icon is grayscale, and the foreground of the
|
||||
// adaptive icon forms a certain contrast with the theme color.
|
||||
if (!backComplex && (isRgbSimilarInHsv(mThemeColor, backMainColor)
|
||||
|| (backIconTester.isGrayscale()
|
||||
&& !isRgbSimilarInHsv(mThemeColor, foreMainColor)))) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
|
||||
}
|
||||
// Using AdaptiveIconDrawable here can help keep the shape consistent with the
|
||||
// current settings.
|
||||
mFinalIconDrawable = new AdaptiveIconDrawable(
|
||||
new ColorDrawable(mThemeColor), 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;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
|
||||
}
|
||||
mFinalIconDrawable = adaptiveIconDrawable;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private View fillViewWithIcon(PhoneWindow win, Context context,
|
||||
int iconSize, Drawable iconDrawable) {
|
||||
final StartingSurfaceWindowView surfaceWindowView =
|
||||
new StartingSurfaceWindowView(context, iconSize);
|
||||
surfaceWindowView.setBackground(new ColorDrawable(mThemeColor));
|
||||
if (iconDrawable != null) {
|
||||
surfaceWindowView.setIconDrawable(iconDrawable);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + surfaceWindowView);
|
||||
}
|
||||
win.setContentView(surfaceWindowView);
|
||||
makeSystemUIColorsTransparent(win);
|
||||
return surfaceWindowView;
|
||||
}
|
||||
|
||||
private void makeSystemUIColorsTransparent(PhoneWindow win) {
|
||||
win.setStatusBarColor(Color.TRANSPARENT);
|
||||
win.setNavigationBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isRgbSimilarInHsv(int a, int b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
final float[] aHsv = new float[3];
|
||||
final float[] bHsv = new float[3];
|
||||
Color.colorToHSV(a, aHsv);
|
||||
Color.colorToHSV(b, bHsv);
|
||||
// Minimum degree of the hue between two colors, the result range is 0-180.
|
||||
int minAngle = (int) Math.abs(aHsv[0] - bHsv[0]);
|
||||
minAngle = (minAngle + 180) % 360 - 180;
|
||||
|
||||
// Calculate the difference between two colors based on the HSV dimensions.
|
||||
final float normalizeH = minAngle / 180f;
|
||||
final double square = Math.pow(normalizeH, 2)
|
||||
+ Math.pow(aHsv[1] - bHsv[1], 2)
|
||||
+ Math.pow(aHsv[2] - bHsv[2], 2);
|
||||
final double mean = square / 3;
|
||||
final double root = Math.sqrt(mean);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "hsvDiff " + minAngle + " a: " + Integer.toHexString(a)
|
||||
+ " b " + Integer.toHexString(b) + " ah " + aHsv[0] + " bh " + bHsv[0]
|
||||
+ " root " + root);
|
||||
}
|
||||
return root < 0.1;
|
||||
}
|
||||
|
||||
private static View makeSplashscreenContentDrawable(PhoneWindow win, Context ctx,
|
||||
int splashscreenContentResId) {
|
||||
// doesn't support windowSplashscreenContent after S
|
||||
// TODO add an allowlist to skip some packages if needed
|
||||
final int targetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "target sdk for package: " + targetSdkVersion);
|
||||
}
|
||||
if (targetSdkVersion >= Build.VERSION_CODES.S) {
|
||||
return null;
|
||||
}
|
||||
if (splashscreenContentResId == 0) {
|
||||
return null;
|
||||
}
|
||||
final Drawable drawable = ctx.getDrawable(splashscreenContentResId);
|
||||
if (drawable == null) {
|
||||
return null;
|
||||
}
|
||||
View view = new View(ctx);
|
||||
view.setBackground(drawable);
|
||||
win.setContentView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
private static class DrawableColorTester {
|
||||
private final ColorTester mColorChecker;
|
||||
|
||||
DrawableColorTester(Drawable drawable) {
|
||||
this(drawable, false /* filterTransparent */);
|
||||
}
|
||||
|
||||
DrawableColorTester(Drawable drawable, boolean filterTransparent) {
|
||||
// Some applications use LayerDrawable for their windowBackground. To ensure that we
|
||||
// only get the real background, so that the color is not affected by the alpha of the
|
||||
// upper layer, try to get the lower layer here. This can also speed up the calculation.
|
||||
if (drawable instanceof LayerDrawable) {
|
||||
LayerDrawable layerDrawable = (LayerDrawable) drawable;
|
||||
if (layerDrawable.getNumberOfLayers() > 0) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "replace drawable with bottom layer drawable");
|
||||
}
|
||||
drawable = layerDrawable.getDrawable(0);
|
||||
}
|
||||
}
|
||||
mColorChecker = drawable instanceof ColorDrawable
|
||||
? new SingleColorTester((ColorDrawable) drawable)
|
||||
: new ComplexDrawableTester(drawable, filterTransparent);
|
||||
}
|
||||
|
||||
public float nonTransparentRatio() {
|
||||
return mColorChecker.nonTransparentRatio();
|
||||
}
|
||||
|
||||
public boolean isComplexColor() {
|
||||
return mColorChecker.isComplexColor();
|
||||
}
|
||||
|
||||
public int getDominateColor() {
|
||||
return mColorChecker.getDominantColor();
|
||||
}
|
||||
|
||||
public boolean isGrayscale() {
|
||||
return mColorChecker.isGrayscale();
|
||||
}
|
||||
|
||||
/**
|
||||
* A help class to check the color information from a Drawable.
|
||||
*/
|
||||
private interface ColorTester {
|
||||
float nonTransparentRatio();
|
||||
boolean isComplexColor();
|
||||
int getDominantColor();
|
||||
boolean isGrayscale();
|
||||
}
|
||||
|
||||
private static boolean isGrayscaleColor(int color) {
|
||||
final int red = Color.red(color);
|
||||
final int green = Color.green(color);
|
||||
final int blue = Color.blue(color);
|
||||
return red == green && green == blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ColorDrawable only.
|
||||
* There will be only one color so don't spend too much resource for it.
|
||||
*/
|
||||
private static class SingleColorTester implements ColorTester {
|
||||
private final ColorDrawable mColorDrawable;
|
||||
|
||||
SingleColorTester(@NonNull ColorDrawable drawable) {
|
||||
mColorDrawable = drawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float nonTransparentRatio() {
|
||||
final int alpha = mColorDrawable.getAlpha();
|
||||
return (float) (alpha / 255);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplexColor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDominantColor() {
|
||||
return mColorDrawable.getColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGrayscale() {
|
||||
return isGrayscaleColor(mColorDrawable.getColor());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For any other Drawable except ColorDrawable.
|
||||
* This will use the Palette API to check the color information and use a quantizer to
|
||||
* filter out transparent colors when needed.
|
||||
*/
|
||||
private static class ComplexDrawableTester implements ColorTester {
|
||||
private static final int MAX_BITMAP_SIZE = 40;
|
||||
private final Palette mPalette;
|
||||
private final boolean mFilterTransparent;
|
||||
private static final TransparentFilterQuantizer TRANSPARENT_FILTER_QUANTIZER =
|
||||
new TransparentFilterQuantizer();
|
||||
|
||||
ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
|
||||
final Rect initialBounds = drawable.copyBounds();
|
||||
int width = drawable.getIntrinsicWidth();
|
||||
int height = drawable.getIntrinsicHeight();
|
||||
|
||||
// Some drawables do not have intrinsic dimensions
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = MAX_BITMAP_SIZE;
|
||||
height = MAX_BITMAP_SIZE;
|
||||
} else {
|
||||
width = Math.min(width, MAX_BITMAP_SIZE);
|
||||
height = Math.min(height, MAX_BITMAP_SIZE);
|
||||
}
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
final Canvas bmpCanvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||
drawable.draw(bmpCanvas);
|
||||
// restore to original bounds
|
||||
drawable.setBounds(initialBounds);
|
||||
|
||||
final Palette.Builder builder = new Palette.Builder(bitmap)
|
||||
.maximumColorCount(5).clearFilters();
|
||||
// The Palette API will ignore Alpha, so it cannot handle transparent pixels, but
|
||||
// sometimes we will need this information to know if this Drawable object is
|
||||
// transparent.
|
||||
mFilterTransparent = filterTransparent;
|
||||
if (mFilterTransparent) {
|
||||
builder.setQuantizer(TRANSPARENT_FILTER_QUANTIZER);
|
||||
}
|
||||
mPalette = builder.generate();
|
||||
bitmap.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float nonTransparentRatio() {
|
||||
return mFilterTransparent ? TRANSPARENT_FILTER_QUANTIZER.mNonTransparentRatio : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplexColor() {
|
||||
return mPalette.getSwatches().size() > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDominantColor() {
|
||||
final Palette.Swatch mainSwatch = mPalette.getDominantSwatch();
|
||||
if (mainSwatch != null) {
|
||||
return mainSwatch.getRgb();
|
||||
}
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGrayscale() {
|
||||
final List<Palette.Swatch> swatches = mPalette.getSwatches();
|
||||
if (swatches != null) {
|
||||
for (int i = swatches.size() - 1; i >= 0; i--) {
|
||||
Palette.Swatch swatch = swatches.get(i);
|
||||
if (!isGrayscaleColor(swatch.getRgb())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class TransparentFilterQuantizer implements Quantizer {
|
||||
private static final int NON_TRANSPARENT = 0xFF000000;
|
||||
private final Quantizer mInnerQuantizer = new VariationalKMeansQuantizer();
|
||||
private float mNonTransparentRatio;
|
||||
@Override
|
||||
public void quantize(final int[] pixels, final int maxColors,
|
||||
final Palette.Filter[] filters) {
|
||||
mNonTransparentRatio = 0;
|
||||
int realSize = 0;
|
||||
for (int i = pixels.length - 1; i > 0; i--) {
|
||||
if ((pixels[i] & NON_TRANSPARENT) != 0) {
|
||||
realSize++;
|
||||
}
|
||||
}
|
||||
if (realSize == 0) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "quantize: this is pure transparent image");
|
||||
}
|
||||
mInnerQuantizer.quantize(pixels, maxColors, filters);
|
||||
return;
|
||||
}
|
||||
mNonTransparentRatio = (float) realSize / pixels.length;
|
||||
final int[] samplePixels = new int[realSize];
|
||||
int rowIndex = 0;
|
||||
for (int i = pixels.length - 1; i > 0; i--) {
|
||||
if ((pixels[i] & NON_TRANSPARENT) == NON_TRANSPARENT) {
|
||||
samplePixels[rowIndex] = pixels[i];
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
mInnerQuantizer.quantize(samplePixels, maxColors, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Palette.Swatch> getQuantizedColors() {
|
||||
return mInnerQuantizer.getQuantizedColors();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class StartingSurfaceWindowView extends FrameLayout {
|
||||
// TODO animate the icon view
|
||||
private final View mIconView;
|
||||
|
||||
StartingSurfaceWindowView(Context context, int iconSize) {
|
||||
super(context);
|
||||
|
||||
final boolean emptyIcon = iconSize == 0;
|
||||
if (emptyIcon) {
|
||||
mIconView = null;
|
||||
} else {
|
||||
mIconView = new View(context);
|
||||
FrameLayout.LayoutParams params =
|
||||
new FrameLayout.LayoutParams(iconSize, iconSize);
|
||||
params.gravity = Gravity.CENTER;
|
||||
addView(mIconView, params);
|
||||
}
|
||||
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
}
|
||||
|
||||
// TODO support animatable icon
|
||||
void setIconDrawable(Drawable icon) {
|
||||
if (mIconView != null) {
|
||||
mIconView.setBackground(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
@@ -64,6 +63,7 @@ import java.util.function.Consumer;
|
||||
* class to remove the starting window of the Task.
|
||||
* @hide
|
||||
*/
|
||||
|
||||
public class StartingSurfaceDrawer {
|
||||
static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
|
||||
static final boolean DEBUG_SPLASH_SCREEN = false;
|
||||
@@ -72,6 +72,7 @@ public class StartingSurfaceDrawer {
|
||||
private final Context mContext;
|
||||
private final DisplayManager mDisplayManager;
|
||||
final ShellExecutor mMainExecutor;
|
||||
private final SplashscreenContentDrawer mSplashscreenContentDrawer;
|
||||
|
||||
// TODO(b/131727939) remove this when clearing ActivityRecord
|
||||
private static final int REMOVE_WHEN_TIMEOUT = 2000;
|
||||
@@ -80,6 +81,7 @@ public class StartingSurfaceDrawer {
|
||||
mContext = context;
|
||||
mDisplayManager = mContext.getSystemService(DisplayManager.class);
|
||||
mMainExecutor = mainExecutor;
|
||||
mSplashscreenContentDrawer = new SplashscreenContentDrawer(context);
|
||||
}
|
||||
|
||||
private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
|
||||
@@ -234,7 +236,6 @@ public class StartingSurfaceDrawer {
|
||||
return;
|
||||
}
|
||||
context = displayContext;
|
||||
|
||||
if (theme != context.getThemeResId() || labelRes != 0) {
|
||||
try {
|
||||
context = context.createPackageContext(
|
||||
@@ -347,7 +348,12 @@ public class StartingSurfaceDrawer {
|
||||
}
|
||||
|
||||
params.setTitle("Splash Screen " + activityInfo.packageName);
|
||||
addSplashscreenContent(win, context, splashscreenContentResId[0]);
|
||||
final View contentView = mSplashscreenContentDrawer.makeSplashScreenContentView(win,
|
||||
context, iconRes, splashscreenContentResId[0]);
|
||||
if (contentView == null) {
|
||||
Slog.w(TAG, "Adding splash screen window for " + activityInfo.packageName + " failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
final View view = win.getDecorView();
|
||||
|
||||
@@ -459,20 +465,4 @@ public class StartingSurfaceDrawer {
|
||||
mTaskSnapshotWindow = taskSnapshotWindow;
|
||||
}
|
||||
}
|
||||
|
||||
private void addSplashscreenContent(PhoneWindow win, Context ctx,
|
||||
int splashscreenContentResId) {
|
||||
if (splashscreenContentResId == 0) {
|
||||
return;
|
||||
}
|
||||
final Drawable drawable = ctx.getDrawable(splashscreenContentResId);
|
||||
if (drawable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We wrap this into a view so the system insets get applied to the drawable.
|
||||
final View v = new View(ctx);
|
||||
v.setBackground(drawable);
|
||||
win.setContentView(v);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user