Creating a common class for loading drawables and handling various badging
Test: Manually tested on device
Bug: 36591157
Change-Id: Ic847537d416650015cecaeb68f47f7f032e0dbaa
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e07b7e4..027ddf5 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -79,6 +79,8 @@
import android.os.storage.VolumeInfo;
import android.provider.Settings;
import android.util.ArrayMap;
+import android.util.IconDrawableFactory;
+import android.util.LauncherIcons;
import android.util.Log;
import android.view.Display;
@@ -1245,16 +1247,9 @@
if (!isManagedProfile(user.getIdentifier())) {
return icon;
}
- Drawable badgeShadow = getDrawable("system",
- com.android.internal.R.drawable.ic_corp_icon_badge_shadow, null);
- Drawable badgeColor = getDrawable("system",
- com.android.internal.R.drawable.ic_corp_icon_badge_color, null);
- badgeColor.setTint(getUserBadgeColor(user));
- Drawable badgeForeground = getDrawable("system",
- com.android.internal.R.drawable.ic_corp_icon_badge_case, null);
-
- Drawable badge = new LayerDrawable(
- new Drawable[] {badgeShadow, badgeColor, badgeForeground });
+ Drawable badge = new LauncherIcons(mContext).getBadgeDrawable(
+ com.android.internal.R.drawable.ic_corp_icon_badge_case,
+ getUserBadgeColor(user));
return getBadgedDrawable(icon, badge, null, true);
}
@@ -1268,14 +1263,6 @@
return getBadgedDrawable(drawable, badgeDrawable, badgeLocation, true);
}
- // Should have enough colors to cope with UserManagerService.getMaxManagedProfiles()
- @VisibleForTesting
- public static final int[] CORP_BADGE_COLORS = new int[] {
- com.android.internal.R.color.profile_badge_1,
- com.android.internal.R.color.profile_badge_2,
- com.android.internal.R.color.profile_badge_3
- };
-
@VisibleForTesting
public static final int[] CORP_BADGE_LABEL_RES_ID = new int[] {
com.android.internal.R.string.managed_profile_label_badge,
@@ -1284,12 +1271,7 @@
};
private int getUserBadgeColor(UserHandle user) {
- int badge = getUserManager().getManagedProfileBadge(user.getIdentifier());
- if (badge < 0) {
- badge = 0;
- }
- int resourceId = CORP_BADGE_COLORS[badge % CORP_BADGE_COLORS.length];
- return Resources.getSystem().getColor(resourceId, null);
+ return IconDrawableFactory.getUserBadgeColor(getUserManager(), user.getIdentifier());
}
@Override
diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java
new file mode 100644
index 0000000..b07942f
--- /dev/null
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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 android.util;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Utility class to load app drawables with appropriate badging.
+ *
+ * @hide
+ */
+public class IconDrawableFactory {
+
+ protected final Context mContext;
+ protected final PackageManager mPm;
+ protected final UserManager mUm;
+ protected final LauncherIcons mLauncherIcons;
+ protected final boolean mEmbedShadow;
+
+ private IconDrawableFactory(Context context, boolean embedShadow) {
+ mContext = context;
+ mPm = context.getPackageManager();
+ mUm = context.getSystemService(UserManager.class);
+ mLauncherIcons = new LauncherIcons(context);
+ mEmbedShadow = embedShadow;
+ }
+
+ protected boolean needsBadging(ApplicationInfo appInfo, @UserIdInt int userId) {
+ return appInfo.isInstantApp() || mUm.isManagedProfile(userId);
+ }
+
+ public Drawable getBadgedIcon(ApplicationInfo appInfo) {
+ return getBadgedIcon(appInfo, UserHandle.getUserId(appInfo.uid));
+ }
+
+ public Drawable getBadgedIcon(ApplicationInfo appInfo, @UserIdInt int userId) {
+ return getBadgedIcon(appInfo, appInfo, userId);
+ }
+
+ public Drawable getBadgedIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo,
+ @UserIdInt int userId) {
+ Drawable icon = mPm.loadUnbadgedItemIcon(itemInfo, appInfo);
+ if (!mEmbedShadow && !needsBadging(appInfo, userId)) {
+ return icon;
+ }
+
+ // Before badging, add shadow to adaptive icon if needed.
+ icon = mLauncherIcons.wrapIconDrawableWithShadow(icon);
+ if (appInfo.isInstantApp()) {
+ int badgeColor = Resources.getSystem().getColor(
+ com.android.internal.R.color.instant_app_badge, null);
+ icon = mLauncherIcons.getBadgedDrawable(icon,
+ com.android.internal.R.drawable.ic_instant_icon_badge_bolt,
+ badgeColor);
+ }
+ if (mUm.isManagedProfile(userId)) {
+ icon = mLauncherIcons.getBadgedDrawable(icon,
+ com.android.internal.R.drawable.ic_corp_icon_badge_case,
+ getUserBadgeColor(mUm, userId));
+ }
+ return icon;
+ }
+
+ // Should have enough colors to cope with UserManagerService.getMaxManagedProfiles()
+ @VisibleForTesting
+ public static final int[] CORP_BADGE_COLORS = new int[] {
+ com.android.internal.R.color.profile_badge_1,
+ com.android.internal.R.color.profile_badge_2,
+ com.android.internal.R.color.profile_badge_3
+ };
+
+ public static int getUserBadgeColor(UserManager um, @UserIdInt int userId) {
+ int badge = um.getManagedProfileBadge(userId);
+ if (badge < 0) {
+ badge = 0;
+ }
+ int resourceId = CORP_BADGE_COLORS[badge % CORP_BADGE_COLORS.length];
+ return Resources.getSystem().getColor(resourceId, null);
+ }
+
+ public static IconDrawableFactory newInstance(Context context) {
+ return new IconDrawableFactory(context, true);
+ }
+
+ public static IconDrawableFactory newInstance(Context context, boolean embedShadow) {
+ return new IconDrawableFactory(context, embedShadow);
+ }
+}
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index 89b9646..402bef9 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -21,10 +21,11 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.graphics.drawable.LayerDrawable;
/**
* Utility class to handle icon treatments (e.g., shadow generation) for the Launcher icons.
@@ -32,78 +33,152 @@
*/
public final class LauncherIcons {
- private final Paint mPaint = new Paint();
- private final Canvas mCanvas = new Canvas();
+ // Percent of actual icon size
+ private static final float ICON_SIZE_BLUR_FACTOR = 0.5f/48;
+ // Percent of actual icon size
+ private static final float ICON_SIZE_KEY_SHADOW_DELTA_FACTOR = 1f/48;
private static final int KEY_SHADOW_ALPHA = 61;
private static final int AMBIENT_SHADOW_ALPHA = 30;
- private static final float BLUR_FACTOR = 0.5f / 48;
- private int mShadowInset;
- private Bitmap mShadowBitmap;
- private int mIconSize;
- private Resources mRes;
+
+ private final SparseArray<Bitmap> mShadowCache = new SparseArray<>();
+ private final int mIconSize;
+ private final Resources mRes;
public LauncherIcons(Context context) {
mRes = context.getResources();
- DisplayMetrics metrics = mRes.getDisplayMetrics();
- mShadowInset = (int)(2 * metrics.density);
- mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
- Paint.FILTER_BITMAP_FLAG));
- mIconSize = (int) mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
- }
-
- /**
- * Draw the drawable into a bitmap.
- */
- public Bitmap createIconBitmap(Drawable icon) {
- final Bitmap bitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
- mPaint.setAlpha(255);
- mCanvas.setBitmap(bitmap);
- int iconInset = 0;
- if (mShadowBitmap != null) {
- mCanvas.drawBitmap(mShadowBitmap, 0, 0, mPaint);
- iconInset = mShadowInset;
- }
-
- icon.setBounds(iconInset, iconInset, mIconSize - iconInset,
- mIconSize - iconInset);
- icon.draw(mCanvas);
- mCanvas.setBitmap(null);
- return bitmap;
+ mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
}
public Drawable wrapIconDrawableWithShadow(Drawable drawable) {
if (!(drawable instanceof AdaptiveIconDrawable)) {
return drawable;
}
- AdaptiveIconDrawable d =
- (AdaptiveIconDrawable) drawable.getConstantState().newDrawable().mutate();
- getShadowBitmap(d);
- Bitmap iconbitmap = createIconBitmap(d);
- return new BitmapDrawable(mRes, iconbitmap);
+ Bitmap shadow = getShadowBitmap((AdaptiveIconDrawable) drawable);
+ return new ShadowDrawable(shadow, drawable);
}
private Bitmap getShadowBitmap(AdaptiveIconDrawable d) {
- if (mShadowBitmap != null) {
- return mShadowBitmap;
+ int shadowSize = Math.max(mIconSize, d.getIntrinsicHeight());
+ synchronized (mShadowCache) {
+ Bitmap shadow = mShadowCache.get(shadowSize);
+ if (shadow != null) {
+ return shadow;
+ }
}
- mShadowBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ALPHA_8);
- mCanvas.setBitmap(mShadowBitmap);
+ d.setBounds(0, 0, shadowSize, shadowSize);
- // Draw key shadow
- mPaint.setColor(Color.TRANSPARENT);
- float blur = BLUR_FACTOR * mIconSize;
- mPaint.setShadowLayer(blur, 0, mShadowInset, KEY_SHADOW_ALPHA << 24);
- d.setBounds(mShadowInset, mShadowInset, mIconSize - mShadowInset, mIconSize - mShadowInset);
- mCanvas.drawPath(d.getIconMask(), mPaint);
+ float blur = ICON_SIZE_BLUR_FACTOR * shadowSize;
+ float keyShadowDistance = ICON_SIZE_KEY_SHADOW_DELTA_FACTOR * shadowSize;
+
+ int bitmapSize = (int) (shadowSize + 2 * blur + keyShadowDistance);
+ Bitmap shadow = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
+
+ Canvas canvas = new Canvas(shadow);
+ canvas.translate(blur + keyShadowDistance / 2, blur);
+
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setColor(Color.TRANSPARENT);
// Draw ambient shadow
- mPaint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
- d.setBounds(mShadowInset, 2 * mShadowInset, mIconSize - mShadowInset, mIconSize);
- mCanvas.drawPath(d.getIconMask(), mPaint);
- mPaint.clearShadowLayer();
+ paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
+ canvas.drawPath(d.getIconMask(), paint);
- return mShadowBitmap;
+ // Draw key shadow
+ canvas.translate(0, keyShadowDistance);
+ paint.setShadowLayer(blur, 0, 0, KEY_SHADOW_ALPHA << 24);
+ canvas.drawPath(d.getIconMask(), paint);
+
+ canvas.setBitmap(null);
+ synchronized (mShadowCache) {
+ mShadowCache.put(shadowSize, shadow);
+ }
+ return shadow;
+ }
+
+ public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
+ return getBadgedDrawable(null, foregroundRes, backgroundColor);
+ }
+
+ public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
+ Resources sysRes = Resources.getSystem();
+
+ Drawable badgeShadow = sysRes.getDrawable(
+ com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
+
+ Drawable badgeColor = sysRes.getDrawable(
+ com.android.internal.R.drawable.ic_corp_icon_badge_color)
+ .getConstantState().newDrawable().mutate();
+ badgeColor.setTint(backgroundColor);
+
+ Drawable badgeForeground = sysRes.getDrawable(foregroundRes);
+
+ Drawable[] drawables = base == null
+ ? new Drawable[] {badgeShadow, badgeColor, badgeForeground }
+ : new Drawable[] {base, badgeShadow, badgeColor, badgeForeground };
+ return new LayerDrawable(drawables);
+ }
+
+ /**
+ * A drawable which draws a shadow bitmap behind a drawable
+ */
+ private static class ShadowDrawable extends DrawableWrapper {
+
+ final MyConstantState mState;
+
+ public ShadowDrawable(Bitmap shadow, Drawable dr) {
+ super(dr);
+ mState = new MyConstantState(shadow, dr.getConstantState());
+ }
+
+ ShadowDrawable(MyConstantState state) {
+ super(state.mChildState.newDrawable());
+ mState = state;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return mState;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ canvas.drawBitmap(mState.mShadow, null, bounds, mState.mPaint);
+ canvas.save();
+ // Ratio of child drawable size to shadow bitmap size
+ float factor = 1 / (1 + 2 * ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR);
+
+ canvas.translate(
+ bounds.width() * factor *
+ (ICON_SIZE_BLUR_FACTOR + ICON_SIZE_KEY_SHADOW_DELTA_FACTOR / 2),
+ bounds.height() * factor * ICON_SIZE_BLUR_FACTOR);
+ canvas.scale(factor, factor);
+ super.draw(canvas);
+ canvas.restore();
+ }
+
+ private static class MyConstantState extends ConstantState {
+
+ final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ final Bitmap mShadow;
+ final ConstantState mChildState;
+
+ MyConstantState(Bitmap shadow, ConstantState childState) {
+ mShadow = shadow;
+ mChildState = childState;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new ShadowDrawable(this);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChildState.getChangingConfigurations();
+ }
+ }
}
}
diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml
deleted file mode 100644
index 8917431..0000000
--- a/core/res/res/drawable/ic_corp_badge.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-Copyright (C) 2014 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="20.0dp"
- android:height="20.0dp"
- android:viewportWidth="20.0"
- android:viewportHeight="20.0">
- <path
- android:pathData="M10.0,10.0m-10.0,0.0a10.0,10.0 0.0,1.0 1.0,20.0 0.0a10.0,10.0 0.0,1.0 1.0,-20.0 0.0"
- android:fillColor="#FF5722"/>
- <path
- android:pathData="M15.2,6.2L4.8,6.2c-0.5,0.0 -0.9,0.4 -0.9,1.0L3.9,10.0c0.0,0.5 0.4,1.0 0.9,1.0l3.8,0.0l0.0,-1.0l2.9,0.0l0.0,1.0l3.8,0.0c0.5,0.0 1.0,-0.4 1.0,-1.0L16.3,7.1C16.2,6.6 15.8,6.2 15.2,6.2z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M8.6,12.9l0.0,-1.0L4.3,11.9l0.0,2.4c0.0,0.5 0.4,0.9 0.9,0.9l9.5,0.0c0.5,0.0 0.9,-0.4 0.9,-0.9l0.0,-2.4l-4.3,0.0l0.0,1.0L8.6,12.9z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M7.1,5.2l0.0,1.0 1.0,0.0 0.0,-1.0 3.799999,0.0 0.0,1.0 1.0,0.0 0.0,-1.0 -1.0,-0.9 -3.799999,0.0z"
- android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/core/res/res/drawable/ic_instant_icon_badge_bolt.xml b/core/res/res/drawable/ic_instant_icon_badge_bolt.xml
new file mode 100644
index 0000000..08512b7
--- /dev/null
+++ b/core/res/res/drawable/ic_instant_icon_badge_bolt.xml
@@ -0,0 +1,29 @@
+<!--
+Copyright (C) 2017 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="64.0dp"
+ android:height="64.0dp"
+ android:viewportWidth="64.0"
+ android:viewportHeight="64.0">
+
+ <!--
+ The path is similar to ic_corp_icon_badge_case, such that it positions on top of
+ ic_corp_icon_badge_shadow.
+ -->
+ <path
+ android:pathData="M43.9 50.9h4v8l6-12h-4v-8l-6 12z"
+ android:fillColor="#757575"/>
+</vector>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 937fc6f..536906c 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -171,6 +171,9 @@
<color name="profile_badge_2">#ff000000</color><!-- Black -->
<color name="profile_badge_3">#ff22f033</color><!-- Green -->
+ <!-- Default instant app badge color -->
+ <color name="instant_app_badge">#FFFFFFFF</color><!-- White -->
+
<!-- Multi-sim sim colors -->
<color name="Teal_700">#ff00796b</color>
<color name="Teal_800">#ff00695c</color>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f2da845..907294d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1322,6 +1322,7 @@
<java-symbol type="drawable" name="ic_corp_user_badge" />
<java-symbol type="drawable" name="ic_corp_badge_no_background" />
<java-symbol type="drawable" name="ic_corp_statusbar_icon" />
+ <java-symbol type="drawable" name="ic_instant_icon_badge_bolt" />
<java-symbol type="drawable" name="emulator_circular_window_overlay" />
<java-symbol type="drawable" name="sim_light_blue" />
@@ -1353,6 +1354,7 @@
<java-symbol type="color" name="profile_badge_1" />
<java-symbol type="color" name="profile_badge_2" />
<java-symbol type="color" name="profile_badge_3" />
+ <java-symbol type="color" name="instant_app_badge" />
<java-symbol type="layout" name="action_bar_home" />
<java-symbol type="layout" name="action_bar_title_item" />