Merge "Add alternate intents and refinement callbacks to ChooserActivity" into mnc-dev

This commit is contained in:
Adam Powell
2015-05-06 21:08:37 +00:00
committed by Android (Google) Code Review
6 changed files with 524 additions and 78 deletions

View File

@@ -8282,6 +8282,7 @@ package android.content {
field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
field public static final deprecated java.lang.String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE";
field public static final java.lang.String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS";
field public static final java.lang.String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT";
field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
@@ -8293,6 +8294,7 @@ package android.content {
field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
@@ -8325,6 +8327,7 @@ package android.content {
field public static final java.lang.String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle";
field public static final java.lang.String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent";
field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER";
field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";

View File

@@ -8508,6 +8508,7 @@ package android.content {
field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
field public static final deprecated java.lang.String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE";
field public static final java.lang.String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS";
field public static final java.lang.String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT";
field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD";
field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE";
@@ -8519,6 +8520,7 @@ package android.content {
field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
@@ -8554,6 +8556,7 @@ package android.content {
field public static final java.lang.String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle";
field public static final java.lang.String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent";
field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER";
field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";

View File

@@ -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,10 +3292,78 @@ 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}.

View File

@@ -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,11 +182,22 @@ 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;
@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -93,7 +93,7 @@ public class UsbResolverActivity extends ResolverActivity {
}
@Override
protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) {
protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
final ResolveInfo ri = target.getResolveInfo();
try {
IBinder b = ServiceManager.getService(USB_SERVICE);
@@ -129,5 +129,6 @@ public class UsbResolverActivity extends ResolverActivity {
} catch (RemoteException e) {
Log.e(TAG, "onIntentSelected failed", e);
}
return true;
}
}