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