1 /* 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 17 package com.android.internal.app; 18 19 import static android.content.Context.ACTIVITY_SERVICE; 20 import static android.graphics.Paint.DITHER_FLAG; 21 import static android.graphics.Paint.FILTER_BITMAP_FLAG; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.content.res.Resources; 29 import android.content.res.Resources.Theme; 30 import android.graphics.Bitmap; 31 import android.graphics.BlurMaskFilter; 32 import android.graphics.BlurMaskFilter.Blur; 33 import android.graphics.Canvas; 34 import android.graphics.Color; 35 import android.graphics.Paint; 36 import android.graphics.PaintFlagsDrawFilter; 37 import android.graphics.PorterDuff; 38 import android.graphics.PorterDuffXfermode; 39 import android.graphics.Rect; 40 import android.graphics.RectF; 41 import android.graphics.drawable.AdaptiveIconDrawable; 42 import android.graphics.drawable.BitmapDrawable; 43 import android.graphics.drawable.ColorDrawable; 44 import android.graphics.drawable.Drawable; 45 import android.graphics.drawable.DrawableWrapper; 46 import android.os.UserHandle; 47 import android.util.AttributeSet; 48 import android.util.Pools.SynchronizedPool; 49 50 import com.android.internal.R; 51 52 import org.xmlpull.v1.XmlPullParser; 53 54 import 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 60 * possibly badged. It is intended to be used only by Sharesheet for the Q release with custom code. 61 */ 62 @Deprecated 63 public class SimpleIconFactory { 64 65 private static final SynchronizedPool<SimpleIconFactory> sPool = 66 new SynchronizedPool<>(Runtime.getRuntime().availableProcessors()); 67 68 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 /** 84 * Obtain a SimpleIconFactory from a pool objects. 85 * 86 * @deprecated Do not use, functionality will be replaced by iconloader lib eventually. 87 */ 88 @Deprecated obtain(Context ctx)89 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 recycle()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 SimpleIconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int badgeBitmapSize)122 private SimpleIconFactory(Context context, int fillResIconDpi, int iconBitmapSize, 123 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 setWrapperBackgroundColor(int color)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. 163 * Note: this method has been modified from iconloaderlib to remove a profile diff check. 164 * 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 createUserBadgedIconBitmap(@ullable Drawable icon, UserHandle user)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; 189 if (user != null /* if modification from iconloaderlib */) { 190 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. 207 * This is custom functionality added to Iconloaderlib that will need to be ported. 208 * 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 createAppBadgedIconBitmap(@ullable Drawable icon, Bitmap renderedAppIcon)217 Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) { 218 // If no icon is provided use the system default 219 if (icon == null) { 220 icon = getFullResDefaultActivityIcon(mFillResIconDpi); 221 } 222 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); 252 } 253 254 mCanvas.setBitmap(null); 255 256 return bitmap; 257 } 258 maskBitmapToCircle(Bitmap bitmap)259 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 getFullResDefaultActivityIcon(int iconDpi)282 private static Drawable getFullResDefaultActivityIcon(int iconDpi) { 283 return Resources.getSystem().getDrawableForDensity(android.R.mipmap.sym_def_app_icon, 284 iconDpi); 285 } 286 createIconBitmap(Drawable icon, float scale)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 */ createIconBitmap(Drawable icon, float scale, int size)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 normalizeAndWrapToAdaptiveIcon(Drawable icon, RectF outIconBounds, float[] outScale)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 */ getScale(@onNull Drawable d, @Nullable RectF outBounds)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 */ convertToConvexArray( float[] xCoordinates, int direction, int topY, int bottomY)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 recreateIcon(Bitmap icon, Canvas out)595 private synchronized void recreateIcon(Bitmap icon, Canvas out) { 596 recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out); 597 } 598 recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter, int ambientAlpha, int keyAlpha, Canvas out)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 FixedScaleDrawable()629 public FixedScaleDrawable() { 630 super(new ColorDrawable()); 631 mScaleX = LEGACY_ICON_SCALE; 632 mScaleY = LEGACY_ICON_SCALE; 633 } 634 635 @Override draw(@onNull Canvas canvas)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 inflate(Resources r, XmlPullParser parser, AttributeSet attrs)645 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { } 646 647 @Override inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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 */ setScale(float scale)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 FixedSizeBitmapDrawable(Bitmap bitmap)674 FixedSizeBitmapDrawable(Bitmap bitmap) { 675 super(null, bitmap); 676 } 677 678 @Override getIntrinsicHeight()679 public int getIntrinsicHeight() { 680 return getBitmap().getWidth(); 681 } 682 683 @Override getIntrinsicWidth()684 public int getIntrinsicWidth() { 685 return getBitmap().getWidth(); 686 } 687 } 688 689 } 690