diff options
author | Hyunyoung Song <hyunyoungs@google.com> | 2018-09-25 17:03:34 -0700 |
---|---|---|
committer | Hyunyoung Song <hyunyoungs@google.com> | 2018-09-26 11:57:37 -0700 |
commit | 48cb7bc7a449c0b4e7b7156c77e01f4060e798b8 (patch) | |
tree | 652b87330d2b61de72812f091ae576cb21c2d4b1 /src/com/android/launcher3/icons/LauncherIcons.java | |
parent | 8e95c9b5e614c4e7f5d8b0a4c40f889ef8d72299 (diff) | |
download | android_packages_apps_Trebuchet-48cb7bc7a449c0b4e7b7156c77e01f4060e798b8.tar.gz android_packages_apps_Trebuchet-48cb7bc7a449c0b4e7b7156c77e01f4060e798b8.tar.bz2 android_packages_apps_Trebuchet-48cb7bc7a449c0b4e7b7156c77e01f4060e798b8.zip |
Move IconNormalizer/ShadowGenerator/LauncherIcons to icons package
Bug: 115891474
Sending out the package name changing CL first before I make
LauncherIconsHandler and tests around it.
Change-Id: Ic10479a06333e1435b392a7072cd08782e710cbd
Diffstat (limited to 'src/com/android/launcher3/icons/LauncherIcons.java')
-rw-r--r-- | src/com/android/launcher3/icons/LauncherIcons.java | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java new file mode 100644 index 000000000..b4cbf6530 --- /dev/null +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.icons; + +import static android.graphics.Paint.DITHER_FLAG; +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + +import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; +import android.os.Build; +import android.os.Process; +import android.os.UserHandle; + +import com.android.launcher3.AppInfo; +import com.android.launcher3.FastBitmapDrawable; +import com.android.launcher3.graphics.BitmapRenderer; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfoWithIcon; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.shortcuts.DeepShortcutManager; +import com.android.launcher3.shortcuts.ShortcutInfoCompat; +import com.android.launcher3.util.Provider; +import com.android.launcher3.util.Themes; + +import androidx.annotation.Nullable; + +/** + * Helper methods for generating various launcher icons + */ +public class LauncherIcons implements AutoCloseable { + + private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE; + + public static final Object sPoolSync = new Object(); + private static LauncherIcons sPool; + + /** + * Return a new Message instance from the global pool. Allows us to + * avoid allocating new objects in many cases. + */ + public static LauncherIcons obtain(Context context) { + synchronized (sPoolSync) { + if (sPool != null) { + LauncherIcons m = sPool; + sPool = m.next; + m.next = null; + return m; + } + } + return new LauncherIcons(context); + } + + /** + * Recycles a LauncherIcons that may be in-use. + */ + public void recycle() { + synchronized (sPoolSync) { + // Clear any temporary state variables + mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; + mDisableColorExtractor = false; + + next = sPool; + sPool = this; + } + } + + @Override + public void close() { + recycle(); + } + + private final Rect mOldBounds = new Rect(); + private final Context mContext; + private final Canvas mCanvas; + private final PackageManager mPm; + private final ColorExtractor mColorExtractor; + private boolean mDisableColorExtractor; + + private final int mFillResIconDpi; + private final int mIconBitmapSize; + + private IconNormalizer mNormalizer; + private ShadowGenerator mShadowGenerator; + + private Drawable mWrapperIcon; + private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; + + // sometimes we store linked lists of these things + private LauncherIcons next; + + private LauncherIcons(Context context) { + mContext = context.getApplicationContext(); + mPm = mContext.getPackageManager(); + mColorExtractor = new ColorExtractor(); + + InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); + mFillResIconDpi = idp.fillResIconDpi; + mIconBitmapSize = idp.iconBitmapSize; + + mCanvas = new Canvas(); + mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG)); + } + + public ShadowGenerator getShadowGenerator() { + if (mShadowGenerator == null) { + mShadowGenerator = new ShadowGenerator(mContext); + } + return mShadowGenerator; + } + + public IconNormalizer getNormalizer() { + if (mNormalizer == null) { + mNormalizer = new IconNormalizer(mContext); + } + return mNormalizer; + } + + /** + * Returns a bitmap suitable for the all apps view. If the package or the resource do not + * exist, it returns null. + */ + public BitmapInfo createIconBitmap(ShortcutIconResource iconRes) { + try { + Resources resources = mPm.getResourcesForApplication(iconRes.packageName); + if (resources != null) { + final int id = resources.getIdentifier(iconRes.resourceName, null, null); + // do not stamp old legacy shortcuts as the app may have already forgotten about it + return createBadgedIconBitmap( + resources.getDrawableForDensity(id, mFillResIconDpi), + Process.myUserHandle() /* only available on primary user */, + 0 /* do not apply legacy treatment */); + } + } catch (Exception e) { + // Icon not found. + } + return null; + } + + /** + * Returns a bitmap which is of the appropriate size to be displayed as an icon + */ + public BitmapInfo createIconBitmap(Bitmap icon) { + if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) { + return BitmapInfo.fromBitmap(icon); + } + return BitmapInfo.fromBitmap( + createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f)); + } + + /** + * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps + * view or workspace. The icon is badged for {@param user}. + * The bitmap is also visually normalized with other icons. + */ + public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) { + return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false, null); + } + + public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk, + boolean isInstantApp) { + return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null); + } + + /** + * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps + * view or workspace. The icon is badged for {@param user}. + * The bitmap is also visually normalized with other icons. + */ + public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk, + boolean isInstantApp, float[] scale) { + if (scale == null) { + scale = new float[1]; + } + icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale); + Bitmap bitmap = createIconBitmap(icon, scale[0]); + if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) { + mCanvas.setBitmap(bitmap); + getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas); + mCanvas.setBitmap(null); + } + + final Bitmap result; + if (user != null && !Process.myUserHandle().equals(user)) { + BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap); + Drawable badged = mPm.getUserBadgedIcon(drawable, user); + if (badged instanceof BitmapDrawable) { + result = ((BitmapDrawable) badged).getBitmap(); + } else { + result = createIconBitmap(badged, 1f); + } + } else if (isInstantApp) { + badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge)); + result = bitmap; + } else { + result = bitmap; + } + return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor); + } + + /** + * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually + * normalized with other icons and has enough spacing to add shadow. + */ + public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) { + RectF iconBounds = new RectF(); + float[] scale = new float[1]; + icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, iconBounds, scale); + return createIconBitmap(icon, + Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds))); + } + + /** + * Sets the background color used for wrapped adaptive icon + */ + public void setWrapperBackgroundColor(int color) { + mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color; + } + + /** + * Disables the dominant color extraction for all icons loaded through this session (until + * this instance is recycled). + */ + public void disableColorExtraction() { + mDisableColorExtractor = true; + } + + private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk, + RectF outIconBounds, float[] outScale) { + float scale = 1f; + if ((Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) || + Utilities.ATLEAST_P) { + boolean[] outShape = new boolean[1]; + if (mWrapperIcon == null) { + mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper) + .mutate(); + } + AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon; + dr.setBounds(0, 0, 1, 1); + scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape); + if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) { + FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground()); + fsd.setDrawable(icon); + fsd.setScale(scale); + icon = dr; + scale = getNormalizer().getScale(icon, outIconBounds, null, null); + + ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor); + } + } else { + scale = getNormalizer().getScale(icon, outIconBounds, null, null); + } + + outScale[0] = scale; + return icon; + } + + /** + * Adds the {@param badge} on top of {@param target} using the badge dimensions. + */ + public void badgeWithDrawable(Bitmap target, Drawable badge) { + mCanvas.setBitmap(target); + badgeWithDrawable(mCanvas, badge); + mCanvas.setBitmap(null); + } + + /** + * Adds the {@param badge} on top of {@param target} using the badge dimensions. + */ + private void badgeWithDrawable(Canvas target, Drawable badge) { + int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size); + badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize, + mIconBitmapSize, mIconBitmapSize); + badge.draw(target); + } + + /** + * @param scale the scale to apply before drawing {@param icon} on the canvas + */ + private Bitmap createIconBitmap(Drawable icon, float scale) { + int width = mIconBitmapSize; + int height = mIconBitmapSize; + + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } else if (icon instanceof BitmapDrawable) { + // Ensure the bitmap has a density. + BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; + Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) { + bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics()); + } + } + + int sourceWidth = icon.getIntrinsicWidth(); + int sourceHeight = icon.getIntrinsicHeight(); + if (sourceWidth > 0 && sourceHeight > 0) { + // Scale the icon proportionally to the icon dimensions + final float ratio = (float) sourceWidth / sourceHeight; + if (sourceWidth > sourceHeight) { + height = (int) (width / ratio); + } else if (sourceHeight > sourceWidth) { + width = (int) (height * ratio); + } + } + // no intrinsic size --> use default size + int textureWidth = mIconBitmapSize; + int textureHeight = mIconBitmapSize; + + Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, + Bitmap.Config.ARGB_8888); + mCanvas.setBitmap(bitmap); + + final int left = (textureWidth-width) / 2; + final int top = (textureHeight-height) / 2; + + mOldBounds.set(icon.getBounds()); + if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) { + int offset = Math.max((int) Math.ceil(BLUR_FACTOR * textureWidth), Math.max(left, top)); + int size = Math.max(width, height); + icon.setBounds(offset, offset, size - offset, size - offset); + } else { + icon.setBounds(left, top, left+width, top+height); + } + mCanvas.save(); + mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2); + icon.draw(mCanvas); + mCanvas.restore(); + icon.setBounds(mOldBounds); + mCanvas.setBitmap(null); + + return bitmap; + } + + public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) { + return createShortcutIcon(shortcutInfo, true /* badged */); + } + + public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged) { + return createShortcutIcon(shortcutInfo, badged, null); + } + + public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, + boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) { + Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext) + .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); + IconCache cache = LauncherAppState.getInstance(mContext).getIconCache(); + + final Bitmap unbadgedBitmap; + if (unbadgedDrawable != null) { + unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0); + } else { + if (fallbackIconProvider != null) { + // Fallback icons are already badged and with appropriate shadow + Bitmap fullIcon = fallbackIconProvider.get(); + if (fullIcon != null) { + return createIconBitmap(fullIcon); + } + } + unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon; + } + + BitmapInfo result = new BitmapInfo(); + if (!badged) { + result.color = Themes.getColorAccent(mContext); + result.icon = unbadgedBitmap; + return result; + } + + final Bitmap unbadgedfinal = unbadgedBitmap; + final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache); + + result.color = badge.iconColor; + result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> { + getShadowGenerator().recreateIcon(unbadgedfinal, c); + badgeWithDrawable(c, new FastBitmapDrawable(badge)); + }); + return result; + } + + public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) { + ComponentName cn = shortcutInfo.getActivity(); + String badgePkg = shortcutInfo.getBadgePackage(mContext); + boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage()); + if (cn != null && !hasBadgePkgSet) { + // Get the app info for the source activity. + AppInfo appInfo = new AppInfo(); + appInfo.user = shortcutInfo.getUserHandle(); + appInfo.componentName = cn; + appInfo.intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(cn); + cache.getTitleAndIcon(appInfo, false); + return appInfo; + } else { + PackageItemInfo pkgInfo = new PackageItemInfo(badgePkg); + cache.getTitleAndIconForApp(pkgInfo, false); + return pkgInfo; + } + } + + /** + * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. + * This allows the badging to be done based on the action bitmap size rather than + * the scaled bitmap size. + */ + private static class FixedSizeBitmapDrawable extends BitmapDrawable { + + public FixedSizeBitmapDrawable(Bitmap bitmap) { + super(null, bitmap); + } + + @Override + public int getIntrinsicHeight() { + return getBitmap().getWidth(); + } + + @Override + public int getIntrinsicWidth() { + return getBitmap().getWidth(); + } + } +} |