Raster and badge directshare with IconFactory

Removes badge view and id. Unifies permission check for icon
loading. Fixes a threading issue in IconFactory so badging works
properly. Partly setting up for label and sublabel loading in a
future commit.

Fixes: 126568207
Test: manual
Change-Id: I265d08cc3a5c1499da252307fea6e28b22d59066
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f250666..ab8177f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -58,6 +58,7 @@
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.metrics.LogMaker;
@@ -1463,14 +1464,6 @@
             return null;
         }
 
-        public Drawable getBadgeIcon() {
-            return null;
-        }
-
-        public CharSequence getBadgeContentDescription() {
-            return null;
-        }
-
         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
             return null;
         }
@@ -1559,31 +1552,49 @@
          */
         // TODO(121287224): Refactor code to apply the suggestion above
         private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
+            Drawable directShareIcon = null;
+
+            // First get the target drawable and associated activity info
             final Icon icon = target.getIcon();
             if (icon != null) {
-                return icon.loadDrawable(ChooserActivity.this);
-            }
-            if (!USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
-                return null;
+                directShareIcon = icon.loadDrawable(ChooserActivity.this);
+            } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
+                Bundle extras = target.getIntentExtras();
+                if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
+                    CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
+                    LauncherApps launcherApps = (LauncherApps) getSystemService(
+                            Context.LAUNCHER_APPS_SERVICE);
+                    final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
+                    q.setPackage(target.getComponentName().getPackageName());
+                    q.setShortcutIds(Arrays.asList(shortcutId.toString()));
+                    q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
+                    final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
+                    if (shortcuts != null && shortcuts.size() > 0) {
+                        directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
+                    }
+                }
             }
 
-            Bundle extras = target.getIntentExtras();
-            if (extras == null || !extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
-                return null;
-            }
-            CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
-            LauncherApps launcherApps = (LauncherApps) getSystemService(
-                    Context.LAUNCHER_APPS_SERVICE);
-            final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
-            q.setPackage(target.getComponentName().getPackageName());
-            q.setShortcutIds(Arrays.asList(shortcutId.toString()));
-            q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
-            final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
-            if (shortcuts != null && shortcuts.size() > 0) {
-                return launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
+            if (directShareIcon == null) return null;
+
+            ActivityInfo info = null;
+            try {
+                info = mPm.getActivityInfo(target.getComponentName(), 0);
+            } catch (NameNotFoundException error) {
+                Log.e(TAG, "Could not find activity associated with ChooserTarget");
             }
 
-            return null;
+            if (info == null) return null;
+
+            // Now fetch app icon and raster with no badging even in work profile
+            Bitmap appIcon = (new ActivityInfoPresentationGetter(info)).getIconBitmap();
+
+            // Raster target drawable with appIcon as a badge
+            SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
+            Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
+            sif.recycle();
+
+            return new BitmapDrawable(getResources(), directShareBadgedIcon);
         }
 
         public float getModifiedScore() {
@@ -1681,16 +1692,6 @@
             return mDisplayIcon;
         }
 
-        @Override
-        public Drawable getBadgeIcon() {
-            return mBadgeIcon;
-        }
-
-        @Override
-        public CharSequence getBadgeContentDescription() {
-            return mBadgeContentDescription;
-        }
-
         public ChooserTarget getChooserTarget() {
             return mChooserTarget;
         }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 12942ab..21152ae 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -42,7 +42,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Color;
+import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -133,8 +133,6 @@
     /** See {@link #setRetainInOnStop}. */
     private boolean mRetainInOnStop;
 
-    SimpleIconFactory mSimpleIconFactory;
-
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override public void onSomePackagesChanged() {
             mAdapter.handlePackagesChanged();
@@ -311,11 +309,6 @@
         // as to mitigate Intent Capturing vulnerability
         mSupportsAlwaysUseOption = supportsAlwaysUseOption && !mUseLayoutForBrowsables;
 
-        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;
         }
@@ -500,64 +493,150 @@
         }
     }
 
-    @Nullable
-    Drawable getIcon(Resources res, int resId) {
-        Drawable result;
-        try {
-            result = res.getDrawableForDensity(resId, mIconDpi);
-        } catch (Resources.NotFoundException e) {
-            result = null;
-        }
-
-        return result;
-    }
 
     /**
-     * Loads the icon for the provided ResolveInfo. Defaults to using the application icon over
+     * Loads the icon for the provided ApplicationInfo. 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 = null;
+    private abstract class TargetPresentationGetter {
+        @Nullable abstract Drawable getIconSubstitute();
+        @Nullable abstract String getAppSubLabel();
 
-        // 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);
-                }
-                if (dr == null) {
-                    final int iconRes = ri.getIconResource();
-                    if (iconRes != 0) {
-                        dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName),
-                                iconRes);
+        private final ApplicationInfo mAi;
+        private final boolean mHasSubstitutePermission;
+
+        TargetPresentationGetter(ApplicationInfo ai) {
+            mAi = ai;
+            mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
+                    android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
+                    mAi.packageName);
+        }
+
+        Drawable getIcon() {
+            return new BitmapDrawable(getResources(), getIconBitmap());
+        }
+
+        Bitmap getIconBitmap() {
+            Drawable dr = null;
+            if (mHasSubstitutePermission) {
+                dr = getIconSubstitute();
+            }
+
+            if (dr == null) {
+                try {
+                    if (mAi.icon != 0) {
+                        dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
                     }
+                } catch (NameNotFoundException ignore) {
+                }
+            }
+
+            // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
+            if (dr == null) {
+                dr = mAi.loadIcon(mPm);
+            }
+
+            SimpleIconFactory sif = SimpleIconFactory.obtain(ResolverActivity.this);
+            Bitmap icon = sif.createUserBadgedIconBitmap(dr, Process.myUserHandle());
+            sif.recycle();
+
+            return icon;
+        }
+
+        String getLabel() {
+            String label = null;
+            // Apps with the substitute permission will always show the sublabel as their label
+            if (mHasSubstitutePermission) {
+                label = getAppSubLabel();
+            }
+
+            if (label == null) {
+                label = (String) mAi.loadLabel(mPm);
+            }
+
+            return label;
+        }
+
+        String getSubLabel() {
+            // Apps with the substitute permission will never have a sublabel
+            if (mHasSubstitutePermission) return null;
+            return getAppSubLabel();
+        }
+
+        @Nullable
+        protected Drawable loadIconFromResource(Resources res, int resId) {
+            return res.getDrawableForDensity(resId, mIconDpi);
+        }
+
+    }
+
+    protected class ResolveInfoPresentationGetter extends TargetPresentationGetter {
+
+        private final ResolveInfo mRi;
+
+        ResolveInfoPresentationGetter(ResolveInfo ri) {
+            super(ri.activityInfo.applicationInfo);
+            mRi = ri;
+        }
+
+        @Override
+        Drawable getIconSubstitute() {
+            Drawable dr = null;
+            try {
+                // Do not use ResolveInfo#getIconResource() as it defaults to the app
+                if (mRi.resolvePackageName != null && mRi.icon != 0) {
+                    dr = loadIconFromResource(
+                            mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon);
                 }
             } catch (NameNotFoundException e) {
                 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
                         + "couldn't find resources for package", e);
             }
+
+            return dr;
         }
 
-        // Use app icons for better user association
-        if (dr == null) {
+        @Override
+        String getAppSubLabel() {
+            return (String) mRi.loadLabel(mPm);
+        }
+    }
+
+    protected class ActivityInfoPresentationGetter extends TargetPresentationGetter {
+        private final ActivityInfo mActivityInfo;
+        protected ActivityInfoPresentationGetter(ActivityInfo activityInfo) {
+            super(activityInfo.applicationInfo);
+            mActivityInfo = activityInfo;
+        }
+
+        @Override
+        Drawable getIconSubstitute() {
+            Drawable dr = null;
             try {
-                dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.applicationInfo),
-                        ri.activityInfo.applicationInfo.icon);
-            } catch (NameNotFoundException ignore) {
+                // Do not use ActivityInfo#getIconResource() as it defaults to the app
+                if (mActivityInfo.icon != 0) {
+                    dr = loadIconFromResource(
+                            mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
+                            mActivityInfo.icon);
+                }
+            } catch (NameNotFoundException e) {
+                Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+                        + "couldn't find resources for package", e);
             }
+
+            return dr;
         }
 
-        // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
-        if (dr == null) {
-            dr = ri.activityInfo.applicationInfo.loadIcon(mPm);
+        @Override
+        String getAppSubLabel() {
+            return (String) mActivityInfo.loadLabel(mPm);
         }
+    }
 
-        return new BitmapDrawable(this.getResources(),
-                mSimpleIconFactory.createUserBadgedIconBitmap(dr, Process.myUserHandle()));
+    Drawable loadIconForResolveInfo(ResolveInfo ri) {
+        return (new ResolveInfoPresentationGetter(ri)).getIcon();
     }
 
     @Override
@@ -1250,33 +1329,6 @@
             return mDisplayIcon;
         }
 
-        public Drawable getBadgeIcon() {
-            // We only expose a badge if we have extended info.
-            // The badge is a higher-priority disambiguation signal
-            // but we don't need one if we wouldn't show extended info at all.
-            if (TextUtils.isEmpty(getExtendedInfo())) {
-                return null;
-            }
-
-            if (mBadge == null && mResolveInfo != null && mResolveInfo.activityInfo != null
-                    && mResolveInfo.activityInfo.applicationInfo != null) {
-                if (mResolveInfo.activityInfo.icon == 0 || mResolveInfo.activityInfo.icon
-                        == mResolveInfo.activityInfo.applicationInfo.icon) {
-                    // Badging an icon with exactly the same icon is silly.
-                    // If the activityInfo icon resid is 0 it will fall back
-                    // to the application's icon, making it a match.
-                    return null;
-                }
-                mBadge = mResolveInfo.activityInfo.applicationInfo.loadIcon(mPm);
-            }
-            return mBadge;
-        }
-
-        @Override
-        public CharSequence getBadgeContentDescription() {
-            return null;
-        }
-
         @Override
         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
             return new DisplayResolveInfo(this, fillInIntent, flags);
@@ -1413,21 +1465,11 @@
         CharSequence getExtendedInfo();
 
         /**
-         * @return The drawable that should be used to represent this target
+         * @return The drawable that should be used to represent this target including badge
          */
         Drawable getDisplayIcon();
 
         /**
-         * @return The (small) icon to badge the target with
-         */
-        Drawable getBadgeIcon();
-
-        /**
-         * @return The content description for the badge icon
-         */
-        CharSequence getBadgeContentDescription();
-
-        /**
          * Clone this target with the given fill-in information.
          */
         TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
@@ -1963,16 +2005,6 @@
                 new LoadAdapterIconTask((DisplayResolveInfo) info).execute();
             }
             holder.icon.setImageDrawable(info.getDisplayIcon());
-            if (holder.badge != null) {
-                final Drawable badge = info.getBadgeIcon();
-                if (badge != null) {
-                    holder.badge.setImageDrawable(badge);
-                    holder.badge.setContentDescription(info.getBadgeContentDescription());
-                    holder.badge.setVisibility(View.VISIBLE);
-                } else {
-                    holder.badge.setVisibility(View.GONE);
-                }
-            }
         }
     }
 
@@ -2027,13 +2059,11 @@
         public TextView text;
         public TextView text2;
         public ImageView icon;
-        public ImageView badge;
 
         public ViewHolder(View view) {
             text = (TextView) view.findViewById(com.android.internal.R.id.text1);
             text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
             icon = (ImageView) view.findViewById(R.id.icon);
-            badge = (ImageView) view.findViewById(R.id.target_badge);
         }
     }
 
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
index eb1530e..a85485d 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -16,11 +16,13 @@
 
 package com.android.internal.app;
 
+import static android.content.Context.ACTIVITY_SERVICE;
 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.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -42,6 +44,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.AttributeSet;
+import android.util.Pools.SynchronizedPool;
 
 import com.android.internal.R;
 
@@ -58,6 +61,9 @@
 @Deprecated
 public class SimpleIconFactory {
 
+    private static final SynchronizedPool<SimpleIconFactory> sPool =
+            new SynchronizedPool<>(Runtime.getRuntime().availableProcessors());
+
     private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
     private static final float BLUR_FACTOR = 0.5f / 48;
 
@@ -74,10 +80,45 @@
     private final Rect mOldBounds = new Rect();
 
     /**
+     * Obtain a SimpleIconFactory from a pool objects.
+     *
      * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
      */
     @Deprecated
-    SimpleIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
+    public static SimpleIconFactory obtain(Context ctx) {
+        SimpleIconFactory instance = sPool.acquire();
+        if (instance == null) {
+            final ActivityManager am = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE);
+            final int iconDpi = (am == null) ? 0 : am.getLauncherLargeIconDensity();
+
+            final Resources r = ctx.getResources();
+            final int iconSize = r.getDimensionPixelSize(R.dimen.resolver_icon_size);
+            final int badgeSize = r.getDimensionPixelSize(R.dimen.resolver_badge_size);
+
+            instance = new SimpleIconFactory(ctx, iconDpi, iconSize, badgeSize);
+            instance.setWrapperBackgroundColor(Color.WHITE);
+        }
+
+        return instance;
+    }
+
+    /**
+     * Recycles the SimpleIconFactory so others may use it.
+     *
+     * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
+     */
+    @Deprecated
+    public void recycle() {
+        // Return to default background color
+        setWrapperBackgroundColor(Color.WHITE);
+        sPool.release(this);
+    }
+
+    /**
+     * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
+     */
+    @Deprecated
+    private SimpleIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
             int badgeBitmapSize) {
         mContext = context.getApplicationContext();
         mPm = mContext.getPackageManager();
@@ -170,7 +211,7 @@
      * @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
      */
     @Deprecated
-    public Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
+    Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
         // Flatten the passed in icon
         float [] scale = new float[1];
 
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 71c153f..a24f892 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -27,22 +27,13 @@
               android:focusable="true"
               android:background="?attr/selectableItemBackgroundBorderless">
 
-    <FrameLayout android:layout_width="wrap_content"
-                 android:layout_height="wrap_content">
-        <ImageView android:id="@+id/icon"
-                   android:layout_width="48dp"
-                   android:layout_height="48dp"
-                   android:layout_marginLeft="3dp"
-                   android:layout_marginRight="3dp"
-                   android:layout_marginBottom="3dp"
-                   android:scaleType="fitCenter" />
-        <ImageView android:id="@+id/target_badge"
-                   android:layout_width="16dp"
-                   android:layout_height="16dp"
-                   android:layout_gravity="end|bottom"
-                   android:visibility="gone"
-                   android:scaleType="fitCenter" />
-    </FrameLayout>
+    <ImageView android:id="@+id/icon"
+               android:layout_width="@dimen/resolver_icon_size"
+               android:layout_height="@dimen/resolver_icon_size"
+               android:layout_marginLeft="3dp"
+               android:layout_marginRight="3dp"
+               android:layout_marginBottom="3dp"
+               android:scaleType="fitCenter" />
 
     <!-- Activity name -->
     <TextView android:id="@android:id/text1"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6629b4c..6956a07 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2774,7 +2774,6 @@
 
   <java-symbol type="layout" name="chooser_row" />
   <java-symbol type="layout" name="chooser_row_direct_share" />
-  <java-symbol type="id" name="target_badge" />
   <java-symbol type="bool" name="config_supportDoubleTapWake" />
   <java-symbol type="drawable" name="ic_perm_device_info" />
   <java-symbol type="string" name="config_radio_access_family" />