1 /* 2 * Copyright (C) 2017 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 androidx.core.graphics.drawable; 18 19 import static android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP; 20 import static android.graphics.drawable.Icon.TYPE_BITMAP; 21 import static android.graphics.drawable.Icon.TYPE_DATA; 22 import static android.graphics.drawable.Icon.TYPE_RESOURCE; 23 import static android.graphics.drawable.Icon.TYPE_URI; 24 25 import static androidx.annotation.RestrictTo.Scope.LIBRARY; 26 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 27 28 import android.app.ActivityManager; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageManager; 34 import android.content.res.ColorStateList; 35 import android.content.res.Resources; 36 import android.graphics.Bitmap; 37 import android.graphics.BitmapFactory; 38 import android.graphics.BitmapShader; 39 import android.graphics.Canvas; 40 import android.graphics.Color; 41 import android.graphics.Matrix; 42 import android.graphics.Paint; 43 import android.graphics.PorterDuff; 44 import android.graphics.Shader; 45 import android.graphics.drawable.BitmapDrawable; 46 import android.graphics.drawable.Drawable; 47 import android.graphics.drawable.Icon; 48 import android.net.Uri; 49 import android.os.Build; 50 import android.os.Bundle; 51 import android.os.Parcelable; 52 import android.text.TextUtils; 53 import android.util.Log; 54 55 import androidx.annotation.ColorInt; 56 import androidx.annotation.DrawableRes; 57 import androidx.annotation.IdRes; 58 import androidx.annotation.IntDef; 59 import androidx.annotation.NonNull; 60 import androidx.annotation.Nullable; 61 import androidx.annotation.RequiresApi; 62 import androidx.annotation.RestrictTo; 63 import androidx.annotation.VisibleForTesting; 64 import androidx.core.content.ContextCompat; 65 import androidx.core.content.res.ResourcesCompat; 66 import androidx.core.os.BuildCompat; 67 68 import java.io.File; 69 import java.io.FileInputStream; 70 import java.io.FileNotFoundException; 71 import java.io.InputStream; 72 import java.lang.annotation.Retention; 73 import java.lang.annotation.RetentionPolicy; 74 import java.lang.reflect.InvocationTargetException; 75 76 /** 77 * Helper for accessing features in {@link android.graphics.drawable.Icon}. 78 */ 79 public class IconCompat { 80 81 private static final String TAG = "IconCompat"; 82 83 /** 84 * Value returned when the type of an {@link Icon} cannot be determined. 85 * @see #getType(Icon) 86 */ 87 public static final int TYPE_UNKOWN = -1; 88 89 /** 90 * @hide 91 */ 92 @RestrictTo(LIBRARY) 93 @IntDef({TYPE_UNKOWN, TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP}) 94 @Retention(RetentionPolicy.SOURCE) 95 public @interface IconType { 96 } 97 98 // Ratio of expected size to actual icon size 99 private static final float ADAPTIVE_ICON_INSET_FACTOR = 1 / 4f; 100 private static final float DEFAULT_VIEW_PORT_SCALE = 1 / (1 + 2 * ADAPTIVE_ICON_INSET_FACTOR); 101 private static final float ICON_DIAMETER_FACTOR = 176f / 192; 102 private static final float BLUR_FACTOR = 0.5f / 48; 103 private static final float KEY_SHADOW_OFFSET_FACTOR = 1f / 48; 104 105 private static final int KEY_SHADOW_ALPHA = 61; 106 private static final int AMBIENT_SHADOW_ALPHA = 30; 107 108 private static final String EXTRA_TYPE = "type"; 109 private static final String EXTRA_OBJ = "obj"; 110 private static final String EXTRA_INT1 = "int1"; 111 private static final String EXTRA_INT2 = "int2"; 112 private static final String EXTRA_TINT_LIST = "tint_list"; 113 private static final String EXTRA_TINT_MODE = "tint_mode"; 114 115 private final int mType; 116 117 // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed 118 // based on the value of mType. 119 120 // TYPE_BITMAP: Bitmap 121 // TYPE_ADAPTIVE_BITMAP: Bitmap 122 // TYPE_RESOURCE: Context 123 // TYPE_URI: String 124 // TYPE_DATA: DataBytes 125 private Object mObj1; 126 127 // TYPE_RESOURCE: resId 128 // TYPE_DATA: data offset 129 private int mInt1; 130 131 // TYPE_DATA: data length 132 private int mInt2; 133 134 private ColorStateList mTintList = null; 135 static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; // SRC_IN 136 private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 137 138 /** 139 * Create an Icon pointing to a drawable resource. 140 * @param context The context for the application whose resources should be used to resolve the 141 * given resource ID. 142 * @param resId ID of the drawable resource 143 * @see android.graphics.drawable.Icon#createWithResource(Context, int) 144 */ createWithResource(Context context, @DrawableRes int resId)145 public static IconCompat createWithResource(Context context, @DrawableRes int resId) { 146 if (context == null) { 147 throw new IllegalArgumentException("Context must not be null."); 148 } 149 final IconCompat rep = new IconCompat(TYPE_RESOURCE); 150 rep.mInt1 = resId; 151 rep.mObj1 = context.getPackageName(); 152 return rep; 153 } 154 155 /** 156 * Create an Icon pointing to a bitmap in memory. 157 * @param bits A valid {@link android.graphics.Bitmap} object 158 * @see android.graphics.drawable.Icon#createWithBitmap(Bitmap) 159 */ createWithBitmap(Bitmap bits)160 public static IconCompat createWithBitmap(Bitmap bits) { 161 if (bits == null) { 162 throw new IllegalArgumentException("Bitmap must not be null."); 163 } 164 final IconCompat rep = new IconCompat(TYPE_BITMAP); 165 rep.mObj1 = bits; 166 return rep; 167 } 168 169 /** 170 * Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined 171 * by {@link android.graphics.drawable.AdaptiveIconDrawable}. 172 * @param bits A valid {@link android.graphics.Bitmap} object 173 * @see android.graphics.drawable.Icon#createWithAdaptiveBitmap(Bitmap) 174 */ createWithAdaptiveBitmap(Bitmap bits)175 public static IconCompat createWithAdaptiveBitmap(Bitmap bits) { 176 if (bits == null) { 177 throw new IllegalArgumentException("Bitmap must not be null."); 178 } 179 final IconCompat rep = new IconCompat(TYPE_ADAPTIVE_BITMAP); 180 rep.mObj1 = bits; 181 return rep; 182 } 183 184 /** 185 * Create an Icon pointing to a compressed bitmap stored in a byte array. 186 * @param data Byte array storing compressed bitmap data of a type that 187 * {@link android.graphics.BitmapFactory} 188 * can decode (see {@link android.graphics.Bitmap.CompressFormat}). 189 * @param offset Offset into <code>data</code> at which the bitmap data starts 190 * @param length Length of the bitmap data 191 * @see android.graphics.drawable.Icon#createWithData(byte[], int, int) 192 */ createWithData(byte[] data, int offset, int length)193 public static IconCompat createWithData(byte[] data, int offset, int length) { 194 if (data == null) { 195 throw new IllegalArgumentException("Data must not be null."); 196 } 197 final IconCompat rep = new IconCompat(TYPE_DATA); 198 rep.mObj1 = data; 199 rep.mInt1 = offset; 200 rep.mInt2 = length; 201 return rep; 202 } 203 204 /** 205 * Create an Icon pointing to an image file specified by URI. 206 * 207 * @param uri A uri referring to local content:// or file:// image data. 208 * @see android.graphics.drawable.Icon#createWithContentUri(String) 209 */ createWithContentUri(String uri)210 public static IconCompat createWithContentUri(String uri) { 211 if (uri == null) { 212 throw new IllegalArgumentException("Uri must not be null."); 213 } 214 final IconCompat rep = new IconCompat(TYPE_URI); 215 rep.mObj1 = uri; 216 return rep; 217 } 218 219 /** 220 * Create an Icon pointing to an image file specified by URI. 221 * 222 * @param uri A uri referring to local content:// or file:// image data. 223 * @see android.graphics.drawable.Icon#createWithContentUri(String) 224 */ createWithContentUri(Uri uri)225 public static IconCompat createWithContentUri(Uri uri) { 226 if (uri == null) { 227 throw new IllegalArgumentException("Uri must not be null."); 228 } 229 return createWithContentUri(uri.toString()); 230 } 231 IconCompat(int mType)232 private IconCompat(int mType) { 233 this.mType = mType; 234 } 235 236 237 /** 238 * Gets the type of the icon provided. 239 * <p> 240 * Note that new types may be added later, so callers should guard against other 241 * types being returned. 242 */ 243 @IconType getType()244 public int getType() { 245 if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) { 246 return getType((Icon) mObj1); 247 } 248 return mType; 249 } 250 251 /** 252 * Gets the package used to create this icon. 253 * <p> 254 * Only valid for icons of type TYPE_RESOURCE. 255 * Note: This package may not be available if referenced in the future, and it is 256 * up to the caller to ensure safety if this package is re-used and/or persisted. 257 */ 258 @NonNull getResPackage()259 public String getResPackage() { 260 if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) { 261 return getResPackage((Icon) mObj1); 262 } 263 if (mType != TYPE_RESOURCE) { 264 throw new IllegalStateException("called getResPackage() on " + this); 265 } 266 return (String) mObj1; 267 } 268 269 /** 270 * Gets the resource id used to create this icon. 271 * <p> 272 * Only valid for icons of type TYPE_RESOURCE. 273 * Note: This resource may not be available if the application changes at all, and it is 274 * up to the caller to ensure safety if this resource is re-used and/or persisted. 275 */ 276 @IdRes getResId()277 public int getResId() { 278 if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) { 279 return getResId((Icon) mObj1); 280 } 281 if (mType != TYPE_RESOURCE) { 282 throw new IllegalStateException("called getResId() on " + this); 283 } 284 return mInt1; 285 } 286 287 /** 288 * Gets the uri used to create this icon. 289 * <p> 290 * Only valid for icons of type TYPE_URI. 291 * Note: This uri may not be available in the future, and it is 292 * up to the caller to ensure safety if this uri is re-used and/or persisted. 293 */ 294 @NonNull getUri()295 public Uri getUri() { 296 if (mType == TYPE_UNKOWN && Build.VERSION.SDK_INT >= 23) { 297 return getUri((Icon) mObj1); 298 } 299 return Uri.parse((String) mObj1); 300 } 301 302 /** 303 * Store a color to use whenever this Icon is drawn. 304 * 305 * @param tint a color, as in {@link Drawable#setTint(int)} 306 * @return this same object, for use in chained construction 307 */ setTint(@olorInt int tint)308 public IconCompat setTint(@ColorInt int tint) { 309 return setTintList(ColorStateList.valueOf(tint)); 310 } 311 312 /** 313 * Store a color to use whenever this Icon is drawn. 314 * 315 * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint 316 * @return this same object, for use in chained construction 317 */ setTintList(ColorStateList tintList)318 public IconCompat setTintList(ColorStateList tintList) { 319 mTintList = tintList; 320 return this; 321 } 322 323 /** 324 * Store a blending mode to use whenever this Icon is drawn. 325 * 326 * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null 327 * @return this same object, for use in chained construction 328 */ setTintMode(PorterDuff.Mode mode)329 public IconCompat setTintMode(PorterDuff.Mode mode) { 330 mTintMode = mode; 331 return this; 332 } 333 334 /** 335 * Convert this compat object to {@link Icon} object. 336 * 337 * @return {@link Icon} object 338 */ 339 @RequiresApi(23) toIcon()340 public Icon toIcon() { 341 Icon icon; 342 switch (mType) { 343 case TYPE_UNKOWN: 344 // When type is unknown we are just wrapping an icon. 345 return (Icon) mObj1; 346 case TYPE_BITMAP: 347 icon = Icon.createWithBitmap((Bitmap) mObj1); 348 break; 349 case TYPE_ADAPTIVE_BITMAP: 350 if (Build.VERSION.SDK_INT >= 26) { 351 icon = Icon.createWithAdaptiveBitmap((Bitmap) mObj1); 352 } else { 353 icon = Icon.createWithBitmap( 354 createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, false)); 355 } 356 break; 357 case TYPE_RESOURCE: 358 icon = Icon.createWithResource((String) mObj1, mInt1); 359 break; 360 case TYPE_DATA: 361 icon = Icon.createWithData((byte[]) mObj1, mInt1, mInt2); 362 break; 363 case TYPE_URI: 364 icon = Icon.createWithContentUri((String) mObj1); 365 break; 366 default: 367 throw new IllegalArgumentException("Unknown type"); 368 } 369 if (mTintList != null) { 370 icon.setTintList(mTintList); 371 } 372 if (mTintMode != DEFAULT_TINT_MODE) { 373 icon.setTintMode(mTintMode); 374 } 375 return icon; 376 } 377 378 379 380 /** 381 * Returns a Drawable that can be used to draw the image inside this Icon, constructing it 382 * if necessary. 383 * 384 * @param context {@link android.content.Context Context} in which to load the drawable; used 385 * to access {@link android.content.res.Resources Resources}, for example. 386 * @return A fresh instance of a drawable for this image, yours to keep. 387 */ loadDrawable(Context context)388 public Drawable loadDrawable(Context context) { 389 if (Build.VERSION.SDK_INT >= 23) { 390 return toIcon().loadDrawable(context); 391 } 392 final Drawable result = loadDrawableInner(context); 393 if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) { 394 result.mutate(); 395 DrawableCompat.setTintList(result, mTintList); 396 DrawableCompat.setTintMode(result, mTintMode); 397 } 398 return result; 399 } 400 401 402 /** 403 * Do the heavy lifting of loading the drawable, but stop short of applying any tint. 404 */ loadDrawableInner(Context context)405 private Drawable loadDrawableInner(Context context) { 406 switch (mType) { 407 case TYPE_BITMAP: 408 return new BitmapDrawable(context.getResources(), (Bitmap) mObj1); 409 case TYPE_ADAPTIVE_BITMAP: 410 return new BitmapDrawable(context.getResources(), 411 createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, false)); 412 case TYPE_RESOURCE: 413 Resources res; 414 // figure out where to load resources from 415 String resPackage = (String) mObj1; 416 if (TextUtils.isEmpty(resPackage)) { 417 // if none is specified, try the given context 418 resPackage = context.getPackageName(); 419 } 420 if ("android".equals(resPackage)) { 421 res = Resources.getSystem(); 422 } else { 423 final PackageManager pm = context.getPackageManager(); 424 try { 425 ApplicationInfo ai = pm.getApplicationInfo( 426 resPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES); 427 if (ai != null) { 428 res = pm.getResourcesForApplication(ai); 429 } else { 430 break; 431 } 432 } catch (PackageManager.NameNotFoundException e) { 433 Log.e(TAG, String.format("Unable to find pkg=%s for icon %s", 434 resPackage, this), e); 435 break; 436 } 437 } 438 try { 439 return ResourcesCompat.getDrawable(res, mInt1, context.getTheme()); 440 } catch (RuntimeException e) { 441 Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s", 442 mInt1, 443 mObj1), 444 e); 445 } 446 break; 447 case TYPE_DATA: 448 return new BitmapDrawable(context.getResources(), 449 BitmapFactory.decodeByteArray((byte[]) mObj1, mInt1, mInt2) 450 ); 451 case TYPE_URI: 452 final Uri uri = Uri.parse((String) mObj1); 453 final String scheme = uri.getScheme(); 454 InputStream is = null; 455 if (ContentResolver.SCHEME_CONTENT.equals(scheme) 456 || ContentResolver.SCHEME_FILE.equals(scheme)) { 457 try { 458 is = context.getContentResolver().openInputStream(uri); 459 } catch (Exception e) { 460 Log.w(TAG, "Unable to load image from URI: " + uri, e); 461 } 462 } else { 463 try { 464 is = new FileInputStream(new File((String) mObj1)); 465 } catch (FileNotFoundException e) { 466 Log.w(TAG, "Unable to load image from path: " + uri, e); 467 } 468 } 469 if (is != null) { 470 return new BitmapDrawable(context.getResources(), 471 BitmapFactory.decodeStream(is)); 472 } 473 break; 474 } 475 return null; 476 } 477 478 /** 479 * @hide 480 */ 481 @RestrictTo(LIBRARY_GROUP) 482 @SuppressWarnings("deprecation") addToShortcutIntent(@onNull Intent outIntent, @Nullable Drawable badge, @NonNull Context c)483 public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge, 484 @NonNull Context c) { 485 Bitmap icon; 486 switch (mType) { 487 case TYPE_BITMAP: 488 icon = (Bitmap) mObj1; 489 if (badge != null) { 490 // Do not modify the original icon when applying a badge 491 icon = icon.copy(icon.getConfig(), true); 492 } 493 break; 494 case TYPE_ADAPTIVE_BITMAP: 495 icon = createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true); 496 break; 497 case TYPE_RESOURCE: 498 try { 499 Context context = c.createPackageContext((String) mObj1, 0); 500 if (badge == null) { 501 outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 502 Intent.ShortcutIconResource.fromContext(context, mInt1)); 503 return; 504 } else { 505 Drawable dr = ContextCompat.getDrawable(context, mInt1); 506 if (dr.getIntrinsicWidth() <= 0 || dr.getIntrinsicHeight() <= 0) { 507 int size = ((ActivityManager) context.getSystemService( 508 Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize(); 509 icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 510 } else { 511 icon = Bitmap.createBitmap(dr.getIntrinsicWidth(), 512 dr.getIntrinsicHeight(), 513 Bitmap.Config.ARGB_8888); 514 } 515 dr.setBounds(0, 0, icon.getWidth(), icon.getHeight()); 516 dr.draw(new Canvas(icon)); 517 } 518 } catch (PackageManager.NameNotFoundException e) { 519 throw new IllegalArgumentException("Can't find package " + mObj1, e); 520 } 521 break; 522 default: 523 throw new IllegalArgumentException("Icon type not supported for intent shortcuts"); 524 } 525 if (badge != null) { 526 // Badge the icon 527 int w = icon.getWidth(); 528 int h = icon.getHeight(); 529 badge.setBounds(w / 2, h / 2, w, h); 530 badge.draw(new Canvas(icon)); 531 } 532 outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon); 533 } 534 535 /** 536 * Adds this Icon to a Bundle that can be read back with the same parameters 537 * to {@link #createFromBundle(Bundle)}. 538 */ toBundle()539 public Bundle toBundle() { 540 Bundle bundle = new Bundle(); 541 switch (mType) { 542 case TYPE_BITMAP: 543 case TYPE_ADAPTIVE_BITMAP: 544 bundle.putParcelable(EXTRA_OBJ, (Bitmap) mObj1); 545 break; 546 case TYPE_UNKOWN: 547 // When unknown just wrapping an Icon. 548 bundle.putParcelable(EXTRA_OBJ, (Parcelable) mObj1); 549 break; 550 case TYPE_RESOURCE: 551 case TYPE_URI: 552 bundle.putString(EXTRA_OBJ, (String) mObj1); 553 break; 554 case TYPE_DATA: 555 bundle.putByteArray(EXTRA_OBJ, (byte[]) mObj1); 556 break; 557 default: 558 throw new IllegalArgumentException("Invalid icon"); 559 } 560 bundle.putInt(EXTRA_TYPE, mType); 561 bundle.putInt(EXTRA_INT1, mInt1); 562 bundle.putInt(EXTRA_INT2, mInt2); 563 if (mTintList != null) { 564 bundle.putParcelable(EXTRA_TINT_LIST, mTintList); 565 } 566 if (mTintMode != DEFAULT_TINT_MODE) { 567 bundle.putString(EXTRA_TINT_MODE, mTintMode.name()); 568 } 569 return bundle; 570 } 571 572 @Override toString()573 public String toString() { 574 if (mType == TYPE_UNKOWN) { 575 return String.valueOf(mObj1); 576 } 577 final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType)); 578 switch (mType) { 579 case TYPE_BITMAP: 580 case TYPE_ADAPTIVE_BITMAP: 581 sb.append(" size=") 582 .append(((Bitmap) mObj1).getWidth()) 583 .append("x") 584 .append(((Bitmap) mObj1).getHeight()); 585 break; 586 case TYPE_RESOURCE: 587 sb.append(" pkg=") 588 .append(getResPackage()) 589 .append(" id=") 590 .append(String.format("0x%08x", getResId())); 591 break; 592 case TYPE_DATA: 593 sb.append(" len=").append(mInt1); 594 if (mInt2 != 0) { 595 sb.append(" off=").append(mInt2); 596 } 597 break; 598 case TYPE_URI: 599 sb.append(" uri=").append(mObj1); 600 break; 601 } 602 if (mTintList != null) { 603 sb.append(" tint="); 604 sb.append(mTintList); 605 } 606 if (mTintMode != DEFAULT_TINT_MODE) { 607 sb.append(" mode=").append(mTintMode); 608 } 609 sb.append(")"); 610 return sb.toString(); 611 } 612 typeToString(int x)613 private static String typeToString(int x) { 614 switch (x) { 615 case TYPE_BITMAP: return "BITMAP"; 616 case TYPE_ADAPTIVE_BITMAP: return "BITMAP_MASKABLE"; 617 case TYPE_DATA: return "DATA"; 618 case TYPE_RESOURCE: return "RESOURCE"; 619 case TYPE_URI: return "URI"; 620 default: return "UNKNOWN"; 621 } 622 } 623 624 /** 625 * Extracts an icon from a bundle that was added using {@link #toBundle()}. 626 */ createFromBundle(@onNull Bundle bundle)627 public static @Nullable IconCompat createFromBundle(@NonNull Bundle bundle) { 628 int type = bundle.getInt(EXTRA_TYPE); 629 IconCompat icon = new IconCompat(type); 630 icon.mInt1 = bundle.getInt(EXTRA_INT1); 631 icon.mInt2 = bundle.getInt(EXTRA_INT2); 632 if (bundle.containsKey(EXTRA_TINT_LIST)) { 633 icon.mTintList = bundle.getParcelable(EXTRA_TINT_LIST); 634 } 635 if (bundle.containsKey(EXTRA_TINT_MODE)) { 636 icon.mTintMode = PorterDuff.Mode.valueOf( 637 bundle.getString(EXTRA_TINT_MODE)); 638 } 639 switch (type) { 640 case TYPE_BITMAP: 641 case TYPE_ADAPTIVE_BITMAP: 642 case TYPE_UNKOWN: 643 icon.mObj1 = bundle.getParcelable(EXTRA_OBJ); 644 break; 645 case TYPE_RESOURCE: 646 case TYPE_URI: 647 icon.mObj1 = bundle.getString(EXTRA_OBJ); 648 break; 649 case TYPE_DATA: 650 icon.mObj1 = bundle.getByteArray(EXTRA_OBJ); 651 break; 652 default: 653 Log.w(TAG, "Unknown type " + type); 654 return null; 655 } 656 return icon; 657 } 658 659 /** 660 * Creates an IconCompat from an Icon. 661 */ 662 @RequiresApi(23) 663 @Nullable createFromIcon(@onNull Icon icon)664 public static IconCompat createFromIcon(@NonNull Icon icon) { 665 IconCompat iconCompat = new IconCompat(TYPE_UNKOWN); 666 iconCompat.mObj1 = icon; 667 return iconCompat; 668 } 669 670 /** 671 * Gets the type of the icon provided. 672 * <p> 673 * Note that new types may be added later, so callers should guard against other 674 * types being returned. Returns {@link #TYPE_UNKOWN} when the type cannot be 675 * determined. 676 */ 677 @IconType 678 @RequiresApi(23) getType(@onNull Icon icon)679 public static int getType(@NonNull Icon icon) { 680 if (BuildCompat.isAtLeastP()) { 681 return icon.getType(); 682 } 683 try { 684 return (int) icon.getClass().getMethod("getType").invoke(icon); 685 } catch (IllegalAccessException e) { 686 Log.e(TAG, "Unable to get icon type " + icon, e); 687 return TYPE_UNKOWN; 688 } catch (InvocationTargetException e) { 689 Log.e(TAG, "Unable to get icon type " + icon, e); 690 return TYPE_UNKOWN; 691 } catch (NoSuchMethodException e) { 692 Log.e(TAG, "Unable to get icon type " + icon, e); 693 return TYPE_UNKOWN; 694 } 695 } 696 697 /** 698 * Gets the package used to create this icon. 699 * <p> 700 * Only valid for icons of type TYPE_RESOURCE. 701 * Note: This package may not be available if referenced in the future, and it is 702 * up to the caller to ensure safety if this package is re-used and/or persisted. 703 * Returns {@code null} when the value cannot be gotten. 704 */ 705 @Nullable 706 @RequiresApi(23) getResPackage(@onNull Icon icon)707 public static String getResPackage(@NonNull Icon icon) { 708 if (BuildCompat.isAtLeastP()) { 709 return icon.getResPackage(); 710 } 711 try { 712 return (String) icon.getClass().getMethod("getResPackage").invoke(icon); 713 } catch (IllegalAccessException e) { 714 Log.e(TAG, "Unable to get icon package", e); 715 return null; 716 } catch (InvocationTargetException e) { 717 Log.e(TAG, "Unable to get icon package", e); 718 return null; 719 } catch (NoSuchMethodException e) { 720 Log.e(TAG, "Unable to get icon package", e); 721 return null; 722 } 723 } 724 725 /** 726 * Gets the resource used to create this icon. 727 * <p> 728 * Only valid for icons of type TYPE_RESOURCE. 729 * Note: This resource may not be available if the application changes at all, and it is 730 * up to the caller to ensure safety if this resource is re-used and/or persisted. 731 * Returns {@code 0} if the id cannot be gotten. 732 */ 733 @IdRes 734 @RequiresApi(23) getResId(@onNull Icon icon)735 public static int getResId(@NonNull Icon icon) { 736 if (BuildCompat.isAtLeastP()) { 737 return icon.getResId(); 738 } 739 try { 740 return (int) icon.getClass().getMethod("getResId").invoke(icon); 741 } catch (IllegalAccessException e) { 742 Log.e(TAG, "Unable to get icon resource", e); 743 return 0; 744 } catch (InvocationTargetException e) { 745 Log.e(TAG, "Unable to get icon resource", e); 746 return 0; 747 } catch (NoSuchMethodException e) { 748 Log.e(TAG, "Unable to get icon resource", e); 749 return 0; 750 } 751 } 752 753 /** 754 * Gets the uri used to create this icon. 755 * <p> 756 * Only valid for icons of type TYPE_URI. 757 * Note: This uri may not be available in the future, and it is 758 * up to the caller to ensure safety if this uri is re-used and/or persisted. 759 * Returns {@code null} if the uri cannot be gotten. 760 */ 761 @Nullable 762 @RequiresApi(23) getUri(@onNull Icon icon)763 public Uri getUri(@NonNull Icon icon) { 764 if (BuildCompat.isAtLeastP()) { 765 return icon.getUri(); 766 } 767 try { 768 return (Uri) icon.getClass().getMethod("getUri").invoke(icon); 769 } catch (IllegalAccessException e) { 770 Log.e(TAG, "Unable to get icon uri", e); 771 return null; 772 } catch (InvocationTargetException e) { 773 Log.e(TAG, "Unable to get icon uri", e); 774 return null; 775 } catch (NoSuchMethodException e) { 776 Log.e(TAG, "Unable to get icon uri", e); 777 return null; 778 } 779 } 780 781 /** 782 * Converts a bitmap following the adaptive icon guide lines, into a bitmap following the 783 * shortcut icon guide lines. 784 * The returned bitmap will always have same width and height and clipped to a circle. 785 * 786 * @param addShadow set to {@code true} only for legacy shortcuts and {@code false} otherwise 787 */ 788 @VisibleForTesting createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow)789 static Bitmap createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow) { 790 int size = (int) (DEFAULT_VIEW_PORT_SCALE * Math.min(adaptiveIconBitmap.getWidth(), 791 adaptiveIconBitmap.getHeight())); 792 793 Bitmap icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 794 Canvas canvas = new Canvas(icon); 795 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 796 797 float center = size * 0.5f; 798 float radius = center * ICON_DIAMETER_FACTOR; 799 800 if (addShadow) { 801 // Draw key shadow 802 float blur = BLUR_FACTOR * size; 803 paint.setColor(Color.TRANSPARENT); 804 paint.setShadowLayer(blur, 0, KEY_SHADOW_OFFSET_FACTOR * size, KEY_SHADOW_ALPHA << 24); 805 canvas.drawCircle(center, center, radius, paint); 806 807 // Draw ambient shadow 808 paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24); 809 canvas.drawCircle(center, center, radius, paint); 810 paint.clearShadowLayer(); 811 } 812 813 // Draw the clipped icon 814 paint.setColor(Color.BLACK); 815 BitmapShader shader = new BitmapShader(adaptiveIconBitmap, Shader.TileMode.CLAMP, 816 Shader.TileMode.CLAMP); 817 Matrix shift = new Matrix(); 818 shift.setTranslate(-(adaptiveIconBitmap.getWidth() - size) / 2, 819 -(adaptiveIconBitmap.getHeight() - size) / 2); 820 shader.setLocalMatrix(shift); 821 paint.setShader(shader); 822 canvas.drawCircle(center, center, radius, paint); 823 824 canvas.setBitmap(null); 825 return icon; 826 } 827 } 828