2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.internal.app;
19 import android.app.Activity;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentSender;
24 import android.content.IntentSender.SendIntentException;
25 import android.content.ServiceConnection;
26 import android.content.pm.ActivityInfo;
27 import android.content.pm.LabeledIntent;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.pm.ResolveInfo;
31 import android.database.DataSetObserver;
32 import android.graphics.drawable.Drawable;
33 import android.graphics.drawable.Icon;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Message;
38 import android.os.Parcelable;
39 import android.os.RemoteException;
40 import android.os.ResultReceiver;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.provider.DocumentsContract;
44 import android.service.chooser.ChooserTarget;
45 import android.service.chooser.ChooserTargetService;
46 import android.service.chooser.IChooserTargetResult;
47 import android.service.chooser.IChooserTargetService;
48 import android.text.TextUtils;
49 import android.util.Log;
50 import android.util.Slog;
51 import android.view.LayoutInflater;
52 import android.view.View;
53 import android.view.View.OnClickListener;
54 import android.view.View.OnLongClickListener;
55 import android.view.ViewGroup;
56 import android.widget.AbsListView;
57 import android.widget.BaseAdapter;
58 import android.widget.ListView;
59 import com.android.internal.R;
60 import com.android.internal.logging.MetricsLogger;
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.Comparator;
65 import java.util.List;
67 public class ChooserActivity extends ResolverActivity {
68 private static final String TAG = "ChooserActivity";
70 private static final boolean DEBUG = false;
72 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
73 private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
75 private Bundle mReplacementExtras;
76 private IntentSender mChosenComponentSender;
77 private IntentSender mRefinementIntentSender;
78 private RefinementResultReceiver mRefinementResultReceiver;
80 private Intent mReferrerFillInIntent;
82 private ChooserListAdapter mChooserListAdapter;
84 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
86 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
87 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
89 private final Handler mChooserHandler = new Handler() {
91 public void handleMessage(Message msg) {
93 case CHOOSER_TARGET_SERVICE_RESULT:
94 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
95 if (isDestroyed()) break;
96 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
97 if (!mServiceConnections.contains(sri.connection)) {
98 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
99 + " returned after being removed from active connections."
100 + " Have you considered returning results faster?");
103 if (sri.resultTargets != null) {
104 mChooserListAdapter.addServiceResults(sri.originalTarget,
107 unbindService(sri.connection);
108 sri.connection.destroy();
109 mServiceConnections.remove(sri.connection);
110 if (mServiceConnections.isEmpty()) {
111 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
112 sendVoiceChoicesIfNeeded();
116 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
118 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
120 unbindRemainingServices();
121 sendVoiceChoicesIfNeeded();
125 super.handleMessage(msg);
131 protected void onCreate(Bundle savedInstanceState) {
132 Intent intent = getIntent();
133 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
134 if (!(targetParcelable instanceof Intent)) {
135 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
137 super.onCreate(null);
140 Intent target = (Intent) targetParcelable;
141 if (target != null) {
142 modifyTargetIntent(target);
144 Parcelable[] targetsParcelable
145 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
146 if (targetsParcelable != null) {
147 final boolean offset = target == null;
148 Intent[] additionalTargets =
149 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
150 for (int i = 0; i < targetsParcelable.length; i++) {
151 if (!(targetsParcelable[i] instanceof Intent)) {
152 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
153 + targetsParcelable[i]);
155 super.onCreate(null);
158 final Intent additionalTarget = (Intent) targetsParcelable[i];
159 if (i == 0 && target == null) {
160 target = additionalTarget;
161 modifyTargetIntent(target);
163 additionalTargets[offset ? i - 1 : i] = additionalTarget;
164 modifyTargetIntent(additionalTarget);
167 setAdditionalTargets(additionalTargets);
170 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
171 CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
172 int defaultTitleRes = 0;
174 defaultTitleRes = com.android.internal.R.string.chooseActivity;
176 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
177 Intent[] initialIntents = null;
179 initialIntents = new Intent[pa.length];
180 for (int i=0; i<pa.length; i++) {
181 if (!(pa[i] instanceof Intent)) {
182 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
184 super.onCreate(null);
187 final Intent in = (Intent) pa[i];
188 modifyTargetIntent(in);
189 initialIntents[i] = in;
193 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
195 mChosenComponentSender = intent.getParcelableExtra(
196 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
197 mRefinementIntentSender = intent.getParcelableExtra(
198 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
199 setSafeForwardingMode(true);
200 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
203 MetricsLogger.action(this, MetricsLogger.ACTION_ACTIVITY_CHOOSER_SHOWN);
207 protected void onDestroy() {
209 if (mRefinementResultReceiver != null) {
210 mRefinementResultReceiver.destroy();
211 mRefinementResultReceiver = null;
213 unbindRemainingServices();
214 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
218 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
219 Intent result = defIntent;
220 if (mReplacementExtras != null) {
221 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
222 if (replExtras != null) {
223 result = new Intent(defIntent);
224 result.putExtras(replExtras);
227 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER)
228 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
229 result = Intent.createChooser(result,
230 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
236 void onActivityStarted(TargetInfo cti) {
237 if (mChosenComponentSender != null) {
238 final ComponentName target = cti.getResolvedComponentName();
239 if (target != null) {
240 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
242 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
243 } catch (IntentSender.SendIntentException e) {
244 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
245 + "the chosen component: " + e);
252 void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
253 boolean alwaysUseOption) {
254 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
255 mChooserListAdapter = (ChooserListAdapter) adapter;
256 adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
257 if (listView != null) {
258 listView.setItemsCanFocus(true);
263 int getLayoutResource() {
264 return R.layout.chooser_grid;
268 boolean shouldGetActivityMetadata() {
273 boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
274 final Intent intent = target.getResolvedIntent();
275 final ResolveInfo resolve = target.getResolveInfo();
277 // When GET_CONTENT is handled by the DocumentsUI system component,
278 // we're okay automatically launching it, since it offers it's own
279 // intent disambiguation UI.
280 if (intent != null && Intent.ACTION_GET_CONTENT.equals(intent.getAction())
281 && resolve != null && resolve.priority > 0
282 && resolve.activityInfo != null && DocumentsContract.PACKAGE_DOCUMENTS_UI
283 .equals(resolve.activityInfo.packageName)) {
290 private void modifyTargetIntent(Intent in) {
291 final String action = in.getAction();
292 if (Intent.ACTION_SEND.equals(action) ||
293 Intent.ACTION_SEND_MULTIPLE.equals(action)) {
294 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
295 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
300 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
301 if (mRefinementIntentSender != null) {
302 final Intent fillIn = new Intent();
303 final List<Intent> sourceIntents = target.getAllSourceIntents();
304 if (!sourceIntents.isEmpty()) {
305 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
306 if (sourceIntents.size() > 1) {
307 final Intent[] alts = new Intent[sourceIntents.size() - 1];
308 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
309 alts[i - 1] = sourceIntents.get(i);
311 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
313 if (mRefinementResultReceiver != null) {
314 mRefinementResultReceiver.destroy();
316 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
317 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
318 mRefinementResultReceiver);
320 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
322 } catch (SendIntentException e) {
323 Log.e(TAG, "Refinement IntentSender failed to send", e);
327 return super.onTargetSelected(target, alwaysCheck);
331 void startSelected(int which, boolean always, boolean filtered) {
332 super.startSelected(which, always, filtered);
334 if (mChooserListAdapter != null) {
335 // Log the index of which type of target the user picked.
336 // Lower values mean the ranking was better.
339 switch (mChooserListAdapter.getPositionTargetType(which)) {
340 case ChooserListAdapter.TARGET_CALLER:
341 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
343 case ChooserListAdapter.TARGET_SERVICE:
344 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
345 value -= mChooserListAdapter.getCallerTargetCount();
347 case ChooserListAdapter.TARGET_STANDARD:
348 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
349 value -= mChooserListAdapter.getCallerTargetCount()
350 + mChooserListAdapter.getServiceTargetCount();
355 MetricsLogger.action(this, cat, value);
360 void queryTargetServices(ChooserListAdapter adapter) {
361 final PackageManager pm = getPackageManager();
362 int targetsToQuery = 0;
363 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
364 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
365 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
366 final Bundle md = ai.metaData;
367 final String serviceName = md != null ? convertServiceName(ai.packageName,
368 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
369 if (serviceName != null) {
370 final ComponentName serviceComponent = new ComponentName(
371 ai.packageName, serviceName);
372 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
373 .setComponent(serviceComponent);
376 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
380 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
381 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
382 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
383 + " permission " + ChooserTargetService.BIND_PERMISSION
384 + " - this service will not be queried for ChooserTargets."
385 + " add android:permission=\""
386 + ChooserTargetService.BIND_PERMISSION + "\""
387 + " to the <service> tag for " + serviceComponent
388 + " in the manifest.");
391 } catch (NameNotFoundException e) {
392 Log.e(TAG, "Could not look up service " + serviceComponent, e);
396 final ChooserTargetServiceConnection conn =
397 new ChooserTargetServiceConnection(this, dri);
398 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
399 UserHandle.CURRENT)) {
401 Log.d(TAG, "Binding service connection for target " + dri
402 + " intent " + serviceIntent);
404 mServiceConnections.add(conn);
408 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
409 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
410 + QUERY_TARGET_SERVICE_LIMIT);
415 if (!mServiceConnections.isEmpty()) {
416 if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for "
417 + WATCHDOG_TIMEOUT_MILLIS + "ms");
418 mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
419 WATCHDOG_TIMEOUT_MILLIS);
421 sendVoiceChoicesIfNeeded();
425 private String convertServiceName(String packageName, String serviceName) {
426 if (TextUtils.isEmpty(serviceName)) {
430 final String fullName;
431 if (serviceName.startsWith(".")) {
432 // Relative to the app package. Prepend the app package name.
433 fullName = packageName + serviceName;
434 } else if (serviceName.indexOf('.') >= 0) {
435 // Fully qualified package name.
436 fullName = serviceName;
443 void unbindRemainingServices() {
445 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
447 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
448 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
449 if (DEBUG) Log.d(TAG, "unbinding " + conn);
453 mServiceConnections.clear();
454 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
457 void onSetupVoiceInteraction() {
458 // Do nothing. We'll send the voice stuff ourselves.
461 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
462 if (mRefinementResultReceiver != null) {
463 mRefinementResultReceiver.destroy();
464 mRefinementResultReceiver = null;
467 if (selectedTarget == null) {
468 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
469 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
470 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
471 + " cannot match refined source intent " + matchingIntent);
472 } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) {
476 onRefinementCanceled();
479 void onRefinementCanceled() {
480 if (mRefinementResultReceiver != null) {
481 mRefinementResultReceiver.destroy();
482 mRefinementResultReceiver = null;
487 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
488 final List<Intent> targetIntents = target.getAllSourceIntents();
489 for (int i = 0, N = targetIntents.size(); i < N; i++) {
490 final Intent targetIntent = targetIntents.get(i);
491 if (targetIntent.filterEquals(matchingIntent)) {
498 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
499 if (targets == null) {
503 final PackageManager pm = getPackageManager();
504 for (int i = targets.size() - 1; i >= 0; i--) {
505 final ChooserTarget target = targets.get(i);
506 final ComponentName targetName = target.getComponentName();
507 if (packageName != null && packageName.equals(targetName.getPackageName())) {
508 // Anything from the original target's package is fine.
514 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
515 remove = !ai.exported || ai.permission != null;
516 } catch (NameNotFoundException e) {
517 Log.e(TAG, "Target " + target + " returned by " + packageName
518 + " component not found");
529 ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
530 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
531 boolean filterLastUsed) {
532 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
533 initialIntents, rList, launchedFromUid, filterLastUsed);
534 if (DEBUG) Log.d(TAG, "Adapter created; querying services");
535 queryTargetServices(adapter);
539 final class ChooserTargetInfo implements TargetInfo {
540 private final DisplayResolveInfo mSourceInfo;
541 private final ResolveInfo mBackupResolveInfo;
542 private final ChooserTarget mChooserTarget;
543 private Drawable mBadgeIcon = null;
544 private CharSequence mBadgeContentDescription;
545 private Drawable mDisplayIcon;
546 private final Intent mFillInIntent;
547 private final int mFillInFlags;
548 private final float mModifiedScore;
550 public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
551 float modifiedScore) {
552 mSourceInfo = sourceInfo;
553 mChooserTarget = chooserTarget;
554 mModifiedScore = modifiedScore;
555 if (sourceInfo != null) {
556 final ResolveInfo ri = sourceInfo.getResolveInfo();
558 final ActivityInfo ai = ri.activityInfo;
559 if (ai != null && ai.applicationInfo != null) {
560 final PackageManager pm = getPackageManager();
561 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
562 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
566 final Icon icon = chooserTarget.getIcon();
567 // TODO do this in the background
568 mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
570 if (sourceInfo != null) {
571 mBackupResolveInfo = null;
573 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
576 mFillInIntent = null;
580 private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
581 mSourceInfo = other.mSourceInfo;
582 mBackupResolveInfo = other.mBackupResolveInfo;
583 mChooserTarget = other.mChooserTarget;
584 mBadgeIcon = other.mBadgeIcon;
585 mBadgeContentDescription = other.mBadgeContentDescription;
586 mDisplayIcon = other.mDisplayIcon;
587 mFillInIntent = fillInIntent;
588 mFillInFlags = flags;
589 mModifiedScore = other.mModifiedScore;
592 public float getModifiedScore() {
593 return mModifiedScore;
597 public Intent getResolvedIntent() {
598 if (mSourceInfo != null) {
599 return mSourceInfo.getResolvedIntent();
601 return getTargetIntent();
605 public ComponentName getResolvedComponentName() {
606 if (mSourceInfo != null) {
607 return mSourceInfo.getResolvedComponentName();
608 } else if (mBackupResolveInfo != null) {
609 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
610 mBackupResolveInfo.activityInfo.name);
615 private Intent getBaseIntentToSend() {
616 Intent result = mSourceInfo != null
617 ? mSourceInfo.getResolvedIntent() : getTargetIntent();
618 if (result == null) {
619 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
621 result = new Intent(result);
622 if (mFillInIntent != null) {
623 result.fillIn(mFillInIntent, mFillInFlags);
625 result.fillIn(mReferrerFillInIntent, 0);
631 public boolean start(Activity activity, Bundle options) {
632 throw new RuntimeException("ChooserTargets should be started as caller.");
636 public boolean startAsCaller(Activity activity, Bundle options, int userId) {
637 final Intent intent = getBaseIntentToSend();
638 if (intent == null) {
641 intent.setComponent(mChooserTarget.getComponentName());
642 intent.putExtras(mChooserTarget.getIntentExtras());
643 activity.startActivityAsCaller(intent, options, true, userId);
648 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
649 throw new RuntimeException("ChooserTargets should be started as caller.");
653 public ResolveInfo getResolveInfo() {
654 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
658 public CharSequence getDisplayLabel() {
659 return mChooserTarget.getTitle();
663 public CharSequence getExtendedInfo() {
664 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
669 public Drawable getDisplayIcon() {
674 public Drawable getBadgeIcon() {
679 public CharSequence getBadgeContentDescription() {
680 return mBadgeContentDescription;
684 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
685 return new ChooserTargetInfo(this, fillInIntent, flags);
689 public List<Intent> getAllSourceIntents() {
690 final List<Intent> results = new ArrayList<>();
691 if (mSourceInfo != null) {
692 // We only queried the service for the first one in our sourceinfo.
693 results.add(mSourceInfo.getAllSourceIntents().get(0));
699 public class ChooserListAdapter extends ResolveListAdapter {
700 public static final int TARGET_BAD = -1;
701 public static final int TARGET_CALLER = 0;
702 public static final int TARGET_SERVICE = 1;
703 public static final int TARGET_STANDARD = 2;
705 private static final int MAX_SERVICE_TARGETS = 8;
707 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
708 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
710 private float mLateFee = 1.f;
712 private final BaseChooserTargetComparator mBaseTargetComparator
713 = new BaseChooserTargetComparator();
715 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
716 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
717 boolean filterLastUsed) {
718 // Don't send the initial intents through the shared ResolverActivity path,
719 // we want to separate them into a different section.
720 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed);
722 if (initialIntents != null) {
723 final PackageManager pm = getPackageManager();
724 for (int i = 0; i < initialIntents.length; i++) {
725 final Intent ii = initialIntents[i];
729 final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
731 Log.w(TAG, "No activity found for " + ii);
734 ResolveInfo ri = new ResolveInfo();
735 ri.activityInfo = ai;
736 UserManager userManager =
737 (UserManager) getSystemService(Context.USER_SERVICE);
738 if (ii instanceof LabeledIntent) {
739 LabeledIntent li = (LabeledIntent)ii;
740 ri.resolvePackageName = li.getSourcePackage();
741 ri.labelRes = li.getLabelResource();
742 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
743 ri.icon = li.getIconResource();
744 ri.iconResourceId = ri.icon;
746 if (userManager.isManagedProfile()) {
747 ri.noResourceId = true;
750 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
751 ri.loadLabel(pm), null, ii));
757 public boolean showsExtendedInfo(TargetInfo info) {
758 // We have badges so we don't need this text shown.
763 public View onCreateView(ViewGroup parent) {
764 return mInflater.inflate(
765 com.android.internal.R.layout.resolve_grid_item, parent, false);
769 public void onListRebuilt() {
770 if (mServiceTargets != null) {
771 pruneServiceTargets();
776 public boolean shouldGetResolvedFilter() {
781 public int getCount() {
782 return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
786 public int getUnfilteredCount() {
787 return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
790 public int getCallerTargetCount() {
791 return mCallerTargets.size();
794 public int getServiceTargetCount() {
795 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
798 public int getStandardTargetCount() {
799 return super.getCount();
802 public int getPositionTargetType(int position) {
805 final int callerTargetCount = getCallerTargetCount();
806 if (position < callerTargetCount) {
807 return TARGET_CALLER;
809 offset += callerTargetCount;
811 final int serviceTargetCount = getServiceTargetCount();
812 if (position - offset < serviceTargetCount) {
813 return TARGET_SERVICE;
815 offset += serviceTargetCount;
817 final int standardTargetCount = super.getCount();
818 if (position - offset < standardTargetCount) {
819 return TARGET_STANDARD;
826 public TargetInfo getItem(int position) {
827 return targetInfoForPosition(position, true);
831 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
834 final int callerTargetCount = getCallerTargetCount();
835 if (position < callerTargetCount) {
836 return mCallerTargets.get(position);
838 offset += callerTargetCount;
840 final int serviceTargetCount = getServiceTargetCount();
841 if (position - offset < serviceTargetCount) {
842 return mServiceTargets.get(position - offset);
844 offset += serviceTargetCount;
846 return filtered ? super.getItem(position - offset)
847 : getDisplayInfoAt(position - offset);
850 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
851 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
853 final float parentScore = getScore(origTarget);
854 Collections.sort(targets, mBaseTargetComparator);
856 for (int i = 0, N = targets.size(); i < N; i++) {
857 final ChooserTarget target = targets.get(i);
858 float targetScore = target.getScore();
859 targetScore *= parentScore;
860 targetScore *= mLateFee;
861 if (i > 0 && targetScore >= lastScore) {
862 // Apply a decay so that the top app can't crowd out everything else.
863 // This incents ChooserTargetServices to define what's truly better.
864 targetScore = lastScore * 0.95f;
866 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
869 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
870 + " base=" + target.getScore()
871 + " lastScore=" + lastScore
872 + " parentScore=" + parentScore
873 + " lateFee=" + mLateFee);
876 lastScore = targetScore;
881 notifyDataSetChanged();
884 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
885 final float newScore = chooserTargetInfo.getModifiedScore();
886 for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
887 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
888 if (newScore > serviceTarget.getModifiedScore()) {
889 mServiceTargets.add(i, chooserTargetInfo);
893 mServiceTargets.add(chooserTargetInfo);
896 private void pruneServiceTargets() {
897 if (DEBUG) Log.d(TAG, "pruneServiceTargets");
898 for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
899 final ChooserTargetInfo cti = mServiceTargets.get(i);
900 if (!hasResolvedTarget(cti.getResolveInfo())) {
901 if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
902 mServiceTargets.remove(i);
908 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
910 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
912 return (int) Math.signum(lhs.getScore() - rhs.getScore());
916 class ChooserRowAdapter extends BaseAdapter {
917 private ChooserListAdapter mChooserListAdapter;
918 private final LayoutInflater mLayoutInflater;
919 private final int mColumnCount = 4;
921 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
922 mChooserListAdapter = wrappedAdapter;
923 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
925 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
927 public void onChanged() {
929 notifyDataSetChanged();
933 public void onInvalidated() {
934 super.onInvalidated();
935 notifyDataSetInvalidated();
941 public int getCount() {
943 Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
944 + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
945 + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
950 public Object getItem(int position) {
951 // We have nothing useful to return here.
956 public long getItemId(int position) {
961 public View getView(int position, View convertView, ViewGroup parent) {
963 if (convertView == null) {
964 holder = createViewHolder(parent);
966 holder = (View[]) convertView.getTag();
968 bindViewHolder(position, holder);
970 // We keep the actual list item view as the last item in the holder array
971 return holder[mColumnCount];
974 View[] createViewHolder(ViewGroup parent) {
975 final View[] holder = new View[mColumnCount + 1];
977 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
979 for (int i = 0; i < mColumnCount; i++) {
980 holder[i] = mChooserListAdapter.createView(row);
981 row.addView(holder[i]);
984 holder[mColumnCount] = row;
988 void bindViewHolder(int rowPosition, View[] holder) {
989 final int start = getFirstRowPosition(rowPosition);
990 final int startType = mChooserListAdapter.getPositionTargetType(start);
992 int end = start + mColumnCount - 1;
993 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
997 final ViewGroup row = (ViewGroup) holder[mColumnCount];
999 if (startType == ChooserListAdapter.TARGET_SERVICE) {
1000 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
1002 row.setBackground(null);
1005 for (int i = 0; i < mColumnCount; i++) {
1006 final View v = holder[i];
1007 if (start + i <= end) {
1008 v.setVisibility(View.VISIBLE);
1009 final int itemIndex = start + i;
1010 mChooserListAdapter.bindView(itemIndex, v);
1011 v.setOnClickListener(new OnClickListener() {
1013 public void onClick(View v) {
1014 startSelected(itemIndex, false, true);
1017 v.setOnLongClickListener(new OnLongClickListener() {
1019 public boolean onLongClick(View v) {
1021 mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
1026 v.setVisibility(View.GONE);
1031 int getFirstRowPosition(int row) {
1032 final int callerCount = mChooserListAdapter.getCallerTargetCount();
1033 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1035 if (row < callerRows) {
1036 return row * mColumnCount;
1039 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1040 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1042 if (row < callerRows + serviceRows) {
1043 return callerCount + (row - callerRows) * mColumnCount;
1046 return callerCount + serviceCount
1047 + (row - callerRows - serviceRows) * mColumnCount;
1051 static class ChooserTargetServiceConnection implements ServiceConnection {
1052 private final DisplayResolveInfo mOriginalTarget;
1053 private ComponentName mConnectedComponent;
1054 private ChooserActivity mChooserActivity;
1055 private final Object mLock = new Object();
1057 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1059 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1060 synchronized (mLock) {
1061 if (mChooserActivity == null) {
1062 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
1063 + mConnectedComponent + "; ignoring...");
1066 mChooserActivity.filterServiceTargets(
1067 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
1068 final Message msg = Message.obtain();
1069 msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1070 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1071 ChooserTargetServiceConnection.this);
1072 mChooserActivity.mChooserHandler.sendMessage(msg);
1077 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
1078 DisplayResolveInfo dri) {
1079 mChooserActivity = chooserActivity;
1080 mOriginalTarget = dri;
1084 public void onServiceConnected(ComponentName name, IBinder service) {
1085 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1086 synchronized (mLock) {
1087 if (mChooserActivity == null) {
1088 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
1092 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1094 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1095 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1096 } catch (RemoteException e) {
1097 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1098 mChooserActivity.unbindService(this);
1100 mChooserActivity.mServiceConnections.remove(this);
1106 public void onServiceDisconnected(ComponentName name) {
1107 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1108 synchronized (mLock) {
1109 if (mChooserActivity == null) {
1111 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
1115 mChooserActivity.unbindService(this);
1117 mChooserActivity.mServiceConnections.remove(this);
1118 if (mChooserActivity.mServiceConnections.isEmpty()) {
1119 mChooserActivity.mChooserHandler.removeMessages(
1120 CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1121 mChooserActivity.sendVoiceChoicesIfNeeded();
1123 mConnectedComponent = null;
1127 public void destroy() {
1128 synchronized (mLock) {
1129 mChooserActivity = null;
1134 public String toString() {
1135 return "ChooserTargetServiceConnection{service="
1136 + mConnectedComponent + ", activity="
1137 + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}";
1141 static class ServiceResultInfo {
1142 public final DisplayResolveInfo originalTarget;
1143 public final List<ChooserTarget> resultTargets;
1144 public final ChooserTargetServiceConnection connection;
1146 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1147 ChooserTargetServiceConnection c) {
1148 originalTarget = ot;
1154 static class RefinementResultReceiver extends ResultReceiver {
1155 private ChooserActivity mChooserActivity;
1156 private TargetInfo mSelectedTarget;
1158 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1161 mChooserActivity = host;
1162 mSelectedTarget = target;
1166 protected void onReceiveResult(int resultCode, Bundle resultData) {
1167 if (mChooserActivity == null) {
1168 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1171 if (resultData == null) {
1172 Log.e(TAG, "RefinementResultReceiver received null resultData");
1176 switch (resultCode) {
1177 case RESULT_CANCELED:
1178 mChooserActivity.onRefinementCanceled();
1181 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1182 if (intentParcelable instanceof Intent) {
1183 mChooserActivity.onRefinementResult(mSelectedTarget,
1184 (Intent) intentParcelable);
1186 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1187 + " in resultData with key Intent.EXTRA_INTENT");
1191 Log.w(TAG, "Unknown result code " + resultCode
1192 + " sent to RefinementResultReceiver");
1197 public void destroy() {
1198 mChooserActivity = null;
1199 mSelectedTarget = null;