From 96ac68a481ce5b794b5227e09ace7c30d6dd5e7b Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 2 Feb 2017 16:37:21 -0800 Subject: Updating the PreloadIconDrawable > The drawable gets the path from MaskIconDrawable path, instead of using a circle > The progress changes are animated as well Bug: 34831873 Change-Id: I4e7f0b610f4fd94de8e0cfcf8b179b775cf0b4d8 --- src/com/android/launcher3/BubbleTextView.java | 26 +- src/com/android/launcher3/FastBitmapDrawable.java | 2 +- .../launcher3/PendingAppWidgetHostView.java | 19 +- src/com/android/launcher3/PreloadIconDrawable.java | 252 ------------------ src/com/android/launcher3/Workspace.java | 1 + src/com/android/launcher3/folder/FolderIcon.java | 13 +- .../launcher3/graphics/DragPreviewProvider.java | 5 - .../launcher3/graphics/DrawableFactory.java | 41 ++- .../launcher3/graphics/FixedScaleDrawable.java | 41 +++ .../android/launcher3/graphics/IconPalette.java | 42 ++- .../android/launcher3/graphics/LauncherIcons.java | 20 +- .../launcher3/graphics/PreloadIconDrawable.java | 289 +++++++++++++++++++++ 12 files changed, 421 insertions(+), 330 deletions(-) delete mode 100644 src/com/android/launcher3/PreloadIconDrawable.java create mode 100644 src/com/android/launcher3/graphics/FixedScaleDrawable.java create mode 100644 src/com/android/launcher3/graphics/PreloadIconDrawable.java (limited to 'src/com/android') diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 2efe31fa0..8043eacdb 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -19,7 +19,6 @@ package com.android.launcher3; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; -import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -27,7 +26,6 @@ import android.graphics.Paint; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.SparseArray; import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; @@ -45,6 +43,7 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.graphics.HolographicOutlineHelper; import com.android.launcher3.graphics.IconPalette; +import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.model.PackageItemInfo; import java.text.NumberFormat; @@ -57,8 +56,6 @@ import java.text.NumberFormat; public class BubbleTextView extends TextView implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView, ItemInfoUpdateReceiver { - private static SparseArray sPreloaderThemes = new SparseArray(2); - // Dimensions in DP private static final float AMBIENT_SHADOW_RADIUS = 2.5f; private static final float KEY_SHADOW_RADIUS = 1f; @@ -423,10 +420,6 @@ public class BubbleTextView extends TextView super.onAttachedToWindow(); if (mBackground != null) mBackground.setCallback(this); - - if (mIcon instanceof PreloadIconDrawable) { - ((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme()); - } mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } @@ -495,7 +488,8 @@ public class BubbleTextView extends TextView if (mIcon instanceof PreloadIconDrawable) { preloadDrawable = (PreloadIconDrawable) mIcon; } else { - preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme()); + preloadDrawable = DrawableFactory.get(getContext()) + .newPendingIcon(info.iconBitmap, getContext()); setIcon(preloadDrawable); } @@ -520,20 +514,6 @@ public class BubbleTextView extends TextView : null; } - private Theme getPreloaderTheme() { - Object tag = getTag(); - int style = ((tag != null) && (tag instanceof ShortcutInfo) && - (((ShortcutInfo) tag).container >= 0)) ? R.style.PreloadIcon_Folder - : R.style.PreloadIcon; - Theme theme = sPreloaderThemes.get(style); - if (theme == null) { - theme = getResources().newTheme(); - theme.applyStyle(style, true); - sPreloaderThemes.put(style, theme); - } - return theme; - } - /** * Sets the icon for this view based on the layout direction. */ diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index df195471b..95d2dafce 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -120,7 +120,7 @@ public class FastBitmapDrawable extends Drawable { public FastBitmapDrawable(Bitmap b) { mBitmap = b; - setBounds(0, 0, b.getWidth(), b.getHeight()); + setFilterBitmap(true); } public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) { diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java index 3256df6fe..354cf2065 100644 --- a/src/com/android/launcher3/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/PendingAppWidgetHostView.java @@ -17,7 +17,6 @@ package com.android.launcher3; import android.content.Context; -import android.content.res.Resources.Theme; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -33,8 +32,8 @@ import android.view.ContextThemeWrapper; import android.view.View; import android.view.View.OnClickListener; -import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.IconCache.ItemInfoUpdateReceiver; +import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.model.PackageItemInfo; public class PendingAppWidgetHostView extends LauncherAppWidgetHostView @@ -42,8 +41,6 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5; private static final float MIN_SATUNATION = 0.7f; - private static Theme sPreloaderTheme; - private final Rect mRect = new Rect(); private View mDefaultView; private OnClickListener mClickListener; @@ -149,13 +146,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView updateSettingColor(); } else { - if (sPreloaderTheme == null) { - sPreloaderTheme = getResources().newTheme(); - sPreloaderTheme.applyStyle(R.style.PreloadIcon, true); - } - - FastBitmapDrawable drawable = drawableFactory.newIcon(mIcon, mInfo); - mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme); + mCenterDrawable = DrawableFactory.get(getContext()) + .newPendingIcon(mIcon, getContext()); mCenterDrawable.setCallback(this); mSettingIconDrawable = null; applyState(); @@ -226,13 +218,10 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding; if (mSettingIconDrawable == null) { - int outset = (mCenterDrawable instanceof PreloadIconDrawable) ? - ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0; - int maxSize = grid.iconSizePx + 2 * outset; + int maxSize = grid.iconSizePx; int size = Math.min(maxSize, Math.min(availableWidth, availableHeight)); mRect.set(0, 0, size, size); - mRect.inset(outset, outset); mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2); mCenterDrawable.setBounds(mRect); } else { diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java deleted file mode 100644 index 973e688d9..000000000 --- a/src/com/android/launcher3/PreloadIconDrawable.java +++ /dev/null @@ -1,252 +0,0 @@ -package com.android.launcher3; - -import android.animation.ObjectAnimator; -import android.content.res.Resources.Theme; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; - -public class PreloadIconDrawable extends Drawable { - - private static final float ANIMATION_PROGRESS_STOPPED = -1.0f; - private static final float ANIMATION_PROGRESS_STARTED = 0f; - private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f; - - private static final float MIN_SATURATION = 0.2f; - private static final float MIN_LIGHTNESS = 0.6f; - - private static final float ICON_SCALE_FACTOR = 0.5f; - private static final int DEFAULT_COLOR = 0xFF009688; - - private static final Rect sTempRect = new Rect(); - - private final RectF mIndicatorRect = new RectF(); - private boolean mIndicatorRectDirty; - - private final Paint mPaint; - public final Drawable mIcon; - - private Drawable mBgDrawable; - private int mRingOutset; - - private int mIndicatorColor = 0; - - /** - * Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon - * is shown with no progress bar. - */ - private int mProgress = 0; - - private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED; - private ObjectAnimator mAnimator; - - public PreloadIconDrawable(Drawable icon, Theme theme) { - mIcon = icon; - - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeCap(Paint.Cap.ROUND); - - setBounds(icon.getBounds()); - applyPreloaderTheme(theme); - onLevelChange(0); - } - - public void applyPreloaderTheme(Theme t) { - TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable); - mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background); - mBgDrawable.setFilterBitmap(true); - mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0)); - mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0); - ta.recycle(); - onBoundsChange(getBounds()); - invalidateSelf(); - } - - @Override - protected void onBoundsChange(Rect bounds) { - mIcon.setBounds(bounds); - if (mBgDrawable != null) { - sTempRect.set(bounds); - sTempRect.inset(-mRingOutset, -mRingOutset); - mBgDrawable.setBounds(sTempRect); - } - mIndicatorRectDirty = true; - } - - public int getOutset() { - return mRingOutset; - } - - /** - * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus - * half the stroke size to accommodate the indicator. - */ - private void initIndicatorRect() { - Drawable d = mBgDrawable; - Rect bounds = d.getBounds(); - - d.getPadding(sTempRect); - // Amount by which padding has to be scaled - float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth(); - float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight(); - mIndicatorRect.set( - bounds.left + sTempRect.left * paddingScaleX, - bounds.top + sTempRect.top * paddingScaleY, - bounds.right - sTempRect.right * paddingScaleX, - bounds.bottom - sTempRect.bottom * paddingScaleY); - - float inset = mPaint.getStrokeWidth() / 2; - mIndicatorRect.inset(inset, inset); - mIndicatorRectDirty = false; - } - - @Override - public void draw(Canvas canvas) { - final Rect r = new Rect(getBounds()); - if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) { - // The draw region has been clipped. - return; - } - if (mIndicatorRectDirty) { - initIndicatorRect(); - } - final float iconScale; - - if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED) - && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) { - mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255)); - mBgDrawable.setAlpha(mPaint.getAlpha()); - mBgDrawable.draw(canvas); - canvas.drawOval(mIndicatorRect, mPaint); - - iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress; - } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) { - mPaint.setAlpha(255); - iconScale = ICON_SCALE_FACTOR; - mBgDrawable.setAlpha(255); - mBgDrawable.draw(canvas); - - if (mProgress >= 100) { - canvas.drawOval(mIndicatorRect, mPaint); - } else if (mProgress > 0) { - canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint); - } - } else { - iconScale = 1; - } - - canvas.save(); - canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY()); - mIcon.draw(canvas); - canvas.restore(); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - mIcon.setAlpha(alpha); - } - - @Override - public void setColorFilter(ColorFilter cf) { - mIcon.setColorFilter(cf); - } - - @Override - protected boolean onLevelChange(int level) { - mProgress = level; - - // Stop Animation - if (mAnimator != null) { - mAnimator.cancel(); - mAnimator = null; - } - mAnimationProgress = ANIMATION_PROGRESS_STOPPED; - if (level > 0) { - // Set the paint color only when the level changes, so that the dominant color - // is only calculated when needed. - mPaint.setColor(getIndicatorColor()); - } - if (mIcon instanceof FastBitmapDrawable) { - ((FastBitmapDrawable) mIcon).setIsDisabled(level < 100); - } - - invalidateSelf(); - return true; - } - - /** - * Runs the finish animation if it is has not been run after last level change. - */ - public void maybePerformFinishedAnimation() { - if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) { - return; - } - if (mAnimator != null) { - mAnimator.cancel(); - } - setAnimationProgress(ANIMATION_PROGRESS_STARTED); - mAnimator = ObjectAnimator.ofFloat(this, "animationProgress", - ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED); - mAnimator.start(); - } - - public void setAnimationProgress(float progress) { - if (progress != mAnimationProgress) { - mAnimationProgress = progress; - invalidateSelf(); - } - } - - public float getAnimationProgress() { - return mAnimationProgress; - } - - public boolean hasNotCompleted() { - return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED; - } - - @Override - public int getIntrinsicHeight() { - return mIcon.getIntrinsicHeight(); - } - - @Override - public int getIntrinsicWidth() { - return mIcon.getIntrinsicWidth(); - } - - private int getIndicatorColor() { - if (mIndicatorColor != 0) { - return mIndicatorColor; - } - if (!(mIcon instanceof FastBitmapDrawable)) { - mIndicatorColor = DEFAULT_COLOR; - return mIndicatorColor; - } - mIndicatorColor = Utilities.findDominantColorByHue( - ((FastBitmapDrawable) mIcon).getBitmap(), 20); - - // Make sure that the dominant color has enough saturation to be visible properly. - float[] hsv = new float[3]; - Color.colorToHSV(mIndicatorColor, hsv); - if (hsv[1] < MIN_SATURATION) { - mIndicatorColor = DEFAULT_COLOR; - return mIndicatorColor; - } - hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]); - mIndicatorColor = Color.HSVToColor(hsv); - return mIndicatorColor; - } -} diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 981941892..c80d4a883 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -72,6 +72,7 @@ import com.android.launcher3.dragndrop.SpringLoadedDragController; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.DragPreviewProvider; +import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 973245b92..5dc963311 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -53,13 +53,11 @@ import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.FolderInfo; import com.android.launcher3.FolderInfo.FolderListener; -import com.android.launcher3.IconCache; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherSettings; import com.android.launcher3.OnAlarmListener; -import com.android.launcher3.PreloadIconDrawable; import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.SimpleOnStylusPressListener; @@ -245,7 +243,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { }; public Drawable prepareCreate(final View destView) { - Drawable animateDrawable = getTopDrawable((TextView) destView); + Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1]; computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), destView.getMeasuredWidth()); return animateDrawable; @@ -270,7 +268,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) { - Drawable animateDrawable = getTopDrawable((TextView) finalView); + Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1]; computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), finalView.getMeasuredWidth()); @@ -771,11 +769,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { } } - private Drawable getTopDrawable(TextView v) { - Drawable d = v.getCompoundDrawables()[1]; - return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d; - } - class FolderPreviewItemAnim { ValueAnimator mValueAnimator; float finalScale; @@ -892,7 +885,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { for (int i = 0; i < mDrawingParams.size(); i++) { PreviewItemDrawingParams p = mDrawingParams.get(i); - p.drawable = getTopDrawable((TextView) items.get(i)); + p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1]; if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) { computePreviewItemDrawingParams(i, nItemsInPreview, p); diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index 1a470ff11..bb136f7a3 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -27,7 +27,6 @@ import android.widget.TextView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetHostView; -import com.android.launcher3.PreloadIconDrawable; import com.android.launcher3.R; import com.android.launcher3.Workspace; import com.android.launcher3.config.ProviderConfig; @@ -185,10 +184,6 @@ public class DragPreviewProvider { } else { bounds.offsetTo(0, 0); } - if (d instanceof PreloadIconDrawable) { - int inset = -((PreloadIconDrawable) d).getOutset(); - bounds.inset(inset, inset); - } return bounds; } diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index 4d4d5087f..249344792 100644 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ b/src/com/android/launcher3/graphics/DrawableFactory.java @@ -21,12 +21,14 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Process; import android.os.UserHandle; import android.support.annotation.UiThread; +import android.util.Log; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfo; @@ -41,9 +43,13 @@ import java.util.HashMap; */ public class DrawableFactory { + private static final String TAG = "DrawableFactory"; + private static DrawableFactory sInstance; private static final Object LOCK = new Object(); + private Path mPreloadProgressPath; + public static DrawableFactory get(Context context) { synchronized (LOCK) { if (sInstance == null) { @@ -61,9 +67,38 @@ public class DrawableFactory { * Returns a FastBitmapDrawable with the icon. */ public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) { - FastBitmapDrawable d = new FastBitmapDrawable(icon); - d.setFilterBitmap(true); - return d; + return new FastBitmapDrawable(icon); + } + + /** + * Returns a FastBitmapDrawable with the icon. + */ + public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) { + if (mPreloadProgressPath == null) { + mPreloadProgressPath = getPreloadProgressPath(context); + } + return new PreloadIconDrawable(icon, mPreloadProgressPath); + } + + + protected Path getPreloadProgressPath(Context context) { + if (Utilities.isAtLeastO()) { + try { + // Try to load the path from Mask Icon + Drawable maskIcon = context.getDrawable(R.drawable.mask_drawable_wrapper); + maskIcon.setBounds(0, 0, + PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE); + return (Path) maskIcon.getClass().getMethod("getIconMask").invoke(maskIcon); + } catch (Exception e) { + Log.e(TAG, "Error loading mask icon", e); + } + } + + // Create a circle static from top center and going clockwise. + Path p = new Path(); + p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0); + p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360); + return p; } public AllAppsBackgroundDrawable getAllAppsBackground(Context context) { diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java new file mode 100644 index 000000000..4be4bd552 --- /dev/null +++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java @@ -0,0 +1,41 @@ +package com.android.launcher3.graphics; + +import android.annotation.TargetApi; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.DrawableWrapper; +import android.os.Build; +import android.util.AttributeSet; + +import org.xmlpull.v1.XmlPullParser; + +/** + * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount. + */ +@TargetApi(Build.VERSION_CODES.N) +public class FixedScaleDrawable extends DrawableWrapper { + + // TODO b/33553066 use the constant defined in MaskableIconDrawable + private static final float LEGACY_ICON_SCALE = .7f * .6667f; + + public FixedScaleDrawable() { + super(new ColorDrawable()); + } + + @Override + public void draw(Canvas canvas) { + int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.scale(LEGACY_ICON_SCALE, LEGACY_ICON_SCALE, + getBounds().exactCenterX(), getBounds().exactCenterY()); + super.draw(canvas); + canvas.restoreToCount(saveCount); + } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { } +} diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java index 27aeaba34..991038cca 100644 --- a/src/com/android/launcher3/graphics/IconPalette.java +++ b/src/com/android/launcher3/graphics/IconPalette.java @@ -32,16 +32,42 @@ public class IconPalette { private static final boolean DEBUG = false; private static final String TAG = "IconPalette"; - public int backgroundColor; - public int textColor; - public int secondaryColor; + private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f; + private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f; + private static final int DEFAULT_PRELOAD_COLOR = 0xFF009688; + + public final int dominantColor; + public final int backgroundColor; + public final int textColor; + public final int secondaryColor; + + private IconPalette(int color) { + dominantColor = color; + backgroundColor = getMutedColor(dominantColor); + textColor = getTextColorForBackground(backgroundColor); + secondaryColor = getLowContrastColor(backgroundColor); + } + + /** + * Returns a color suitable for the progress bar color of preload icon. + */ + public int getPreloadProgressColor() { + int result = dominantColor; + + // Make sure that the dominant color has enough saturation to be visible properly. + float[] hsv = new float[3]; + Color.colorToHSV(result, hsv); + if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) { + result = DEFAULT_PRELOAD_COLOR; + } else { + hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]); + result = Color.HSVToColor(hsv); + } + return result; + } public static IconPalette fromDominantColor(int dominantColor) { - IconPalette palette = new IconPalette(); - palette.backgroundColor = getMutedColor(dominantColor); - palette.textColor = getTextColorForBackground(palette.backgroundColor); - palette.secondaryColor = getLowContrastColor(palette.backgroundColor); - return palette; + return new IconPalette(dominantColor); } /** diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java index 472b9135b..3fffb5bb5 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -29,13 +29,10 @@ import android.graphics.PaintFlagsDrawFilter; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; -import android.graphics.drawable.ScaleDrawable; import android.os.Process; import android.os.UserHandle; -import android.view.Gravity; import com.android.launcher3.AppInfo; import com.android.launcher3.IconCache; @@ -52,8 +49,6 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; * Helper methods for generating various launcher icons */ public class LauncherIcons { - // TODO b/33553066 use the constant defined in MaskableIconDrawable - private static final float LEGACY_ICON_SCALE = .7f * .6667f; private static final Rect sOldBounds = new Rect(); private static final Canvas sCanvas = new Canvas(); @@ -236,17 +231,16 @@ public class LauncherIcons { if (!(ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isAtLeastO())) { return drawable; } - int color = context.getResources().getColor(R.color.legacy_icon_background); - ColorDrawable colorDrawable = new ColorDrawable(color); - ScaleDrawable scaleDrawable = new ScaleDrawable(drawable, - Gravity.CENTER, LEGACY_ICON_SCALE, LEGACY_ICON_SCALE); - scaleDrawable.setLevel(1); + try { Class clazz = Class.forName("android.graphics.drawable.MaskableIconDrawable"); - if (!clazz.isAssignableFrom(drawable.getClass())){ + if (!clazz.isAssignableFrom(drawable.getClass())) { + Drawable maskWrapper = + context.getDrawable(R.drawable.mask_drawable_wrapper).mutate(); + ((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(maskWrapper)) + .setDrawable(drawable); - return (Drawable) clazz.getConstructor(Drawable.class, Drawable.class) - .newInstance(colorDrawable, scaleDrawable); + return maskWrapper; } } catch (Exception e) { return drawable; diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java new file mode 100644 index 000000000..bc07ce1a5 --- /dev/null +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2017 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.launcher3.graphics; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.Rect; +import android.util.Property; +import android.util.SparseArray; +import android.view.animation.LinearInterpolator; + +import com.android.launcher3.FastBitmapDrawable; + +import java.lang.ref.WeakReference; + +/** + * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon. + */ +public class PreloadIconDrawable extends FastBitmapDrawable { + + private static final Property INTERNAL_STATE = + new Property(Float.TYPE, "internalStateProgress") { + @Override + public Float get(PreloadIconDrawable object) { + return object.mInternalStateProgress; + } + + @Override + public void set(PreloadIconDrawable object, Float value) { + object.setInternalProgress(value); + } + }; + + public static final int PATH_SIZE = 100; + + private static final float PROGRESS_WIDTH = 7; + private static final float PROGRESS_GAP = 2; + private static final int MAX_PAINT_ALPHA = 255; + + private static final long DURATION_SCALE = 500; + + // The smaller the number, the faster the animation would be. + // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE + private static final float COMPLETE_ANIM_FRACTION = 0.3f; + + private static final int COLOR_TRACK = 0x77EEEEEE; + private static final int COLOR_SHADOW = 0x55000000; + + private static final float SMALL_SCALE = 0.75f; + + private static final SparseArray> sShadowCache = new SparseArray<>(); + + private final Matrix mTmpMatrix = new Matrix(); + private final PathMeasure mPathMeasure = new PathMeasure(); + + // Path in [0, 100] bounds. + private final Path mProgressPath; + + private final Path mScaledTrackPath; + private final Path mScaledProgressPath; + private final Paint mProgressPaint; + + private Bitmap mShadowBitmap; + private int mIndicatorColor = 0; + + private int mTrackAlpha; + private float mTrackLength; + private float mIconScale; + + private boolean mRanFinishAnimation; + + // Progress of the internal state. [0, 1] indicates the fraction of completed progress, + // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation. + private float mInternalStateProgress; + + private ObjectAnimator mCurrentAnim; + + /** + * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar. + */ + public PreloadIconDrawable(Bitmap b, Path progressPath) { + super(b); + mProgressPath = progressPath; + mScaledTrackPath = new Path(); + mScaledProgressPath = new Path(); + + mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + + setInternalProgress(0); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mTmpMatrix.setScale( + (bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE, + (bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE); + mTmpMatrix.postTranslate( + bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP, + bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP); + + mProgressPath.transform(mTmpMatrix, mScaledTrackPath); + float scale = bounds.width() / PATH_SIZE; + mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale); + + mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(), + (PROGRESS_GAP ) * scale); + mPathMeasure.setPath(mScaledTrackPath, true); + mTrackLength = mPathMeasure.getLength(); + + setInternalProgress(mInternalStateProgress); + } + + private Bitmap getShadowBitmap(int width, int height, float shadowRadius) { + int key = (width << 16) | height; + WeakReference shadowRef = sShadowCache.get(key); + Bitmap shadow = shadowRef != null ? shadowRef.get() : null; + if (shadow != null) { + return shadow; + } + shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(shadow); + mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW); + mProgressPaint.setColor(COLOR_TRACK); + mProgressPaint.setAlpha(MAX_PAINT_ALPHA); + c.drawPath(mScaledTrackPath, mProgressPaint); + mProgressPaint.clearShadowLayer(); + c.setBitmap(null); + + sShadowCache.put(key, new WeakReference<>(shadow)); + return shadow; + } + + @Override + public void draw(Canvas canvas) { + if (mRanFinishAnimation) { + super.draw(canvas); + return; + } + + // Draw track. + mProgressPaint.setColor(mIndicatorColor); + mProgressPaint.setAlpha(mTrackAlpha); + if (mShadowBitmap != null) { + canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint); + } + canvas.drawPath(mScaledProgressPath, mProgressPaint); + + int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + Rect bounds = getBounds(); + + canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY()); + drawInternal(canvas); + canvas.restoreToCount(saveCount); + } + + /** + * Updates the install progress based on the level + */ + @Override + protected boolean onLevelChange(int level) { + // Run the animation if we have already been bound. + updateInternalState(level * 0.01f, getBounds().width() > 0, false); + return true; + } + + /** + * Runs the finish animation if it is has not been run after last call to + * {@link #onLevelChange} + */ + public void maybePerformFinishedAnimation() { + // If the drawable was recently initialized, skip the progress animation. + if (mInternalStateProgress == 0) { + mInternalStateProgress = 1; + } + updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true); + } + + public boolean hasNotCompleted() { + return !mRanFinishAnimation; + } + + private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) { + if (mCurrentAnim != null) { + mCurrentAnim.cancel(); + mCurrentAnim = null; + } + + if (Float.compare(finalProgress, mInternalStateProgress) == 0) { + return; + } + if (!shouldAnimate || mRanFinishAnimation) { + setInternalProgress(finalProgress); + } else { + mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress); + mCurrentAnim.setDuration( + (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE)); + mCurrentAnim.setInterpolator(new LinearInterpolator()); + if (isFinish) { + mCurrentAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRanFinishAnimation = true; + } + }); + } + mCurrentAnim.start(); + } + + } + + /** + * Sets the internal progress and updates the UI accordingly + * for progress <= 0: + * - icon in the small scale and disabled state + * - progress track is visible + * - progress bar is not visible + * for 0 < progress < 1 + * - icon in the small scale and disabled state + * - progress track is visible + * - progress bar is visible with dominant color. Progress bar is drawn as a fraction of + * {@link #mScaledTrackPath}. + * @see PathMeasure#getSegment(float, float, Path, boolean) + * for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION) + * - we calculate fraction of progress in the above range + * - progress track is drawn with alpha based on fraction + * - progress bar is drawn at 100% with alpha based on fraction + * - icon is scaled up based on fraction and is drawn in enabled state + * for progress >= (1 + COMPLETE_ANIM_FRACTION) + * - only icon is drawn in normal state + */ + private void setInternalProgress(float progress) { + mInternalStateProgress = progress; + if (progress <= 0) { + mIconScale = SMALL_SCALE; + mScaledTrackPath.reset(); + mTrackAlpha = MAX_PAINT_ALPHA; + setIsDisabled(true); + } else if (mIndicatorColor == 0) { + // Update the indicator color + mIndicatorColor = getIconPalette().getPreloadProgressColor(); + } + + if (progress < 1 && progress > 0) { + mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true); + mIconScale = SMALL_SCALE; + mTrackAlpha = MAX_PAINT_ALPHA; + setIsDisabled(true); + } else if (progress >= 1) { + setIsDisabled(false); + mScaledTrackPath.set(mScaledProgressPath); + float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION; + + if (fraction >= 1) { + // Animation has completed + mIconScale = 1; + mTrackAlpha = 0; + } else { + mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA); + mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction; + } + } + invalidateSelf(); + } +} -- cgit v1.2.3