1 /* 2 * Copyright (C) 2016 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.launcher3.graphics; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.Intent.ShortcutIconResource; 23 import android.content.pm.PackageManager; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.Canvas; 27 import android.graphics.Paint; 28 import android.graphics.PaintFlagsDrawFilter; 29 import android.graphics.Rect; 30 import android.graphics.RectF; 31 import android.graphics.drawable.AdaptiveIconDrawable; 32 import android.graphics.drawable.BitmapDrawable; 33 import android.graphics.drawable.Drawable; 34 import android.graphics.drawable.PaintDrawable; 35 import android.os.Build; 36 import android.os.Process; 37 import android.os.UserHandle; 38 import android.support.annotation.Nullable; 39 40 import com.android.launcher3.AppInfo; 41 import com.android.launcher3.FastBitmapDrawable; 42 import com.android.launcher3.IconCache; 43 import com.android.launcher3.LauncherAppState; 44 import com.android.launcher3.R; 45 import com.android.launcher3.Utilities; 46 import com.android.launcher3.config.FeatureFlags; 47 import com.android.launcher3.model.PackageItemInfo; 48 import com.android.launcher3.shortcuts.DeepShortcutManager; 49 import com.android.launcher3.shortcuts.ShortcutInfoCompat; 50 import com.android.launcher3.util.Provider; 51 52 /** 53 * Helper methods for generating various launcher icons 54 */ 55 public class LauncherIcons { 56 57 private static final Rect sOldBounds = new Rect(); 58 private static final Canvas sCanvas = new Canvas(); 59 60 static { sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG))61 sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, 62 Paint.FILTER_BITMAP_FLAG)); 63 } 64 65 /** 66 * Returns a bitmap suitable for the all apps view. If the package or the resource do not 67 * exist, it returns null. 68 */ createIconBitmap(ShortcutIconResource iconRes, Context context)69 public static Bitmap createIconBitmap(ShortcutIconResource iconRes, Context context) { 70 PackageManager packageManager = context.getPackageManager(); 71 // the resource 72 try { 73 Resources resources = packageManager.getResourcesForApplication(iconRes.packageName); 74 if (resources != null) { 75 final int id = resources.getIdentifier(iconRes.resourceName, null, null); 76 return createIconBitmap(resources.getDrawableForDensity( 77 id, LauncherAppState.getIDP(context).fillResIconDpi), context); 78 } 79 } catch (Exception e) { 80 // Icon not found. 81 } 82 return null; 83 } 84 85 /** 86 * Returns a bitmap which is of the appropriate size to be displayed as an icon 87 */ createIconBitmap(Bitmap icon, Context context)88 public static Bitmap createIconBitmap(Bitmap icon, Context context) { 89 final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize; 90 if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) { 91 return icon; 92 } 93 return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context); 94 } 95 96 /** 97 * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}. 98 * The bitmap is also visually normalized with other icons. 99 */ createBadgedIconBitmap( Drawable icon, UserHandle user, Context context, int iconAppTargetSdk)100 public static Bitmap createBadgedIconBitmap( 101 Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) { 102 103 IconNormalizer normalizer; 104 float scale = 1f; 105 if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) { 106 normalizer = IconNormalizer.getInstance(context); 107 if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) { 108 boolean[] outShape = new boolean[1]; 109 AdaptiveIconDrawable dr = (AdaptiveIconDrawable) 110 context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); 111 dr.setBounds(0, 0, 1, 1); 112 scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape); 113 if (FeatureFlags.LEGACY_ICON_TREATMENT && 114 !outShape[0]){ 115 Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale); 116 if (wrappedIcon != icon) { 117 icon = wrappedIcon; 118 scale = normalizer.getScale(icon, null, null, null); 119 } 120 } 121 } else { 122 scale = normalizer.getScale(icon, null, null, null); 123 } 124 } 125 Bitmap bitmap = createIconBitmap(icon, context, scale); 126 if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO && 127 icon instanceof AdaptiveIconDrawable) { 128 bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap); 129 } 130 return badgeIconForUser(bitmap, user, context); 131 } 132 133 /** 134 * Badges the provided icon with the user badge if required. 135 */ badgeIconForUser(Bitmap icon, UserHandle user, Context context)136 public static Bitmap badgeIconForUser(Bitmap icon, UserHandle user, Context context) { 137 if (user != null && !Process.myUserHandle().equals(user)) { 138 BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon); 139 Drawable badged = context.getPackageManager().getUserBadgedIcon( 140 drawable, user); 141 if (badged instanceof BitmapDrawable) { 142 return ((BitmapDrawable) badged).getBitmap(); 143 } else { 144 return createIconBitmap(badged, context); 145 } 146 } else { 147 return icon; 148 } 149 } 150 151 /** 152 * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually 153 * normalized with other icons and has enough spacing to add shadow. 154 */ createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk)155 public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk) { 156 RectF iconBounds = new RectF(); 157 IconNormalizer normalizer; 158 float scale = 1f; 159 if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) { 160 normalizer = IconNormalizer.getInstance(context); 161 if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) { 162 boolean[] outShape = new boolean[1]; 163 AdaptiveIconDrawable dr = (AdaptiveIconDrawable) 164 context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); 165 dr.setBounds(0, 0, 1, 1); 166 scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape); 167 if (Utilities.ATLEAST_OREO && FeatureFlags.LEGACY_ICON_TREATMENT && 168 !outShape[0]) { 169 Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale); 170 if (wrappedIcon != icon) { 171 icon = wrappedIcon; 172 scale = normalizer.getScale(icon, iconBounds, null, null); 173 } 174 } 175 } else { 176 scale = normalizer.getScale(icon, iconBounds, null, null); 177 } 178 179 } 180 scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds)); 181 return createIconBitmap(icon, context, scale); 182 } 183 184 /** 185 * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using 186 * {@link #createScaledBitmapWithoutShadow(Drawable, Context, int)} 187 */ addShadowToIcon(Bitmap icon, Context context)188 public static Bitmap addShadowToIcon(Bitmap icon, Context context) { 189 return ShadowGenerator.getInstance(context).recreateIcon(icon); 190 } 191 192 /** 193 * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions. 194 */ badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context)195 public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) { 196 return badgeWithDrawable(srcTgt, new FastBitmapDrawable(badge), context); 197 } 198 badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context)199 public static Bitmap badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context) { 200 int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size); 201 synchronized (sCanvas) { 202 sCanvas.setBitmap(srcTgt); 203 int iconSize = srcTgt.getWidth(); 204 badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize); 205 badge.draw(sCanvas); 206 sCanvas.setBitmap(null); 207 } 208 return srcTgt; 209 } 210 211 /** 212 * Returns a bitmap suitable for the all apps view. 213 */ createIconBitmap(Drawable icon, Context context)214 public static Bitmap createIconBitmap(Drawable icon, Context context) { 215 float scale = 1f; 216 if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO && 217 icon instanceof AdaptiveIconDrawable) { 218 scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0)); 219 } 220 Bitmap bitmap = createIconBitmap(icon, context, scale); 221 if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO && 222 icon instanceof AdaptiveIconDrawable) { 223 bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap); 224 } 225 return bitmap; 226 } 227 228 /** 229 * @param scale the scale to apply before drawing {@param icon} on the canvas 230 */ createIconBitmap(Drawable icon, Context context, float scale)231 public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) { 232 synchronized (sCanvas) { 233 final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize; 234 int width = iconBitmapSize; 235 int height = iconBitmapSize; 236 237 if (icon instanceof PaintDrawable) { 238 PaintDrawable painter = (PaintDrawable) icon; 239 painter.setIntrinsicWidth(width); 240 painter.setIntrinsicHeight(height); 241 } else if (icon instanceof BitmapDrawable) { 242 // Ensure the bitmap has a density. 243 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; 244 Bitmap bitmap = bitmapDrawable.getBitmap(); 245 if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) { 246 bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); 247 } 248 } 249 250 int sourceWidth = icon.getIntrinsicWidth(); 251 int sourceHeight = icon.getIntrinsicHeight(); 252 if (sourceWidth > 0 && sourceHeight > 0) { 253 // Scale the icon proportionally to the icon dimensions 254 final float ratio = (float) sourceWidth / sourceHeight; 255 if (sourceWidth > sourceHeight) { 256 height = (int) (width / ratio); 257 } else if (sourceHeight > sourceWidth) { 258 width = (int) (height * ratio); 259 } 260 } 261 // no intrinsic size --> use default size 262 int textureWidth = iconBitmapSize; 263 int textureHeight = iconBitmapSize; 264 265 Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, 266 Bitmap.Config.ARGB_8888); 267 final Canvas canvas = sCanvas; 268 canvas.setBitmap(bitmap); 269 270 final int left = (textureWidth-width) / 2; 271 final int top = (textureHeight-height) / 2; 272 273 sOldBounds.set(icon.getBounds()); 274 if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) { 275 int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize), 276 Math.min(left, top)); 277 int size = Math.max(width, height); 278 icon.setBounds(offset, offset, size, size); 279 } else { 280 icon.setBounds(left, top, left+width, top+height); 281 } 282 canvas.save(Canvas.MATRIX_SAVE_FLAG); 283 canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2); 284 icon.draw(canvas); 285 canvas.restore(); 286 icon.setBounds(sOldBounds); 287 canvas.setBitmap(null); 288 289 return bitmap; 290 } 291 } 292 293 /** 294 * If the platform is running O but the app is not providing AdaptiveIconDrawable, then 295 * shrink the legacy icon and set it as foreground. Use color drawable as background to 296 * create AdaptiveIconDrawable. 297 */ wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale)298 static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) { 299 if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) { 300 return drawable; 301 } 302 303 try { 304 if (!(drawable instanceof AdaptiveIconDrawable)) { 305 AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable) 306 context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); 307 FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground()); 308 fsd.setDrawable(drawable); 309 fsd.setScale(scale); 310 return (Drawable) iconWrapper; 311 } 312 } catch (Exception e) { 313 return drawable; 314 } 315 return drawable; 316 } 317 createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context)318 public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) { 319 return createShortcutIcon(shortcutInfo, context, true /* badged */); 320 } 321 createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, boolean badged)322 public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, 323 boolean badged) { 324 return createShortcutIcon(shortcutInfo, context, badged, null); 325 } 326 createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, final Bitmap fallbackIcon)327 public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, 328 final Bitmap fallbackIcon) { 329 Provider<Bitmap> fallbackIconProvider = new Provider<Bitmap>() { 330 @Override 331 public Bitmap get() { 332 // If the shortcut is pinned but no longer has an icon in the system, 333 // keep the current icon instead of reverting to the default icon. 334 return fallbackIcon; 335 } 336 }; 337 return createShortcutIcon(shortcutInfo, context, true, fallbackIconProvider); 338 } 339 createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider)340 public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, 341 boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) { 342 LauncherAppState app = LauncherAppState.getInstance(context); 343 Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context) 344 .getShortcutIconDrawable(shortcutInfo, 345 app.getInvariantDeviceProfile().fillResIconDpi); 346 IconCache cache = app.getIconCache(); 347 Bitmap unbadgedBitmap = null; 348 if (unbadgedDrawable != null) { 349 unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow( 350 unbadgedDrawable, context, 0); 351 } else { 352 if (fallbackIconProvider != null) { 353 unbadgedBitmap = fallbackIconProvider.get(); 354 } 355 if (unbadgedBitmap == null) { 356 unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()); 357 } 358 } 359 360 if (!badged) { 361 return unbadgedBitmap; 362 } 363 unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context); 364 return badgeWithBitmap(unbadgedBitmap, getShortcutInfoBadge(shortcutInfo, cache), context); 365 } 366 getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache)367 public static Bitmap getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) { 368 final Bitmap badgeBitmap; 369 ComponentName cn = shortcutInfo.getActivity(); 370 if (cn != null) { 371 // Get the app info for the source activity. 372 AppInfo appInfo = new AppInfo(); 373 appInfo.user = shortcutInfo.getUserHandle(); 374 appInfo.componentName = cn; 375 appInfo.intent = new Intent(Intent.ACTION_MAIN) 376 .addCategory(Intent.CATEGORY_LAUNCHER) 377 .setComponent(cn); 378 cache.getTitleAndIcon(appInfo, false); 379 badgeBitmap = appInfo.iconBitmap; 380 } else { 381 PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage()); 382 cache.getTitleAndIconForApp(pkgInfo, false); 383 badgeBitmap = pkgInfo.iconBitmap; 384 } 385 return badgeBitmap; 386 } 387 388 /** 389 * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. 390 * This allows the badging to be done based on the action bitmap size rather than 391 * the scaled bitmap size. 392 */ 393 private static class FixedSizeBitmapDrawable extends BitmapDrawable { 394 FixedSizeBitmapDrawable(Bitmap bitmap)395 public FixedSizeBitmapDrawable(Bitmap bitmap) { 396 super(null, bitmap); 397 } 398 399 @Override getIntrinsicHeight()400 public int getIntrinsicHeight() { 401 return getBitmap().getWidth(); 402 } 403 404 @Override getIntrinsicWidth()405 public int getIntrinsicWidth() { 406 return getBitmap().getWidth(); 407 } 408 } 409 } 410