blob: d618cdf86865aea26af67b35f3fbbc18f722ddda [file] [log] [blame]
Mike Digman220587e2019-03-13 11:21:42 -07001/*
2 * Copyright (C) 2019 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
17package com.android.internal.app;
18
Mike Digman9c4ae502019-03-19 17:02:25 -070019import static android.content.Context.ACTIVITY_SERVICE;
Mike Digman220587e2019-03-13 11:21:42 -070020import static android.graphics.Paint.DITHER_FLAG;
21import static android.graphics.Paint.FILTER_BITMAP_FLAG;
22
23import android.annotation.NonNull;
24import android.annotation.Nullable;
Mike Digman9c4ae502019-03-19 17:02:25 -070025import android.app.ActivityManager;
Mike Digman220587e2019-03-13 11:21:42 -070026import android.content.Context;
27import android.content.pm.PackageManager;
28import android.content.res.Resources;
29import android.content.res.Resources.Theme;
30import android.graphics.Bitmap;
31import android.graphics.BlurMaskFilter;
32import android.graphics.BlurMaskFilter.Blur;
33import android.graphics.Canvas;
34import android.graphics.Color;
35import android.graphics.Paint;
36import android.graphics.PaintFlagsDrawFilter;
Mike Digman6f31c532019-04-12 10:02:49 -070037import android.graphics.PorterDuff;
38import android.graphics.PorterDuffXfermode;
Mike Digman220587e2019-03-13 11:21:42 -070039import android.graphics.Rect;
40import android.graphics.RectF;
41import android.graphics.drawable.AdaptiveIconDrawable;
42import android.graphics.drawable.BitmapDrawable;
43import android.graphics.drawable.ColorDrawable;
44import android.graphics.drawable.Drawable;
45import android.graphics.drawable.DrawableWrapper;
Mike Digman220587e2019-03-13 11:21:42 -070046import android.os.UserHandle;
47import android.util.AttributeSet;
Mike Digman9c4ae502019-03-19 17:02:25 -070048import android.util.Pools.SynchronizedPool;
Mike Digman220587e2019-03-13 11:21:42 -070049
50import com.android.internal.R;
51
52import org.xmlpull.v1.XmlPullParser;
53
54import java.nio.ByteBuffer;
55
56
57/**
58 * @deprecated Use the Launcher3 Iconloaderlib at packages/apps/Launcher3/iconloaderlib. This class
59 * is a temporary fork of Iconloader. It combines all necessary methods to render app icons that are
Mike Digman6f31c532019-04-12 10:02:49 -070060 * possibly badged. It is intended to be used only by Sharesheet for the Q release with custom code.
Mike Digman220587e2019-03-13 11:21:42 -070061 */
62@Deprecated
63public class SimpleIconFactory {
64
Mike Digman9c4ae502019-03-19 17:02:25 -070065 private static final SynchronizedPool<SimpleIconFactory> sPool =
66 new SynchronizedPool<>(Runtime.getRuntime().availableProcessors());
67
Mike Digman220587e2019-03-13 11:21:42 -070068 private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
69 private static final float BLUR_FACTOR = 0.5f / 48;
70
71 private Context mContext;
72 private Canvas mCanvas;
73 private PackageManager mPm;
74
75 private int mFillResIconDpi;
76 private int mIconBitmapSize;
77 private int mBadgeBitmapSize;
78 private int mWrapperBackgroundColor;
79
80 private Drawable mWrapperIcon;
81 private final Rect mOldBounds = new Rect();
82
83 /**
Mike Digman9c4ae502019-03-19 17:02:25 -070084 * Obtain a SimpleIconFactory from a pool objects.
85 *
Mike Digman220587e2019-03-13 11:21:42 -070086 * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
87 */
88 @Deprecated
Mike Digman9c4ae502019-03-19 17:02:25 -070089 public static SimpleIconFactory obtain(Context ctx) {
90 SimpleIconFactory instance = sPool.acquire();
91 if (instance == null) {
92 final ActivityManager am = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE);
93 final int iconDpi = (am == null) ? 0 : am.getLauncherLargeIconDensity();
94
95 final Resources r = ctx.getResources();
96 final int iconSize = r.getDimensionPixelSize(R.dimen.resolver_icon_size);
97 final int badgeSize = r.getDimensionPixelSize(R.dimen.resolver_badge_size);
98
99 instance = new SimpleIconFactory(ctx, iconDpi, iconSize, badgeSize);
100 instance.setWrapperBackgroundColor(Color.WHITE);
101 }
102
103 return instance;
104 }
105
106 /**
107 * Recycles the SimpleIconFactory so others may use it.
108 *
109 * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
110 */
111 @Deprecated
112 public void recycle() {
113 // Return to default background color
114 setWrapperBackgroundColor(Color.WHITE);
115 sPool.release(this);
116 }
117
118 /**
119 * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
120 */
121 @Deprecated
122 private SimpleIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
Mike Digman220587e2019-03-13 11:21:42 -0700123 int badgeBitmapSize) {
124 mContext = context.getApplicationContext();
125 mPm = mContext.getPackageManager();
126 mIconBitmapSize = iconBitmapSize;
127 mBadgeBitmapSize = badgeBitmapSize;
128 mFillResIconDpi = fillResIconDpi;
129
130 mCanvas = new Canvas();
131 mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
132
133 // Normalizer init
134 // Use twice the icon size as maximum size to avoid scaling down twice.
135 mMaxSize = iconBitmapSize * 2;
136 mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
137 mScaleCheckCanvas = new Canvas(mBitmap);
138 mPixels = new byte[mMaxSize * mMaxSize];
139 mLeftBorder = new float[mMaxSize];
140 mRightBorder = new float[mMaxSize];
141 mBounds = new Rect();
142 mAdaptiveIconBounds = new Rect();
143 mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
144
145 // Shadow generator init
146 mDefaultBlurMaskFilter = new BlurMaskFilter(iconBitmapSize * BLUR_FACTOR,
147 Blur.NORMAL);
148 }
149
150 /**
151 * Sets the background color used for wrapped adaptive icon
152 *
153 * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
154 */
155 @Deprecated
156 void setWrapperBackgroundColor(int color) {
157 mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
158 }
159
160 /**
161 * Creates bitmap using the source drawable and various parameters.
162 * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
Mike Digmanb2e5e712019-04-19 15:49:10 -0700163 * Note: this method has been modified from iconloaderlib to remove a profile diff check.
Mike Digman220587e2019-03-13 11:21:42 -0700164 *
165 * @param icon source of the icon associated with a user that has no badge,
166 * likely user 0
167 * @param user info can be used for a badge
168 * @return a bitmap suitable for disaplaying as an icon at various system UIs.
169 *
170 * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
171 */
172 @Deprecated
173 Bitmap createUserBadgedIconBitmap(@Nullable Drawable icon, UserHandle user) {
174 float [] scale = new float[1];
175
176 // If no icon is provided use the system default
177 if (icon == null) {
178 icon = getFullResDefaultActivityIcon(mFillResIconDpi);
179 }
180 icon = normalizeAndWrapToAdaptiveIcon(icon, null, scale);
181 Bitmap bitmap = createIconBitmap(icon, scale[0]);
182 if (icon instanceof AdaptiveIconDrawable) {
183 mCanvas.setBitmap(bitmap);
184 recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
185 mCanvas.setBitmap(null);
186 }
187
188 final Bitmap result;
Mike Digmanb2e5e712019-04-19 15:49:10 -0700189 if (user != null /* if modification from iconloaderlib */) {
Mike Digman220587e2019-03-13 11:21:42 -0700190 BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
191 Drawable badged = mPm.getUserBadgedIcon(drawable, user);
192 if (badged instanceof BitmapDrawable) {
193 result = ((BitmapDrawable) badged).getBitmap();
194 } else {
195 result = createIconBitmap(badged, 1f);
196 }
197 } else {
198 result = bitmap;
199 }
200
201 return result;
202 }
203
204 /**
205 * Creates bitmap using the source drawable and flattened pre-rendered app icon.
206 * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
Mike Digman6f31c532019-04-12 10:02:49 -0700207 * This is custom functionality added to Iconloaderlib that will need to be ported.
Mike Digman220587e2019-03-13 11:21:42 -0700208 *
209 * @param icon source of the icon associated with a user that has no badge
210 * @param renderedAppIcon pre-rendered app icon to use as a badge, likely the output
211 * of createUserBadgedIconBitmap for user 0
212 * @return a bitmap suitable for disaplaying as an icon at various system UIs.
213 *
214 * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
215 */
216 @Deprecated
arangelovb0802dc2019-10-18 18:03:44 +0100217 public Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
Mike Digman220587e2019-03-13 11:21:42 -0700218 // If no icon is provided use the system default
219 if (icon == null) {
220 icon = getFullResDefaultActivityIcon(mFillResIconDpi);
221 }
Mike Digman6f31c532019-04-12 10:02:49 -0700222
223 // Direct share icons cannot be adaptive, most will arrive as bitmaps. To get reliable
224 // presentation, force all DS icons to be circular. Scale DS image so it completely fills.
225 int w = icon.getIntrinsicWidth();
226 int h = icon.getIntrinsicHeight();
227 float scale = 1;
228 if (h > w && w > 0) {
229 scale = (float) h / w;
230 } else if (w > h && h > 0) {
231 scale = (float) w / h;
232 }
233 Bitmap bitmap = createIconBitmap(icon, scale);
234 bitmap = maskBitmapToCircle(bitmap);
235 icon = new BitmapDrawable(mContext.getResources(), bitmap);
236
237 // We now have a circular masked and scaled icon, inset and apply shadow
238 scale = getScale(icon, null);
239 bitmap = createIconBitmap(icon, scale);
240
241 mCanvas.setBitmap(bitmap);
242 recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
243
244 if (renderedAppIcon != null) {
245 // Now scale down and apply the badge to the bottom right corner of the flattened icon
246 renderedAppIcon = Bitmap.createScaledBitmap(renderedAppIcon, mBadgeBitmapSize,
247 mBadgeBitmapSize, false);
248
249 // Paint the provided badge on top of the flattened icon
250 mCanvas.drawBitmap(renderedAppIcon, mIconBitmapSize - mBadgeBitmapSize,
251 mIconBitmapSize - mBadgeBitmapSize, null);
Mike Digman220587e2019-03-13 11:21:42 -0700252 }
253
Mike Digman220587e2019-03-13 11:21:42 -0700254 mCanvas.setBitmap(null);
255
256 return bitmap;
257 }
258
Mike Digman6f31c532019-04-12 10:02:49 -0700259 private Bitmap maskBitmapToCircle(Bitmap bitmap) {
260 final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
261 bitmap.getHeight(), Bitmap.Config.ARGB_8888);
262 final Canvas canvas = new Canvas(output);
263 final Paint paint = new Paint();
264 paint.setAntiAlias(true);
265
266 // Draw mask
267 paint.setColor(0xffffffff);
268 canvas.drawARGB(0, 0, 0, 0);
269 canvas.drawCircle(bitmap.getWidth() / 2f,
270 bitmap.getHeight() / 2f,
271 bitmap.getWidth() / 2f - 1 /* -1 to avoid circles with flat sides */,
272 paint);
273
274 // Draw masked bitmap
275 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
276 final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
277 canvas.drawBitmap(bitmap, rect, rect, paint);
278
279 return output;
280 }
281
Mike Digman220587e2019-03-13 11:21:42 -0700282 private static Drawable getFullResDefaultActivityIcon(int iconDpi) {
283 return Resources.getSystem().getDrawableForDensity(android.R.mipmap.sym_def_app_icon,
284 iconDpi);
285 }
286
287 private Bitmap createIconBitmap(Drawable icon, float scale) {
288 return createIconBitmap(icon, scale, mIconBitmapSize);
289 }
290
291 /**
292 * @param icon drawable that should be flattened to a bitmap
293 * @param scale the scale to apply before drawing {@param icon} on the canvas
294 */
295 private Bitmap createIconBitmap(Drawable icon, float scale, int size) {
296 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
297
298 mCanvas.setBitmap(bitmap);
299 mOldBounds.set(icon.getBounds());
300
301 if (icon instanceof AdaptiveIconDrawable) {
302 int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
303 Math.round(size * (1 - scale) / 2));
304 icon.setBounds(offset, offset, size - offset, size - offset);
305 icon.draw(mCanvas);
306 } else {
307 if (icon instanceof BitmapDrawable) {
308 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
309 Bitmap b = bitmapDrawable.getBitmap();
310 if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
311 bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
312 }
313 }
314 int width = size;
315 int height = size;
316
317 int intrinsicWidth = icon.getIntrinsicWidth();
318 int intrinsicHeight = icon.getIntrinsicHeight();
319 if (intrinsicWidth > 0 && intrinsicHeight > 0) {
320 // Scale the icon proportionally to the icon dimensions
321 final float ratio = (float) intrinsicWidth / intrinsicHeight;
322 if (intrinsicWidth > intrinsicHeight) {
323 height = (int) (width / ratio);
324 } else if (intrinsicHeight > intrinsicWidth) {
325 width = (int) (height * ratio);
326 }
327 }
328 final int left = (size - width) / 2;
329 final int top = (size - height) / 2;
330 icon.setBounds(left, top, left + width, top + height);
331 mCanvas.save();
332 mCanvas.scale(scale, scale, size / 2, size / 2);
333 icon.draw(mCanvas);
334 mCanvas.restore();
335
336 }
337
338 icon.setBounds(mOldBounds);
339 mCanvas.setBitmap(null);
340 return bitmap;
341 }
342
343 private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, RectF outIconBounds,
344 float[] outScale) {
345 float scale = 1f;
346
347 if (mWrapperIcon == null) {
348 mWrapperIcon = mContext.getDrawable(
349 R.drawable.iconfactory_adaptive_icon_drawable_wrapper).mutate();
350 }
351
352 AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
353 dr.setBounds(0, 0, 1, 1);
354 scale = getScale(icon, outIconBounds);
355 if (!(icon instanceof AdaptiveIconDrawable)) {
356 FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
357 fsd.setDrawable(icon);
358 fsd.setScale(scale);
359 icon = dr;
360 scale = getScale(icon, outIconBounds);
361
362 ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
363 }
364
365 outScale[0] = scale;
366 return icon;
367 }
368
369
370 /* Normalization block */
371
372 private static final float SCALE_NOT_INITIALIZED = 0;
373 // Ratio of icon visible area to full icon size for a square shaped icon
374 private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
375 // Ratio of icon visible area to full icon size for a circular shaped icon
376 private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
377
378 private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
379
380 // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
381 private static final float LINEAR_SCALE_SLOPE =
382 (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
383
384 private static final int MIN_VISIBLE_ALPHA = 40;
385
386 private float mAdaptiveIconScale;
387 private final Rect mAdaptiveIconBounds;
388 private final Rect mBounds;
389 private final int mMaxSize;
390 private final byte[] mPixels;
391 private final float[] mLeftBorder;
392 private final float[] mRightBorder;
393 private final Bitmap mBitmap;
394 private final Canvas mScaleCheckCanvas;
395
396 /**
397 * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
398 * matches the design guidelines for a launcher icon.
399 *
400 * We first calculate the convex hull of the visible portion of the icon.
401 * This hull then compared with the bounding rectangle of the hull to find how closely it
402 * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not
403 * an ideal solution but it gives satisfactory result without affecting the performance.
404 *
405 * This closeness is used to determine the ratio of hull area to the full icon size.
406 * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
407 *
408 * @param outBounds optional rect to receive the fraction distance from each edge.
409 */
410 private synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
411 if (d instanceof AdaptiveIconDrawable) {
412 if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
413 if (outBounds != null) {
414 outBounds.set(mAdaptiveIconBounds);
415 }
416 return mAdaptiveIconScale;
417 }
418 }
419 int width = d.getIntrinsicWidth();
420 int height = d.getIntrinsicHeight();
421 if (width <= 0 || height <= 0) {
422 width = width <= 0 || width > mMaxSize ? mMaxSize : width;
423 height = height <= 0 || height > mMaxSize ? mMaxSize : height;
424 } else if (width > mMaxSize || height > mMaxSize) {
425 int max = Math.max(width, height);
426 width = mMaxSize * width / max;
427 height = mMaxSize * height / max;
428 }
429
430 mBitmap.eraseColor(Color.TRANSPARENT);
431 d.setBounds(0, 0, width, height);
432 d.draw(mScaleCheckCanvas);
433
434 ByteBuffer buffer = ByteBuffer.wrap(mPixels);
435 buffer.rewind();
436 mBitmap.copyPixelsToBuffer(buffer);
437
438 // Overall bounds of the visible icon.
439 int topY = -1;
440 int bottomY = -1;
441 int leftX = mMaxSize + 1;
442 int rightX = -1;
443
444 // Create border by going through all pixels one row at a time and for each row find
445 // the first and the last non-transparent pixel. Set those values to mLeftBorder and
446 // mRightBorder and use -1 if there are no visible pixel in the row.
447
448 // buffer position
449 int index = 0;
450 // buffer shift after every row, width of buffer = mMaxSize
451 int rowSizeDiff = mMaxSize - width;
452 // first and last position for any row.
453 int firstX, lastX;
454
455 for (int y = 0; y < height; y++) {
456 firstX = lastX = -1;
457 for (int x = 0; x < width; x++) {
458 if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
459 if (firstX == -1) {
460 firstX = x;
461 }
462 lastX = x;
463 }
464 index++;
465 }
466 index += rowSizeDiff;
467
468 mLeftBorder[y] = firstX;
469 mRightBorder[y] = lastX;
470
471 // If there is at least one visible pixel, update the overall bounds.
472 if (firstX != -1) {
473 bottomY = y;
474 if (topY == -1) {
475 topY = y;
476 }
477
478 leftX = Math.min(leftX, firstX);
479 rightX = Math.max(rightX, lastX);
480 }
481 }
482
483 if (topY == -1 || rightX == -1) {
484 // No valid pixels found. Do not scale.
485 return 1;
486 }
487
488 convertToConvexArray(mLeftBorder, 1, topY, bottomY);
489 convertToConvexArray(mRightBorder, -1, topY, bottomY);
490
491 // Area of the convex hull
492 float area = 0;
493 for (int y = 0; y < height; y++) {
494 if (mLeftBorder[y] <= -1) {
495 continue;
496 }
497 area += mRightBorder[y] - mLeftBorder[y] + 1;
498 }
499
500 // Area of the rectangle required to fit the convex hull
501 float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
502 float hullByRect = area / rectArea;
503
504 float scaleRequired;
505 if (hullByRect < CIRCLE_AREA_BY_RECT) {
506 scaleRequired = MAX_CIRCLE_AREA_FACTOR;
507 } else {
508 scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
509 }
510 mBounds.left = leftX;
511 mBounds.right = rightX;
512
513 mBounds.top = topY;
514 mBounds.bottom = bottomY;
515
516 if (outBounds != null) {
517 outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
518 1 - ((float) mBounds.right) / width,
519 1 - ((float) mBounds.bottom) / height);
520 }
521 float areaScale = area / (width * height);
522 // Use sqrt of the final ratio as the images is scaled across both width and height.
523 float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
524 if (d instanceof AdaptiveIconDrawable && mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
525 mAdaptiveIconScale = scale;
526 mAdaptiveIconBounds.set(mBounds);
527 }
528 return scale;
529 }
530
531 /**
532 * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
533 * (except on either ends) with appropriate values.
534 * @param xCoordinates map of x coordinate per y.
535 * @param direction 1 for left border and -1 for right border.
536 * @param topY the first Y position (inclusive) with a valid value.
537 * @param bottomY the last Y position (inclusive) with a valid value.
538 */
539 private static void convertToConvexArray(
540 float[] xCoordinates, int direction, int topY, int bottomY) {
541 int total = xCoordinates.length;
542 // The tangent at each pixel.
543 float[] angles = new float[total - 1];
544
545 int first = topY; // First valid y coordinate
546 int last = -1; // Last valid y coordinate which didn't have a missing value
547
548 float lastAngle = Float.MAX_VALUE;
549
550 for (int i = topY + 1; i <= bottomY; i++) {
551 if (xCoordinates[i] <= -1) {
552 continue;
553 }
554 int start;
555
556 if (lastAngle == Float.MAX_VALUE) {
557 start = first;
558 } else {
559 float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
560 start = last;
561 // If this position creates a concave angle, keep moving up until we find a
562 // position which creates a convex angle.
563 if ((currentAngle - lastAngle) * direction < 0) {
564 while (start > first) {
565 start--;
566 currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
567 if ((currentAngle - angles[start]) * direction >= 0) {
568 break;
569 }
570 }
571 }
572 }
573
574 // Reset from last check
575 lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
576 // Update all the points from start.
577 for (int j = start; j < i; j++) {
578 angles[j] = lastAngle;
579 xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
580 }
581 last = i;
582 }
583 }
584
585 /* Shadow generator block */
586
587 private static final float KEY_SHADOW_DISTANCE = 1f / 48;
588 private static final int KEY_SHADOW_ALPHA = 61;
589 private static final int AMBIENT_SHADOW_ALPHA = 30;
590
591 private Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
592 private Paint mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
593 private BlurMaskFilter mDefaultBlurMaskFilter;
594
595 private synchronized void recreateIcon(Bitmap icon, Canvas out) {
596 recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
597 }
598
599 private synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
600 int ambientAlpha, int keyAlpha, Canvas out) {
601 int[] offset = new int[2];
602 mBlurPaint.setMaskFilter(blurMaskFilter);
603 Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
604
605 // Draw ambient shadow
606 mDrawPaint.setAlpha(ambientAlpha);
607 out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
608
609 // Draw key shadow
610 mDrawPaint.setAlpha(keyAlpha);
611 out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconBitmapSize,
612 mDrawPaint);
613
614 // Draw the icon
615 mDrawPaint.setAlpha(255); // TODO if b/128609682 not fixed by launch use .setAlpha(254)
616 out.drawBitmap(icon, 0, 0, mDrawPaint);
617 }
618
619 /* Classes */
620
621 /**
622 * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
623 */
624 public static class FixedScaleDrawable extends DrawableWrapper {
625
626 private static final float LEGACY_ICON_SCALE = .7f * .6667f;
627 private float mScaleX, mScaleY;
628
629 public FixedScaleDrawable() {
630 super(new ColorDrawable());
631 mScaleX = LEGACY_ICON_SCALE;
632 mScaleY = LEGACY_ICON_SCALE;
633 }
634
635 @Override
636 public void draw(@NonNull Canvas canvas) {
637 int saveCount = canvas.save();
638 canvas.scale(mScaleX, mScaleY,
639 getBounds().exactCenterX(), getBounds().exactCenterY());
640 super.draw(canvas);
641 canvas.restoreToCount(saveCount);
642 }
643
644 @Override
645 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
646
647 @Override
648 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
649
650 /**
651 * Sets the scale associated with this drawable
652 * @param scale
653 */
654 public void setScale(float scale) {
655 float h = getIntrinsicHeight();
656 float w = getIntrinsicWidth();
657 mScaleX = scale * LEGACY_ICON_SCALE;
658 mScaleY = scale * LEGACY_ICON_SCALE;
659 if (h > w && w > 0) {
660 mScaleX *= w / h;
661 } else if (w > h && h > 0) {
662 mScaleY *= h / w;
663 }
664 }
665 }
666
667 /**
668 * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
669 * This allows the badging to be done based on the action bitmap size rather than
670 * the scaled bitmap size.
671 */
672 private static class FixedSizeBitmapDrawable extends BitmapDrawable {
673
674 FixedSizeBitmapDrawable(Bitmap bitmap) {
675 super(null, bitmap);
676 }
677
678 @Override
679 public int getIntrinsicHeight() {
680 return getBitmap().getWidth();
681 }
682
683 @Override
684 public int getIntrinsicWidth() {
685 return getBitmap().getWidth();
686 }
687 }
688
689}