Polish of the app widgets cross-profiles feature.
1. Added API for badging an arbitrary drawable at a given location.
2. Updated the icon and previewImage deprecation as they are no longer
returning a badged drawable. The methods to load the icon and the
preview are now just making it easier for a developer to get the
drawables.
3. Fixed a bug in AppWidgetServiceImpl leading to a crash when a user
is removed.
4. Fixed a bug in AppWidgetHost which was unnecessarily caching its
package name and having code paths where the cached value was not
populated when calling into the system.
bug:14991269
Change-Id: I50d011a6597d88814715d5ec04ee67815e8ce0bd
diff --git a/api/current.txt b/api/current.txt
index 7743a59..e1460f2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5834,7 +5834,7 @@
field public static final int WIDGET_CATEGORY_RECENTS = 4; // 0x4
field public int autoAdvanceViewId;
field public android.content.ComponentName configure;
- field public deprecated int icon;
+ field public int icon;
field public int initialKeyguardLayout;
field public int initialLayout;
field public deprecated java.lang.String label;
@@ -5842,7 +5842,7 @@
field public int minResizeHeight;
field public int minResizeWidth;
field public int minWidth;
- field public deprecated int previewImage;
+ field public int previewImage;
field public android.content.ComponentName provider;
field public int resizeMode;
field public int updatePeriodMillis;
@@ -22775,7 +22775,8 @@
public class UserManager {
method public android.os.Bundle getApplicationRestrictions(java.lang.String);
- method public android.graphics.drawable.Drawable getBadgedDrawableForUser(android.graphics.drawable.Drawable, android.os.UserHandle);
+ method public android.graphics.drawable.Drawable getBadgedDrawableForUser(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
+ method public android.graphics.drawable.Drawable getBadgedIconForUser(android.graphics.drawable.Drawable, android.os.UserHandle);
method public java.lang.CharSequence getBadgedLabelForUser(java.lang.CharSequence, android.os.UserHandle);
method public long getSerialNumberForUser(android.os.UserHandle);
method public int getUserCount();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 797a0a0..5b36b8b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2562,7 +2562,7 @@
// Note: This assumes that the current user can read the profile badge of the
// originating user.
UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- Drawable badge = userManager.getBadgeForUser(new UserHandle(mOriginatingUserId));
+ Drawable badge = userManager.getBadgeForUser(new UserHandle(mOriginatingUserId), 0);
if (badge == null) {
return null;
}
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index e7b68f5..66a6eb6 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -32,7 +32,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.RemoteViews;
@@ -57,7 +56,6 @@
private DisplayMetrics mDisplayMetrics;
Context mContext;
- String mPackageName;
Handler mHandler;
int mHostId;
Callbacks mCallbacks = new Callbacks();
@@ -154,10 +152,7 @@
int[] updatedIds;
ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
try {
- if (mPackageName == null) {
- mPackageName = mContext.getPackageName();
- }
- updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId,
+ updatedIds = sService.startListening(mCallbacks, mContext.getPackageName(), mHostId,
updatedViews);
}
catch (RemoteException e) {
@@ -176,7 +171,7 @@
*/
public void stopListening() {
try {
- sService.stopListening(mPackageName, mHostId);
+ sService.stopListening(mContext.getPackageName(), mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -194,10 +189,7 @@
*/
public int allocateAppWidgetId() {
try {
- if (mPackageName == null) {
- mPackageName = mContext.getPackageName();
- }
- return sService.allocateAppWidgetId(mPackageName, mHostId);
+ return sService.allocateAppWidgetId(mContext.getPackageName(), mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -326,7 +318,7 @@
}
RemoteViews views;
try {
- views = sService.getAppWidgetViews(mPackageName, appWidgetId);
+ views = sService.getAppWidgetViews(mContext.getPackageName(), appWidgetId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index e4dad5a..6835368 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -20,13 +20,11 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.content.ComponentName;
import android.os.UserHandle;
-import android.os.UserManager;
/**
* Describes the meta data for an installed AppWidget provider. The fields in this class
@@ -166,10 +164,7 @@
*
* <p>This field corresponds to the <code>android:icon</code> attribute in
* the <code><receiver></code> element in the AndroidManifest.xml file.
- *
- * @deprecated Use {@link #loadIcon(android.content.Context, int)}.
*/
- @Deprecated
public int icon;
/**
@@ -186,10 +181,7 @@
*
* <p>This field corresponds to the <code>android:previewImage</code> attribute in
* the <code><receiver></code> element in the AndroidManifest.xml file.
- *
- * @deprecated User {@link #loadPreviewImage(android.content.Context, int)}.
*/
- @Deprecated
public int previewImage;
/**
@@ -271,16 +263,11 @@
* The loaded icon corresponds to the <code>android:icon</code> attribute in
* the <code><receiver></code> element in the AndroidManifest.xml file.
* </p>
- * <p>
- * <strong>Note:</strong> If you care about widgets from different profiles, you
- * should use this method to load the icon as the system will apply the correct
- * badging when applicable, so the user knows which profile a widget comes from.
- * </p>
*
* @param context Context for accessing resources.
* @param density The optional desired density as per
* {@link android.util.DisplayMetrics#densityDpi}.
- * @return The potentially badged provider icon.
+ * @return The provider icon.
*/
public final Drawable loadIcon(Context context, int density) {
return loadDrawable(context, density, providerInfo.getIconResource());
@@ -296,19 +283,12 @@
* The loaded image corresponds to the <code>android:previewImage</code> attribute
* in the <code><receiver></code> element in the AndroidManifest.xml file.
* </p>
- * <p>
- * <strong>Note:</strong> If you care about widgets from different profiles, you
- * should use this method to load the preview image as the system will apply the
- * correct badging when applicable, so the user knows which profile a previewed
- * widget comes from.
- * </p>
*
* @param context Context for accessing resources.
* @param density The optional desired density as per
* {@link android.util.DisplayMetrics#densityDpi}.
- * @return The potentially badged widget preview image.
+ * @return The widget preview image.
*/
- @SuppressWarnings("deprecation")
public final Drawable loadPreviewImage(Context context, int density) {
return loadDrawable(context, density, previewImage);
}
@@ -384,27 +364,16 @@
try {
Resources resources = context.getPackageManager().getResourcesForApplication(
providerInfo.applicationInfo);
-
- final Drawable drawable;
if (resourceId > 0) {
if (density <= 0) {
density = context.getResources().getDisplayMetrics().densityDpi;
}
- drawable = resources.getDrawableForDensity(resourceId, density);
- } else {
- drawable = providerInfo.loadIcon(context.getPackageManager());
- }
-
- if (drawable instanceof BitmapDrawable) {
- UserManager userManager = (UserManager) context.getSystemService(
- Context.USER_SERVICE);
- return userManager.getBadgedDrawableForUser(drawable, getProfile());
+ return resources.getDrawableForDensity(resourceId, density);
}
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
/* ignore */
}
-
- return null;
+ return providerInfo.loadIcon(context.getPackageManager());
}
/**
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 5d48868..0cff08b 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -179,8 +179,7 @@
}
if (originalIcon instanceof BitmapDrawable) {
- return mUm.getBadgedDrawableForUser(
- originalIcon, mUser);
+ return mUm.getBadgedIconForUser(originalIcon, mUser);
} else {
Log.e(TAG, "Unable to create badged icon for " + mActivityInfo);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 13f93a7..04e6227 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -755,17 +755,50 @@
/**
* If the target user is a managed profile of the calling user or the caller
* is itself a managed profile, then this returns a badged copy of the given
- * icon to be able to distinguish it from the original icon.
- * <P>
- * If the original drawable is not a BitmapDrawable, then the original
- * drawable is returned.
- * </P>
+ * icon to be able to distinguish it from the original icon. For badging an
+ * arbitrary drawable use {@link #getBadgedDrawableForUser(
+ * android.graphics.drawable.Drawable, UserHandle, android.graphics.Rect, int)}.
+ * <p>
+ * If the original drawable is a BitmapDrawable and the backing bitmap is
+ * mutable as per {@link android.graphics.Bitmap#isMutable()}, the bading
+ * is performed in place and the original drawable is returned.
+ * </p>
*
* @param icon The icon to badge.
* @param user The target user.
* @return A drawable that combines the original icon and a badge as
* determined by the system.
*/
+ public Drawable getBadgedIconForUser(Drawable icon, UserHandle user) {
+ final int badgeResId = getBadgeResIdForUser(user.getIdentifier());
+ if (badgeResId == 0) {
+ return icon;
+ }
+ Drawable badgeIcon = mContext.getPackageManager()
+ .getDrawable("system", badgeResId, null);
+ return getBadgedDrawable(icon, badgeIcon, null, true);
+ }
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a badged copy of the given
+ * icon to be able to distinguish it from the original icon.
+ * <p>
+ * If the original drawable is not a BitmapDrawable, then the original
+ * drawable is returned.
+ * </p>
+ *
+ * @param icon The icon to badge.
+ * @param user The target user.
+ * @return A drawable that combines the original icon and a badge as
+ * determined by the system.
+ *
+ * @deprecation Use {@link #getBadgedIconForUser(
+ * android.graphics.drawable.Drawable, UserHandle)}
+ *
+ * @hide
+ */
+ @Deprecated
public Drawable getBadgedDrawableForUser(Drawable icon, UserHandle user) {
int badgeResId = getBadgeResIdForUser(user.getIdentifier());
if (badgeResId == 0) {
@@ -773,12 +806,45 @@
} else {
Drawable badgeIcon = mContext.getPackageManager()
.getDrawable("system", badgeResId, null);
- return getMergedDrawable(icon, badgeIcon);
+ return getBadgedDrawable(icon, badgeIcon, null, false);
}
}
/**
* If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a badged copy of the given
+ * drawable allowing the user to distinguish it from the original drawable.
+ * The caller can specify the location in the bounds of the drawable to be
+ * badged where the badge should be applied as well as the density of the
+ * badge to be used.
+ * <p>
+ * If the original drawable is a BitmapDrawable and the backing bitmap is
+ * mutable as per {@link android.graphics.Bitmap#isMutable()}, the bading
+ * is performed in place and the original drawable is returned.
+ * </p>
+ *
+ * @param badgedDrawable The drawable to badge.
+ * @param user The target user.
+ * @param badgeLocation Where in the bounds of the badged drawable to place
+ * the badge. If not provided, the badge is applied on top of the entire
+ * drawable being badged.
+ * @param badgeDensity The optional desired density for the badge as per
+ * {@link android.util.DisplayMetrics#densityDpi}. If not provided,
+ * the density of the display is used.
+ * @return A drawable that combines the original drawable and a badge as
+ * determined by the system.
+ */
+ public Drawable getBadgedDrawableForUser(Drawable badgedDrawable, UserHandle user,
+ Rect badgeLocation, int badgeDensity) {
+ Drawable badgeDrawable = getBadgeForUser(user, badgeDensity);
+ if (badgeDrawable == null) {
+ return badgedDrawable;
+ }
+ return getBadgedDrawable(badgedDrawable, badgeDrawable, badgeLocation, true);
+ }
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
* is itself a managed profile, then this returns a copy of the label with
* badging for accessibility services like talkback. E.g. passing in "Email"
* and it might return "Work Email" for Email in the work profile.
@@ -812,14 +878,20 @@
* icon to include in a view to distinguish it from the original icon.
*
* @param user The target user.
+ * @param density The optional desired density for the badge as per
+ * {@link android.util.DisplayMetrics#densityDpi}. If not provided
+ * the density of the current display is used.
* @return the drawable or null if no drawable is required.
* @hide
*/
- public Drawable getBadgeForUser(UserHandle user) {
+ public Drawable getBadgeForUser(UserHandle user, int density) {
UserInfo userInfo = getUserIfProfile(user.getIdentifier());
if (userInfo != null && userInfo.isManagedProfile()) {
- return Resources.getSystem().getDrawable(
- com.android.internal.R.drawable.ic_corp_badge);
+ if (density <= 0) {
+ density = mContext.getResources().getDisplayMetrics().densityDpi;
+ }
+ return Resources.getSystem().getDrawableForDensity(
+ com.android.internal.R.drawable.ic_corp_badge, density);
}
return null;
}
@@ -847,20 +919,57 @@
return null;
}
- private Drawable getMergedDrawable(Drawable icon, Drawable badge) {
- final int width = icon.getIntrinsicWidth();
- final int height = icon.getIntrinsicHeight();
- Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- icon.setBounds(0, 0, width, height);
- icon.draw(canvas);
- badge.setBounds(0, 0, width, height);
- badge.draw(canvas);
- BitmapDrawable merged = new BitmapDrawable(bitmap);
- if (icon instanceof BitmapDrawable) {
- merged.setTargetDensity(((BitmapDrawable) icon).getBitmap().getDensity());
+ private Drawable getBadgedDrawable(Drawable badgedDrawable, Drawable badgeDrawable,
+ Rect badgeLocation, boolean tryBadgeInPlace) {
+ final int badgedWidth = badgedDrawable.getIntrinsicWidth();
+ final int badgedHeight = badgedDrawable.getIntrinsicHeight();
+ final boolean canBadgeInPlace = tryBadgeInPlace
+ && (badgedDrawable instanceof BitmapDrawable)
+ && ((BitmapDrawable) badgedDrawable).getBitmap().isMutable();
+
+ final Bitmap bitmap;
+ if (canBadgeInPlace) {
+ bitmap = ((BitmapDrawable) badgedDrawable).getBitmap();
+ } else {
+ bitmap = Bitmap.createBitmap(badgedWidth, badgedHeight, Config.ARGB_8888);
}
- return merged;
+ Canvas canvas = new Canvas(bitmap);
+
+ if (!canBadgeInPlace) {
+ badgedDrawable.setBounds(0, 0, badgedWidth, badgedHeight);
+ badgedDrawable.draw(canvas);
+ }
+
+ if (badgeLocation != null) {
+ if (badgeLocation.left < 0 || badgeLocation.top < 0
+ || badgeLocation.right > badgedWidth || badgeLocation.bottom > badgedHeight) {
+ throw new IllegalArgumentException("Badge location " + badgeLocation
+ + " not in badged drawable bounds "
+ + new Rect(0, 0, badgedWidth, badgedHeight));
+ }
+ badgeDrawable.setBounds(0, 0, badgeLocation.width(), badgeLocation.height());
+
+ canvas.save();
+ canvas.translate(badgeLocation.left, badgeLocation.top);
+ badgeDrawable.draw(canvas);
+ canvas.restore();
+ } else {
+ badgeDrawable.setBounds(0, 0, badgedWidth, badgedHeight);
+ badgeDrawable.draw(canvas);
+ }
+
+ if (!canBadgeInPlace) {
+ BitmapDrawable mergedDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
+
+ if (badgedDrawable instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) badgedDrawable;
+ mergedDrawable.setTargetDensity(bitmapDrawable.getBitmap().getDensity());
+ }
+
+ return mergedDrawable;
+ }
+
+ return badgedDrawable;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 0a9e062..7ec0cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1145,7 +1145,7 @@
if (profileIcon != null) {
Drawable profileDrawable
- = mUserManager.getBadgeForUser(entry.notification.getUser());
+ = mUserManager.getBadgeForUser(entry.notification.getUser(), 0);
if (profileDrawable != null) {
profileIcon.setImageDrawable(profileDrawable);
profileIcon.setVisibility(View.VISIBLE);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index bdaf9ec..e9d0c46 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2732,13 +2732,16 @@
}
// Take a note we no longer have state for this user.
- final int index = mLoadedUserIds.indexOfKey(userId);
- if (index >= 0) {
- mLoadedUserIds.removeAt(index);
+ final int userIndex = mLoadedUserIds.indexOfKey(userId);
+ if (userIndex >= 0) {
+ mLoadedUserIds.removeAt(userIndex);
}
// Remove the widget id counter.
- mNextAppWidgetIds.removeAt(userId);
+ final int nextIdIndex = mNextAppWidgetIds.indexOfKey(userId);
+ if (nextIdIndex >= 0) {
+ mNextAppWidgetIds.removeAt(nextIdIndex);
+ }
}
}