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>&lt;receiver&gt;</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>&lt;receiver&gt;</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>&lt;receiver&gt;</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>&lt;receiver&gt;</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);
+            }
         }
     }