summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/graphics
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2016-09-01 12:50:11 -0700
committerSunny Goyal <sunnygoyal@google.com>2016-09-02 12:34:33 -0700
commit10629b077c65b648be6a8603a092322d8a2c2c50 (patch)
tree0542a6a28717bd0e29fe6df63fcddb94fc8ae78e /src/com/android/launcher3/graphics
parent6677eb7668344ef05637bc48982439ed336c7499 (diff)
downloadandroid_packages_apps_Trebuchet-10629b077c65b648be6a8603a092322d8a2c2c50.tar.gz
android_packages_apps_Trebuchet-10629b077c65b648be6a8603a092322d8a2c2c50.tar.bz2
android_packages_apps_Trebuchet-10629b077c65b648be6a8603a092322d8a2c2c50.zip
Moving some image handling classes to .graphics package
Change-Id: Id6d3d0b9c345a503ff2e09f073eb4b6449e21c7e
Diffstat (limited to 'src/com/android/launcher3/graphics')
-rw-r--r--src/com/android/launcher3/graphics/DragPreviewProvider.java3
-rw-r--r--src/com/android/launcher3/graphics/HolographicOutlineHelper.java229
-rw-r--r--src/com/android/launcher3/graphics/IconNormalizer.java250
-rw-r--r--src/com/android/launcher3/graphics/LauncherIcons.java258
4 files changed, 738 insertions, 2 deletions
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index bc91c15be..7ad1e3a43 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -24,7 +24,6 @@ import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.TextView;
-import com.android.launcher3.HolographicOutlineHelper;
import com.android.launcher3.Launcher;
import com.android.launcher3.PreloadIconDrawable;
import com.android.launcher3.Workspace;
@@ -137,7 +136,7 @@ public class DragPreviewProvider {
mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8);
canvas.setBitmap(b);
drawDragView(canvas);
- HolographicOutlineHelper.obtain(mView.getContext())
+ HolographicOutlineHelper.getInstance(mView.getContext())
.applyExpensiveOutlineWithBlur(b, canvas);
canvas.setBitmap(null);
return b;
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
new file mode 100644
index 000000000..0d70bdee1
--- /dev/null
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2008 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.graphics;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.SparseArray;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.config.ProviderConfig;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Utility class to generate shadow and outline effect, which are used for click feedback
+ * and drag-n-drop respectively.
+ */
+public class HolographicOutlineHelper {
+
+ private static HolographicOutlineHelper sInstance;
+
+ private final Canvas mCanvas = new Canvas();
+ private final Paint mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private final Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private final Paint mErasePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+
+ private final BlurMaskFilter mMediumOuterBlurMaskFilter;
+ private final BlurMaskFilter mThinOuterBlurMaskFilter;
+ private final BlurMaskFilter mMediumInnerBlurMaskFilter;
+
+ private final float mShadowBitmapShift;
+ private final BlurMaskFilter mShadowBlurMaskFilter;
+
+ // We have 4 different icon sizes: homescreen, hotseat, folder & all-apps
+ private final SparseArray<Bitmap> mBitmapCache = new SparseArray<>(4);
+
+ private HolographicOutlineHelper(Context context) {
+ Resources res = context.getResources();
+
+ float mediumBlur = res.getDimension(R.dimen.blur_size_medium_outline);
+ mMediumOuterBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.OUTER);
+ mMediumInnerBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.NORMAL);
+
+ mThinOuterBlurMaskFilter = new BlurMaskFilter(
+ res.getDimension(R.dimen.blur_size_thin_outline), BlurMaskFilter.Blur.OUTER);
+
+ mShadowBitmapShift = res.getDimension(R.dimen.blur_size_click_shadow);
+ mShadowBlurMaskFilter = new BlurMaskFilter(mShadowBitmapShift, BlurMaskFilter.Blur.NORMAL);
+
+ mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ }
+
+ public static HolographicOutlineHelper getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new HolographicOutlineHelper(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ /**
+ * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
+ * bitmap.
+ */
+ public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) {
+ applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, true);
+ }
+
+ public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas,
+ boolean clipAlpha) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) {
+ throw new RuntimeException("Outline blue is only supported on alpha bitmaps");
+ }
+
+ // We start by removing most of the alpha channel so as to ignore shadows, and
+ // other types of partial transparency when defining the shape of the object
+ if (clipAlpha) {
+ byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()];
+ ByteBuffer buffer = ByteBuffer.wrap(pixels);
+ buffer.rewind();
+ srcDst.copyPixelsToBuffer(buffer);
+
+ for (int i = 0; i < pixels.length; i++) {
+ if ((pixels[i] & 0xFF) < 188) {
+ pixels[i] = 0;
+ }
+ }
+
+ buffer.rewind();
+ srcDst.copyPixelsFromBuffer(buffer);
+ }
+
+ // calculate the outer blur first
+ mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
+ int[] outerBlurOffset = new int[2];
+ Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset);
+
+ mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
+ int[] brightOutlineOffset = new int[2];
+ Bitmap brightOutline = srcDst.extractAlpha(mBlurPaint, brightOutlineOffset);
+
+ // calculate the inner blur
+ srcDstCanvas.setBitmap(srcDst);
+ srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
+ mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter);
+ int[] thickInnerBlurOffset = new int[2];
+ Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset);
+
+ // mask out the inner blur
+ srcDstCanvas.setBitmap(thickInnerBlur);
+ srcDstCanvas.drawBitmap(srcDst, -thickInnerBlurOffset[0],
+ -thickInnerBlurOffset[1], mErasePaint);
+ srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(),
+ mErasePaint);
+ srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1],
+ mErasePaint);
+
+ // draw the inner and outer blur
+ srcDstCanvas.setBitmap(srcDst);
+ srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
+ mDrawPaint);
+ srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1],
+ mDrawPaint);
+
+ // draw the bright outline
+ srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1],
+ mDrawPaint);
+
+ // cleanup
+ srcDstCanvas.setBitmap(null);
+ brightOutline.recycle();
+ thickOuterBlur.recycle();
+ thickInnerBlur.recycle();
+ }
+
+ public Bitmap createMediumDropShadow(BubbleTextView view) {
+ return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true);
+ }
+
+ public Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
+ return createMediumDropShadow(drawable, 1f, 1f, shouldCache);
+ }
+
+ Bitmap createMediumDropShadow(Drawable drawable, float scaleX, float scaleY,
+ boolean shouldCache) {
+ if (drawable == null) {
+ return null;
+ }
+ Rect rect = drawable.getBounds();
+
+ int bitmapWidth = (int) (rect.width() * scaleX);
+ int bitmapHeight = (int) (rect.height() * scaleY);
+ if (bitmapHeight <= 0 || bitmapWidth <= 0) {
+ return null;
+ }
+
+ int key = (bitmapWidth << 16) | bitmapHeight;
+ Bitmap cache = shouldCache ? mBitmapCache.get(key) : null;
+ if (cache == null) {
+ cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
+ mCanvas.setBitmap(cache);
+
+ if (shouldCache) {
+ mBitmapCache.put(key, cache);
+ }
+ } else {
+ mCanvas.setBitmap(cache);
+ mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+ }
+
+ int saveCount = mCanvas.save();
+ mCanvas.scale(scaleX, scaleY);
+ mCanvas.translate(-rect.left, -rect.top);
+ drawable.draw(mCanvas);
+ mCanvas.restoreToCount(saveCount);
+ mCanvas.setBitmap(null);
+
+ mBlurPaint.setMaskFilter(mShadowBlurMaskFilter);
+
+ int extraSize = (int) (2 * mShadowBitmapShift);
+
+ int resultWidth = bitmapWidth + extraSize;
+ int resultHeight = bitmapHeight + extraSize;
+ key = (resultWidth << 16) | resultHeight;
+ Bitmap result = shouldCache ? mBitmapCache.get(key) : null;
+ if (result == null) {
+ result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
+ mCanvas.setBitmap(result);
+ } else {
+ // Use put instead of delete, to avoid unnecessary shrinking of cache array
+ mBitmapCache.put(key, null);
+ mCanvas.setBitmap(result);
+ mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+ }
+ mCanvas.drawBitmap(cache, mShadowBitmapShift, mShadowBitmapShift, mBlurPaint);
+ mCanvas.setBitmap(null);
+ return result;
+ }
+
+ public void recycleShadowBitmap(Bitmap bitmap) {
+ if (bitmap != null) {
+ mBitmapCache.put((bitmap.getWidth() << 16) | bitmap.getHeight(), bitmap);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
new file mode 100644
index 000000000..f90b2d72e
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 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.graphics;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.LauncherAppState;
+
+import java.nio.ByteBuffer;
+
+public class IconNormalizer {
+
+ // Ratio of icon visible area to full icon size for a square shaped icon
+ private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
+ // Ratio of icon visible area to full icon size for a circular shaped icon
+ private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
+
+ private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
+
+ // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
+ private static final float LINEAR_SCALE_SLOPE =
+ (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
+
+ private static final int MIN_VISIBLE_ALPHA = 40;
+
+ private static final Object LOCK = new Object();
+ private static IconNormalizer sIconNormalizer;
+
+ private final int mMaxSize;
+ private final Bitmap mBitmap;
+ private final Canvas mCanvas;
+ private final byte[] mPixels;
+
+ // for each y, stores the position of the leftmost x and the rightmost x
+ private final float[] mLeftBorder;
+ private final float[] mRightBorder;
+
+ private IconNormalizer() {
+ // Use twice the icon size as maximum size to avoid scaling down twice.
+ mMaxSize = LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize * 2;
+ mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
+ mCanvas = new Canvas(mBitmap);
+ mPixels = new byte[mMaxSize * mMaxSize];
+
+ mLeftBorder = new float[mMaxSize];
+ mRightBorder = new float[mMaxSize];
+ }
+
+ /**
+ * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
+ * matches the design guidelines for a launcher icon.
+ *
+ * We first calculate the convex hull of the visible portion of the icon.
+ * This hull then compared with the bounding rectangle of the hull to find how closely it
+ * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
+ * ideal solution but it gives satisfactory result without affecting the performance.
+ *
+ * This closeness is used to determine the ratio of hull area to the full icon size.
+ * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
+ *
+ * @param outBounds optional rect to receive the fraction distance from each edge.
+ */
+ public synchronized float getScale(Drawable d, RectF outBounds) {
+ int width = d.getIntrinsicWidth();
+ int height = d.getIntrinsicHeight();
+ if (width <= 0 || height <= 0) {
+ width = width <= 0 || width > mMaxSize ? mMaxSize : width;
+ height = height <= 0 || height > mMaxSize ? mMaxSize : height;
+ } else if (width > mMaxSize || height > mMaxSize) {
+ int max = Math.max(width, height);
+ width = mMaxSize * width / max;
+ height = mMaxSize * height / max;
+ }
+
+ mBitmap.eraseColor(Color.TRANSPARENT);
+ d.setBounds(0, 0, width, height);
+ d.draw(mCanvas);
+
+ ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+ buffer.rewind();
+ mBitmap.copyPixelsToBuffer(buffer);
+
+ // Overall bounds of the visible icon.
+ int topY = -1;
+ int bottomY = -1;
+ int leftX = mMaxSize + 1;
+ int rightX = -1;
+
+ // Create border by going through all pixels one row at a time and for each row find
+ // the first and the last non-transparent pixel. Set those values to mLeftBorder and
+ // mRightBorder and use -1 if there are no visible pixel in the row.
+
+ // buffer position
+ int index = 0;
+ // buffer shift after every row, width of buffer = mMaxSize
+ int rowSizeDiff = mMaxSize - width;
+ // first and last position for any row.
+ int firstX, lastX;
+
+ for (int y = 0; y < height; y++) {
+ firstX = lastX = -1;
+ for (int x = 0; x < width; x++) {
+ if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+ if (firstX == -1) {
+ firstX = x;
+ }
+ lastX = x;
+ }
+ index++;
+ }
+ index += rowSizeDiff;
+
+ mLeftBorder[y] = firstX;
+ mRightBorder[y] = lastX;
+
+ // If there is at least one visible pixel, update the overall bounds.
+ if (firstX != -1) {
+ bottomY = y;
+ if (topY == -1) {
+ topY = y;
+ }
+
+ leftX = Math.min(leftX, firstX);
+ rightX = Math.max(rightX, lastX);
+ }
+ }
+
+ if (topY == -1 || rightX == -1) {
+ // No valid pixels found. Do not scale.
+ return 1;
+ }
+
+ convertToConvexArray(mLeftBorder, 1, topY, bottomY);
+ convertToConvexArray(mRightBorder, -1, topY, bottomY);
+
+ // Area of the convex hull
+ float area = 0;
+ for (int y = 0; y < height; y++) {
+ if (mLeftBorder[y] <= -1) {
+ continue;
+ }
+ area += mRightBorder[y] - mLeftBorder[y] + 1;
+ }
+
+ // Area of the rectangle required to fit the convex hull
+ float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
+ float hullByRect = area / rectArea;
+
+ float scaleRequired;
+ if (hullByRect < CIRCLE_AREA_BY_RECT) {
+ scaleRequired = MAX_CIRCLE_AREA_FACTOR;
+ } else {
+ scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
+ }
+
+ if (outBounds != null) {
+ outBounds.left = ((float) leftX) / width;
+ outBounds.right = 1 - ((float) rightX) / width;
+
+ outBounds.top = ((float) topY) / height;
+ outBounds.bottom = 1 - ((float) bottomY) / height;
+ }
+
+ float areaScale = area / (width * height);
+ // Use sqrt of the final ratio as the images is scaled across both width and height.
+ float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
+ return scale;
+ }
+
+ /**
+ * Modifies {@param xCordinates} to represent a convex border. Fills in all missing values
+ * (except on either ends) with appropriate values.
+ * @param xCordinates map of x coordinate per y.
+ * @param direction 1 for left border and -1 for right border.
+ * @param topY the first Y position (inclusive) with a valid value.
+ * @param bottomY the last Y position (inclusive) with a valid value.
+ */
+ private static void convertToConvexArray(
+ float[] xCordinates, int direction, int topY, int bottomY) {
+ int total = xCordinates.length;
+ // The tangent at each pixel.
+ float[] angles = new float[total - 1];
+
+ int first = topY; // First valid y coordinate
+ int last = -1; // Last valid y coordinate which didn't have a missing value
+
+ float lastAngle = Float.MAX_VALUE;
+
+ for (int i = topY + 1; i <= bottomY; i++) {
+ if (xCordinates[i] <= -1) {
+ continue;
+ }
+ int start;
+
+ if (lastAngle == Float.MAX_VALUE) {
+ start = first;
+ } else {
+ float currentAngle = (xCordinates[i] - xCordinates[last]) / (i - last);
+ start = last;
+ // If this position creates a concave angle, keep moving up until we find a
+ // position which creates a convex angle.
+ if ((currentAngle - lastAngle) * direction < 0) {
+ while (start > first) {
+ start --;
+ currentAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+ if ((currentAngle - angles[start]) * direction >= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Reset from last check
+ lastAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+ // Update all the points from start.
+ for (int j = start; j < i; j++) {
+ angles[j] = lastAngle;
+ xCordinates[j] = xCordinates[start] + lastAngle * (j - start);
+ }
+ last = i;
+ }
+ }
+
+ public static IconNormalizer getInstance() {
+ synchronized (LOCK) {
+ if (sIconNormalizer == null) {
+ sIconNormalizer = new IconNormalizer();
+ }
+ }
+ return sIconNormalizer;
+ }
+}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
new file mode 100644
index 000000000..9f3f3571d
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -0,0 +1,258 @@
+/*
+ * 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.graphics;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Helper methods for generating various launcher icons
+ */
+public class LauncherIcons {
+
+ private static final Rect sOldBounds = new Rect();
+ private static final Canvas sCanvas = new Canvas();
+
+ static {
+ sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ }
+
+
+ public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
+ byte[] data = c.getBlob(iconIndex);
+ try {
+ return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view. If the package or the resource do not
+ * exist, it returns null.
+ */
+ public static Bitmap createIconBitmap(String packageName, String resourceName,
+ Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ // the resource
+ try {
+ Resources resources = packageManager.getResourcesForApplication(packageName);
+ if (resources != null) {
+ final int id = resources.getIdentifier(resourceName, null, null);
+ return createIconBitmap(
+ resources.getDrawableForDensity(id, LauncherAppState.getInstance()
+ .getInvariantDeviceProfile().fillResIconDpi), context);
+ }
+ } catch (Exception e) {
+ // Icon not found.
+ }
+ return null;
+ }
+
+ private static int getIconBitmapSize() {
+ return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
+ }
+
+ /**
+ * Returns a bitmap which is of the appropriate size to be displayed as an icon
+ */
+ public static Bitmap createIconBitmap(Bitmap icon, Context context) {
+ final int iconBitmapSize = getIconBitmapSize();
+ if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+ return icon;
+ }
+ return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
+ * The bitmap is also visually normalized with other icons.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Bitmap createBadgedIconBitmap(
+ Drawable icon, UserHandleCompat user, Context context) {
+ float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+ 1 : IconNormalizer.getInstance().getScale(icon, null);
+ Bitmap bitmap = createIconBitmap(icon, context, scale);
+ return badgeIconForUser(bitmap, user, context);
+ }
+
+ /**
+ * Badges the provided icon with the user badge if required.
+ */
+ public static Bitmap badgeIconForUser(Bitmap icon, UserHandleCompat user, Context context) {
+ if (Utilities.ATLEAST_LOLLIPOP && user != null
+ && !UserHandleCompat.myUserHandle().equals(user)) {
+ BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
+ Drawable badged = context.getPackageManager().getUserBadgedIcon(
+ drawable, user.getUser());
+ if (badged instanceof BitmapDrawable) {
+ return ((BitmapDrawable) badged).getBitmap();
+ } else {
+ return createIconBitmap(badged, context);
+ }
+ } else {
+ return icon;
+ }
+ }
+
+ /**
+ * 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 static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
+ RectF iconBounds = new RectF();
+ float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+ 1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
+ scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
+ return createIconBitmap(icon, context, scale);
+ }
+
+ /**
+ * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
+ * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
+ */
+ public static Bitmap addShadowToIcon(Bitmap icon) {
+ return ShadowGenerator.getInstance().recreateIcon(icon);
+ }
+
+ /**
+ * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
+ int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+ synchronized (sCanvas) {
+ sCanvas.setBitmap(srcTgt);
+ sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
+ new Rect(srcTgt.getWidth() - badgeSize,
+ srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
+ new Paint(Paint.FILTER_BITMAP_FLAG));
+ sCanvas.setBitmap(null);
+ }
+ return srcTgt;
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view.
+ */
+ public static Bitmap createIconBitmap(Drawable icon, Context context) {
+ return createIconBitmap(icon, context, 1.0f /* scale */);
+ }
+
+ /**
+ * @param scale the scale to apply before drawing {@param icon} on the canvas
+ */
+ public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
+ synchronized (sCanvas) {
+ final int iconBitmapSize = getIconBitmapSize();
+
+ int width = iconBitmapSize;
+ int height = iconBitmapSize;
+
+ 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(context.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 = iconBitmapSize;
+ int textureHeight = iconBitmapSize;
+
+ final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = sCanvas;
+ canvas.setBitmap(bitmap);
+
+ final int left = (textureWidth-width) / 2;
+ final int top = (textureHeight-height) / 2;
+
+ sOldBounds.set(icon.getBounds());
+ icon.setBounds(left, top, left+width, top+height);
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+ icon.draw(canvas);
+ canvas.restore();
+ icon.setBounds(sOldBounds);
+ canvas.setBitmap(null);
+
+ return bitmap;
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+}