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.service.chooser.ChooserTarget;
44 import android.service.chooser.ChooserTargetService;
45 import android.service.chooser.IChooserTargetResult;
46 import android.service.chooser.IChooserTargetService;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.util.Slog;
50 import android.view.LayoutInflater;
51 import android.view.View;
52 import android.view.View.OnClickListener;
53 import android.view.View.OnLongClickListener;
54 import android.view.ViewGroup;
55 import android.widget.AbsListView;
56 import android.widget.BaseAdapter;
57 import android.widget.ListView;
58 import com.android.internal.R;
59 import com.android.internal.logging.MetricsLogger;
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.Comparator;
64 import java.util.List;
66 public class ChooserActivity extends ResolverActivity {
67 private static final String TAG = "ChooserActivity";
69 private static final boolean DEBUG = false;
71 private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
72 private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
74 private Bundle mReplacementExtras;
75 private IntentSender mChosenComponentSender;
76 private IntentSender mRefinementIntentSender;
77 private RefinementResultReceiver mRefinementResultReceiver;
79 private Intent mReferrerFillInIntent;
81 private ChooserListAdapter mChooserListAdapter;
83 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
85 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
86 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
88 private final Handler mChooserHandler = new Handler() {
90 public void handleMessage(Message msg) {
92 case CHOOSER_TARGET_SERVICE_RESULT:
93 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
94 if (isDestroyed()) break;
95 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
96 if (!mServiceConnections.contains(sri.connection)) {
97 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
98 + " returned after being removed from active connections."
99 + " Have you considered returning results faster?");
102 if (sri.resultTargets != null) {
103 mChooserListAdapter.addServiceResults(sri.originalTarget,
106 unbindService(sri.connection);
107 sri.connection.destroy();
108 mServiceConnections.remove(sri.connection);
109 if (mServiceConnections.isEmpty()) {
110 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
111 sendVoiceChoicesIfNeeded();
115 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
117 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
119 unbindRemainingServices();
120 sendVoiceChoicesIfNeeded();
124 super.handleMessage(msg);
130 protected void onCreate(Bundle savedInstanceState) {
131 Intent intent = getIntent();
132 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
133 if (!(targetParcelable instanceof Intent)) {
134 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
136 super.onCreate(null);
139 Intent target = (Intent) targetParcelable;
140 if (target != null) {
141 modifyTargetIntent(target);
143 Parcelable[] targetsParcelable
144 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
145 if (targetsParcelable != null) {
146 final boolean offset = target == null;
147 Intent[] additionalTargets =
148 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
149 for (int i = 0; i < targetsParcelable.length; i++) {
150 if (!(targetsParcelable[i] instanceof Intent)) {
151 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
152 + targetsParcelable[i]);
154 super.onCreate(null);
157 final Intent additionalTarget = (Intent) targetsParcelable[i];
158 if (i == 0 && target == null) {
159 target = additionalTarget;
160 modifyTargetIntent(target);
162 additionalTargets[offset ? i - 1 : i] = additionalTarget;
163 modifyTargetIntent(additionalTarget);
166 setAdditionalTargets(additionalTargets);
169 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
170 CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
171 int defaultTitleRes = 0;
173 defaultTitleRes = com.android.internal.R.string.chooseActivity;
175 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
176 Intent[] initialIntents = null;
178 initialIntents = new Intent[pa.length];
179 for (int i=0; i<pa.length; i++) {
180 if (!(pa[i] instanceof Intent)) {
181 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
183 super.onCreate(null);
186 final Intent in = (Intent) pa[i];
187 modifyTargetIntent(in);
188 initialIntents[i] = in;
192 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
194 mChosenComponentSender = intent.getParcelableExtra(
195 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
196 mRefinementIntentSender = intent.getParcelableExtra(
197 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
198 setSafeForwardingMode(true);
199 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
202 MetricsLogger.action(this, MetricsLogger.ACTION_ACTIVITY_CHOOSER_SHOWN);
206 protected void onDestroy() {
208 if (mRefinementResultReceiver != null) {
209 mRefinementResultReceiver.destroy();
210 mRefinementResultReceiver = null;
212 unbindRemainingServices();
213 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
217 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
218 Intent result = defIntent;
219 if (mReplacementExtras != null) {
220 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
221 if (replExtras != null) {
222 result = new Intent(defIntent);
223 result.putExtras(replExtras);
226 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER)
227 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
228 result = Intent.createChooser(result,
229 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
235 void onActivityStarted(TargetInfo cti) {
236 if (mChosenComponentSender != null) {
237 final ComponentName target = cti.getResolvedComponentName();
238 if (target != null) {
239 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
241 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
242 } catch (IntentSender.SendIntentException e) {
243 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
244 + "the chosen component: " + e);
251 void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
252 boolean alwaysUseOption) {
253 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
254 mChooserListAdapter = (ChooserListAdapter) adapter;
255 adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
256 if (listView != null) {
257 listView.setItemsCanFocus(true);
262 int getLayoutResource() {
263 return R.layout.chooser_grid;
267 boolean shouldGetActivityMetadata() {
272 boolean shouldAutoLaunchSingleChoice() {
276 private void modifyTargetIntent(Intent in) {
277 final String action = in.getAction();
278 if (Intent.ACTION_SEND.equals(action) ||
279 Intent.ACTION_SEND_MULTIPLE.equals(action)) {
280 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
281 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
286 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
287 if (mRefinementIntentSender != null) {
288 final Intent fillIn = new Intent();
289 final List<Intent> sourceIntents = target.getAllSourceIntents();
290 if (!sourceIntents.isEmpty()) {
291 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
292 if (sourceIntents.size() > 1) {
293 final Intent[] alts = new Intent[sourceIntents.size() - 1];
294 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
295 alts[i - 1] = sourceIntents.get(i);
297 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
299 if (mRefinementResultReceiver != null) {
300 mRefinementResultReceiver.destroy();
302 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
303 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
304 mRefinementResultReceiver);
306 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
308 } catch (SendIntentException e) {
309 Log.e(TAG, "Refinement IntentSender failed to send", e);
313 return super.onTargetSelected(target, alwaysCheck);
317 void startSelected(int which, boolean always, boolean filtered) {
318 super.startSelected(which, always, filtered);
320 if (mChooserListAdapter != null) {
321 // Log the index of which type of target the user picked.
322 // Lower values mean the ranking was better.
325 switch (mChooserListAdapter.getPositionTargetType(which)) {
326 case ChooserListAdapter.TARGET_CALLER:
327 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
329 case ChooserListAdapter.TARGET_SERVICE:
330 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
331 value -= mChooserListAdapter.getCallerTargetCount();
333 case ChooserListAdapter.TARGET_STANDARD:
334 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
335 value -= mChooserListAdapter.getCallerTargetCount()
336 + mChooserListAdapter.getServiceTargetCount();
341 MetricsLogger.action(this, cat, value);
346 void queryTargetServices(ChooserListAdapter adapter) {
347 final PackageManager pm = getPackageManager();
348 int targetsToQuery = 0;
349 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
350 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
351 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
352 final Bundle md = ai.metaData;
353 final String serviceName = md != null ? convertServiceName(ai.packageName,
354 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
355 if (serviceName != null) {
356 final ComponentName serviceComponent = new ComponentName(
357 ai.packageName, serviceName);
358 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
359 .setComponent(serviceComponent);
362 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
366 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
367 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
368 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
369 + " permission " + ChooserTargetService.BIND_PERMISSION
370 + " - this service will not be queried for ChooserTargets."
371 + " add android:permission=\""
372 + ChooserTargetService.BIND_PERMISSION + "\""
373 + " to the <service> tag for " + serviceComponent
374 + " in the manifest.");
377 } catch (NameNotFoundException e) {
378 Log.e(TAG, "Could not look up service " + serviceComponent, e);
382 final ChooserTargetServiceConnection conn =
383 new ChooserTargetServiceConnection(this, dri);
384 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
385 UserHandle.CURRENT)) {
387 Log.d(TAG, "Binding service connection for target " + dri
388 + " intent " + serviceIntent);
390 mServiceConnections.add(conn);
394 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
395 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
396 + QUERY_TARGET_SERVICE_LIMIT);
401 if (!mServiceConnections.isEmpty()) {
402 if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for "
403 + WATCHDOG_TIMEOUT_MILLIS + "ms");
404 mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
405 WATCHDOG_TIMEOUT_MILLIS);
407 sendVoiceChoicesIfNeeded();
411 private String convertServiceName(String packageName, String serviceName) {
412 if (TextUtils.isEmpty(serviceName)) {
416 final String fullName;
417 if (serviceName.startsWith(".")) {
418 // Relative to the app package. Prepend the app package name.
419 fullName = packageName + serviceName;
420 } else if (serviceName.indexOf('.') >= 0) {
421 // Fully qualified package name.
422 fullName = serviceName;
429 void unbindRemainingServices() {
431 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
433 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
434 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
435 if (DEBUG) Log.d(TAG, "unbinding " + conn);
439 mServiceConnections.clear();
440 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
443 void onSetupVoiceInteraction() {
444 // Do nothing. We'll send the voice stuff ourselves.
447 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
448 if (mRefinementResultReceiver != null) {
449 mRefinementResultReceiver.destroy();
450 mRefinementResultReceiver = null;
453 if (selectedTarget == null) {
454 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
455 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
456 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
457 + " cannot match refined source intent " + matchingIntent);
458 } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) {
462 onRefinementCanceled();
465 void onRefinementCanceled() {
466 if (mRefinementResultReceiver != null) {
467 mRefinementResultReceiver.destroy();
468 mRefinementResultReceiver = null;
473 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
474 final List<Intent> targetIntents = target.getAllSourceIntents();
475 for (int i = 0, N = targetIntents.size(); i < N; i++) {
476 final Intent targetIntent = targetIntents.get(i);
477 if (targetIntent.filterEquals(matchingIntent)) {
484 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
485 if (targets == null) {
489 final PackageManager pm = getPackageManager();
490 for (int i = targets.size() - 1; i >= 0; i--) {
491 final ChooserTarget target = targets.get(i);
492 final ComponentName targetName = target.getComponentName();
493 if (packageName != null && packageName.equals(targetName.getPackageName())) {
494 // Anything from the original target's package is fine.
500 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
501 remove = !ai.exported || ai.permission != null;
502 } catch (NameNotFoundException e) {
503 Log.e(TAG, "Target " + target + " returned by " + packageName
504 + " component not found");
515 ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
516 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
517 boolean filterLastUsed) {
518 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
519 initialIntents, rList, launchedFromUid, filterLastUsed);
520 if (DEBUG) Log.d(TAG, "Adapter created; querying services");
521 queryTargetServices(adapter);
525 final class ChooserTargetInfo implements TargetInfo {
526 private final DisplayResolveInfo mSourceInfo;
527 private final ResolveInfo mBackupResolveInfo;
528 private final ChooserTarget mChooserTarget;
529 private Drawable mBadgeIcon = null;
530 private CharSequence mBadgeContentDescription;
531 private Drawable mDisplayIcon;
532 private final Intent mFillInIntent;
533 private final int mFillInFlags;
534 private final float mModifiedScore;
536 public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
537 float modifiedScore) {
538 mSourceInfo = sourceInfo;
539 mChooserTarget = chooserTarget;
540 mModifiedScore = modifiedScore;
541 if (sourceInfo != null) {
542 final ResolveInfo ri = sourceInfo.getResolveInfo();
544 final ActivityInfo ai = ri.activityInfo;
545 if (ai != null && ai.applicationInfo != null) {
546 final PackageManager pm = getPackageManager();
547 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
548 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
552 final Icon icon = chooserTarget.getIcon();
553 // TODO do this in the background
554 mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
556 if (sourceInfo != null) {
557 mBackupResolveInfo = null;
559 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
562 mFillInIntent = null;
566 private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
567 mSourceInfo = other.mSourceInfo;
568 mBackupResolveInfo = other.mBackupResolveInfo;
569 mChooserTarget = other.mChooserTarget;
570 mBadgeIcon = other.mBadgeIcon;
571 mBadgeContentDescription = other.mBadgeContentDescription;
572 mDisplayIcon = other.mDisplayIcon;
573 mFillInIntent = fillInIntent;
574 mFillInFlags = flags;
575 mModifiedScore = other.mModifiedScore;
578 public float getModifiedScore() {
579 return mModifiedScore;
583 public Intent getResolvedIntent() {
584 if (mSourceInfo != null) {
585 return mSourceInfo.getResolvedIntent();
587 return getTargetIntent();
591 public ComponentName getResolvedComponentName() {
592 if (mSourceInfo != null) {
593 return mSourceInfo.getResolvedComponentName();
594 } else if (mBackupResolveInfo != null) {
595 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
596 mBackupResolveInfo.activityInfo.name);
601 private Intent getBaseIntentToSend() {
602 Intent result = mSourceInfo != null
603 ? mSourceInfo.getResolvedIntent() : getTargetIntent();
604 if (result == null) {
605 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
607 result = new Intent(result);
608 if (mFillInIntent != null) {
609 result.fillIn(mFillInIntent, mFillInFlags);
611 result.fillIn(mReferrerFillInIntent, 0);
617 public boolean start(Activity activity, Bundle options) {
618 throw new RuntimeException("ChooserTargets should be started as caller.");
622 public boolean startAsCaller(Activity activity, Bundle options, int userId) {
623 final Intent intent = getBaseIntentToSend();
624 if (intent == null) {
627 intent.setComponent(mChooserTarget.getComponentName());
628 intent.putExtras(mChooserTarget.getIntentExtras());
629 activity.startActivityAsCaller(intent, options, true, userId);
634 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
635 throw new RuntimeException("ChooserTargets should be started as caller.");
639 public ResolveInfo getResolveInfo() {
640 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
644 public CharSequence getDisplayLabel() {
645 return mChooserTarget.getTitle();
649 public CharSequence getExtendedInfo() {
650 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
655 public Drawable getDisplayIcon() {
660 public Drawable getBadgeIcon() {
665 public CharSequence getBadgeContentDescription() {
666 return mBadgeContentDescription;
670 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
671 return new ChooserTargetInfo(this, fillInIntent, flags);
675 public List<Intent> getAllSourceIntents() {
676 final List<Intent> results = new ArrayList<>();
677 if (mSourceInfo != null) {
678 // We only queried the service for the first one in our sourceinfo.
679 results.add(mSourceInfo.getAllSourceIntents().get(0));
685 public class ChooserListAdapter extends ResolveListAdapter {
686 public static final int TARGET_BAD = -1;
687 public static final int TARGET_CALLER = 0;
688 public static final int TARGET_SERVICE = 1;
689 public static final int TARGET_STANDARD = 2;
691 private static final int MAX_SERVICE_TARGETS = 8;
693 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
694 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
696 private float mLateFee = 1.f;
698 private final BaseChooserTargetComparator mBaseTargetComparator
699 = new BaseChooserTargetComparator();
701 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
702 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
703 boolean filterLastUsed) {
704 // Don't send the initial intents through the shared ResolverActivity path,
705 // we want to separate them into a different section.
706 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed);
708 if (initialIntents != null) {
709 final PackageManager pm = getPackageManager();
710 for (int i = 0; i < initialIntents.length; i++) {
711 final Intent ii = initialIntents[i];
715 final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
717 Log.w(TAG, "No activity found for " + ii);
720 ResolveInfo ri = new ResolveInfo();
721 ri.activityInfo = ai;
722 UserManager userManager =
723 (UserManager) getSystemService(Context.USER_SERVICE);
724 if (ii instanceof LabeledIntent) {
725 LabeledIntent li = (LabeledIntent)ii;
726 ri.resolvePackageName = li.getSourcePackage();
727 ri.labelRes = li.getLabelResource();
728 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
729 ri.icon = li.getIconResource();
730 ri.iconResourceId = ri.icon;
732 if (userManager.isManagedProfile()) {
733 ri.noResourceId = true;
736 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
737 ri.loadLabel(pm), null, ii));
743 public boolean showsExtendedInfo(TargetInfo info) {
744 // We have badges so we don't need this text shown.
749 public View onCreateView(ViewGroup parent) {
750 return mInflater.inflate(
751 com.android.internal.R.layout.resolve_grid_item, parent, false);
755 public void onListRebuilt() {
756 if (mServiceTargets != null) {
757 pruneServiceTargets();
762 public boolean shouldGetResolvedFilter() {
767 public int getCount() {
768 return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
772 public int getUnfilteredCount() {
773 return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
776 public int getCallerTargetCount() {
777 return mCallerTargets.size();
780 public int getServiceTargetCount() {
781 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
784 public int getStandardTargetCount() {
785 return super.getCount();
788 public int getPositionTargetType(int position) {
791 final int callerTargetCount = getCallerTargetCount();
792 if (position < callerTargetCount) {
793 return TARGET_CALLER;
795 offset += callerTargetCount;
797 final int serviceTargetCount = getServiceTargetCount();
798 if (position - offset < serviceTargetCount) {
799 return TARGET_SERVICE;
801 offset += serviceTargetCount;
803 final int standardTargetCount = super.getCount();
804 if (position - offset < standardTargetCount) {
805 return TARGET_STANDARD;
812 public TargetInfo getItem(int position) {
813 return targetInfoForPosition(position, true);
817 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
820 final int callerTargetCount = getCallerTargetCount();
821 if (position < callerTargetCount) {
822 return mCallerTargets.get(position);
824 offset += callerTargetCount;
826 final int serviceTargetCount = getServiceTargetCount();
827 if (position - offset < serviceTargetCount) {
828 return mServiceTargets.get(position - offset);
830 offset += serviceTargetCount;
832 return filtered ? super.getItem(position - offset)
833 : getDisplayInfoAt(position - offset);
836 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
837 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
839 final float parentScore = getScore(origTarget);
840 Collections.sort(targets, mBaseTargetComparator);
842 for (int i = 0, N = targets.size(); i < N; i++) {
843 final ChooserTarget target = targets.get(i);
844 float targetScore = target.getScore();
845 targetScore *= parentScore;
846 targetScore *= mLateFee;
847 if (i > 0 && targetScore >= lastScore) {
848 // Apply a decay so that the top app can't crowd out everything else.
849 // This incents ChooserTargetServices to define what's truly better.
850 targetScore = lastScore * 0.95f;
852 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
855 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
856 + " base=" + target.getScore()
857 + " lastScore=" + lastScore
858 + " parentScore=" + parentScore
859 + " lateFee=" + mLateFee);
862 lastScore = targetScore;
867 notifyDataSetChanged();
870 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
871 final float newScore = chooserTargetInfo.getModifiedScore();
872 for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
873 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
874 if (newScore > serviceTarget.getModifiedScore()) {
875 mServiceTargets.add(i, chooserTargetInfo);
879 mServiceTargets.add(chooserTargetInfo);
882 private void pruneServiceTargets() {
883 if (DEBUG) Log.d(TAG, "pruneServiceTargets");
884 for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
885 final ChooserTargetInfo cti = mServiceTargets.get(i);
886 if (!hasResolvedTarget(cti.getResolveInfo())) {
887 if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
888 mServiceTargets.remove(i);
894 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
896 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
898 return (int) Math.signum(lhs.getScore() - rhs.getScore());
902 class ChooserRowAdapter extends BaseAdapter {
903 private ChooserListAdapter mChooserListAdapter;
904 private final LayoutInflater mLayoutInflater;
905 private final int mColumnCount = 4;
907 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
908 mChooserListAdapter = wrappedAdapter;
909 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
911 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
913 public void onChanged() {
915 notifyDataSetChanged();
919 public void onInvalidated() {
920 super.onInvalidated();
921 notifyDataSetInvalidated();
927 public int getCount() {
929 Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
930 + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
931 + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
936 public Object getItem(int position) {
937 // We have nothing useful to return here.
942 public long getItemId(int position) {
947 public View getView(int position, View convertView, ViewGroup parent) {
949 if (convertView == null) {
950 holder = createViewHolder(parent);
952 holder = (View[]) convertView.getTag();
954 bindViewHolder(position, holder);
956 // We keep the actual list item view as the last item in the holder array
957 return holder[mColumnCount];
960 View[] createViewHolder(ViewGroup parent) {
961 final View[] holder = new View[mColumnCount + 1];
963 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
965 for (int i = 0; i < mColumnCount; i++) {
966 holder[i] = mChooserListAdapter.createView(row);
967 row.addView(holder[i]);
970 holder[mColumnCount] = row;
974 void bindViewHolder(int rowPosition, View[] holder) {
975 final int start = getFirstRowPosition(rowPosition);
976 final int startType = mChooserListAdapter.getPositionTargetType(start);
978 int end = start + mColumnCount - 1;
979 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
983 final ViewGroup row = (ViewGroup) holder[mColumnCount];
985 if (startType == ChooserListAdapter.TARGET_SERVICE) {
986 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
988 row.setBackground(null);
991 for (int i = 0; i < mColumnCount; i++) {
992 final View v = holder[i];
993 if (start + i <= end) {
994 v.setVisibility(View.VISIBLE);
995 final int itemIndex = start + i;
996 mChooserListAdapter.bindView(itemIndex, v);
997 v.setOnClickListener(new OnClickListener() {
999 public void onClick(View v) {
1000 startSelected(itemIndex, false, true);
1003 v.setOnLongClickListener(new OnLongClickListener() {
1005 public boolean onLongClick(View v) {
1007 mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
1012 v.setVisibility(View.GONE);
1017 int getFirstRowPosition(int row) {
1018 final int callerCount = mChooserListAdapter.getCallerTargetCount();
1019 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1021 if (row < callerRows) {
1022 return row * mColumnCount;
1025 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1026 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1028 if (row < callerRows + serviceRows) {
1029 return callerCount + (row - callerRows) * mColumnCount;
1032 return callerCount + serviceCount
1033 + (row - callerRows - serviceRows) * mColumnCount;
1037 static class ChooserTargetServiceConnection implements ServiceConnection {
1038 private final DisplayResolveInfo mOriginalTarget;
1039 private ComponentName mConnectedComponent;
1040 private ChooserActivity mChooserActivity;
1041 private final Object mLock = new Object();
1043 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1045 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1046 synchronized (mLock) {
1047 if (mChooserActivity == null) {
1048 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
1049 + mConnectedComponent + "; ignoring...");
1052 mChooserActivity.filterServiceTargets(
1053 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
1054 final Message msg = Message.obtain();
1055 msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1056 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1057 ChooserTargetServiceConnection.this);
1058 mChooserActivity.mChooserHandler.sendMessage(msg);
1063 public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
1064 DisplayResolveInfo dri) {
1065 mChooserActivity = chooserActivity;
1066 mOriginalTarget = dri;
1070 public void onServiceConnected(ComponentName name, IBinder service) {
1071 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1072 synchronized (mLock) {
1073 if (mChooserActivity == null) {
1074 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
1078 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1080 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1081 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1082 } catch (RemoteException e) {
1083 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1084 mChooserActivity.unbindService(this);
1086 mChooserActivity.mServiceConnections.remove(this);
1092 public void onServiceDisconnected(ComponentName name) {
1093 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1094 synchronized (mLock) {
1095 if (mChooserActivity == null) {
1097 "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
1101 mChooserActivity.unbindService(this);
1103 mChooserActivity.mServiceConnections.remove(this);
1104 if (mChooserActivity.mServiceConnections.isEmpty()) {
1105 mChooserActivity.mChooserHandler.removeMessages(
1106 CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1107 mChooserActivity.sendVoiceChoicesIfNeeded();
1109 mConnectedComponent = null;
1113 public void destroy() {
1114 synchronized (mLock) {
1115 mChooserActivity = null;
1120 public String toString() {
1121 return "ChooserTargetServiceConnection{service="
1122 + mConnectedComponent + ", activity="
1123 + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}";
1127 static class ServiceResultInfo {
1128 public final DisplayResolveInfo originalTarget;
1129 public final List<ChooserTarget> resultTargets;
1130 public final ChooserTargetServiceConnection connection;
1132 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1133 ChooserTargetServiceConnection c) {
1134 originalTarget = ot;
1140 static class RefinementResultReceiver extends ResultReceiver {
1141 private ChooserActivity mChooserActivity;
1142 private TargetInfo mSelectedTarget;
1144 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1147 mChooserActivity = host;
1148 mSelectedTarget = target;
1152 protected void onReceiveResult(int resultCode, Bundle resultData) {
1153 if (mChooserActivity == null) {
1154 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1157 if (resultData == null) {
1158 Log.e(TAG, "RefinementResultReceiver received null resultData");
1162 switch (resultCode) {
1163 case RESULT_CANCELED:
1164 mChooserActivity.onRefinementCanceled();
1167 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1168 if (intentParcelable instanceof Intent) {
1169 mChooserActivity.onRefinementResult(mSelectedTarget,
1170 (Intent) intentParcelable);
1172 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1173 + " in resultData with key Intent.EXTRA_INTENT");
1177 Log.w(TAG, "Unknown result code " + resultCode
1178 + " sent to RefinementResultReceiver");
1183 public void destroy() {
1184 mChooserActivity = null;
1185 mSelectedTarget = null;