Merge "Open dialog panel after long pressing a direct share target to allow pin/unpin the target. Communicate with ShortcutManager/Launcherapps to store pin info in ShortcutService."
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 595c963..3cb39e7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1620,29 +1620,48 @@
         return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
     }
 
-    private void showTargetDetails(DisplayResolveInfo ti) {
-        if (ti == null) return;
+    private void showTargetDetails(TargetInfo targetInfo) {
+        if (targetInfo == null) return;
 
         ArrayList<DisplayResolveInfo> targetList;
+        ChooserTargetActionsDialogFragment fragment = new ChooserTargetActionsDialogFragment();
+        Bundle bundle = new Bundle();
 
-        // For multiple targets, include info on all targets
-        if (ti instanceof MultiDisplayResolveInfo) {
-            MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
+        if (targetInfo instanceof SelectableTargetInfo) {
+            SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo;
+            if (selectableTargetInfo.getDisplayResolveInfo() == null
+                    || selectableTargetInfo.getChooserTarget() == null) {
+                Log.e(TAG, "displayResolveInfo or chooserTarget in selectableTargetInfo are null");
+                return;
+            }
+            targetList = new ArrayList<>();
+            targetList.add(selectableTargetInfo.getDisplayResolveInfo());
+            bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_ID_KEY,
+                    selectableTargetInfo.getChooserTarget().getIntentExtras().getString(
+                            Intent.EXTRA_SHORTCUT_ID));
+            bundle.putBoolean(ChooserTargetActionsDialogFragment.IS_SHORTCUT_PINNED_KEY,
+                    selectableTargetInfo.isPinned());
+            bundle.putParcelable(ChooserTargetActionsDialogFragment.INTENT_FILTER_KEY,
+                    getTargetIntentFilter());
+            if (selectableTargetInfo.getDisplayLabel() != null) {
+                bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_TITLE_KEY,
+                        selectableTargetInfo.getDisplayLabel().toString());
+            }
+        } else if (targetInfo instanceof MultiDisplayResolveInfo) {
+            // For multiple targets, include info on all targets
+            MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
             targetList = mti.getTargets();
         } else {
             targetList = new ArrayList<DisplayResolveInfo>();
-            targetList.add(ti);
+            targetList.add((DisplayResolveInfo) targetInfo);
         }
-
-        ChooserTargetActionsDialogFragment f = new ChooserTargetActionsDialogFragment();
-        Bundle b = new Bundle();
-        b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
+        bundle.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
-        b.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
+        bundle.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
                 targetList);
-        f.setArguments(b);
+        fragment.setArguments(bundle);
 
-        f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+        fragment.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
     }
 
     private void modifyTargetIntent(Intent in) {
@@ -2868,8 +2887,10 @@
     private boolean shouldShowTargetDetails(TargetInfo ti) {
         ComponentName nearbyShare = getNearbySharingComponent();
         //  Suppress target details for nearby share to hide pin/unpin action
-        return !(nearbyShare != null && nearbyShare.equals(ti.getResolvedComponentName())
-                && shouldNearbyShareBeFirstInRankedRow());
+        boolean isNearbyShare = nearbyShare != null && nearbyShare.equals(
+                ti.getResolvedComponentName()) && shouldNearbyShareBeFirstInRankedRow();
+        return ti instanceof SelectableTargetInfo
+                || (ti instanceof DisplayResolveInfo && !isNearbyShare);
     }
 
     /**
@@ -2966,8 +2987,6 @@
         private DirectShareViewHolder mDirectShareViewHolder;
         private int mChooserTargetWidth = 0;
         private boolean mShowAzLabelIfPoss;
-
-        private boolean mHideContentPreview = false;
         private boolean mLayoutRequested = false;
 
         private int mFooterHeight = 0;
@@ -3038,7 +3057,6 @@
          * personal and work tabs.
          */
         public void hideContentPreview() {
-            mHideContentPreview = true;
             mLayoutRequested = true;
             notifyDataSetChanged();
         }
@@ -3228,18 +3246,15 @@
                     }
                 });
 
-                // Direct Share targets should not show any menu
-                if (!isDirectShare) {
-                    v.setOnLongClickListener(v1 -> {
-                        final TargetInfo ti = mChooserListAdapter.targetInfoForPosition(
-                                holder.getItemIndex(column), true);
-                        // This should always be the case for non-DS targets, check for validity
-                        if (ti instanceof DisplayResolveInfo && shouldShowTargetDetails(ti)) {
-                            showTargetDetails((DisplayResolveInfo) ti);
-                        }
-                        return true;
-                    });
-                }
+                // Show menu for both direct share and app share targets after long click.
+                v.setOnLongClickListener(v1 -> {
+                    TargetInfo ti = mChooserListAdapter.targetInfoForPosition(
+                            holder.getItemIndex(column), true);
+                    if (shouldShowTargetDetails(ti)) {
+                        showTargetDetails(ti);
+                    }
+                    return true;
+                });
 
                 holder.addView(i, v);
 
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 140fabc..f6445849 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -74,6 +74,7 @@
     public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
     /** {@link #getBaseScore} */
     public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
+    private static final float PINNED_SHORTCUT_TARGET_SCORE_BOOST = 1000.f;
 
     private final int mMaxShortcutTargetsPerApp;
     private final ChooserListCommunicator mChooserListCommunicator;
@@ -275,8 +276,10 @@
             Drawable bkg = mContext.getDrawable(R.drawable.chooser_group_background);
             holder.text.setPaddingRelative(0, 0, bkg.getIntrinsicWidth() /* end */, 0);
             holder.text.setBackground(bkg);
-        } else if (info.isPinned() && getPositionTargetType(position) == TARGET_STANDARD) {
-            // If the target is pinned and in the suggested row show a pinned indicator
+        } else if (info.isPinned() && (getPositionTargetType(position) == TARGET_STANDARD
+                || getPositionTargetType(position) == TARGET_SERVICE)) {
+            // If the appShare or directShare target is pinned and in the suggested row show a
+            // pinned indicator
             Drawable bkg = mContext.getDrawable(R.drawable.chooser_pinned_background);
             holder.text.setPaddingRelative(bkg.getIntrinsicWidth() /* start */, 0, 0, 0);
             holder.text.setBackground(bkg);
@@ -523,11 +526,16 @@
                     targetScore = lastScore * 0.95f;
                 }
             }
+            ShortcutInfo shortcutInfo = isShortcutResult ? directShareToShortcutInfos.get(target)
+                    : null;
+            if ((shortcutInfo != null) && shortcutInfo.isPinned()) {
+                targetScore += PINNED_SHORTCUT_TARGET_SCORE_BOOST;
+            }
             UserHandle userHandle = getUserHandle();
             Context contextAsUser = mContext.createContextAsUser(userHandle, 0 /* flags */);
             boolean isInserted = insertServiceTarget(new SelectableTargetInfo(contextAsUser,
                     origTarget, target, targetScore, mSelectableTargetInfoCommunicator,
-                    (isShortcutResult ? directShareToShortcutInfos.get(target) : null)));
+                    shortcutInfo));
 
             if (isInserted && isShortcutResult) {
                 mNumShortcutResults++;
diff --git a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java
index 9afc0e9..4f1f380 100644
--- a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java
@@ -29,9 +29,14 @@
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.DialogInterface;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -51,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * Shows a dialog with actions to take on a chooser target.
@@ -60,9 +66,17 @@
 
     protected ArrayList<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
     protected UserHandle mUserHandle;
+    protected String mShortcutId;
+    protected String mShortcutTitle;
+    protected boolean mIsShortcutPinned;
+    protected IntentFilter mIntentFilter;
 
     public static final String USER_HANDLE_KEY = "user_handle";
     public static final String TARGET_INFOS_KEY = "target_infos";
+    public static final String SHORTCUT_ID_KEY = "shortcut_id";
+    public static final String SHORTCUT_TITLE_KEY = "shortcut_title";
+    public static final String IS_SHORTCUT_PINNED_KEY = "is_shortcut_pinned";
+    public static final String INTENT_FILTER_KEY = "intent_filter";
 
     public ChooserTargetActionsDialogFragment() {}
 
@@ -79,6 +93,10 @@
     void setStateFromBundle(Bundle b) {
         mTargetInfos = (ArrayList<DisplayResolveInfo>) b.get(TARGET_INFOS_KEY);
         mUserHandle = (UserHandle) b.get(USER_HANDLE_KEY);
+        mShortcutId = b.getString(SHORTCUT_ID_KEY);
+        mShortcutTitle = b.getString(SHORTCUT_TITLE_KEY);
+        mIsShortcutPinned = b.getBoolean(IS_SHORTCUT_PINNED_KEY);
+        mIntentFilter = (IntentFilter) b.get(INTENT_FILTER_KEY);
     }
 
     @Override
@@ -89,6 +107,11 @@
                 mUserHandle);
         outState.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
                 mTargetInfos);
+        outState.putString(ChooserTargetActionsDialogFragment.SHORTCUT_ID_KEY, mShortcutId);
+        outState.putBoolean(ChooserTargetActionsDialogFragment.IS_SHORTCUT_PINNED_KEY,
+                mIsShortcutPinned);
+        outState.putString(ChooserTargetActionsDialogFragment.SHORTCUT_TITLE_KEY, mShortcutTitle);
+        outState.putParcelable(ChooserTargetActionsDialogFragment.INTENT_FILTER_KEY, mIntentFilter);
     }
 
     /**
@@ -121,7 +144,7 @@
         RecyclerView rv = v.findViewById(R.id.listContainer);
 
         final ResolveInfoPresentationGetter pg = getProvidingAppPresentationGetter();
-        title.setText(pg.getLabel());
+        title.setText(isShortcutTarget() ? mShortcutTitle : pg.getLabel());
         icon.setImageDrawable(pg.getIcon(mUserHandle));
         rv.setAdapter(new VHAdapter(items));
 
@@ -180,11 +203,45 @@
 
     @Override
     public void onClick(DialogInterface dialog, int which) {
-        pinComponent(mTargetInfos.get(which).getResolvedComponentName());
+        if (isShortcutTarget()) {
+            toggleShortcutPinned(mTargetInfos.get(which).getResolvedComponentName());
+        } else {
+            pinComponent(mTargetInfos.get(which).getResolvedComponentName());
+        }
         ((ChooserActivity) getActivity()).handlePackagesChanged();
         dismiss();
     }
 
+    private void toggleShortcutPinned(ComponentName name) {
+        if (mIntentFilter == null) {
+            return;
+        }
+        // Fetch existing pinned shortcuts of the given package.
+        List<String> pinnedShortcuts = getPinnedShortcutsFromPackageAsUser(getContext(),
+                mUserHandle, mIntentFilter, name.getPackageName());
+        // If the shortcut has already been pinned, unpin it; otherwise, pin it.
+        if (mIsShortcutPinned) {
+            pinnedShortcuts.remove(mShortcutId);
+        } else {
+            pinnedShortcuts.add(mShortcutId);
+        }
+        // Update pinned shortcut list in ShortcutService via LauncherApps
+        getContext().getSystemService(LauncherApps.class).pinShortcuts(
+                name.getPackageName(), pinnedShortcuts, mUserHandle);
+    }
+
+    private static List<String> getPinnedShortcutsFromPackageAsUser(Context context,
+            UserHandle user, IntentFilter filter, String packageName) {
+        Context contextAsUser = context.createContextAsUser(user, 0 /* flags */);
+        List<ShortcutManager.ShareShortcutInfo> targets = contextAsUser.getSystemService(
+                ShortcutManager.class).getShareTargets(filter);
+        return targets.stream()
+                .map(ShortcutManager.ShareShortcutInfo::getShortcutInfo)
+                .filter(s -> s.isPinned() && s.getPackage().equals(packageName))
+                .map(ShortcutInfo::getId)
+                .collect(Collectors.toList());
+    }
+
     private void pinComponent(ComponentName name) {
         SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
         final String key = name.flattenToString();
@@ -211,12 +268,13 @@
     @NonNull
     protected CharSequence getItemLabel(DisplayResolveInfo dri) {
         final PackageManager pm = getContext().getPackageManager();
-        return getPinLabel(dri.isPinned(), dri.getResolveInfo().loadLabel(pm));
+        return getPinLabel(isPinned(dri),
+                isShortcutTarget() ? "" : dri.getResolveInfo().loadLabel(pm));
     }
 
     @Nullable
     protected Drawable getItemIcon(DisplayResolveInfo dri) {
-        return getPinIcon(dri.isPinned());
+        return getPinIcon(isPinned(dri));
     }
 
     private ResolveInfoPresentationGetter getProvidingAppPresentationGetter() {
@@ -229,4 +287,11 @@
                 mTargetInfos.get(0).getResolveInfo());
     }
 
+    private boolean isPinned(DisplayResolveInfo dri) {
+        return isShortcutTarget() ? mIsShortcutPinned : dri.isPinned();
+    }
+
+    private boolean isShortcutTarget() {
+        return mShortcutId != null;
+    }
 }
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 0153363..264e4f7 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -64,6 +64,7 @@
     private Drawable mDisplayIcon;
     private final Intent mFillInIntent;
     private final int mFillInFlags;
+    private final boolean mIsPinned;
     private final float mModifiedScore;
     private boolean mIsSuspended = false;
 
@@ -77,6 +78,7 @@
         mModifiedScore = modifiedScore;
         mPm = mContext.getPackageManager();
         mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator;
+        mIsPinned = shortcutInfo != null && shortcutInfo.isPinned();
         if (sourceInfo != null) {
             final ResolveInfo ri = sourceInfo.getResolveInfo();
             if (ri != null) {
@@ -120,6 +122,7 @@
         mFillInIntent = fillInIntent;
         mFillInFlags = flags;
         mModifiedScore = other.mModifiedScore;
+        mIsPinned = other.mIsPinned;
 
         mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
     }
@@ -292,7 +295,7 @@
 
     @Override
     public boolean isPinned() {
-        return mSourceInfo != null && mSourceInfo.isPinned();
+        return mIsPinned;
     }
 
     /**