diff options
author | Adam Powell <adamp@google.com> | 2015-05-06 21:08:37 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-05-06 21:09:20 +0000 |
commit | cfd6e9dfd063cba795497f251aa6f7fe2554f10b (patch) | |
tree | dbe31f4920cc667bb98d59a07b91377ce8dfc888 /core/java | |
parent | 0a323036e2351ce4e9347ca1a1b5664e0f6e7672 (diff) | |
parent | 2ed547e55f820a9c705872d802b051d8ae9c906b (diff) | |
download | frameworks_base-cfd6e9dfd063cba795497f251aa6f7fe2554f10b.zip frameworks_base-cfd6e9dfd063cba795497f251aa6f7fe2554f10b.tar.gz frameworks_base-cfd6e9dfd063cba795497f251aa6f7fe2554f10b.tar.bz2 |
Merge "Add alternate intents and refinement callbacks to ChooserActivity" into mnc-dev
Diffstat (limited to 'core/java')
-rw-r--r-- | core/java/android/content/Intent.java | 71 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ChooserActivity.java | 233 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 289 |
3 files changed, 516 insertions, 77 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 54fe786..d0298cd 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -17,6 +17,7 @@ package android.content; import android.content.pm.ApplicationInfo; +import android.os.ResultReceiver; import android.provider.MediaStore; import android.util.ArraySet; @@ -3291,11 +3292,79 @@ public class Intent implements Parcelable, Cloneable { /** * An Intent describing the choices you would like shown with - * {@link #ACTION_PICK_ACTIVITY}. + * {@link #ACTION_PICK_ACTIVITY} or {@link #ACTION_CHOOSER}. */ public static final String EXTRA_INTENT = "android.intent.extra.INTENT"; /** + * An Intent[] describing additional, alternate choices you would like shown with + * {@link #ACTION_CHOOSER}. + * + * <p>An app may be capable of providing several different payload types to complete a + * user's intended action. For example, an app invoking {@link #ACTION_SEND} to share photos + * with another app may use EXTRA_ALTERNATE_INTENTS to have the chooser transparently offer + * several different supported sending mechanisms for sharing, such as the actual "image/*" + * photo data or a hosted link where the photos can be viewed.</p> + * + * <p>The intent present in {@link #EXTRA_INTENT} will be treated as the + * first/primary/preferred intent in the set. Additional intents specified in + * this extra are ordered; by default intents that appear earlier in the array will be + * preferred over intents that appear later in the array as matches for the same + * target component. To alter this preference, a calling app may also supply + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p> + */ + public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; + + /** + * An {@link IntentSender} for an Activity that will be invoked when the user makes a selection + * from the chooser activity presented by {@link #ACTION_CHOOSER}. + * + * <p>An app preparing an action for another app to complete may wish to allow the user to + * disambiguate between several options for completing the action based on the chosen target + * or otherwise refine the action before it is invoked. + * </p> + * + * <p>When sent, this IntentSender may be filled in with the following extras:</p> + * <ul> + * <li>{@link #EXTRA_INTENT} The first intent that matched the user's chosen target</li> + * <li>{@link #EXTRA_ALTERNATE_INTENTS} Any additional intents that also matched the user's + * chosen target beyond the first</li> + * <li>{@link #EXTRA_RESULT_RECEIVER} A {@link ResultReceiver} that the refinement activity + * should fill in and send once the disambiguation is complete</li> + * </ul> + */ + public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER + = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; + + /** + * A {@link ResultReceiver} used to return data back to the sender. + * + * <p>Used to complete an app-specific + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER refinement} for {@link #ACTION_CHOOSER}.</p> + * + * <p>If {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} is present in the intent + * used to start a {@link #ACTION_CHOOSER} activity this extra will be + * {@link #fillIn(Intent, int) filled in} to that {@link IntentSender} and sent + * when the user selects a target component from the chooser. It is up to the recipient + * to send a result to this ResultReceiver to signal that disambiguation is complete + * and that the chooser should invoke the user's choice.</p> + * + * <p>The disambiguator should provide a Bundle to the ResultReceiver with an intent + * assigned to the key {@link #EXTRA_INTENT}. This supplied intent will be used by the chooser + * to match and fill in the final Intent or ChooserTarget before starting it. + * The supplied intent must {@link #filterEquals(Intent) match} one of the intents from + * {@link #EXTRA_INTENT} or {@link #EXTRA_ALTERNATE_INTENTS} passed to + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} to be accepted.</p> + * + * <p>The result code passed to the ResultReceiver should be + * {@link android.app.Activity#RESULT_OK} if the refinement succeeded and the supplied intent's + * target in the chooser should be started, or {@link android.app.Activity#RESULT_CANCELED} if + * the chooser should finish without starting a target.</p> + */ + public static final String EXTRA_RESULT_RECEIVER + = "android.intent.extra.RESULT_RECEIVER"; + + /** * A CharSequence dialog title to provide to the user when used with a * {@link #ACTION_CHOOSER}. */ diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index e347faa..62ca1f0 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; @@ -34,6 +35,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Parcelable; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.UserHandle; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; @@ -53,11 +55,13 @@ public class ChooserActivity extends ResolverActivity { private static final boolean DEBUG = false; - private static final int QUERY_TARGET_LIMIT = 5; + private static final int QUERY_TARGET_SERVICE_LIMIT = 5; private static final int WATCHDOG_TIMEOUT_MILLIS = 5000; private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; + private IntentSender mRefinementIntentSender; + private RefinementResultReceiver mRefinementResultReceiver; private ChooserTarget[] mCallerChooserTargets; @@ -113,6 +117,32 @@ public class ChooserActivity extends ResolverActivity { if (target != null) { modifyTargetIntent(target); } + Parcelable[] targetsParcelable + = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS); + if (targetsParcelable != null) { + final boolean offset = target == null; + Intent[] additionalTargets = + new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length]; + for (int i = 0; i < targetsParcelable.length; i++) { + if (!(targetsParcelable[i] instanceof Intent)) { + Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: " + + targetsParcelable[i]); + finish(); + super.onCreate(null); + return; + } + final Intent additionalTarget = (Intent) targetsParcelable[i]; + if (i == 0 && target == null) { + target = additionalTarget; + modifyTargetIntent(target); + } else { + additionalTargets[offset ? i - 1 : i] = additionalTarget; + modifyTargetIntent(additionalTarget); + } + } + setAdditionalTargets(additionalTargets); + } + mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); int defaultTitleRes = 0; @@ -125,7 +155,7 @@ public class ChooserActivity extends ResolverActivity { initialIntents = new Intent[pa.length]; for (int i=0; i<pa.length; i++) { if (!(pa[i] instanceof Intent)) { - Log.w("ChooserActivity", "Initial intent #" + i + " not an Intent: " + pa[i]); + Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]); finish(); super.onCreate(null); return; @@ -141,8 +171,7 @@ public class ChooserActivity extends ResolverActivity { final ChooserTarget[] targets = new ChooserTarget[pa.length]; for (int i = 0; i < pa.length; i++) { if (!(pa[i] instanceof ChooserTarget)) { - Log.w("ChooserActivity", "Chooser target #" + i + " is not a ChooserTarget: " + - pa[i]); + Log.w(TAG, "Chooser target #" + i + " is not a ChooserTarget: " + pa[i]); finish(); super.onCreate(null); return; @@ -153,12 +182,23 @@ public class ChooserActivity extends ResolverActivity { } mChosenComponentSender = intent.getParcelableExtra( Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); + mRefinementIntentSender = intent.getParcelableExtra( + Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); setSafeForwardingMode(true); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); } @Override + protected void onDestroy() { + super.onDestroy(); + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + } + + @Override public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { Intent result = defIntent; if (mReplacementExtras != null) { @@ -211,6 +251,37 @@ public class ChooserActivity extends ResolverActivity { } } + @Override + protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { + if (mRefinementIntentSender != null) { + final Intent fillIn = new Intent(); + final List<Intent> sourceIntents = target.getAllSourceIntents(); + if (!sourceIntents.isEmpty()) { + fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0)); + if (sourceIntents.size() > 1) { + final Intent[] alts = new Intent[sourceIntents.size() - 1]; + for (int i = 1, N = sourceIntents.size(); i < N; i++) { + alts[i - 1] = sourceIntents.get(i); + } + fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts); + } + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + } + mRefinementResultReceiver = new RefinementResultReceiver(this, target, null); + fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER, + mRefinementResultReceiver); + try { + mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null); + return false; + } catch (SendIntentException e) { + Log.e(TAG, "Refinement IntentSender failed to send", e); + } + } + } + return super.onTargetSelected(target, alwaysCheck); + } + void queryTargetServices(ChooserListAdapter adapter) { final PackageManager pm = getPackageManager(); int targetsToQuery = 0; @@ -258,8 +329,9 @@ public class ChooserActivity extends ResolverActivity { targetsToQuery++; } } - if (targetsToQuery >= QUERY_TARGET_LIMIT) { - if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + QUERY_TARGET_LIMIT); + if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) { + if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + + QUERY_TARGET_SERVICE_LIMIT); break; } } @@ -303,6 +375,43 @@ public class ChooserActivity extends ResolverActivity { mTargetResultHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); } + void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + + if (selectedTarget == null) { + Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); + } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { + Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget + + " cannot match refined source intent " + matchingIntent); + } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) { + finish(); + return; + } + onRefinementCanceled(); + } + + void onRefinementCanceled() { + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + finish(); + } + + boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) { + final List<Intent> targetIntents = target.getAllSourceIntents(); + for (int i = 0, N = targetIntents.size(); i < N; i++) { + final Intent targetIntent = targetIntents.get(i); + if (targetIntent.filterEquals(matchingIntent)) { + return true; + } + } + return false; + } + @Override ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { @@ -313,17 +422,19 @@ public class ChooserActivity extends ResolverActivity { return adapter; } - class ChooserTargetInfo implements TargetInfo { - private final TargetInfo mSourceInfo; + final class ChooserTargetInfo implements TargetInfo { + private final DisplayResolveInfo mSourceInfo; private final ResolveInfo mBackupResolveInfo; private final ChooserTarget mChooserTarget; private final Drawable mDisplayIcon; + private final Intent mFillInIntent; + private final int mFillInFlags; public ChooserTargetInfo(ChooserTarget target) { this(null, target); } - public ChooserTargetInfo(TargetInfo sourceInfo, ChooserTarget chooserTarget) { + public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget) { mSourceInfo = sourceInfo; mChooserTarget = chooserTarget; mDisplayIcon = new BitmapDrawable(getResources(), chooserTarget.getIcon()); @@ -333,6 +444,18 @@ public class ChooserActivity extends ResolverActivity { } else { mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0); } + + mFillInIntent = null; + mFillInFlags = 0; + } + + private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) { + mSourceInfo = other.mSourceInfo; + mBackupResolveInfo = other.mBackupResolveInfo; + mChooserTarget = other.mChooserTarget; + mDisplayIcon = other.mDisplayIcon; + mFillInIntent = fillInIntent; + mFillInFlags = flags; } @Override @@ -358,22 +481,42 @@ public class ChooserActivity extends ResolverActivity { } private Intent getFillInIntent() { - return mSourceInfo != null ? mSourceInfo.getResolvedIntent() : getTargetIntent(); + Intent result = mSourceInfo != null + ? mSourceInfo.getResolvedIntent() : getTargetIntent(); + if (result == null) { + Log.e(TAG, "ChooserTargetInfo#getFillInIntent: no fillIn intent available"); + } else if (mFillInIntent != null) { + result = new Intent(result); + result.fillIn(mFillInIntent, mFillInFlags); + } + return result; } @Override public boolean start(Activity activity, Bundle options) { - return mChooserTarget.sendIntent(activity, getFillInIntent()); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntent(activity, intent); } @Override public boolean startAsCaller(Activity activity, Bundle options, int userId) { - return mChooserTarget.sendIntentAsCaller(activity, getFillInIntent(), userId); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntentAsCaller(activity, intent, userId); } @Override public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { - return mChooserTarget.sendIntentAsUser(activity, getFillInIntent(), user); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntentAsUser(activity, intent, user); } @Override @@ -395,6 +538,21 @@ public class ChooserActivity extends ResolverActivity { public Drawable getDisplayIcon() { return mDisplayIcon; } + + @Override + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { + return new ChooserTargetInfo(this, fillInIntent, flags); + } + + @Override + public List<Intent> getAllSourceIntents() { + final List<Intent> results = new ArrayList<>(); + if (mSourceInfo != null) { + // We only queried the service for the first one in our sourceinfo. + results.add(mSourceInfo.getAllSourceIntents().get(0)); + } + return results; + } } public class ChooserListAdapter extends ResolveListAdapter { @@ -542,4 +700,53 @@ public class ChooserActivity extends ResolverActivity { connection = c; } } + + static class RefinementResultReceiver extends ResultReceiver { + private ChooserActivity mChooserActivity; + private TargetInfo mSelectedTarget; + + public RefinementResultReceiver(ChooserActivity host, TargetInfo target, + Handler handler) { + super(handler); + mChooserActivity = host; + mSelectedTarget = target; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (mChooserActivity == null) { + Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); + return; + } + if (resultData == null) { + Log.e(TAG, "RefinementResultReceiver received null resultData"); + return; + } + + switch (resultCode) { + case RESULT_CANCELED: + mChooserActivity.onRefinementCanceled(); + break; + case RESULT_OK: + Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); + if (intentParcelable instanceof Intent) { + mChooserActivity.onRefinementResult(mSelectedTarget, + (Intent) intentParcelable); + } else { + Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" + + " in resultData with key Intent.EXTRA_INTENT"); + } + break; + default: + Log.w(TAG, "Unknown result code " + resultCode + + " sent to RefinementResultReceiver"); + break; + } + } + + public void destroy() { + mChooserActivity = null; + mSelectedTarget = null; + } + } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 8dd7836..2048664 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -102,7 +102,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private int mLastSelected = AbsListView.INVALID_POSITION; private boolean mResolvingHome = false; private int mProfileSwitchMessageId = -1; - private Intent mIntent; + private final ArrayList<Intent> mIntents = new ArrayList<>(); private UsageStatsManager mUsm; private Map<String, UsageStats> mStats; @@ -229,7 +229,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); mIconDpi = am.getLauncherLargeIconDensity(); - mIntent = new Intent(intent); + mIntents.add(0, new Intent(intent)); mAdapter = createAdapter(this, initialIntents, rList, mLaunchedFromUid, alwaysUseOption); final int layoutId; @@ -250,7 +250,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return; } - int count = mAdapter.mList.size(); + int count = mAdapter.mDisplayList.size(); if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { setContentView(layoutId); mAdapterView = (AbsListView) findViewById(R.id.resolver_list); @@ -376,8 +376,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + protected final void setAdditionalTargets(Intent[] intents) { + if (intents != null) { + for (Intent intent : intents) { + mIntents.add(intent); + } + } + } + public Intent getTargetIntent() { - return mIntent; + return mIntents.isEmpty() ? null : mIntents.get(0); } private String getReferrerPackageName() { @@ -630,8 +638,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } TargetInfo target = mAdapter.targetInfoForPosition(which, filtered); - onTargetSelected(target, always); - finish(); + if (onTargetSelected(target, always)) { + finish(); + } } /** @@ -641,7 +650,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return defIntent; } - protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) { + protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { final ResolveInfo ri = target.getResolveInfo(); final Intent intent = target != null ? target.getResolvedIntent() : null; @@ -728,7 +737,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic ComponentName[] set = new ComponentName[N]; int bestMatch = 0; for (int i=0; i<N; i++) { - ResolveInfo r = mAdapter.mOrigResolveList.get(i); + ResolveInfo r = mAdapter.mOrigResolveList.get(i).getResolveInfoAt(0); set[i] = new ComponentName(r.activityInfo.packageName, r.activityInfo.name); if (r.match > bestMatch) bestMatch = r.match; @@ -774,6 +783,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (target != null) { safelyStartActivity(target); } + return true; } void safelyStartActivity(TargetInfo cti) { @@ -837,15 +847,17 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private Drawable mDisplayIcon; private final CharSequence mExtendedInfo; private final Intent mResolvedIntent; + private final List<Intent> mSourceIntents = new ArrayList<>(); - DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, + DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) { + mSourceIntents.add(originalIntent); mResolveInfo = pri; mDisplayLabel = pLabel; mExtendedInfo = pInfo; final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent : - getReplacementIntent(pri.activityInfo, mIntent)); + getReplacementIntent(pri.activityInfo, getTargetIntent())); intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); final ActivityInfo ai = mResolveInfo.activityInfo; @@ -854,6 +866,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mResolvedIntent = intent; } + private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) { + mSourceIntents.addAll(other.getAllSourceIntents()); + mResolveInfo = other.mResolveInfo; + mDisplayLabel = other.mDisplayLabel; + mDisplayIcon = other.mDisplayIcon; + mExtendedInfo = other.mExtendedInfo; + mResolvedIntent = new Intent(other.mResolvedIntent); + mResolvedIntent.fillIn(fillInIntent, flags); + } + public ResolveInfo getResolveInfo() { return mResolveInfo; } @@ -866,6 +888,20 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return mDisplayIcon; } + @Override + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { + return new DisplayResolveInfo(this, fillInIntent, flags); + } + + @Override + public List<Intent> getAllSourceIntents() { + return mSourceIntents; + } + + public void addAlternateSourceIntent(Intent alt) { + mSourceIntents.add(alt); + } + public void setDisplayIcon(Drawable icon) { mDisplayIcon = icon; } @@ -986,6 +1022,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic * @return The drawable that should be used to represent this target */ public Drawable getDisplayIcon(); + + /** + * Clone this target with the given fill-in information. + */ + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags); + + /** + * @return the list of supported source intents deduped against this single target + */ + public List<Intent> getAllSourceIntents(); } class ResolveListAdapter extends BaseAdapter { @@ -998,8 +1044,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic protected final LayoutInflater mInflater; - List<DisplayResolveInfo> mList; - List<ResolveInfo> mOrigResolveList; + List<DisplayResolveInfo> mDisplayList; + List<ResolvedComponentInfo> mOrigResolveList; private int mLastChosenPosition = -1; private boolean mFilterLastUsed; @@ -1010,7 +1056,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mBaseResolveList = rList; mLaunchedFromUid = launchedFromUid; mInflater = LayoutInflater.from(context); - mList = new ArrayList<>(); + mDisplayList = new ArrayList<>(); mFilterLastUsed = filterLastUsed; rebuildList(); } @@ -1027,7 +1073,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic public DisplayResolveInfo getFilteredItem() { if (mFilterLastUsed && mLastChosenPosition >= 0) { // Not using getItem since it offsets to dodge this position for the list - return mList.get(mLastChosenPosition); + return mDisplayList.get(mLastChosenPosition); } return null; } @@ -1048,11 +1094,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } private void rebuildList() { - List<ResolveInfo> currentResolveList; + List<ResolvedComponentInfo> currentResolveList = null; try { + final Intent primaryIntent = getTargetIntent(); mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity( - mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()), + primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()), PackageManager.MATCH_DEFAULT_ONLY); } catch (RemoteException re) { Log.d(TAG, "Error calling setLastChosenActivity\n" + re); @@ -1060,15 +1107,27 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // Clear the value of mOtherProfile from previous call. mOtherProfile = null; - mList.clear(); + mDisplayList.clear(); if (mBaseResolveList != null) { - currentResolveList = mOrigResolveList = mBaseResolveList; + currentResolveList = mOrigResolveList = new ArrayList<>(); + addResolveListDedupe(currentResolveList, getTargetIntent(), mBaseResolveList); } else { - currentResolveList = mOrigResolveList = mPm.queryIntentActivities(mIntent, - PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter() ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata() ? PackageManager.GET_META_DATA : 0) - ); + final boolean shouldGetResolvedFilter = shouldGetResolvedFilter(); + final boolean shouldGetActivityMetadata = shouldGetActivityMetadata(); + for (int i = 0, N = mIntents.size(); i < N; i++) { + final Intent intent = mIntents.get(i); + final List<ResolveInfo> infos = mPm.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)); + if (infos != null) { + if (currentResolveList == null) { + currentResolveList = mOrigResolveList = new ArrayList<>(); + } + addResolveListDedupe(currentResolveList, intent, infos); + } + } + // Filter out any activities that the launched uid does not // have permission for. We don't do this when we have an explicit // list of resolved activities, because that only happens when @@ -1076,14 +1135,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // they gave us. if (currentResolveList != null) { for (int i=currentResolveList.size()-1; i >= 0; i--) { - ActivityInfo ai = currentResolveList.get(i).activityInfo; + ActivityInfo ai = currentResolveList.get(i) + .getResolveInfoAt(0).activityInfo; int granted = ActivityManager.checkComponentPermission( ai.permission, mLaunchedFromUid, ai.applicationInfo.uid, ai.exported); if (granted != PackageManager.PERMISSION_GRANTED) { // Access not allowed! if (mOrigResolveList == currentResolveList) { - mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); + mOrigResolveList = new ArrayList<>(mOrigResolveList); } currentResolveList.remove(i); } @@ -1094,9 +1154,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) { // Only display the first matches that are either of equal // priority or have asked to be default options. - ResolveInfo r0 = currentResolveList.get(0); + ResolvedComponentInfo rci0 = currentResolveList.get(0); + ResolveInfo r0 = rci0.getResolveInfoAt(0); for (int i=1; i<N; i++) { - ResolveInfo ri = currentResolveList.get(i); + ResolveInfo ri = currentResolveList.get(i).getResolveInfoAt(0); if (DEBUG) Log.v( TAG, r0.activityInfo.name + "=" + @@ -1107,7 +1168,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic r0.isDefault != ri.isDefault) { while (i < N) { if (mOrigResolveList == currentResolveList) { - mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); + mOrigResolveList = new ArrayList<>(mOrigResolveList); } currentResolveList.remove(i); N--; @@ -1115,9 +1176,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } if (N > 1) { - Comparator<ResolveInfo> rComparator = - new ResolverComparator(ResolverActivity.this, mIntent); - Collections.sort(currentResolveList, rComparator); + Collections.sort(currentResolveList, + new ResolverComparator(ResolverActivity.this, getTargetIntent())); } // First put the initial items at the top. if (mInitialIntents != null) { @@ -1146,14 +1206,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic ri.nonLocalizedLabel = li.getNonLocalizedLabel(); ri.icon = li.getIconResource(); } - addResolveInfo(new DisplayResolveInfo(ri, + addResolveInfo(new DisplayResolveInfo(ii, ri, ri.loadLabel(getPackageManager()), null, ii)); } } // Check for applications with same name and use application name or // package name if necessary - r0 = currentResolveList.get(0); + rci0 = currentResolveList.get(0); + r0 = rci0.getResolveInfoAt(0); int start = 0; CharSequence r0Label = r0.loadLabel(mPm); mHasExtendedInfo = false; @@ -1161,7 +1222,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (r0Label == null) { r0Label = r0.activityInfo.packageName; } - ResolveInfo ri = currentResolveList.get(i); + ResolvedComponentInfo rci = currentResolveList.get(i); + ResolveInfo ri = rci.getResolveInfoAt(0); CharSequence riLabel = ri.loadLabel(mPm); if (riLabel == null) { riLabel = ri.activityInfo.packageName; @@ -1169,13 +1231,14 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (riLabel.equals(r0Label)) { continue; } - processGroup(currentResolveList, start, (i-1), r0, r0Label); + processGroup(currentResolveList, start, (i-1), rci0, r0Label); + rci0 = rci; r0 = ri; r0Label = riLabel; start = i; } // Process last group - processGroup(currentResolveList, start, (N-1), r0, r0Label); + processGroup(currentResolveList, start, (N-1), rci0, r0Label); } // Layout doesn't handle both profile button and last chosen @@ -1188,6 +1251,36 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic onListRebuilt(); } + private void addResolveListDedupe(List<ResolvedComponentInfo> into, Intent intent, + List<ResolveInfo> from) { + final int fromCount = from.size(); + final int intoCount = into.size(); + for (int i = 0; i < fromCount; i++) { + final ResolveInfo newInfo = from.get(i); + boolean found = false; + // Only loop to the end of into as it was before we started; no dupes in from. + for (int j = 0; j < intoCount; j++) { + final ResolvedComponentInfo rci = into.get(i); + if (isSameResolvedComponent(newInfo, rci)) { + found = true; + rci.add(intent, newInfo); + break; + } + } + if (!found) { + into.add(new ResolvedComponentInfo(new ComponentName( + newInfo.activityInfo.packageName, newInfo.activityInfo.name), + intent, newInfo)); + } + } + } + + private boolean isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b) { + final ActivityInfo ai = a.activityInfo; + return ai.packageName.equals(b.name.getPackageName()) + && ai.name.equals(b.name.getClassName()); + } + public void onListRebuilt() { // This space for rent } @@ -1196,18 +1289,18 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return mFilterLastUsed; } - private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, - CharSequence roLabel) { + private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, + ResolvedComponentInfo ro, CharSequence roLabel) { // Process labels from start to i int num = end - start+1; if (num == 1) { // No duplicate labels. Use label for entry at start - addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null)); - updateLastChosenPosition(ro); + addResolveInfoWithAlternates(ro, null, roLabel); } else { mHasExtendedInfo = true; boolean usePkg = false; - CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm); + CharSequence startApp = ro.getResolveInfoAt(0).activityInfo.applicationInfo + .loadLabel(mPm); if (startApp == null) { usePkg = true; } @@ -1217,7 +1310,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic new HashSet<CharSequence>(); duplicates.add(startApp); for (int j = start+1; j <= end ; j++) { - ResolveInfo jRi = rList.get(j); + ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); if ( (jApp == null) || (duplicates.contains(jApp))) { usePkg = true; @@ -1230,26 +1323,46 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic duplicates.clear(); } for (int k = start; k <= end; k++) { - ResolveInfo add = rList.get(k); + final ResolvedComponentInfo rci = rList.get(k); + final ResolveInfo add = rci.getResolveInfoAt(0); + final CharSequence extraInfo; if (usePkg) { - // Use application name for all entries from start to end-1 - addResolveInfo(new DisplayResolveInfo(add, roLabel, - add.activityInfo.packageName, null)); - } else { // Use package name for all entries from start to end-1 - addResolveInfo(new DisplayResolveInfo(add, roLabel, - add.activityInfo.applicationInfo.loadLabel(mPm), null)); + extraInfo = add.activityInfo.packageName; + } else { + // Use application name for all entries from start to end-1 + extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); } - updateLastChosenPosition(add); + addResolveInfoWithAlternates(rci, extraInfo, roLabel); + } + } + } + + private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, + CharSequence extraInfo, CharSequence roLabel) { + final int count = rci.getCount(); + final Intent intent = rci.getIntentAt(0); + final ResolveInfo add = rci.getResolveInfoAt(0); + final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent); + final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel, + extraInfo, replaceIntent); + addResolveInfo(dri); + if (replaceIntent == intent) { + // Only add alternates if we didn't get a specific replacement from + // the caller. If we have one it trumps potential alternates. + for (int i = 1, N = count; i < N; i++) { + final Intent altIntent = rci.getIntentAt(i); + dri.addAlternateSourceIntent(altIntent); } } + updateLastChosenPosition(add); } private void updateLastChosenPosition(ResolveInfo info) { if (mLastChosen != null && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName) && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) { - mLastChosenPosition = mList.size() - 1; + mLastChosenPosition = mDisplayList.size() - 1; } } @@ -1259,20 +1372,21 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // The first one we see gets special treatment. mOtherProfile = dri; } else { - mList.add(dri); + mDisplayList.add(dri); } } public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { - return (filtered ? getItem(position) : mList.get(position)).getResolveInfo(); + return (filtered ? getItem(position) : mDisplayList.get(position)) + .getResolveInfo(); } public TargetInfo targetInfoForPosition(int position, boolean filtered) { - return filtered ? getItem(position) : mList.get(position); + return filtered ? getItem(position) : mDisplayList.get(position); } public int getCount() { - int result = mList.size(); + int result = mDisplayList.size(); if (mFilterLastUsed && mLastChosenPosition >= 0) { result--; } @@ -1283,7 +1397,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { position++; } - return mList.get(position); + return mDisplayList.get(position); } public long getItemId(int position) { @@ -1295,8 +1409,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } public boolean hasResolvedTarget(ResolveInfo info) { - for (int i = 0, N = mList.size(); i < N; i++) { - if (info.equals(mList.get(i).getResolveInfo())) { + for (int i = 0, N = mDisplayList.size(); i < N; i++) { + if (info.equals(mDisplayList.get(i).getResolveInfo())) { return true; } } @@ -1304,11 +1418,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } protected int getDisplayResolveInfoCount() { - return mList.size(); + return mDisplayList.size(); } protected DisplayResolveInfo getDisplayResolveInfo(int index) { - return mList.get(index); + // Used to query services. We only query services for primary targets, not alternates. + return mDisplayList.get(index); } public final View getView(int position, View convertView, ViewGroup parent) { @@ -1349,6 +1464,52 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + static final class ResolvedComponentInfo { + public final ComponentName name; + private final List<Intent> mIntents = new ArrayList<>(); + private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); + + public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { + this.name = name; + add(intent, info); + } + + public void add(Intent intent, ResolveInfo info) { + mIntents.add(intent); + mResolveInfos.add(info); + } + + public int getCount() { + return mIntents.size(); + } + + public Intent getIntentAt(int index) { + return index >= 0 ? mIntents.get(index) : null; + } + + public ResolveInfo getResolveInfoAt(int index) { + return index >= 0 ? mResolveInfos.get(index) : null; + } + + public int findIntent(Intent intent) { + for (int i = 0, N = mIntents.size(); i < N; i++) { + if (intent.equals(mIntents.get(i))) { + return i; + } + } + return -1; + } + + public int findResolveInfo(ResolveInfo info) { + for (int i = 0, N = mResolveInfos.size(); i < N; i++) { + if (info.equals(mResolveInfos.get(i))) { + return i; + } + } + return -1; + } + } + static class ViewHolder { public TextView text; public TextView text2; @@ -1435,7 +1596,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic && match <= IntentFilter.MATCH_CATEGORY_PATH; } - class ResolverComparator implements Comparator<ResolveInfo> { + class ResolverComparator implements Comparator<ResolvedComponentInfo> { private final Collator mCollator; private final boolean mHttp; @@ -1446,7 +1607,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } @Override - public int compare(ResolveInfo lhs, ResolveInfo rhs) { + public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) { + final ResolveInfo lhs = lhsp.getResolveInfoAt(0); + final ResolveInfo rhs = rhsp.getResolveInfoAt(0); + // We want to put the one targeted to another user at the end of the dialog. if (lhs.targetUserId != UserHandle.USER_CURRENT) { return 1; @@ -1487,7 +1651,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (stats != null) { return stats.getTotalTimeInForeground(); } - } return 0; } |