summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java69
-rw-r--r--core/java/com/android/internal/app/SimpleIconFactory.java609
-rw-r--r--core/res/res/drawable/iconfactory_adaptive_icon_drawable_wrapper.xml24
-rw-r--r--core/res/res/layout/resolve_list_item.xml4
-rw-r--r--core/res/res/values/dimens.xml2
-rw-r--r--core/res/res/values/symbols.xml5
6 files changed, 693 insertions, 20 deletions
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b55700e5985d..12942abc8159 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -42,6 +42,8 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
@@ -49,6 +51,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PatternMatcher;
+import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
@@ -56,7 +59,6 @@ import android.os.UserManager;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
@@ -131,7 +133,7 @@ public class ResolverActivity extends Activity {
/** See {@link #setRetainInOnStop}. */
private boolean mRetainInOnStop;
- IconDrawableFactory mIconFactory;
+ SimpleIconFactory mSimpleIconFactory;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override public void onSomePackagesChanged() {
@@ -309,7 +311,11 @@ public class ResolverActivity extends Activity {
// as to mitigate Intent Capturing vulnerability
mSupportsAlwaysUseOption = supportsAlwaysUseOption && !mUseLayoutForBrowsables;
- mIconFactory = IconDrawableFactory.newInstance(this, true);
+ final int iconSize = getResources().getDimensionPixelSize(R.dimen.resolver_icon_size);
+ final int badgeSize = getResources().getDimensionPixelSize(R.dimen.resolver_badge_size);
+ mSimpleIconFactory = new SimpleIconFactory(this, mIconDpi, iconSize, badgeSize);
+ mSimpleIconFactory.setWrapperBackgroundColor(Color.WHITE);
+
if (configureContentView(mIntents, initialIntents, rList)) {
return;
}
@@ -494,6 +500,7 @@ public class ResolverActivity extends Activity {
}
}
+ @Nullable
Drawable getIcon(Resources res, int resId) {
Drawable result;
try {
@@ -505,26 +512,52 @@ public class ResolverActivity extends Activity {
return result;
}
+ /**
+ * Loads the icon for the provided ResolveInfo. Defaults to using the application icon over
+ * any IntentFilter or Activity icon to increase user understanding, with an exception for
+ * applications that hold the right permission. Always attempts to use icon resources over
+ * PackageManager loading mechanisms so badging can be done by iconloader.
+ */
Drawable loadIconForResolveInfo(ResolveInfo ri) {
- Drawable dr;
- try {
- if (ri.resolvePackageName != null && ri.icon != 0) {
- dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon);
- if (dr != null) {
- return mIconFactory.getShadowedIcon(dr);
+ Drawable dr = null;
+
+ // Allow for app icon override given the right permission
+ if (PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
+ android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
+ ri.activityInfo.applicationInfo.packageName)) {
+ try {
+ if (ri.resolvePackageName != null && ri.icon != 0) {
+ dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon);
}
- }
- final int iconRes = ri.getIconResource();
- if (iconRes != 0) {
- dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes);
- if (dr != null) {
- return mIconFactory.getShadowedIcon(dr);
+ if (dr == null) {
+ final int iconRes = ri.getIconResource();
+ if (iconRes != 0) {
+ dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName),
+ iconRes);
+ }
}
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+ + "couldn't find resources for package", e);
}
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Couldn't find resources for package", e);
}
- return mIconFactory.getBadgedIcon(ri.activityInfo.applicationInfo);
+
+ // Use app icons for better user association
+ if (dr == null) {
+ try {
+ dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.applicationInfo),
+ ri.activityInfo.applicationInfo.icon);
+ } catch (NameNotFoundException ignore) {
+ }
+ }
+
+ // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
+ if (dr == null) {
+ dr = ri.activityInfo.applicationInfo.loadIcon(mPm);
+ }
+
+ return new BitmapDrawable(this.getResources(),
+ mSimpleIconFactory.createUserBadgedIconBitmap(dr, Process.myUserHandle()));
}
@Override
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
new file mode 100644
index 000000000000..eb1530e97b7f
--- /dev/null
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2019 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.internal.app;
+
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+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.DrawableWrapper;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * @deprecated Use the Launcher3 Iconloaderlib at packages/apps/Launcher3/iconloaderlib. This class
+ * is a temporary fork of Iconloader. It combines all necessary methods to render app icons that are
+ * possibly badged. It is intended to be used only by Sharesheet for the Q release.
+ */
+@Deprecated
+public class SimpleIconFactory {
+
+ private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
+ private static final float BLUR_FACTOR = 0.5f / 48;
+
+ private Context mContext;
+ private Canvas mCanvas;
+ private PackageManager mPm;
+
+ private int mFillResIconDpi;
+ private int mIconBitmapSize;
+ private int mBadgeBitmapSize;
+ private int mWrapperBackgroundColor;
+
+ private Drawable mWrapperIcon;
+ private final Rect mOldBounds = new Rect();
+
+ /**
+ * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
+ */
+ @Deprecated
+ SimpleIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
+ int badgeBitmapSize) {
+ mContext = context.getApplicationContext();
+ mPm = mContext.getPackageManager();
+ mIconBitmapSize = iconBitmapSize;
+ mBadgeBitmapSize = badgeBitmapSize;
+ mFillResIconDpi = fillResIconDpi;
+
+ mCanvas = new Canvas();
+ mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+
+ // Normalizer init
+ // Use twice the icon size as maximum size to avoid scaling down twice.
+ mMaxSize = iconBitmapSize * 2;
+ mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
+ mScaleCheckCanvas = new Canvas(mBitmap);
+ mPixels = new byte[mMaxSize * mMaxSize];
+ mLeftBorder = new float[mMaxSize];
+ mRightBorder = new float[mMaxSize];
+ mBounds = new Rect();
+ mAdaptiveIconBounds = new Rect();
+ mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
+
+ // Shadow generator init
+ mDefaultBlurMaskFilter = new BlurMaskFilter(iconBitmapSize * BLUR_FACTOR,
+ Blur.NORMAL);
+ }
+
+ /**
+ * Sets the background color used for wrapped adaptive icon
+ *
+ * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
+ */
+ @Deprecated
+ void setWrapperBackgroundColor(int color) {
+ mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
+ }
+
+ /**
+ * Creates bitmap using the source drawable and various parameters.
+ * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
+ *
+ * @param icon source of the icon associated with a user that has no badge,
+ * likely user 0
+ * @param user info can be used for a badge
+ * @return a bitmap suitable for disaplaying as an icon at various system UIs.
+ *
+ * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
+ */
+ @Deprecated
+ Bitmap createUserBadgedIconBitmap(@Nullable Drawable icon, UserHandle user) {
+ float [] scale = new float[1];
+
+ // If no icon is provided use the system default
+ if (icon == null) {
+ icon = getFullResDefaultActivityIcon(mFillResIconDpi);
+ }
+ icon = normalizeAndWrapToAdaptiveIcon(icon, null, scale);
+ Bitmap bitmap = createIconBitmap(icon, scale[0]);
+ if (icon instanceof AdaptiveIconDrawable) {
+ mCanvas.setBitmap(bitmap);
+ 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 {
+ result = bitmap;
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates bitmap using the source drawable and flattened pre-rendered app icon.
+ * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
+ *
+ * @param icon source of the icon associated with a user that has no badge
+ * @param renderedAppIcon pre-rendered app icon to use as a badge, likely the output
+ * of createUserBadgedIconBitmap for user 0
+ * @return a bitmap suitable for disaplaying as an icon at various system UIs.
+ *
+ * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
+ */
+ @Deprecated
+ public Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
+ // Flatten the passed in icon
+ float [] scale = new float[1];
+
+ // If no icon is provided use the system default
+ if (icon == null) {
+ icon = getFullResDefaultActivityIcon(mFillResIconDpi);
+ }
+ icon = normalizeAndWrapToAdaptiveIcon(icon, null, scale);
+ Bitmap bitmap = createIconBitmap(icon, scale[0]);
+ if (icon instanceof AdaptiveIconDrawable) {
+ mCanvas.setBitmap(bitmap);
+ recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
+ mCanvas.setBitmap(null);
+ }
+
+ // Now scale down and apply the badge to the bottom right corner of the flattened icon
+ renderedAppIcon = Bitmap.createScaledBitmap(renderedAppIcon, mBadgeBitmapSize,
+ mBadgeBitmapSize, false);
+
+ // Paint the provided badge on top of the flattened icon
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawBitmap(renderedAppIcon, mIconBitmapSize - mBadgeBitmapSize,
+ mIconBitmapSize - mBadgeBitmapSize, null);
+ mCanvas.setBitmap(null);
+
+ return bitmap;
+ }
+
+ private static Drawable getFullResDefaultActivityIcon(int iconDpi) {
+ return Resources.getSystem().getDrawableForDensity(android.R.mipmap.sym_def_app_icon,
+ iconDpi);
+ }
+
+ private Bitmap createIconBitmap(Drawable icon, float scale) {
+ return createIconBitmap(icon, scale, mIconBitmapSize);
+ }
+
+ /**
+ * @param icon drawable that should be flattened to a bitmap
+ * @param scale the scale to apply before drawing {@param icon} on the canvas
+ */
+ private Bitmap createIconBitmap(Drawable icon, float scale, int size) {
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
+ mCanvas.setBitmap(bitmap);
+ mOldBounds.set(icon.getBounds());
+
+ if (icon instanceof AdaptiveIconDrawable) {
+ int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
+ Math.round(size * (1 - scale) / 2));
+ icon.setBounds(offset, offset, size - offset, size - offset);
+ icon.draw(mCanvas);
+ } else {
+ if (icon instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap b = bitmapDrawable.getBitmap();
+ if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
+ }
+ }
+ int width = size;
+ int height = size;
+
+ int intrinsicWidth = icon.getIntrinsicWidth();
+ int intrinsicHeight = icon.getIntrinsicHeight();
+ if (intrinsicWidth > 0 && intrinsicHeight > 0) {
+ // Scale the icon proportionally to the icon dimensions
+ final float ratio = (float) intrinsicWidth / intrinsicHeight;
+ if (intrinsicWidth > intrinsicHeight) {
+ height = (int) (width / ratio);
+ } else if (intrinsicHeight > intrinsicWidth) {
+ width = (int) (height * ratio);
+ }
+ }
+ final int left = (size - width) / 2;
+ final int top = (size - height) / 2;
+ icon.setBounds(left, top, left + width, top + height);
+ mCanvas.save();
+ mCanvas.scale(scale, scale, size / 2, size / 2);
+ icon.draw(mCanvas);
+ mCanvas.restore();
+
+ }
+
+ icon.setBounds(mOldBounds);
+ mCanvas.setBitmap(null);
+ return bitmap;
+ }
+
+ private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, RectF outIconBounds,
+ float[] outScale) {
+ float scale = 1f;
+
+ if (mWrapperIcon == null) {
+ mWrapperIcon = mContext.getDrawable(
+ R.drawable.iconfactory_adaptive_icon_drawable_wrapper).mutate();
+ }
+
+ AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
+ dr.setBounds(0, 0, 1, 1);
+ scale = getScale(icon, outIconBounds);
+ if (!(icon instanceof AdaptiveIconDrawable)) {
+ FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
+ fsd.setDrawable(icon);
+ fsd.setScale(scale);
+ icon = dr;
+ scale = getScale(icon, outIconBounds);
+
+ ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
+ }
+
+ outScale[0] = scale;
+ return icon;
+ }
+
+
+ /* Normalization block */
+
+ private static final float SCALE_NOT_INITIALIZED = 0;
+ // 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 float mAdaptiveIconScale;
+ private final Rect mAdaptiveIconBounds;
+ private final Rect mBounds;
+ private final int mMaxSize;
+ private final byte[] mPixels;
+ private final float[] mLeftBorder;
+ private final float[] mRightBorder;
+ private final Bitmap mBitmap;
+ private final Canvas mScaleCheckCanvas;
+
+ /**
+ * 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.
+ */
+ private synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
+ if (d instanceof AdaptiveIconDrawable) {
+ if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
+ if (outBounds != null) {
+ outBounds.set(mAdaptiveIconBounds);
+ }
+ return mAdaptiveIconScale;
+ }
+ }
+ 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(mScaleCheckCanvas);
+
+ 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);
+ }
+ mBounds.left = leftX;
+ mBounds.right = rightX;
+
+ mBounds.top = topY;
+ mBounds.bottom = bottomY;
+
+ if (outBounds != null) {
+ outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
+ 1 - ((float) mBounds.right) / width,
+ 1 - ((float) mBounds.bottom) / 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;
+ if (d instanceof AdaptiveIconDrawable && mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
+ mAdaptiveIconScale = scale;
+ mAdaptiveIconBounds.set(mBounds);
+ }
+ return scale;
+ }
+
+ /**
+ * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
+ * (except on either ends) with appropriate values.
+ * @param xCoordinates 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[] xCoordinates, int direction, int topY, int bottomY) {
+ int total = xCoordinates.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 (xCoordinates[i] <= -1) {
+ continue;
+ }
+ int start;
+
+ if (lastAngle == Float.MAX_VALUE) {
+ start = first;
+ } else {
+ float currentAngle = (xCoordinates[i] - xCoordinates[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 = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+ if ((currentAngle - angles[start]) * direction >= 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Reset from last check
+ lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+ // Update all the points from start.
+ for (int j = start; j < i; j++) {
+ angles[j] = lastAngle;
+ xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
+ }
+ last = i;
+ }
+ }
+
+ /* Shadow generator block */
+
+ private static final float KEY_SHADOW_DISTANCE = 1f / 48;
+ private static final int KEY_SHADOW_ALPHA = 61;
+ private static final int AMBIENT_SHADOW_ALPHA = 30;
+
+ private Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private Paint mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ private BlurMaskFilter mDefaultBlurMaskFilter;
+
+ private synchronized void recreateIcon(Bitmap icon, Canvas out) {
+ recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
+ }
+
+ private synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
+ int ambientAlpha, int keyAlpha, Canvas out) {
+ int[] offset = new int[2];
+ mBlurPaint.setMaskFilter(blurMaskFilter);
+ Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
+
+ // Draw ambient shadow
+ mDrawPaint.setAlpha(ambientAlpha);
+ out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
+
+ // Draw key shadow
+ mDrawPaint.setAlpha(keyAlpha);
+ out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconBitmapSize,
+ mDrawPaint);
+
+ // Draw the icon
+ mDrawPaint.setAlpha(255); // TODO if b/128609682 not fixed by launch use .setAlpha(254)
+ out.drawBitmap(icon, 0, 0, mDrawPaint);
+ }
+
+ /* Classes */
+
+ /**
+ * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
+ */
+ public static class FixedScaleDrawable extends DrawableWrapper {
+
+ private static final float LEGACY_ICON_SCALE = .7f * .6667f;
+ private float mScaleX, mScaleY;
+
+ public FixedScaleDrawable() {
+ super(new ColorDrawable());
+ mScaleX = LEGACY_ICON_SCALE;
+ mScaleY = LEGACY_ICON_SCALE;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ int saveCount = canvas.save();
+ canvas.scale(mScaleX, mScaleY,
+ getBounds().exactCenterX(), getBounds().exactCenterY());
+ super.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
+
+ /**
+ * Sets the scale associated with this drawable
+ * @param scale
+ */
+ public void setScale(float scale) {
+ float h = getIntrinsicHeight();
+ float w = getIntrinsicWidth();
+ mScaleX = scale * LEGACY_ICON_SCALE;
+ mScaleY = scale * LEGACY_ICON_SCALE;
+ if (h > w && w > 0) {
+ mScaleX *= w / h;
+ } else if (w > h && h > 0) {
+ mScaleY *= h / w;
+ }
+ }
+ }
+
+ /**
+ * 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 {
+
+ FixedSizeBitmapDrawable(Bitmap bitmap) {
+ super(null, bitmap);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return getBitmap().getWidth();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return getBitmap().getWidth();
+ }
+ }
+
+}
diff --git a/core/res/res/drawable/iconfactory_adaptive_icon_drawable_wrapper.xml b/core/res/res/drawable/iconfactory_adaptive_icon_drawable_wrapper.xml
new file mode 100644
index 000000000000..3dd276dfdaac
--- /dev/null
+++ b/core/res/res/drawable/iconfactory_adaptive_icon_drawable_wrapper.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/white"/>
+ <foreground>
+ <drawable
+ class="com.android.internal.app.SimpleIconFactory$FixedScaleDrawable"/>
+ </foreground>
+</adaptive-icon> \ No newline at end of file
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 5d5283296f4b..0bdb25a8d307 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -29,8 +29,8 @@
<!-- Activity icon when presenting dialog
Size will be filled in by ResolverActivity -->
<ImageView android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/resolver_icon_size"
+ android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="?attr/listPreferredItemPaddingStart"
android:layout_marginTop="12dp"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index fafd8fe29f8c..7134eed8dde8 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -723,4 +723,6 @@
<dimen name="chooser_edge_margin_normal">24dp</dimen>
<dimen name="chooser_preview_image_font_size">20sp</dimen>
<dimen name="chooser_preview_width">-1px</dimen>
+ <dimen name="resolver_icon_size">42dp</dimen>
+ <dimen name="resolver_badge_size">18dp</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 76d41a0a87ef..6629b4c7f585 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3704,4 +3704,9 @@
<!-- For Auto-Brightness -->
<java-symbol type="string" name="config_displayLightSensorType" />
+
+ <java-symbol type="drawable" name="iconfactory_adaptive_icon_drawable_wrapper"/>
+ <java-symbol type="dimen" name="resolver_icon_size"/>
+ <java-symbol type="dimen" name="resolver_badge_size"/>
+
</resources>