diff options
| -rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 69 | ||||
| -rw-r--r-- | core/java/com/android/internal/app/SimpleIconFactory.java | 609 | ||||
| -rw-r--r-- | core/res/res/drawable/iconfactory_adaptive_icon_drawable_wrapper.xml | 24 | ||||
| -rw-r--r-- | core/res/res/layout/resolve_list_item.xml | 4 | ||||
| -rw-r--r-- | core/res/res/values/dimens.xml | 2 | ||||
| -rw-r--r-- | core/res/res/values/symbols.xml | 5 |
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> |