• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.icons;
18 
19 import static com.android.launcher3.icons.BaseIconFactory.getBadgeSizeForIconSize;
20 import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE;
21 import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
22 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
23 
24 import android.animation.ObjectAnimator;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.ColorFilter;
29 import android.graphics.ColorMatrix;
30 import android.graphics.ColorMatrixColorFilter;
31 import android.graphics.Paint;
32 import android.graphics.PixelFormat;
33 import android.graphics.Rect;
34 import android.graphics.drawable.Drawable;
35 import android.util.FloatProperty;
36 import android.view.animation.AccelerateInterpolator;
37 import android.view.animation.DecelerateInterpolator;
38 import android.view.animation.Interpolator;
39 import android.view.animation.PathInterpolator;
40 
41 import androidx.annotation.Nullable;
42 import androidx.annotation.VisibleForTesting;
43 import androidx.core.graphics.ColorUtils;
44 
45 import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
46 
47 public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
48 
49     private static final Interpolator ACCEL = new AccelerateInterpolator();
50     private static final Interpolator DEACCEL = new DecelerateInterpolator();
51     private static final Interpolator HOVER_EMPHASIZED_DECELERATE_INTERPOLATOR =
52             new PathInterpolator(0.05f, 0.7f, 0.1f, 1.0f);
53 
54     @VisibleForTesting protected static final float PRESSED_SCALE = 1.1f;
55     @VisibleForTesting protected static final float HOVERED_SCALE = 1.1f;
56     public static final int WHITE_SCRIM_ALPHA = 138;
57 
58     private static final float DISABLED_DESATURATION = 1f;
59     private static final float DISABLED_BRIGHTNESS = 0.5f;
60     protected static final int FULLY_OPAQUE = 255;
61 
62     public static final int CLICK_FEEDBACK_DURATION = 200;
63     public static final int HOVER_FEEDBACK_DURATION = 300;
64 
65     private static boolean sFlagHoverEnabled = false;
66 
67     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
68     protected final Bitmap mBitmap;
69     protected final int mIconColor;
70 
71     @Nullable private ColorFilter mColorFilter;
72 
73     @VisibleForTesting protected boolean mIsPressed;
74     @VisibleForTesting protected boolean mIsHovered;
75     protected boolean mIsDisabled;
76     float mDisabledAlpha = 1f;
77 
78     @DrawableCreationFlags int mCreationFlags = 0;
79 
80     // Animator and properties for the fast bitmap drawable's scale
81     @VisibleForTesting protected static final FloatProperty<FastBitmapDrawable> SCALE
82             = new FloatProperty<FastBitmapDrawable>("scale") {
83         @Override
84         public Float get(FastBitmapDrawable fastBitmapDrawable) {
85             return fastBitmapDrawable.mScale;
86         }
87 
88         @Override
89         public void setValue(FastBitmapDrawable fastBitmapDrawable, float value) {
90             fastBitmapDrawable.mScale = value;
91             fastBitmapDrawable.invalidateSelf();
92         }
93     };
94     @VisibleForTesting protected ObjectAnimator mScaleAnimation;
95     private float mScale = 1;
96     private int mAlpha = 255;
97 
98     private Drawable mBadge;
99 
FastBitmapDrawable(Bitmap b)100     public FastBitmapDrawable(Bitmap b) {
101         this(b, Color.TRANSPARENT);
102     }
103 
FastBitmapDrawable(BitmapInfo info)104     public FastBitmapDrawable(BitmapInfo info) {
105         this(info.icon, info.color);
106     }
107 
FastBitmapDrawable(Bitmap b, int iconColor)108     protected FastBitmapDrawable(Bitmap b, int iconColor) {
109         mBitmap = b;
110         mIconColor = iconColor;
111         setFilterBitmap(true);
112     }
113 
114     @Override
onBoundsChange(Rect bounds)115     protected void onBoundsChange(Rect bounds) {
116         super.onBoundsChange(bounds);
117         updateBadgeBounds(bounds);
118     }
119 
updateBadgeBounds(Rect bounds)120     private void updateBadgeBounds(Rect bounds) {
121         if (mBadge != null) {
122             setBadgeBounds(mBadge, bounds);
123         }
124     }
125 
126     @Override
draw(Canvas canvas)127     public final void draw(Canvas canvas) {
128         if (mScale != 1f) {
129             int count = canvas.save();
130             Rect bounds = getBounds();
131             canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
132             drawInternal(canvas, bounds);
133             if (mBadge != null) {
134                 mBadge.draw(canvas);
135             }
136             canvas.restoreToCount(count);
137         } else {
138             drawInternal(canvas, getBounds());
139             if (mBadge != null) {
140                 mBadge.draw(canvas);
141             }
142         }
143     }
144 
drawInternal(Canvas canvas, Rect bounds)145     protected void drawInternal(Canvas canvas, Rect bounds) {
146         canvas.drawBitmap(mBitmap, null, bounds, mPaint);
147     }
148 
149     /**
150      * Returns the primary icon color, slightly tinted white
151      */
getIconColor()152     public int getIconColor() {
153         int whiteScrim = setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA);
154         return ColorUtils.compositeColors(whiteScrim, mIconColor);
155     }
156 
157     /**
158      * Returns if this represents a themed icon
159      */
isThemed()160     public boolean isThemed() {
161         return false;
162     }
163 
164     /**
165      * Returns true if the drawable was created with theme, even if it doesn't
166      * support theming itself.
167      */
isCreatedForTheme()168     public boolean isCreatedForTheme() {
169         return isThemed() || (mCreationFlags & FLAG_THEMED) != 0;
170     }
171 
172     @Override
setColorFilter(ColorFilter cf)173     public void setColorFilter(ColorFilter cf) {
174         mColorFilter = cf;
175         updateFilter();
176     }
177 
178     @Override
getOpacity()179     public int getOpacity() {
180         return PixelFormat.TRANSLUCENT;
181     }
182 
183     @Override
setAlpha(int alpha)184     public void setAlpha(int alpha) {
185         if (mAlpha != alpha) {
186             mAlpha = alpha;
187             mPaint.setAlpha(alpha);
188             invalidateSelf();
189             if (mBadge != null) {
190                 mBadge.setAlpha(alpha);
191             }
192         }
193     }
194 
195     @Override
setFilterBitmap(boolean filterBitmap)196     public void setFilterBitmap(boolean filterBitmap) {
197         mPaint.setFilterBitmap(filterBitmap);
198         mPaint.setAntiAlias(filterBitmap);
199     }
200 
201     @Override
getAlpha()202     public int getAlpha() {
203         return mAlpha;
204     }
205 
resetScale()206     public void resetScale() {
207         if (mScaleAnimation != null) {
208             mScaleAnimation.cancel();
209             mScaleAnimation = null;
210         }
211         mScale = 1;
212         invalidateSelf();
213     }
214 
getAnimatedScale()215     public float getAnimatedScale() {
216         return mScaleAnimation == null ? 1 : mScale;
217     }
218 
219     @Override
getIntrinsicWidth()220     public int getIntrinsicWidth() {
221         return mBitmap.getWidth();
222     }
223 
224     @Override
getIntrinsicHeight()225     public int getIntrinsicHeight() {
226         return mBitmap.getHeight();
227     }
228 
229     @Override
getMinimumWidth()230     public int getMinimumWidth() {
231         return getBounds().width();
232     }
233 
234     @Override
getMinimumHeight()235     public int getMinimumHeight() {
236         return getBounds().height();
237     }
238 
239     @Override
isStateful()240     public boolean isStateful() {
241         return true;
242     }
243 
244     @Override
getColorFilter()245     public ColorFilter getColorFilter() {
246         return mPaint.getColorFilter();
247     }
248 
249     @Override
onStateChange(int[] state)250     protected boolean onStateChange(int[] state) {
251         boolean isPressed = false;
252         boolean isHovered = false;
253         for (int s : state) {
254             if (s == android.R.attr.state_pressed) {
255                 isPressed = true;
256                 break;
257             } else if (sFlagHoverEnabled && s == android.R.attr.state_hovered) {
258                 isHovered = true;
259                 // Do not break on hovered state, as pressed state should take precedence.
260             }
261         }
262         if (mIsPressed != isPressed || mIsHovered != isHovered) {
263             if (mScaleAnimation != null) {
264                 mScaleAnimation.cancel();
265             }
266 
267             float endScale = isPressed ? PRESSED_SCALE : (isHovered ? HOVERED_SCALE : 1f);
268             if (mScale != endScale) {
269                 if (isVisible()) {
270                     Interpolator interpolator =
271                             isPressed != mIsPressed ? (isPressed ? ACCEL : DEACCEL)
272                                     : HOVER_EMPHASIZED_DECELERATE_INTERPOLATOR;
273                     int duration =
274                             isPressed != mIsPressed ? CLICK_FEEDBACK_DURATION
275                                     : HOVER_FEEDBACK_DURATION;
276                     mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, endScale);
277                     mScaleAnimation.setDuration(duration);
278                     mScaleAnimation.setInterpolator(interpolator);
279                     mScaleAnimation.start();
280                 } else {
281                     mScale = endScale;
282                     invalidateSelf();
283                 }
284             }
285             mIsPressed = isPressed;
286             mIsHovered = isHovered;
287             return true;
288         }
289         return false;
290     }
291 
setIsDisabled(boolean isDisabled)292     public void setIsDisabled(boolean isDisabled) {
293         if (mIsDisabled != isDisabled) {
294             mIsDisabled = isDisabled;
295             updateFilter();
296         }
297     }
298 
isDisabled()299     protected boolean isDisabled() {
300         return mIsDisabled;
301     }
302 
setBadge(Drawable badge)303     public void setBadge(Drawable badge) {
304         if (mBadge != null) {
305             mBadge.setCallback(null);
306         }
307         mBadge = badge;
308         if (mBadge != null) {
309             mBadge.setCallback(this);
310         }
311         updateBadgeBounds(getBounds());
312         updateFilter();
313     }
314 
315     @VisibleForTesting
getBadge()316     public Drawable getBadge() {
317         return mBadge;
318     }
319 
320     /**
321      * Updates the paint to reflect the current brightness and saturation.
322      */
updateFilter()323     protected void updateFilter() {
324         mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter(mDisabledAlpha) : mColorFilter);
325         if (mBadge != null) {
326             mBadge.setColorFilter(getColorFilter());
327         }
328         invalidateSelf();
329     }
330 
newConstantState()331     protected FastBitmapConstantState newConstantState() {
332         return new FastBitmapConstantState(mBitmap, mIconColor);
333     }
334 
335     @Override
getConstantState()336     public final ConstantState getConstantState() {
337         FastBitmapConstantState cs = newConstantState();
338         cs.mIsDisabled = mIsDisabled;
339         if (mBadge != null) {
340             cs.mBadgeConstantState = mBadge.getConstantState();
341         }
342         cs.mCreationFlags = mCreationFlags;
343         return cs;
344     }
345 
getDisabledColorFilter()346     public static ColorFilter getDisabledColorFilter() {
347         return getDisabledColorFilter(1);
348     }
349 
350     // Returns if the FastBitmapDrawable contains a badge.
hasBadge()351     public boolean hasBadge() {
352         return (mCreationFlags & FLAG_NO_BADGE) == 0;
353     }
354 
getDisabledColorFilter(float disabledAlpha)355     private static ColorFilter getDisabledColorFilter(float disabledAlpha) {
356         ColorMatrix tempBrightnessMatrix = new ColorMatrix();
357         ColorMatrix tempFilterMatrix = new ColorMatrix();
358 
359         tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
360         float scale = 1 - DISABLED_BRIGHTNESS;
361         int brightnessI =   (int) (255 * DISABLED_BRIGHTNESS);
362         float[] mat = tempBrightnessMatrix.getArray();
363         mat[0] = scale;
364         mat[6] = scale;
365         mat[12] = scale;
366         mat[4] = brightnessI;
367         mat[9] = brightnessI;
368         mat[14] = brightnessI;
369         mat[18] = disabledAlpha;
370         tempFilterMatrix.preConcat(tempBrightnessMatrix);
371         return new ColorMatrixColorFilter(tempFilterMatrix);
372     }
373 
getDisabledColor(int color)374     protected static final int getDisabledColor(int color) {
375         int component = (Color.red(color) + Color.green(color) + Color.blue(color)) / 3;
376         float scale = 1 - DISABLED_BRIGHTNESS;
377         int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
378         component = Math.min(Math.round(scale * component + brightnessI), FULLY_OPAQUE);
379         return Color.rgb(component, component, component);
380     }
381 
382     /**
383      * Sets the bounds for the badge drawable based on the main icon bounds
384      */
setBadgeBounds(Drawable badge, Rect iconBounds)385     public static void setBadgeBounds(Drawable badge, Rect iconBounds) {
386         int size = getBadgeSizeForIconSize(iconBounds.width());
387         badge.setBounds(iconBounds.right - size, iconBounds.bottom - size,
388                 iconBounds.right, iconBounds.bottom);
389     }
390 
391     @Override
invalidateDrawable(Drawable who)392     public void invalidateDrawable(Drawable who) {
393         if (who == mBadge) {
394             invalidateSelf();
395         }
396     }
397 
398     @Override
scheduleDrawable(Drawable who, Runnable what, long when)399     public void scheduleDrawable(Drawable who, Runnable what, long when) {
400         if (who == mBadge) {
401             scheduleSelf(what, when);
402         }
403     }
404 
405     @Override
unscheduleDrawable(Drawable who, Runnable what)406     public void unscheduleDrawable(Drawable who, Runnable what) {
407         unscheduleSelf(what);
408     }
409 
410     /**
411      * Sets whether hover state functionality is enabled.
412      */
setFlagHoverEnabled(boolean isFlagHoverEnabled)413     public static void setFlagHoverEnabled(boolean isFlagHoverEnabled) {
414         sFlagHoverEnabled = isFlagHoverEnabled;
415     }
416 
417     protected static class FastBitmapConstantState extends ConstantState {
418         protected final Bitmap mBitmap;
419         protected final int mIconColor;
420 
421         // These are initialized later so that subclasses don't need to
422         // pass everything in constructor
423         protected boolean mIsDisabled;
424         private ConstantState mBadgeConstantState;
425 
426         @DrawableCreationFlags int mCreationFlags = 0;
427 
FastBitmapConstantState(Bitmap bitmap, int color)428         public FastBitmapConstantState(Bitmap bitmap, int color) {
429             mBitmap = bitmap;
430             mIconColor = color;
431         }
432 
createDrawable()433         protected FastBitmapDrawable createDrawable() {
434             return new FastBitmapDrawable(mBitmap, mIconColor);
435         }
436 
437         @Override
newDrawable()438         public final FastBitmapDrawable newDrawable() {
439             FastBitmapDrawable drawable = createDrawable();
440             drawable.setIsDisabled(mIsDisabled);
441             if (mBadgeConstantState != null) {
442                 drawable.setBadge(mBadgeConstantState.newDrawable());
443             }
444             drawable.mCreationFlags = mCreationFlags;
445             return drawable;
446         }
447 
448         @Override
getChangingConfigurations()449         public int getChangingConfigurations() {
450             return 0;
451         }
452     }
453 }
454