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