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 mServiceConnections.remove(sri.connection);
108 if (mServiceConnections.isEmpty()) {
109 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
110 sendVoiceChoicesIfNeeded();
114 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
116 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
118 unbindRemainingServices();
119 sendVoiceChoicesIfNeeded();
123 super.handleMessage(msg);
129 protected void onCreate(Bundle savedInstanceState) {
130 Intent intent = getIntent();
131 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
132 if (!(targetParcelable instanceof Intent)) {
133 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
135 super.onCreate(null);
138 Intent target = (Intent) targetParcelable;
139 if (target != null) {
140 modifyTargetIntent(target);
142 Parcelable[] targetsParcelable
143 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
144 if (targetsParcelable != null) {
145 final boolean offset = target == null;
146 Intent[] additionalTargets =
147 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
148 for (int i = 0; i < targetsParcelable.length; i++) {
149 if (!(targetsParcelable[i] instanceof Intent)) {
150 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
151 + targetsParcelable[i]);
153 super.onCreate(null);
156 final Intent additionalTarget = (Intent) targetsParcelable[i];
157 if (i == 0 && target == null) {
158 target = additionalTarget;
159 modifyTargetIntent(target);
161 additionalTargets[offset ? i - 1 : i] = additionalTarget;
162 modifyTargetIntent(additionalTarget);
165 setAdditionalTargets(additionalTargets);
168 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
169 CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
170 int defaultTitleRes = 0;
172 defaultTitleRes = com.android.internal.R.string.chooseActivity;
174 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
175 Intent[] initialIntents = null;
177 initialIntents = new Intent[pa.length];
178 for (int i=0; i<pa.length; i++) {
179 if (!(pa[i] instanceof Intent)) {
180 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
182 super.onCreate(null);
185 final Intent in = (Intent) pa[i];
186 modifyTargetIntent(in);
187 initialIntents[i] = in;
191 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
193 mChosenComponentSender = intent.getParcelableExtra(
194 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
195 mRefinementIntentSender = intent.getParcelableExtra(
196 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
197 setSafeForwardingMode(true);
198 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
201 MetricsLogger.action(this, MetricsLogger.ACTION_ACTIVITY_CHOOSER_SHOWN);
205 protected void onDestroy() {
207 if (mRefinementResultReceiver != null) {
208 mRefinementResultReceiver.destroy();
209 mRefinementResultReceiver = null;
214 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
215 Intent result = defIntent;
216 if (mReplacementExtras != null) {
217 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
218 if (replExtras != null) {
219 result = new Intent(defIntent);
220 result.putExtras(replExtras);
223 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER)
224 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
225 result = Intent.createChooser(result,
226 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
232 void onActivityStarted(TargetInfo cti) {
233 if (mChosenComponentSender != null) {
234 final ComponentName target = cti.getResolvedComponentName();
235 if (target != null) {
236 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
238 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
239 } catch (IntentSender.SendIntentException e) {
240 Slog.e(TAG, "Unable to launch supplied IntentSender to report "
241 + "the chosen component: " + e);
248 void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
249 boolean alwaysUseOption) {
250 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
251 mChooserListAdapter = (ChooserListAdapter) adapter;
252 adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
253 if (listView != null) {
254 listView.setItemsCanFocus(true);
259 int getLayoutResource() {
260 return R.layout.chooser_grid;
264 boolean shouldGetActivityMetadata() {
268 private void modifyTargetIntent(Intent in) {
269 final String action = in.getAction();
270 if (Intent.ACTION_SEND.equals(action) ||
271 Intent.ACTION_SEND_MULTIPLE.equals(action)) {
272 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
273 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
278 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
279 if (mRefinementIntentSender != null) {
280 final Intent fillIn = new Intent();
281 final List<Intent> sourceIntents = target.getAllSourceIntents();
282 if (!sourceIntents.isEmpty()) {
283 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
284 if (sourceIntents.size() > 1) {
285 final Intent[] alts = new Intent[sourceIntents.size() - 1];
286 for (int i = 1, N = sourceIntents.size(); i < N; i++) {
287 alts[i - 1] = sourceIntents.get(i);
289 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
291 if (mRefinementResultReceiver != null) {
292 mRefinementResultReceiver.destroy();
294 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
295 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
296 mRefinementResultReceiver);
298 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
300 } catch (SendIntentException e) {
301 Log.e(TAG, "Refinement IntentSender failed to send", e);
305 return super.onTargetSelected(target, alwaysCheck);
309 void startSelected(int which, boolean always, boolean filtered) {
310 super.startSelected(which, always, filtered);
312 if (mChooserListAdapter != null) {
313 // Log the index of which type of target the user picked.
314 // Lower values mean the ranking was better.
317 switch (mChooserListAdapter.getPositionTargetType(which)) {
318 case ChooserListAdapter.TARGET_CALLER:
319 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
321 case ChooserListAdapter.TARGET_SERVICE:
322 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
323 value -= mChooserListAdapter.getCallerTargetCount();
325 case ChooserListAdapter.TARGET_STANDARD:
326 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
327 value -= mChooserListAdapter.getCallerTargetCount()
328 + mChooserListAdapter.getServiceTargetCount();
333 MetricsLogger.action(this, cat, value);
338 void queryTargetServices(ChooserListAdapter adapter) {
339 final PackageManager pm = getPackageManager();
340 int targetsToQuery = 0;
341 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
342 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
343 final ActivityInfo ai = dri.getResolveInfo().activityInfo;
344 final Bundle md = ai.metaData;
345 final String serviceName = md != null ? convertServiceName(ai.packageName,
346 md.getString(ChooserTargetService.META_DATA_NAME)) : null;
347 if (serviceName != null) {
348 final ComponentName serviceComponent = new ComponentName(
349 ai.packageName, serviceName);
350 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
351 .setComponent(serviceComponent);
354 Log.d(TAG, "queryTargets found target with service " + serviceComponent);
358 final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
359 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
360 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
361 + " permission " + ChooserTargetService.BIND_PERMISSION
362 + " - this service will not be queried for ChooserTargets."
363 + " add android:permission=\""
364 + ChooserTargetService.BIND_PERMISSION + "\""
365 + " to the <service> tag for " + serviceComponent
366 + " in the manifest.");
369 } catch (NameNotFoundException e) {
370 Log.e(TAG, "Could not look up service " + serviceComponent, e);
374 final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(dri);
375 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
376 UserHandle.CURRENT)) {
378 Log.d(TAG, "Binding service connection for target " + dri
379 + " intent " + serviceIntent);
381 mServiceConnections.add(conn);
385 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
386 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
387 + QUERY_TARGET_SERVICE_LIMIT);
392 if (!mServiceConnections.isEmpty()) {
393 if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for "
394 + WATCHDOG_TIMEOUT_MILLIS + "ms");
395 mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
396 WATCHDOG_TIMEOUT_MILLIS);
398 sendVoiceChoicesIfNeeded();
402 private String convertServiceName(String packageName, String serviceName) {
403 if (TextUtils.isEmpty(serviceName)) {
407 final String fullName;
408 if (serviceName.startsWith(".")) {
409 // Relative to the app package. Prepend the app package name.
410 fullName = packageName + serviceName;
411 } else if (serviceName.indexOf('.') >= 0) {
412 // Fully qualified package name.
413 fullName = serviceName;
420 void unbindRemainingServices() {
422 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
424 for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
425 final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
426 if (DEBUG) Log.d(TAG, "unbinding " + conn);
429 mServiceConnections.clear();
430 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
433 void onSetupVoiceInteraction() {
434 // Do nothing. We'll send the voice stuff ourselves.
437 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
438 if (mRefinementResultReceiver != null) {
439 mRefinementResultReceiver.destroy();
440 mRefinementResultReceiver = null;
443 if (selectedTarget == null) {
444 Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
445 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
446 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
447 + " cannot match refined source intent " + matchingIntent);
448 } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) {
452 onRefinementCanceled();
455 void onRefinementCanceled() {
456 if (mRefinementResultReceiver != null) {
457 mRefinementResultReceiver.destroy();
458 mRefinementResultReceiver = null;
463 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
464 final List<Intent> targetIntents = target.getAllSourceIntents();
465 for (int i = 0, N = targetIntents.size(); i < N; i++) {
466 final Intent targetIntent = targetIntents.get(i);
467 if (targetIntent.filterEquals(matchingIntent)) {
474 void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
475 if (targets == null) {
479 final PackageManager pm = getPackageManager();
480 for (int i = targets.size() - 1; i >= 0; i--) {
481 final ChooserTarget target = targets.get(i);
482 final ComponentName targetName = target.getComponentName();
483 if (packageName != null && packageName.equals(targetName.getPackageName())) {
484 // Anything from the original target's package is fine.
490 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
491 remove = !ai.exported || ai.permission != null;
492 } catch (NameNotFoundException e) {
493 Log.e(TAG, "Target " + target + " returned by " + packageName
494 + " component not found");
505 ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
506 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
507 boolean filterLastUsed) {
508 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
509 initialIntents, rList, launchedFromUid, filterLastUsed);
510 if (DEBUG) Log.d(TAG, "Adapter created; querying services");
511 queryTargetServices(adapter);
515 final class ChooserTargetInfo implements TargetInfo {
516 private final DisplayResolveInfo mSourceInfo;
517 private final ResolveInfo mBackupResolveInfo;
518 private final ChooserTarget mChooserTarget;
519 private Drawable mBadgeIcon = null;
520 private CharSequence mBadgeContentDescription;
521 private Drawable mDisplayIcon;
522 private final Intent mFillInIntent;
523 private final int mFillInFlags;
524 private final float mModifiedScore;
526 public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
527 float modifiedScore) {
528 mSourceInfo = sourceInfo;
529 mChooserTarget = chooserTarget;
530 mModifiedScore = modifiedScore;
531 if (sourceInfo != null) {
532 final ResolveInfo ri = sourceInfo.getResolveInfo();
534 final ActivityInfo ai = ri.activityInfo;
535 if (ai != null && ai.applicationInfo != null) {
536 final PackageManager pm = getPackageManager();
537 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
538 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
542 final Icon icon = chooserTarget.getIcon();
543 // TODO do this in the background
544 mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
546 if (sourceInfo != null) {
547 mBackupResolveInfo = null;
549 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
552 mFillInIntent = null;
556 private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
557 mSourceInfo = other.mSourceInfo;
558 mBackupResolveInfo = other.mBackupResolveInfo;
559 mChooserTarget = other.mChooserTarget;
560 mBadgeIcon = other.mBadgeIcon;
561 mBadgeContentDescription = other.mBadgeContentDescription;
562 mDisplayIcon = other.mDisplayIcon;
563 mFillInIntent = fillInIntent;
564 mFillInFlags = flags;
565 mModifiedScore = other.mModifiedScore;
568 public float getModifiedScore() {
569 return mModifiedScore;
573 public Intent getResolvedIntent() {
574 if (mSourceInfo != null) {
575 return mSourceInfo.getResolvedIntent();
577 return getTargetIntent();
581 public ComponentName getResolvedComponentName() {
582 if (mSourceInfo != null) {
583 return mSourceInfo.getResolvedComponentName();
584 } else if (mBackupResolveInfo != null) {
585 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
586 mBackupResolveInfo.activityInfo.name);
591 private Intent getBaseIntentToSend() {
592 Intent result = mSourceInfo != null
593 ? mSourceInfo.getResolvedIntent() : getTargetIntent();
594 if (result == null) {
595 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
597 result = new Intent(result);
598 if (mFillInIntent != null) {
599 result.fillIn(mFillInIntent, mFillInFlags);
601 result.fillIn(mReferrerFillInIntent, 0);
607 public boolean start(Activity activity, Bundle options) {
608 throw new RuntimeException("ChooserTargets should be started as caller.");
612 public boolean startAsCaller(Activity activity, Bundle options, int userId) {
613 final Intent intent = getBaseIntentToSend();
614 if (intent == null) {
617 intent.setComponent(mChooserTarget.getComponentName());
618 intent.putExtras(mChooserTarget.getIntentExtras());
619 activity.startActivityAsCaller(intent, options, true, userId);
624 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
625 throw new RuntimeException("ChooserTargets should be started as caller.");
629 public ResolveInfo getResolveInfo() {
630 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
634 public CharSequence getDisplayLabel() {
635 return mChooserTarget.getTitle();
639 public CharSequence getExtendedInfo() {
640 return mSourceInfo != null ? mSourceInfo.getExtendedInfo() : null;
644 public Drawable getDisplayIcon() {
649 public Drawable getBadgeIcon() {
654 public CharSequence getBadgeContentDescription() {
655 return mBadgeContentDescription;
659 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
660 return new ChooserTargetInfo(this, fillInIntent, flags);
664 public List<Intent> getAllSourceIntents() {
665 final List<Intent> results = new ArrayList<>();
666 if (mSourceInfo != null) {
667 // We only queried the service for the first one in our sourceinfo.
668 results.add(mSourceInfo.getAllSourceIntents().get(0));
674 public class ChooserListAdapter extends ResolveListAdapter {
675 public static final int TARGET_BAD = -1;
676 public static final int TARGET_CALLER = 0;
677 public static final int TARGET_SERVICE = 1;
678 public static final int TARGET_STANDARD = 2;
680 private static final int MAX_SERVICE_TARGETS = 8;
682 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
683 private final List<TargetInfo> mCallerTargets = new ArrayList<>();
685 private float mLateFee = 1.f;
687 private final BaseChooserTargetComparator mBaseTargetComparator
688 = new BaseChooserTargetComparator();
690 public ChooserListAdapter(Context context, List<Intent> payloadIntents,
691 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
692 boolean filterLastUsed) {
693 // Don't send the initial intents through the shared ResolverActivity path,
694 // we want to separate them into a different section.
695 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed);
697 if (initialIntents != null) {
698 final PackageManager pm = getPackageManager();
699 for (int i = 0; i < initialIntents.length; i++) {
700 final Intent ii = initialIntents[i];
704 final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
706 Log.w(TAG, "No activity found for " + ii);
709 ResolveInfo ri = new ResolveInfo();
710 ri.activityInfo = ai;
711 UserManager userManager =
712 (UserManager) getSystemService(Context.USER_SERVICE);
713 if (ii instanceof LabeledIntent) {
714 LabeledIntent li = (LabeledIntent)ii;
715 ri.resolvePackageName = li.getSourcePackage();
716 ri.labelRes = li.getLabelResource();
717 ri.nonLocalizedLabel = li.getNonLocalizedLabel();
718 ri.icon = li.getIconResource();
719 ri.iconResourceId = ri.icon;
721 if (userManager.isManagedProfile()) {
722 ri.noResourceId = true;
725 mCallerTargets.add(new DisplayResolveInfo(ii, ri,
726 ri.loadLabel(pm), null, ii));
732 public boolean showsExtendedInfo(TargetInfo info) {
733 // Reserve space to show extended info if any one of the items in the adapter has
734 // extended info. This keeps grid item sizes uniform.
735 return hasExtendedInfo();
739 public View onCreateView(ViewGroup parent) {
740 return mInflater.inflate(
741 com.android.internal.R.layout.resolve_grid_item, parent, false);
745 public void onListRebuilt() {
746 if (mServiceTargets != null) {
747 pruneServiceTargets();
752 public boolean shouldGetResolvedFilter() {
757 public int getCount() {
758 return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
762 public int getUnfilteredCount() {
763 return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
766 public int getCallerTargetCount() {
767 return mCallerTargets.size();
770 public int getServiceTargetCount() {
771 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
774 public int getStandardTargetCount() {
775 return super.getCount();
778 public int getPositionTargetType(int position) {
781 final int callerTargetCount = getCallerTargetCount();
782 if (position < callerTargetCount) {
783 return TARGET_CALLER;
785 offset += callerTargetCount;
787 final int serviceTargetCount = getServiceTargetCount();
788 if (position - offset < serviceTargetCount) {
789 return TARGET_SERVICE;
791 offset += serviceTargetCount;
793 final int standardTargetCount = super.getCount();
794 if (position - offset < standardTargetCount) {
795 return TARGET_STANDARD;
802 public TargetInfo getItem(int position) {
803 return targetInfoForPosition(position, true);
807 public TargetInfo targetInfoForPosition(int position, boolean filtered) {
810 final int callerTargetCount = getCallerTargetCount();
811 if (position < callerTargetCount) {
812 return mCallerTargets.get(position);
814 offset += callerTargetCount;
816 final int serviceTargetCount = getServiceTargetCount();
817 if (position - offset < serviceTargetCount) {
818 return mServiceTargets.get(position - offset);
820 offset += serviceTargetCount;
822 return filtered ? super.getItem(position - offset)
823 : getDisplayInfoAt(position - offset);
826 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
827 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
829 final float parentScore = getScore(origTarget);
830 Collections.sort(targets, mBaseTargetComparator);
832 for (int i = 0, N = targets.size(); i < N; i++) {
833 final ChooserTarget target = targets.get(i);
834 float targetScore = target.getScore();
835 targetScore *= parentScore;
836 targetScore *= mLateFee;
837 if (i > 0 && targetScore >= lastScore) {
838 // Apply a decay so that the top app can't crowd out everything else.
839 // This incents ChooserTargetServices to define what's truly better.
840 targetScore = lastScore * 0.95f;
842 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
845 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
846 + " base=" + target.getScore()
847 + " lastScore=" + lastScore
848 + " parentScore=" + parentScore
849 + " lateFee=" + mLateFee);
852 lastScore = targetScore;
857 notifyDataSetChanged();
860 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
861 final float newScore = chooserTargetInfo.getModifiedScore();
862 for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
863 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
864 if (newScore > serviceTarget.getModifiedScore()) {
865 mServiceTargets.add(i, chooserTargetInfo);
869 mServiceTargets.add(chooserTargetInfo);
872 private void pruneServiceTargets() {
873 if (DEBUG) Log.d(TAG, "pruneServiceTargets");
874 for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
875 final ChooserTargetInfo cti = mServiceTargets.get(i);
876 if (!hasResolvedTarget(cti.getResolveInfo())) {
877 if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
878 mServiceTargets.remove(i);
884 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
886 public int compare(ChooserTarget lhs, ChooserTarget rhs) {
888 return (int) Math.signum(lhs.getScore() - rhs.getScore());
892 class ChooserRowAdapter extends BaseAdapter {
893 private ChooserListAdapter mChooserListAdapter;
894 private final LayoutInflater mLayoutInflater;
895 private final int mColumnCount = 4;
897 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
898 mChooserListAdapter = wrappedAdapter;
899 mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
901 wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
903 public void onChanged() {
905 notifyDataSetChanged();
909 public void onInvalidated() {
910 super.onInvalidated();
911 notifyDataSetInvalidated();
917 public int getCount() {
919 Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
920 + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
921 + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
926 public Object getItem(int position) {
927 // We have nothing useful to return here.
932 public long getItemId(int position) {
937 public View getView(int position, View convertView, ViewGroup parent) {
939 if (convertView == null) {
940 holder = createViewHolder(parent);
942 holder = (View[]) convertView.getTag();
944 bindViewHolder(position, holder);
946 // We keep the actual list item view as the last item in the holder array
947 return holder[mColumnCount];
950 View[] createViewHolder(ViewGroup parent) {
951 final View[] holder = new View[mColumnCount + 1];
953 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
955 for (int i = 0; i < mColumnCount; i++) {
956 holder[i] = mChooserListAdapter.createView(row);
957 row.addView(holder[i]);
960 holder[mColumnCount] = row;
964 void bindViewHolder(int rowPosition, View[] holder) {
965 final int start = getFirstRowPosition(rowPosition);
966 final int startType = mChooserListAdapter.getPositionTargetType(start);
968 int end = start + mColumnCount - 1;
969 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
973 final ViewGroup row = (ViewGroup) holder[mColumnCount];
975 if (startType == ChooserListAdapter.TARGET_SERVICE) {
976 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
978 row.setBackground(null);
981 for (int i = 0; i < mColumnCount; i++) {
982 final View v = holder[i];
983 if (start + i <= end) {
984 v.setVisibility(View.VISIBLE);
985 final int itemIndex = start + i;
986 mChooserListAdapter.bindView(itemIndex, v);
987 v.setOnClickListener(new OnClickListener() {
989 public void onClick(View v) {
990 startSelected(itemIndex, false, true);
993 v.setOnLongClickListener(new OnLongClickListener() {
995 public boolean onLongClick(View v) {
997 mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
1002 v.setVisibility(View.GONE);
1007 int getFirstRowPosition(int row) {
1008 final int callerCount = mChooserListAdapter.getCallerTargetCount();
1009 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1011 if (row < callerRows) {
1012 return row * mColumnCount;
1015 final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1016 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1018 if (row < callerRows + serviceRows) {
1019 return callerCount + (row - callerRows) * mColumnCount;
1022 return callerCount + serviceCount
1023 + (row - callerRows - serviceRows) * mColumnCount;
1027 class ChooserTargetServiceConnection implements ServiceConnection {
1028 private final DisplayResolveInfo mOriginalTarget;
1030 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1032 public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1033 filterServiceTargets(mOriginalTarget.getResolveInfo().activityInfo.packageName,
1035 final Message msg = Message.obtain();
1036 msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1037 msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1038 ChooserTargetServiceConnection.this);
1039 mChooserHandler.sendMessage(msg);
1043 public ChooserTargetServiceConnection(DisplayResolveInfo dri) {
1044 mOriginalTarget = dri;
1048 public void onServiceConnected(ComponentName name, IBinder service) {
1049 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1050 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1052 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1053 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1054 } catch (RemoteException e) {
1055 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1056 unbindService(this);
1057 mServiceConnections.remove(this);
1062 public void onServiceDisconnected(ComponentName name) {
1063 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1064 unbindService(this);
1065 mServiceConnections.remove(this);
1066 if (mServiceConnections.isEmpty()) {
1067 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1068 sendVoiceChoicesIfNeeded();
1073 public String toString() {
1074 return mOriginalTarget.getResolveInfo().activityInfo.toString();
1078 static class ServiceResultInfo {
1079 public final DisplayResolveInfo originalTarget;
1080 public final List<ChooserTarget> resultTargets;
1081 public final ChooserTargetServiceConnection connection;
1083 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1084 ChooserTargetServiceConnection c) {
1085 originalTarget = ot;
1091 static class RefinementResultReceiver extends ResultReceiver {
1092 private ChooserActivity mChooserActivity;
1093 private TargetInfo mSelectedTarget;
1095 public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1098 mChooserActivity = host;
1099 mSelectedTarget = target;
1103 protected void onReceiveResult(int resultCode, Bundle resultData) {
1104 if (mChooserActivity == null) {
1105 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1108 if (resultData == null) {
1109 Log.e(TAG, "RefinementResultReceiver received null resultData");
1113 switch (resultCode) {
1114 case RESULT_CANCELED:
1115 mChooserActivity.onRefinementCanceled();
1118 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1119 if (intentParcelable instanceof Intent) {
1120 mChooserActivity.onRefinementResult(mSelectedTarget,
1121 (Intent) intentParcelable);
1123 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1124 + " in resultData with key Intent.EXTRA_INTENT");
1128 Log.w(TAG, "Unknown result code " + resultCode
1129 + " sent to RefinementResultReceiver");
1134 public void destroy() {
1135 mChooserActivity = null;
1136 mSelectedTarget = null;