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 androidx.annotation.RestrictTo.Scope.LIBRARY; 20 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 21 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; 22 23 import android.app.ActivityManager; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.content.res.ColorStateList; 30 import android.content.res.Resources; 31 import android.graphics.Bitmap; 32 import android.graphics.BitmapFactory; 33 import android.graphics.BitmapShader; 34 import android.graphics.Canvas; 35 import android.graphics.Color; 36 import android.graphics.Matrix; 37 import android.graphics.Paint; 38 import android.graphics.PorterDuff; 39 import android.graphics.Shader; 40 import android.graphics.drawable.AdaptiveIconDrawable; 41 import android.graphics.drawable.BitmapDrawable; 42 import android.graphics.drawable.Drawable; 43 import android.graphics.drawable.Icon; 44 import android.net.Uri; 45 import android.os.Build; 46 import android.os.Bundle; 47 import android.os.Parcelable; 48 import android.text.TextUtils; 49 import android.util.Log; 50 51 import androidx.annotation.ColorInt; 52 import androidx.annotation.DrawableRes; 53 import androidx.annotation.IdRes; 54 import androidx.annotation.IntDef; 55 import androidx.annotation.RequiresApi; 56 import androidx.annotation.RestrictTo; 57 import androidx.annotation.VisibleForTesting; 58 import androidx.core.content.ContextCompat; 59 import androidx.core.content.res.ResourcesCompat; 60 import androidx.core.util.ObjectsCompat; 61 import androidx.core.util.Preconditions; 62 import androidx.versionedparcelable.CustomVersionedParcelable; 63 import androidx.versionedparcelable.NonParcelField; 64 import androidx.versionedparcelable.ParcelField; 65 import androidx.versionedparcelable.VersionedParcelize; 66 67 import org.jspecify.annotations.NonNull; 68 import org.jspecify.annotations.Nullable; 69 70 import java.io.ByteArrayOutputStream; 71 import java.io.File; 72 import java.io.FileInputStream; 73 import java.io.FileNotFoundException; 74 import java.io.InputStream; 75 import java.lang.annotation.Retention; 76 import java.lang.annotation.RetentionPolicy; 77 import java.lang.reflect.InvocationTargetException; 78 import java.nio.charset.Charset; 79 80 /** 81 * Helper for accessing features in {@link android.graphics.drawable.Icon}. 82 */ 83 @VersionedParcelize(allowSerialization = true, ignoreParcelables = true, isCustom = true, 84 jetifyAs = "android.support.v4.graphics.drawable.IconCompat") 85 public class IconCompat extends CustomVersionedParcelable { 86 87 private static final String TAG = "IconCompat"; 88 89 /** 90 * Value returned when the type of an {@link Icon} cannot be determined. 91 */ 92 public static final int TYPE_UNKNOWN = -1; 93 /** 94 * An icon that was created using {@link #createWithBitmap(Bitmap)}. 95 */ 96 public static final int TYPE_BITMAP = Icon.TYPE_BITMAP; 97 /** 98 * An icon that was created using {@link #createWithResource}. 99 */ 100 public static final int TYPE_RESOURCE = Icon.TYPE_RESOURCE; 101 /** 102 * An icon that was created using {@link #createWithData(byte[], int, int)}. 103 */ 104 public static final int TYPE_DATA = Icon.TYPE_DATA; 105 /** 106 * An icon that was created using {@link #createWithContentUri}. 107 */ 108 public static final int TYPE_URI = Icon.TYPE_URI; 109 /** 110 * An icon that was created using {@link #createWithAdaptiveBitmap}. 111 */ 112 public static final int TYPE_ADAPTIVE_BITMAP = Icon.TYPE_ADAPTIVE_BITMAP; 113 114 /** 115 * An icon that was created using {@link #createWithAdaptiveBitmapContentUri}. 116 */ 117 public static final int TYPE_URI_ADAPTIVE_BITMAP = Icon.TYPE_URI_ADAPTIVE_BITMAP; 118 119 /** 120 */ 121 @RestrictTo(LIBRARY) 122 @IntDef({TYPE_UNKNOWN, TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP, 123 TYPE_URI_ADAPTIVE_BITMAP}) 124 @Retention(RetentionPolicy.SOURCE) 125 public @interface IconType { 126 } 127 128 // Ratio of expected size to actual icon size 129 private static final float ADAPTIVE_ICON_INSET_FACTOR = 1 / 4f; 130 private static final float DEFAULT_VIEW_PORT_SCALE = 1 / (1 + 2 * ADAPTIVE_ICON_INSET_FACTOR); 131 private static final float ICON_DIAMETER_FACTOR = 176f / 192; 132 private static final float BLUR_FACTOR = 0.5f / 48; 133 private static final float KEY_SHADOW_OFFSET_FACTOR = 1f / 48; 134 135 private static final int KEY_SHADOW_ALPHA = 61; 136 private static final int AMBIENT_SHADOW_ALPHA = 30; 137 138 @VisibleForTesting 139 static final String EXTRA_TYPE = "type"; 140 @VisibleForTesting 141 static final String EXTRA_OBJ = "obj"; 142 @VisibleForTesting 143 static final String EXTRA_INT1 = "int1"; 144 @VisibleForTesting 145 static final String EXTRA_INT2 = "int2"; 146 @VisibleForTesting 147 static final String EXTRA_TINT_LIST = "tint_list"; 148 @VisibleForTesting 149 static final String EXTRA_TINT_MODE = "tint_mode"; 150 @VisibleForTesting 151 static final String EXTRA_STRING1 = "string1"; 152 153 /** 154 */ 155 @RestrictTo(LIBRARY_GROUP_PREFIX) 156 @ParcelField(value = 1, 157 defaultValue = "androidx.core.graphics.drawable.IconCompat.TYPE_UNKNOWN") 158 public int mType = TYPE_UNKNOWN; 159 160 // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed 161 // based on the value of mType. 162 163 // TYPE_BITMAP: Bitmap 164 // TYPE_ADAPTIVE_BITMAP: Bitmap 165 // TYPE_RESOURCE: String 166 // TYPE_URI: String 167 // TYPE_DATA: DataBytes 168 @NonParcelField 169 Object mObj1; 170 171 /** 172 */ 173 @RestrictTo(LIBRARY) 174 @ParcelField(value = 2, defaultValue = "null") 175 public byte @Nullable [] mData = null; 176 /** 177 */ 178 @RestrictTo(LIBRARY) 179 @ParcelField(value = 3, defaultValue = "null") 180 public @Nullable Parcelable mParcelable = null; 181 182 // TYPE_RESOURCE: resId 183 // TYPE_DATA: data offset 184 /** 185 */ 186 @RestrictTo(LIBRARY) 187 @ParcelField(value = 4, defaultValue = "0") 188 public int mInt1 = 0; 189 190 // TYPE_DATA: data length 191 /** 192 */ 193 @RestrictTo(LIBRARY) 194 @ParcelField(value = 5, defaultValue = "0") 195 public int mInt2 = 0; 196 197 /** 198 */ 199 @RestrictTo(LIBRARY) 200 @ParcelField(value = 6, defaultValue = "null") 201 public @Nullable ColorStateList mTintList = null; 202 203 static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; // SRC_IN 204 @NonParcelField 205 PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 206 /** 207 */ 208 @RestrictTo(LIBRARY) 209 @ParcelField(value = 7, defaultValue = "null") 210 public @Nullable String mTintModeStr = null; 211 212 /** 213 */ 214 @RestrictTo(LIBRARY) 215 @ParcelField(value = 8, defaultValue = "null") 216 public @Nullable String mString1; 217 218 /** 219 * Create an Icon pointing to a drawable resource. 220 * @param context The context for the application whose resources should be used to resolve the 221 * given resource ID. 222 * @param resId ID of the drawable resource 223 * @see android.graphics.drawable.Icon#createWithResource(Context, int) 224 */ createWithResource(@onNull Context context, @DrawableRes int resId)225 public static @NonNull IconCompat createWithResource(@NonNull Context context, 226 @DrawableRes int resId) { 227 ObjectsCompat.requireNonNull(context); 228 return createWithResource(context.getResources(), context.getPackageName(), resId); 229 } 230 231 /** 232 */ 233 @RestrictTo(LIBRARY_GROUP_PREFIX) createWithResource(@ullable Resources r, @NonNull String pkg, @DrawableRes int resId)234 public static @NonNull IconCompat createWithResource(@Nullable Resources r, 235 @NonNull String pkg, 236 @DrawableRes int resId) { 237 ObjectsCompat.requireNonNull(pkg); 238 if (resId == 0) { 239 throw new IllegalArgumentException("Drawable resource ID must not be 0"); 240 } 241 final IconCompat rep = new IconCompat(TYPE_RESOURCE); 242 rep.mInt1 = resId; 243 if (r != null) { 244 try { 245 rep.mObj1 = r.getResourceName(resId); 246 } catch (Resources.NotFoundException e) { 247 throw new IllegalArgumentException("Icon resource cannot be found"); 248 } 249 } else { 250 rep.mObj1 = pkg; 251 } 252 rep.mString1 = pkg; 253 return rep; 254 } 255 256 /** 257 * Create an Icon pointing to a bitmap in memory. 258 * @param bits A valid {@link android.graphics.Bitmap} object 259 * @see android.graphics.drawable.Icon#createWithBitmap(Bitmap) 260 */ createWithBitmap(@onNull Bitmap bits)261 public static @NonNull IconCompat createWithBitmap(@NonNull Bitmap bits) { 262 ObjectsCompat.requireNonNull(bits); 263 final IconCompat rep = new IconCompat(TYPE_BITMAP); 264 rep.mObj1 = bits; 265 return rep; 266 } 267 268 /** 269 * Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined 270 * by {@link android.graphics.drawable.AdaptiveIconDrawable}. 271 * @param bits A valid {@link android.graphics.Bitmap} object 272 * @see android.graphics.drawable.Icon#createWithAdaptiveBitmap(Bitmap) 273 */ createWithAdaptiveBitmap(@onNull Bitmap bits)274 public static @NonNull IconCompat createWithAdaptiveBitmap(@NonNull Bitmap bits) { 275 ObjectsCompat.requireNonNull(bits); 276 final IconCompat rep = new IconCompat(TYPE_ADAPTIVE_BITMAP); 277 rep.mObj1 = bits; 278 return rep; 279 } 280 281 /** 282 * Create an Icon pointing to a compressed bitmap stored in a byte array. 283 * @param data Byte array storing compressed bitmap data of a type that 284 * {@link android.graphics.BitmapFactory} 285 * can decode (see {@link android.graphics.Bitmap.CompressFormat}). 286 * @param offset Offset into <code>data</code> at which the bitmap data starts 287 * @param length Length of the bitmap data 288 * @see android.graphics.drawable.Icon#createWithData(byte[], int, int) 289 */ createWithData(byte @NonNull [] data, int offset, int length)290 public static @NonNull IconCompat createWithData(byte @NonNull [] data, int offset, 291 int length) { 292 ObjectsCompat.requireNonNull(data); 293 final IconCompat rep = new IconCompat(TYPE_DATA); 294 rep.mObj1 = data; 295 rep.mInt1 = offset; 296 rep.mInt2 = length; 297 return rep; 298 } 299 300 /** 301 * Create an Icon pointing to an image file specified by URI. 302 * 303 * @param uri A uri referring to local content:// or file:// image data. 304 * @see android.graphics.drawable.Icon#createWithContentUri(String) 305 */ createWithContentUri(@onNull String uri)306 public static @NonNull IconCompat createWithContentUri(@NonNull String uri) { 307 ObjectsCompat.requireNonNull(uri); 308 final IconCompat rep = new IconCompat(TYPE_URI); 309 rep.mObj1 = uri; 310 return rep; 311 } 312 313 /** 314 * Create an Icon pointing to an image file specified by URI. 315 * 316 * @param uri A uri referring to local content:// or file:// image data. 317 * @see android.graphics.drawable.Icon#createWithContentUri(String) 318 */ createWithContentUri(@onNull Uri uri)319 public static @NonNull IconCompat createWithContentUri(@NonNull Uri uri) { 320 ObjectsCompat.requireNonNull(uri); 321 return createWithContentUri(uri.toString()); 322 } 323 324 /** 325 * Create an Icon pointing to an image file specified by URI. Image file should follow the icon 326 * design guideline defined by {@link AdaptiveIconDrawable}. 327 * 328 * @param uri A uri referring to local content:// or file:// image data. 329 * @see android.graphics.drawable.Icon#createWithAdaptiveBitmapContentUri(String) 330 */ createWithAdaptiveBitmapContentUri(@onNull String uri)331 public static @NonNull IconCompat createWithAdaptiveBitmapContentUri(@NonNull String uri) { 332 ObjectsCompat.requireNonNull(uri); 333 final IconCompat rep = new IconCompat(TYPE_URI_ADAPTIVE_BITMAP); 334 rep.mObj1 = uri; 335 return rep; 336 } 337 338 /** 339 * Create an Icon pointing to an image file specified by URI. Image file should follow the icon 340 * design guideline defined by {@link AdaptiveIconDrawable}. 341 * 342 * @param uri A uri referring to local content:// or file:// image data. 343 * @see android.graphics.drawable.Icon#createWithAdaptiveBitmapContentUri(String) 344 */ createWithAdaptiveBitmapContentUri(@onNull Uri uri)345 public static @NonNull IconCompat createWithAdaptiveBitmapContentUri(@NonNull Uri uri) { 346 ObjectsCompat.requireNonNull(uri); 347 return createWithAdaptiveBitmapContentUri(uri.toString()); 348 } 349 350 /** 351 * Used for VersionedParcelable. 352 */ 353 @RestrictTo(LIBRARY) IconCompat()354 public IconCompat() { 355 } 356 IconCompat(int mType)357 IconCompat(int mType) { 358 this.mType = mType; 359 } 360 361 /** 362 * Gets the type of the icon provided. 363 * <p> 364 * Note that new types may be added later, so callers should guard against other 365 * types being returned. 366 */ 367 @IconType getType()368 public int getType() { 369 if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) { 370 return Api23Impl.getType(mObj1); 371 } 372 return mType; 373 } 374 375 /** 376 * Gets the package used to create this icon. 377 * <p> 378 * Only valid for icons of type TYPE_RESOURCE. 379 * Note: This package may not be available if referenced in the future, and it is 380 * up to the caller to ensure safety if this package is re-used and/or persisted. 381 */ getResPackage()382 public @NonNull String getResPackage() { 383 if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) { 384 return Api23Impl.getResPackage(mObj1); 385 } 386 if (mType != TYPE_RESOURCE) { 387 throw new IllegalStateException("called getResPackage() on " + this); 388 } 389 // Before aosp/1307777, we don't put the package name to mString1. Try to get the 390 // package name from the full resource name string. Note that this is not always the same 391 // as "the package used to create this icon" and this was what aosp/1307777 tried to fix. 392 if (mString1 == null || TextUtils.isEmpty(mString1)) { 393 return ((String) mObj1).split(":", -1)[0]; 394 } else { 395 // The name of the getResPackage() API is a bit confusing. It actually returns 396 // the app package name rather than the package name in the resource table. 397 return mString1; 398 } 399 } 400 401 /** 402 * Gets the drawable resource id used to create this icon. 403 * <p> 404 * Only valid for icons of type TYPE_RESOURCE. 405 * Note: This resource may not be available if the application changes at all, and it is 406 * up to the caller to ensure safety if this resource is re-used and/or persisted. 407 */ 408 @DrawableRes getResId()409 public int getResId() { 410 if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) { 411 return Api23Impl.getResId(mObj1); 412 } 413 if (mType != TYPE_RESOURCE) { 414 throw new IllegalStateException("called getResId() on " + this); 415 } 416 return mInt1; 417 } 418 419 /** 420 * Gets the bitmap used to create this icon. 421 * <p> 422 * Only valid for icons of type TYPE_BITMAP. 423 * Note: This bitmap may not be available in the future, and it is 424 * up to the caller to ensure safety if this bitmap is re-used and/or persisted. 425 * 426 */ 427 @RestrictTo(LIBRARY_GROUP_PREFIX) getBitmap()428 public @Nullable Bitmap getBitmap() { 429 if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) { 430 if (mObj1 instanceof Bitmap) { 431 return (Bitmap) mObj1; 432 } 433 return null; 434 } 435 if (mType == TYPE_BITMAP) { 436 return (Bitmap) mObj1; 437 } else if (mType == TYPE_ADAPTIVE_BITMAP) { 438 return createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true); 439 } else { 440 throw new IllegalStateException("called getBitmap() on " + this); 441 } 442 } 443 444 /** 445 * Gets the uri used to create this icon. 446 * <p> 447 * Only valid for icons of type TYPE_URI. 448 * Note: This uri may not be available in the future, and it is 449 * up to the caller to ensure safety if this uri is re-used and/or persisted. 450 */ getUri()451 public @NonNull Uri getUri() { 452 if (mType == TYPE_UNKNOWN && Build.VERSION.SDK_INT >= 23) { 453 return Api23Impl.getUri(mObj1); 454 } 455 if (mType != TYPE_URI && mType != TYPE_URI_ADAPTIVE_BITMAP) { 456 throw new IllegalStateException("called getUri() on " + this); 457 } 458 return Uri.parse((String) mObj1); 459 } 460 461 /** 462 * Store a color to use whenever this Icon is drawn. 463 * 464 * @param tint a color, as in {@link Drawable#setTint(int)} 465 * @return this same object, for use in chained construction 466 */ setTint(@olorInt int tint)467 public @NonNull IconCompat setTint(@ColorInt int tint) { 468 return setTintList(ColorStateList.valueOf(tint)); 469 } 470 471 /** 472 * Store a color to use whenever this Icon is drawn. 473 * 474 * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint 475 * @return this same object, for use in chained construction 476 */ setTintList(@ullable ColorStateList tintList)477 public @NonNull IconCompat setTintList(@Nullable ColorStateList tintList) { 478 mTintList = tintList; 479 return this; 480 } 481 482 /** 483 * Store a blending mode to use whenever this Icon is drawn. 484 * 485 * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null 486 * @return this same object, for use in chained construction 487 */ setTintMode(PorterDuff.@ullable Mode mode)488 public @NonNull IconCompat setTintMode(PorterDuff.@Nullable Mode mode) { 489 mTintMode = mode; 490 return this; 491 } 492 493 /** 494 * @deprecated Use {@link #toIcon(Context)} to generate the {@link Icon} object. 495 */ 496 @RequiresApi(23) 497 @Deprecated toIcon()498 public @NonNull Icon toIcon() { 499 return toIcon(null); 500 } 501 502 /** 503 * Convert this compat object to {@link Icon} object. 504 * 505 * @return {@link Icon} object 506 */ 507 @RequiresApi(23) toIcon(@ullable Context context)508 public @NonNull Icon toIcon(@Nullable Context context) { 509 if (Build.VERSION.SDK_INT >= 23) { 510 return Api23Impl.toIcon(this, context); 511 } else { 512 throw new UnsupportedOperationException( 513 "This method is only supported on API level 23+"); 514 } 515 } 516 517 /** 518 */ 519 @RestrictTo(LIBRARY_GROUP_PREFIX) checkResource(@onNull Context context)520 public void checkResource(@NonNull Context context) { 521 if (mType == TYPE_RESOURCE && mObj1 != null) { 522 String fullResName = (String) mObj1; 523 if (!fullResName.contains(":")) { 524 return; 525 } 526 // Do some splitting to parse out each of the components. 527 String resName = fullResName.split(":", -1)[1]; 528 String resType = resName.split("/", -1)[0]; 529 resName = resName.split("/", -1)[1]; 530 String resPackage = fullResName.split(":", -1)[0]; 531 if ("0_resource_name_obfuscated".equals(resName)) { 532 // All obfuscated resources have the same name, so not going to look up the 533 // resource identifier from the resource name. 534 Log.i(TAG, "Found obfuscated resource, not trying to update resource id for it"); 535 return; 536 } 537 String appPackage = getResPackage(); 538 Resources res = getResources(context, appPackage); 539 int id = res.getIdentifier(resName, resType, resPackage); 540 if (mInt1 != id) { 541 Log.i(TAG, "Id has changed for " + appPackage + " " + fullResName); 542 mInt1 = id; 543 } 544 } 545 } 546 547 /** 548 * Returns a Drawable that can be used to draw the image inside this Icon, constructing it 549 * if necessary. 550 * 551 * @param context {@link android.content.Context Context} in which to load the drawable; used 552 * to access {@link android.content.res.Resources Resources}, for example. 553 * @return A fresh instance of a drawable for this image, yours to keep. 554 */ loadDrawable(@onNull Context context)555 public @Nullable Drawable loadDrawable(@NonNull Context context) { 556 checkResource(context); 557 if (Build.VERSION.SDK_INT >= 23) { 558 return Api23Impl.loadDrawable(toIcon(context), context); 559 } 560 final Drawable result = loadDrawableInner(context); 561 if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) { 562 result.mutate(); 563 DrawableCompat.setTintList(result, mTintList); 564 DrawableCompat.setTintMode(result, mTintMode); 565 } 566 return result; 567 } 568 569 /** 570 * Do the heavy lifting of loading the drawable, but stop short of applying any tint. 571 */ loadDrawableInner(Context context)572 private Drawable loadDrawableInner(Context context) { 573 switch (mType) { 574 case TYPE_BITMAP: 575 return new BitmapDrawable(context.getResources(), (Bitmap) mObj1); 576 case TYPE_ADAPTIVE_BITMAP: 577 return new BitmapDrawable(context.getResources(), 578 createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, false)); 579 case TYPE_RESOURCE: 580 // figure out where to load resources from 581 String resPackage = getResPackage(); 582 if (TextUtils.isEmpty(resPackage)) { 583 // if none is specified, try the given context 584 resPackage = context.getPackageName(); 585 } 586 Resources res = getResources(context, resPackage); 587 try { 588 return ResourcesCompat.getDrawable(res, mInt1, context.getTheme()); 589 } catch (RuntimeException e) { 590 Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s", 591 mInt1, 592 mObj1), 593 e); 594 } 595 break; 596 case TYPE_DATA: 597 return new BitmapDrawable(context.getResources(), 598 BitmapFactory.decodeByteArray((byte[]) mObj1, mInt1, mInt2) 599 ); 600 case TYPE_URI: 601 InputStream is = getUriInputStream(context); 602 if (is != null) { 603 return new BitmapDrawable(context.getResources(), 604 BitmapFactory.decodeStream(is)); 605 } 606 break; 607 case TYPE_URI_ADAPTIVE_BITMAP: 608 is = getUriInputStream(context); 609 if (is != null) { 610 if (Build.VERSION.SDK_INT >= 26) { 611 return Api26Impl.createAdaptiveIconDrawable(null, 612 new BitmapDrawable(context.getResources(), 613 BitmapFactory.decodeStream(is))); 614 } else { 615 return new BitmapDrawable(context.getResources(), 616 createLegacyIconFromAdaptiveIcon( 617 BitmapFactory.decodeStream(is), false)); 618 } 619 } 620 break; 621 } 622 return null; 623 } 624 625 /** 626 * Create an input stream for bitmap by resolving corresponding content uri. 627 * 628 */ 629 @RestrictTo(LIBRARY_GROUP) getUriInputStream(@onNull Context context)630 public @Nullable InputStream getUriInputStream(@NonNull Context context) { 631 final Uri uri = getUri(); 632 final String scheme = uri.getScheme(); 633 if (ContentResolver.SCHEME_CONTENT.equals(scheme) 634 || ContentResolver.SCHEME_FILE.equals(scheme)) { 635 try { 636 return context.getContentResolver().openInputStream(uri); 637 } catch (Exception e) { 638 Log.w(TAG, "Unable to load image from URI: " + uri, e); 639 } 640 } else { 641 try { 642 return new FileInputStream(new File((String) mObj1)); 643 } catch (FileNotFoundException e) { 644 Log.w(TAG, "Unable to load image from path: " + uri, e); 645 } 646 } 647 return null; 648 } 649 650 @SuppressWarnings("deprecation") getResources(Context context, String resPackage)651 static Resources getResources(Context context, String resPackage) { 652 if ("android".equals(resPackage)) { 653 return Resources.getSystem(); 654 } else { 655 final PackageManager pm = context.getPackageManager(); 656 try { 657 ApplicationInfo ai = pm.getApplicationInfo( 658 resPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES); 659 if (ai != null) { 660 return pm.getResourcesForApplication(ai); 661 } else { 662 return null; 663 } 664 } catch (PackageManager.NameNotFoundException e) { 665 Log.e(TAG, String.format("Unable to find pkg=%s for icon", 666 resPackage), e); 667 return null; 668 } 669 } 670 } 671 672 673 /** 674 */ 675 @RestrictTo(LIBRARY_GROUP_PREFIX) 676 @SuppressWarnings("deprecation") addToShortcutIntent(@onNull Intent outIntent, @Nullable Drawable badge, @NonNull Context c)677 public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge, 678 @NonNull Context c) { 679 checkResource(c); 680 Bitmap icon; 681 switch (mType) { 682 case TYPE_BITMAP: 683 icon = (Bitmap) mObj1; 684 if (badge != null) { 685 // Do not modify the original icon when applying a badge 686 icon = icon.copy(icon.getConfig(), true); 687 } 688 break; 689 case TYPE_ADAPTIVE_BITMAP: 690 icon = createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true); 691 break; 692 case TYPE_RESOURCE: 693 try { 694 Context context = c.createPackageContext(getResPackage(), 0); 695 if (badge == null) { 696 outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 697 Intent.ShortcutIconResource.fromContext(context, mInt1)); 698 return; 699 } else { 700 Drawable dr = ContextCompat.getDrawable(context, mInt1); 701 if (dr.getIntrinsicWidth() <= 0 || dr.getIntrinsicHeight() <= 0) { 702 int size = ((ActivityManager) context.getSystemService( 703 Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize(); 704 icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 705 } else { 706 icon = Bitmap.createBitmap(dr.getIntrinsicWidth(), 707 dr.getIntrinsicHeight(), 708 Bitmap.Config.ARGB_8888); 709 } 710 dr.setBounds(0, 0, icon.getWidth(), icon.getHeight()); 711 dr.draw(new Canvas(icon)); 712 } 713 } catch (PackageManager.NameNotFoundException e) { 714 throw new IllegalArgumentException("Can't find package " + mObj1, e); 715 } 716 break; 717 default: 718 throw new IllegalArgumentException("Icon type not supported for intent shortcuts"); 719 } 720 if (badge != null) { 721 // Badge the icon 722 int w = icon.getWidth(); 723 int h = icon.getHeight(); 724 badge.setBounds(w / 2, h / 2, w, h); 725 badge.draw(new Canvas(icon)); 726 } 727 outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon); 728 } 729 730 /** 731 * Adds this Icon to a Bundle that can be read back with the same parameters 732 * to {@link #createFromBundle(Bundle)}. 733 */ toBundle()734 public @NonNull Bundle toBundle() { 735 Bundle bundle = new Bundle(); 736 switch (mType) { 737 case TYPE_BITMAP: 738 case TYPE_ADAPTIVE_BITMAP: 739 bundle.putParcelable(EXTRA_OBJ, (Bitmap) mObj1); 740 break; 741 case TYPE_UNKNOWN: 742 // When unknown just wrapping an Icon. 743 bundle.putParcelable(EXTRA_OBJ, (Parcelable) mObj1); 744 break; 745 case TYPE_RESOURCE: 746 case TYPE_URI: 747 case TYPE_URI_ADAPTIVE_BITMAP: 748 bundle.putString(EXTRA_OBJ, (String) mObj1); 749 break; 750 case TYPE_DATA: 751 bundle.putByteArray(EXTRA_OBJ, (byte[]) mObj1); 752 break; 753 default: 754 throw new IllegalArgumentException("Invalid icon"); 755 } 756 bundle.putInt(EXTRA_TYPE, mType); 757 bundle.putInt(EXTRA_INT1, mInt1); 758 bundle.putInt(EXTRA_INT2, mInt2); 759 bundle.putString(EXTRA_STRING1, mString1); 760 if (mTintList != null) { 761 bundle.putParcelable(EXTRA_TINT_LIST, mTintList); 762 } 763 if (mTintMode != DEFAULT_TINT_MODE) { 764 bundle.putString(EXTRA_TINT_MODE, mTintMode.name()); 765 } 766 return bundle; 767 } 768 769 @Override toString()770 public @NonNull String toString() { 771 if (mType == TYPE_UNKNOWN) { 772 return String.valueOf(mObj1); 773 } 774 final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType)); 775 switch (mType) { 776 case TYPE_BITMAP: 777 case TYPE_ADAPTIVE_BITMAP: 778 sb.append(" size=") 779 .append(((Bitmap) mObj1).getWidth()) 780 .append("x") 781 .append(((Bitmap) mObj1).getHeight()); 782 break; 783 case TYPE_RESOURCE: 784 sb.append(" pkg=") 785 .append(mString1) 786 .append(" id=") 787 .append(String.format("0x%08x", getResId())); 788 break; 789 case TYPE_DATA: 790 sb.append(" len=").append(mInt1); 791 if (mInt2 != 0) { 792 sb.append(" off=").append(mInt2); 793 } 794 break; 795 case TYPE_URI: 796 case TYPE_URI_ADAPTIVE_BITMAP: 797 sb.append(" uri=").append(mObj1); 798 break; 799 } 800 if (mTintList != null) { 801 sb.append(" tint="); 802 sb.append(mTintList); 803 } 804 if (mTintMode != DEFAULT_TINT_MODE) { 805 sb.append(" mode=").append(mTintMode); 806 } 807 sb.append(")"); 808 return sb.toString(); 809 } 810 811 @Override onPreParceling(boolean isStream)812 public void onPreParceling(boolean isStream) { 813 mTintModeStr = mTintMode.name(); 814 switch (mType) { 815 case TYPE_UNKNOWN: 816 if (isStream) { 817 // We can't determine how to serialize this icon, so throw so the caller knows. 818 throw new IllegalArgumentException("Can't serialize Icon created with " 819 + "IconCompat#createFromIcon"); 820 } else { 821 mParcelable = (Parcelable) mObj1; 822 } 823 break; 824 case TYPE_ADAPTIVE_BITMAP: 825 case TYPE_BITMAP: 826 if (isStream) { 827 Bitmap bitmap = (Bitmap) mObj1; 828 ByteArrayOutputStream data = new ByteArrayOutputStream(); 829 bitmap.compress(Bitmap.CompressFormat.PNG, 90, data); 830 mData = data.toByteArray(); 831 } else { 832 mParcelable = (Parcelable) mObj1; 833 } 834 break; 835 case TYPE_URI: 836 case TYPE_URI_ADAPTIVE_BITMAP: 837 mData = mObj1.toString().getBytes(Charset.forName("UTF-16")); 838 break; 839 case TYPE_RESOURCE: 840 mData = ((String) mObj1).getBytes(Charset.forName("UTF-16")); 841 break; 842 case TYPE_DATA: 843 mData = (byte[]) mObj1; 844 break; 845 } 846 } 847 848 @Override onPostParceling()849 public void onPostParceling() { 850 mTintMode = PorterDuff.Mode.valueOf(mTintModeStr); 851 switch (mType) { 852 case TYPE_UNKNOWN: 853 if (mParcelable != null) { 854 mObj1 = mParcelable; 855 } else { 856 throw new IllegalArgumentException("Invalid icon"); 857 } 858 break; 859 case TYPE_ADAPTIVE_BITMAP: 860 case TYPE_BITMAP: 861 if (mParcelable != null) { 862 mObj1 = mParcelable; 863 } else { 864 // This is data now. 865 mObj1 = mData; 866 mType = TYPE_DATA; 867 mInt1 = 0; 868 mInt2 = mData.length; 869 } 870 break; 871 case TYPE_URI: 872 case TYPE_URI_ADAPTIVE_BITMAP: 873 case TYPE_RESOURCE: 874 mObj1 = new String(mData, Charset.forName("UTF-16")); 875 // Slice, which may contain a IconCompat object, supports serialization to file. 876 // In the old format, we don't store the app package name separately. To keep 877 // the backward-compatibility, we have no choice but read the package name from the 878 // full resource name string. 879 if (mType == TYPE_RESOURCE) { 880 if (mString1 == null) { 881 mString1 = ((String) mObj1).split(":", -1)[0]; 882 } 883 } 884 break; 885 case TYPE_DATA: 886 mObj1 = mData; 887 break; 888 } 889 } 890 typeToString(int x)891 private static String typeToString(int x) { 892 switch (x) { 893 case TYPE_BITMAP: return "BITMAP"; 894 case TYPE_ADAPTIVE_BITMAP: return "BITMAP_MASKABLE"; 895 case TYPE_DATA: return "DATA"; 896 case TYPE_RESOURCE: return "RESOURCE"; 897 case TYPE_URI: return "URI"; 898 case TYPE_URI_ADAPTIVE_BITMAP: return "URI_MASKABLE"; 899 default: return "UNKNOWN"; 900 } 901 } 902 903 /** 904 * Extracts an icon from a bundle that was added using {@link #toBundle()}. 905 */ 906 @SuppressWarnings("deprecation") createFromBundle(@onNull Bundle bundle)907 public static @Nullable IconCompat createFromBundle(@NonNull Bundle bundle) { 908 int type = bundle.getInt(EXTRA_TYPE); 909 IconCompat icon = new IconCompat(type); 910 icon.mInt1 = bundle.getInt(EXTRA_INT1); 911 icon.mInt2 = bundle.getInt(EXTRA_INT2); 912 icon.mString1 = bundle.getString(EXTRA_STRING1); 913 if (bundle.containsKey(EXTRA_TINT_LIST)) { 914 icon.mTintList = bundle.getParcelable(EXTRA_TINT_LIST); 915 } 916 if (bundle.containsKey(EXTRA_TINT_MODE)) { 917 icon.mTintMode = PorterDuff.Mode.valueOf( 918 bundle.getString(EXTRA_TINT_MODE)); 919 } 920 switch (type) { 921 case TYPE_BITMAP: 922 case TYPE_ADAPTIVE_BITMAP: 923 case TYPE_UNKNOWN: 924 icon.mObj1 = bundle.getParcelable(EXTRA_OBJ); 925 break; 926 case TYPE_RESOURCE: 927 case TYPE_URI: 928 case TYPE_URI_ADAPTIVE_BITMAP: 929 icon.mObj1 = bundle.getString(EXTRA_OBJ); 930 break; 931 case TYPE_DATA: 932 icon.mObj1 = bundle.getByteArray(EXTRA_OBJ); 933 break; 934 default: 935 Log.w(TAG, "Unknown type " + type); 936 return null; 937 } 938 return icon; 939 } 940 941 /** 942 * Creates an IconCompat from an Icon. 943 */ 944 @RequiresApi(23) createFromIcon(@onNull Context context, @NonNull Icon icon)945 public static @NonNull IconCompat createFromIcon(@NonNull Context context, 946 @NonNull Icon icon) { 947 Preconditions.checkNotNull(icon); 948 return Api23Impl.createFromIcon(context, icon); 949 } 950 951 /** 952 * Creates an IconCompat from an Icon. 953 */ 954 @RestrictTo(LIBRARY_GROUP_PREFIX) 955 @RequiresApi(23) createFromIcon(@onNull Icon icon)956 public static @NonNull IconCompat createFromIcon(@NonNull Icon icon) { 957 return Api23Impl.createFromIconInner(icon); 958 } 959 960 /** 961 * Creates an IconCompat from an Icon, or returns null if the given Icon is created from 962 * resource 0. 963 */ 964 @RestrictTo(LIBRARY_GROUP_PREFIX) 965 @RequiresApi(23) createFromIconOrNullIfZeroResId(@onNull Icon icon)966 public static @Nullable IconCompat createFromIconOrNullIfZeroResId(@NonNull Icon icon) { 967 if (Api23Impl.getType(icon) == TYPE_RESOURCE && Api23Impl.getResId(icon) == 0) { 968 return null; 969 } 970 return Api23Impl.createFromIconInner(icon); 971 } 972 973 /** 974 * Converts a bitmap following the adaptive icon guide lines, into a bitmap following the 975 * shortcut icon guide lines. 976 * The returned bitmap will always have same width and height and clipped to a circle. 977 * 978 * @param addShadow set to {@code true} only for legacy shortcuts and {@code false} otherwise 979 */ 980 @VisibleForTesting createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow)981 static Bitmap createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow) { 982 int size = (int) (DEFAULT_VIEW_PORT_SCALE * Math.min(adaptiveIconBitmap.getWidth(), 983 adaptiveIconBitmap.getHeight())); 984 985 Bitmap icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 986 Canvas canvas = new Canvas(icon); 987 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 988 989 float center = size * 0.5f; 990 float radius = center * ICON_DIAMETER_FACTOR; 991 992 if (addShadow) { 993 // Draw key shadow 994 float blur = BLUR_FACTOR * size; 995 paint.setColor(Color.TRANSPARENT); 996 paint.setShadowLayer(blur, 0, KEY_SHADOW_OFFSET_FACTOR * size, KEY_SHADOW_ALPHA << 24); 997 canvas.drawCircle(center, center, radius, paint); 998 999 // Draw ambient shadow 1000 paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24); 1001 canvas.drawCircle(center, center, radius, paint); 1002 paint.clearShadowLayer(); 1003 } 1004 1005 // Draw the clipped icon 1006 paint.setColor(Color.BLACK); 1007 BitmapShader shader = new BitmapShader(adaptiveIconBitmap, Shader.TileMode.CLAMP, 1008 Shader.TileMode.CLAMP); 1009 Matrix shift = new Matrix(); 1010 shift.setTranslate(-(adaptiveIconBitmap.getWidth() - size) / 2.0f, 1011 -(adaptiveIconBitmap.getHeight() - size) / 2.0f); 1012 shader.setLocalMatrix(shift); 1013 paint.setShader(shader); 1014 canvas.drawCircle(center, center, radius, paint); 1015 1016 canvas.setBitmap(null); 1017 return icon; 1018 } 1019 1020 @RequiresApi(28) 1021 static class Api28Impl { Api28Impl()1022 private Api28Impl() { 1023 // This class is not instantiable. 1024 } 1025 getResPackage(Object icon)1026 static String getResPackage(Object icon) { 1027 return ((Icon) icon).getResPackage(); 1028 } 1029 getType(Object icon)1030 static int getType(Object icon) { 1031 return ((Icon) icon).getType(); 1032 } 1033 getResId(Object icon)1034 static int getResId(Object icon) { 1035 return ((Icon) icon).getResId(); 1036 } 1037 getUri(Object icon)1038 static Uri getUri(Object icon) { 1039 return ((Icon) icon).getUri(); 1040 } 1041 } 1042 1043 @RequiresApi(26) 1044 static class Api26Impl { Api26Impl()1045 private Api26Impl() { 1046 // This class is not instantiable. 1047 } 1048 createAdaptiveIconDrawable(Drawable backgroundDrawable, Drawable foregroundDrawable)1049 static Drawable createAdaptiveIconDrawable(Drawable backgroundDrawable, 1050 Drawable foregroundDrawable) { 1051 return new AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable); 1052 } 1053 createWithAdaptiveBitmap(Bitmap bits)1054 static Icon createWithAdaptiveBitmap(Bitmap bits) { 1055 return Icon.createWithAdaptiveBitmap(bits); 1056 } 1057 } 1058 1059 @RequiresApi(30) 1060 static class Api30Impl { Api30Impl()1061 private Api30Impl() { 1062 // This class is not instantiable. 1063 } 1064 createWithAdaptiveBitmapContentUri(Uri uri)1065 static Icon createWithAdaptiveBitmapContentUri(Uri uri) { 1066 return Icon.createWithAdaptiveBitmapContentUri(uri); 1067 } 1068 1069 } 1070 1071 @RequiresApi(23) 1072 static class Api23Impl { Api23Impl()1073 private Api23Impl() { 1074 // This class is not instantiable. 1075 } 1076 createFromIcon(@onNull Context context, @NonNull Icon icon)1077 static @NonNull IconCompat createFromIcon(@NonNull Context context, @NonNull Icon icon) { 1078 switch (getType(icon)) { 1079 case TYPE_RESOURCE: 1080 String resPackage = getResPackage(icon); 1081 try { 1082 return createWithResource(getResources(context, resPackage), resPackage, 1083 getResId(icon)); 1084 } catch (Resources.NotFoundException e) { 1085 throw new IllegalArgumentException("Icon resource cannot be found"); 1086 } 1087 case TYPE_URI: 1088 return createWithContentUri(getUri(icon)); 1089 case TYPE_URI_ADAPTIVE_BITMAP: 1090 return createWithAdaptiveBitmapContentUri(getUri(icon)); 1091 } 1092 IconCompat iconCompat = new IconCompat(TYPE_UNKNOWN); 1093 iconCompat.mObj1 = icon; 1094 return iconCompat; 1095 } 1096 1097 /** 1098 * Gets the type of the icon provided. 1099 * <p> 1100 * Note that new types may be added later, so callers should guard against other 1101 * types being returned. Returns {@link #TYPE_UNKNOWN} when the type cannot be 1102 * determined. 1103 */ 1104 @IconType getType(@onNull Object icon)1105 static int getType(@NonNull Object icon) { 1106 if (Build.VERSION.SDK_INT >= 28) { 1107 return Api28Impl.getType(icon); 1108 } else { 1109 try { 1110 return (int) icon.getClass().getMethod("getType").invoke(icon); 1111 } catch (IllegalAccessException e) { 1112 Log.e(TAG, "Unable to get icon type " + icon, e); 1113 return TYPE_UNKNOWN; 1114 } catch (InvocationTargetException e) { 1115 Log.e(TAG, "Unable to get icon type " + icon, e); 1116 return TYPE_UNKNOWN; 1117 } catch (NoSuchMethodException e) { 1118 Log.e(TAG, "Unable to get icon type " + icon, e); 1119 return TYPE_UNKNOWN; 1120 } 1121 } 1122 } 1123 1124 /** 1125 * Gets the package used to create this icon. 1126 * <p> 1127 * Only valid for icons of type TYPE_RESOURCE. 1128 * Note: This package may not be available if referenced in the future, and it is 1129 * up to the caller to ensure safety if this package is re-used and/or persisted. 1130 * Returns {@code null} when the value cannot be gotten. 1131 */ getResPackage(@onNull Object icon)1132 static @Nullable String getResPackage(@NonNull Object icon) { 1133 if (Build.VERSION.SDK_INT >= 28) { 1134 return Api28Impl.getResPackage(icon); 1135 } else { 1136 try { 1137 return (String) icon.getClass().getMethod("getResPackage").invoke(icon); 1138 } catch (IllegalAccessException e) { 1139 Log.e(TAG, "Unable to get icon package", e); 1140 return null; 1141 } catch (InvocationTargetException e) { 1142 Log.e(TAG, "Unable to get icon package", e); 1143 return null; 1144 } catch (NoSuchMethodException e) { 1145 Log.e(TAG, "Unable to get icon package", e); 1146 return null; 1147 } 1148 } 1149 } 1150 1151 /** 1152 * Used internally to avoid casting to Icon class in code accessible to SDK < 23. 1153 */ createFromIconInner(@onNull Object icon)1154 static IconCompat createFromIconInner(@NonNull Object icon) { 1155 Preconditions.checkNotNull(icon); 1156 switch (getType(icon)) { 1157 case TYPE_RESOURCE: 1158 return createWithResource(null, getResPackage(icon), getResId(icon)); 1159 case TYPE_URI: 1160 return createWithContentUri(getUri(icon)); 1161 case TYPE_URI_ADAPTIVE_BITMAP: 1162 return createWithAdaptiveBitmapContentUri(getUri(icon)); 1163 } 1164 IconCompat iconCompat = new IconCompat(TYPE_UNKNOWN); 1165 iconCompat.mObj1 = icon; 1166 return iconCompat; 1167 } 1168 1169 /** 1170 * Gets the resource used to create this icon. 1171 * <p> 1172 * Only valid for icons of type TYPE_RESOURCE. 1173 * Note: This resource may not be available if the application changes at all, and it is 1174 * up to the caller to ensure safety if this resource is re-used and/or persisted. 1175 * Returns {@code 0} if the id cannot be gotten. 1176 */ 1177 @IdRes 1178 @DrawableRes getResId(@onNull Object icon)1179 static int getResId(@NonNull Object icon) { 1180 if (Build.VERSION.SDK_INT >= 28) { 1181 return Api28Impl.getResId(icon); 1182 } else { 1183 try { 1184 return (int) icon.getClass().getMethod("getResId").invoke(icon); 1185 } catch (IllegalAccessException e) { 1186 Log.e(TAG, "Unable to get icon resource", e); 1187 return 0; 1188 } catch (InvocationTargetException e) { 1189 Log.e(TAG, "Unable to get icon resource", e); 1190 return 0; 1191 } catch (NoSuchMethodException e) { 1192 Log.e(TAG, "Unable to get icon resource", e); 1193 return 0; 1194 } 1195 } 1196 } 1197 1198 /** 1199 * Gets the uri used to create this icon. 1200 * <p> 1201 * Only valid for icons of type TYPE_URI. 1202 * Note: This uri may not be available in the future, and it is 1203 * up to the caller to ensure safety if this uri is re-used and/or persisted. 1204 * Returns {@code null} if the uri cannot be gotten. 1205 */ getUri(@onNull Object icon)1206 static @Nullable Uri getUri(@NonNull Object icon) { 1207 if (Build.VERSION.SDK_INT >= 28) { 1208 return Api28Impl.getUri(icon); 1209 } else { 1210 try { 1211 return (Uri) icon.getClass().getMethod("getUri").invoke(icon); 1212 } catch (IllegalAccessException e) { 1213 Log.e(TAG, "Unable to get icon uri", e); 1214 return null; 1215 } catch (InvocationTargetException e) { 1216 Log.e(TAG, "Unable to get icon uri", e); 1217 return null; 1218 } catch (NoSuchMethodException e) { 1219 Log.e(TAG, "Unable to get icon uri", e); 1220 return null; 1221 } 1222 } 1223 } 1224 toIcon(IconCompat iconCompat, Context context)1225 static Icon toIcon(IconCompat iconCompat, Context context) { 1226 Icon icon; 1227 switch (iconCompat.mType) { 1228 case TYPE_UNKNOWN: 1229 // When type is unknown we are just wrapping an icon. 1230 return (Icon) iconCompat.mObj1; 1231 case TYPE_BITMAP: 1232 icon = Icon.createWithBitmap((Bitmap) iconCompat.mObj1); 1233 break; 1234 case TYPE_ADAPTIVE_BITMAP: 1235 if (Build.VERSION.SDK_INT >= 26) { 1236 icon = Api26Impl.createWithAdaptiveBitmap((Bitmap) iconCompat.mObj1); 1237 } else { 1238 icon = Icon.createWithBitmap( 1239 createLegacyIconFromAdaptiveIcon((Bitmap) iconCompat.mObj1, false)); 1240 } 1241 break; 1242 case TYPE_RESOURCE: 1243 icon = Icon.createWithResource(iconCompat.getResPackage(), iconCompat.mInt1); 1244 break; 1245 case TYPE_DATA: 1246 icon = Icon.createWithData((byte[]) iconCompat.mObj1, iconCompat.mInt1, 1247 iconCompat.mInt2); 1248 break; 1249 case TYPE_URI: 1250 icon = Icon.createWithContentUri((String) iconCompat.mObj1); 1251 break; 1252 case TYPE_URI_ADAPTIVE_BITMAP: 1253 if (Build.VERSION.SDK_INT >= 30) { 1254 icon = Api30Impl.createWithAdaptiveBitmapContentUri(iconCompat.getUri()); 1255 break; 1256 } 1257 if (context == null) { 1258 throw new IllegalArgumentException( 1259 "Context is required to resolve the file uri of the icon: " 1260 + iconCompat.getUri()); 1261 } 1262 InputStream is = iconCompat.getUriInputStream(context); 1263 if (is == null) { 1264 throw new IllegalStateException( 1265 "Cannot load adaptive icon from uri: " + iconCompat.getUri()); 1266 } 1267 if (Build.VERSION.SDK_INT >= 26) { 1268 icon = Api26Impl.createWithAdaptiveBitmap(BitmapFactory.decodeStream(is)); 1269 } else { 1270 icon = Icon.createWithBitmap(createLegacyIconFromAdaptiveIcon( 1271 BitmapFactory.decodeStream(is), false)); 1272 } 1273 break; 1274 default: 1275 throw new IllegalArgumentException("Unknown type"); 1276 } 1277 if (iconCompat.mTintList != null) { 1278 icon.setTintList(iconCompat.mTintList); 1279 } 1280 if (iconCompat.mTintMode != DEFAULT_TINT_MODE) { 1281 icon.setTintMode(iconCompat.mTintMode); 1282 } 1283 return icon; 1284 } 1285 loadDrawable(Icon icon, Context context)1286 static Drawable loadDrawable(Icon icon, Context context) { 1287 return icon.loadDrawable(context); 1288 } 1289 } 1290 } 1291