From 3484638cad97e255a412b0489a63873fb3ca4218 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 9 Jul 2014 00:09:28 -0700 Subject: [PATCH] Adding a circular progress bar for preloader icons Change-Id: I1b5ba61c01a16a8cb5d3f9e31f827f8c99a1ffc9 --- proguard.flags | 5 + res/drawable-xxhdpi/bg_preloader.png | Bin 0 -> 3785 bytes res/drawable-xxhdpi/bg_preloader_progress.png | Bin 0 -> 1131 bytes res/values/integers.xml | 22 --- src/com/android/launcher3/BubbleTextView.java | 28 ++- src/com/android/launcher3/FolderIcon.java | 9 +- src/com/android/launcher3/LauncherModel.java | 1 + .../launcher3/PreloadIconDrawable.java | 168 ++++++++++++++++++ src/com/android/launcher3/ShortcutInfo.java | 11 +- 9 files changed, 208 insertions(+), 36 deletions(-) create mode 100644 res/drawable-xxhdpi/bg_preloader.png create mode 100644 res/drawable-xxhdpi/bg_preloader_progress.png delete mode 100644 res/values/integers.xml create mode 100644 src/com/android/launcher3/PreloadIconDrawable.java diff --git a/proguard.flags b/proguard.flags index a922e919d2..0b28c0ef4e 100644 --- a/proguard.flags +++ b/proguard.flags @@ -52,3 +52,8 @@ -keep class com.android.launcher3.MemoryDumpActivity { *; } + +-keep class com.android.launcher3.PreloadIconDrawable { + public float getAnimationProgress(); + public void setAnimationProgress(float); +} diff --git a/res/drawable-xxhdpi/bg_preloader.png b/res/drawable-xxhdpi/bg_preloader.png new file mode 100644 index 0000000000000000000000000000000000000000..56b80607ddca6ffe9d2e8639b7ed85d25a3c8299 GIT binary patch literal 3785 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 czL{H9e@$KC>fG_0`+rF>!6T|EcL5JtI za_T%@dMx**+LP`%ts;B8COwL&nbXSJQXjZv`qwKTCvu5hT(&J%{^H?pX4h(6l}umn zX>+&h<# zqwsxm_Nv2*q7VNv_J-#LExfl~;mgc-_F?tW6Y92?v1w#1UR@U}uzK~peeZ5sJMwrW z_AhY1p!`Dc%Psa41=aA_^Pjh`QGap$Hvf-p3+87h*6%-A{zd)ezlYtSQ75~5{{9P$ z3W;BQu0(Kdve|E^&x+M=xBWS^o9%jScyc4h?LCus@dvUfCH;5Z>hd+`VdunOrfdE^ z=+r&3Jp8-n)=kE$O}`R%{+VIO#zg9|kyU%D;$>eP2G*>#qSxaq1n%CpicpB`DTUFgCD;mEHmgk9bmFH19I zULf%7nMbJc)pm=hmB)55T}go@kvZo6mAS=`EJw&T*HhXP*Hq$hQA%xHeqqn;tob$tTAnwMoQu!Qwv))ZXbU9CABs-(eWY utZTKaI`qyCFCJa5ja@hYcYgT*1pJFiE?J6tcm4uqe+ExiKbLh*2~7ZFlQuyB literal 0 HcmV?d00001 diff --git a/res/values/integers.xml b/res/values/integers.xml deleted file mode 100644 index 7d26d85955..0000000000 --- a/res/values/integers.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - 127 - \ No newline at end of file diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 57dcea0444..3f619a8124 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -136,7 +136,8 @@ public class BubbleTextView extends TextView { setContentDescription(info.contentDescription); } setTag(info); - if (info.isPromise()) { + + if (info.wasPromise) { applyState(); } } @@ -431,42 +432,55 @@ public class BubbleTextView extends TextView { } public void applyState() { - int alpha = getResources().getInteger(R.integer.promise_icon_alpha); + final int progressLevel; final int state = getState(); if (DEBUG) Log.d(TAG, "applying icon state: " + state); switch(state) { case ShortcutInfo.PACKAGE_STATE_DEFAULT: super.setText(mDefaultText); - alpha = 255; + progressLevel = 100; break; case ShortcutInfo.PACKAGE_STATE_ENQUEUED: setText(R.string.package_state_enqueued); + progressLevel = 0; break; case ShortcutInfo.PACKAGE_STATE_DOWNLOADING: setText(R.string.package_state_downloading); + // TODO(sunnygoyal): fix progress + progressLevel = 30; break; case ShortcutInfo.PACKAGE_STATE_INSTALLING: setText(R.string.package_state_installing); + progressLevel = 100; break; case ShortcutInfo.PACKAGE_STATE_ERROR: setText(R.string.package_state_error); + progressLevel = 0; break; case ShortcutInfo.PACKAGE_STATE_UNKNOWN: default: + progressLevel = 0; setText(R.string.package_state_unknown); break; } - if (DEBUG) Log.d(TAG, "setting icon alpha to: " + alpha); + Drawable[] drawables = getCompoundDrawables(); - for (int i = 0; i < drawables.length; i++) { - if (drawables[i] != null) { - drawables[i].setAlpha(alpha); + Drawable top = drawables[1]; + if ((top != null) && !(top instanceof PreloadIconDrawable)) { + top = new PreloadIconDrawable(top, getResources()); + setCompoundDrawables(drawables[0], top, drawables[2], drawables[3]); + } + if (top != null) { + top.setLevel(progressLevel); + if ((top instanceof PreloadIconDrawable) + && (state == ShortcutInfo.PACKAGE_STATE_DEFAULT)) { + ((PreloadIconDrawable) top).maybePerformFinishedAnimation(); } } } diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index ab8976a594..4f674f55a0 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -605,7 +605,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { computePreviewDrawingParams(mAnimParams.drawable); } else { v = (TextView) items.get(0); - d = v.getCompoundDrawables()[1]; + d = getTopDrawable(v); computePreviewDrawingParams(d); } @@ -614,7 +614,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { for (int i = nItemsInPreview - 1; i >= 0; i--) { v = (TextView) items.get(i); if (!mHiddenItems.contains(v.getTag())) { - d = v.getCompoundDrawables()[1]; + d = getTopDrawable(v); mParams = computePreviewItemDrawingParams(i, mParams); mParams.drawable = d; drawPreviewItem(canvas, mParams); @@ -625,6 +625,11 @@ 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; + } + private void animateFirstItem(final Drawable d, int duration, final boolean reverse, final Runnable onCompleteRunnable) { final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index f1e73eb1d8..b01db71941 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -3047,6 +3047,7 @@ public class LauncherModel extends BroadcastReceiver info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user)); info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; info.restoredIntent = intent; + info.wasPromise = true; info.setState(ShortcutInfo.PACKAGE_STATE_UNKNOWN); return info; } diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java new file mode 100644 index 0000000000..d9365cc1fa --- /dev/null +++ b/src/com/android/launcher3/PreloadIconDrawable.java @@ -0,0 +1,168 @@ +package com.android.launcher3; + +import android.animation.ObjectAnimator; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +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 ICON_SCALE_FACTOR = 0.6f; + + private static Bitmap sProgressBg, sProgressFill; + + private final Rect mCanvasClipRect = new Rect(); + private final RectF mRect = new RectF(); + private final Path mProgressPath = new Path(); + private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + + final Drawable mIcon; + + /** + * 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 boolean mPathChanged; + + private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED; + private ObjectAnimator mAnimator; + + public PreloadIconDrawable(Drawable icon, Resources res) { + mIcon = icon; + + setBounds(icon.getBounds()); + mPathChanged = false; + + if (sProgressBg == null) { + sProgressBg = BitmapFactory.decodeResource(res, R.drawable.bg_preloader); + } + if (sProgressFill == null) { + sProgressFill = BitmapFactory.decodeResource(res, R.drawable.bg_preloader_progress); + } + } + + @Override + public void draw(Canvas canvas) { + final Rect r = getBounds(); + if (canvas.getClipBounds(mCanvasClipRect) && !Rect.intersects(mCanvasClipRect, r)) { + // The draw region has been clipped. + return; + } + final float iconScale; + + if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED) + && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) { + mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255)); + canvas.drawBitmap(sProgressBg, null, r, mPaint); + canvas.drawBitmap(sProgressFill, null, r, mPaint); + iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress; + + } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) { + mPaint.setAlpha(255); + iconScale = ICON_SCALE_FACTOR; + canvas.drawBitmap(sProgressBg, null, r, mPaint); + + if (mProgress >= 100) { + canvas.drawBitmap(sProgressFill, null, r, mPaint); + } else if (mProgress > 0) { + if (mPathChanged) { + mProgressPath.reset(); + mProgressPath.moveTo(r.exactCenterX(), r.centerY()); + + mRect.set(r); + mProgressPath.arcTo(mRect, -90, mProgress * 3.6f); + mProgressPath.close(); + mPathChanged = false; + } + + canvas.save(); + canvas.clipPath(mProgressPath); + canvas.drawBitmap(sProgressFill, null, r, mPaint); + canvas.restore(); + } + } else { + iconScale = 1; + } + + canvas.save(); + canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY()); + mIcon.draw(canvas); + canvas.restore(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + mIcon.setBounds(bounds); + mPathChanged = true; + } + + @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; + mPathChanged = true; + + // Stop Animation + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator = null; + } + mAnimationProgress = ANIMATION_PROGRESS_STOPPED; + + 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; + } +} diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index d2573a4a19..7e1f0d6493 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -16,13 +16,9 @@ package com.android.launcher3; -import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.util.Log; @@ -96,6 +92,11 @@ public class ShortcutInfo extends ItemInfo { */ Intent restoredIntent; + /** + * This is set once to indicate that it was a promise info at some point of its life. + */ + boolean wasPromise = false; + ShortcutInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } @@ -119,7 +120,7 @@ public class ShortcutInfo extends ItemInfo { } } - ShortcutInfo(Intent intent, CharSequence title, String contentDescrition, + ShortcutInfo(Intent intent, CharSequence title, String contentDescription, Bitmap icon, UserHandleCompat user) { this(); this.intent = intent;