From 59b5e8bf29a0419002b42ce06673dc5378d56063 Mon Sep 17 00:00:00 2001 From: Song Hu Date: Wed, 6 Apr 2022 22:14:17 -0700 Subject: [PATCH] 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. Bug: 210453009 Test: manual test on local phone Test: atest ChooserActivityTest Change-Id: Ibcc83037ca801ee0ecf2306eae954677529fa849 (cherry picked from commit 63932258739a89f59a37081cfc80741ea4980202) Merged-In: Ibcc83037ca801ee0ecf2306eae954677529fa849 --- .../android/internal/app/ChooserActivity.java | 75 +++++++++++-------- .../internal/app/ChooserListAdapter.java | 14 +++- .../ChooserTargetActionsDialogFragment.java | 73 +++++++++++++++++- .../app/chooser/SelectableTargetInfo.java | 5 +- 4 files changed, 129 insertions(+), 38 deletions(-) diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index d4a8a164803fb..49ee91c4c2a7b 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1616,29 +1616,48 @@ public class ChooserActivity extends ResolverActivity implements 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 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(); - 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) { @@ -2864,8 +2883,10 @@ public class ChooserActivity extends ResolverActivity implements 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); } /** @@ -2962,8 +2983,6 @@ public class ChooserActivity extends ResolverActivity implements private DirectShareViewHolder mDirectShareViewHolder; private int mChooserTargetWidth = 0; private boolean mShowAzLabelIfPoss; - - private boolean mHideContentPreview = false; private boolean mLayoutRequested = false; private int mFooterHeight = 0; @@ -3034,7 +3053,6 @@ public class ChooserActivity extends ResolverActivity implements * personal and work tabs. */ public void hideContentPreview() { - mHideContentPreview = true; mLayoutRequested = true; notifyDataSetChanged(); } @@ -3224,18 +3242,15 @@ public class ChooserActivity extends ResolverActivity implements } }); - // 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 140fabc4f2687..f6445849d8973 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -74,6 +74,7 @@ public class ChooserListAdapter extends ResolverListAdapter { 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 @@ public class ChooserListAdapter extends ResolverListAdapter { 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 @@ public class ChooserListAdapter extends ResolverListAdapter { 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 9afc0e9e97b06..4f1f380c3a770 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.ActivityManager; 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 com.android.internal.widget.RecyclerView; 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 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment protected ArrayList 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 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment void setStateFromBundle(Bundle b) { mTargetInfos = (ArrayList) 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 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment 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 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment 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 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment @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 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 getPinnedShortcutsFromPackageAsUser(Context context, + UserHandle user, IntentFilter filter, String packageName) { + Context contextAsUser = context.createContextAsUser(user, 0 /* flags */); + List 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 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment @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 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment 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 015336388b5dd..264e4f76d35df 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 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { 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 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { 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 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { mFillInIntent = fillInIntent; mFillInFlags = flags; mModifiedScore = other.mModifiedScore; + mIsPinned = other.mIsPinned; mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle()); } @@ -292,7 +295,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { @Override public boolean isPinned() { - return mSourceInfo != null && mSourceInfo.isPinned(); + return mIsPinned; } /**