OSDN Git Service

am fe7ceca1: (-s ours) am 9ceec2f0: am f6c75158: am 12c54dfa: am c9f7abe3: Merge...
[android-x86/frameworks-base.git] / core / java / com / android / internal / app / ChooserActivity.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.internal.app;
18
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;
60
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.Comparator;
64 import java.util.List;
65
66 public class ChooserActivity extends ResolverActivity {
67     private static final String TAG = "ChooserActivity";
68
69     private static final boolean DEBUG = false;
70
71     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
72     private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
73
74     private Bundle mReplacementExtras;
75     private IntentSender mChosenComponentSender;
76     private IntentSender mRefinementIntentSender;
77     private RefinementResultReceiver mRefinementResultReceiver;
78
79     private Intent mReferrerFillInIntent;
80
81     private ChooserListAdapter mChooserListAdapter;
82
83     private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
84
85     private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
86     private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
87
88     private final Handler mChooserHandler = new Handler() {
89         @Override
90         public void handleMessage(Message msg) {
91             switch (msg.what) {
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?");
100                         break;
101                     }
102                     if (sri.resultTargets != null) {
103                         mChooserListAdapter.addServiceResults(sri.originalTarget,
104                                 sri.resultTargets);
105                     }
106                     unbindService(sri.connection);
107                     mServiceConnections.remove(sri.connection);
108                     if (mServiceConnections.isEmpty()) {
109                         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
110                         sendVoiceChoicesIfNeeded();
111                     }
112                     break;
113
114                 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
115                     if (DEBUG) {
116                         Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
117                     }
118                     unbindRemainingServices();
119                     sendVoiceChoicesIfNeeded();
120                     break;
121
122                 default:
123                     super.handleMessage(msg);
124             }
125         }
126     };
127
128     @Override
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);
134             finish();
135             super.onCreate(null);
136             return;
137         }
138         Intent target = (Intent) targetParcelable;
139         if (target != null) {
140             modifyTargetIntent(target);
141         }
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]);
152                     finish();
153                     super.onCreate(null);
154                     return;
155                 }
156                 final Intent additionalTarget = (Intent) targetsParcelable[i];
157                 if (i == 0 && target == null) {
158                     target = additionalTarget;
159                     modifyTargetIntent(target);
160                 } else {
161                     additionalTargets[offset ? i - 1 : i] = additionalTarget;
162                     modifyTargetIntent(additionalTarget);
163                 }
164             }
165             setAdditionalTargets(additionalTargets);
166         }
167
168         mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
169         CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
170         int defaultTitleRes = 0;
171         if (title == null) {
172             defaultTitleRes = com.android.internal.R.string.chooseActivity;
173         }
174         Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
175         Intent[] initialIntents = null;
176         if (pa != 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]);
181                     finish();
182                     super.onCreate(null);
183                     return;
184                 }
185                 final Intent in = (Intent) pa[i];
186                 modifyTargetIntent(in);
187                 initialIntents[i] = in;
188             }
189         }
190
191         mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
192
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,
199                 null, false);
200
201         MetricsLogger.action(this, MetricsLogger.ACTION_ACTIVITY_CHOOSER_SHOWN);
202     }
203
204     @Override
205     protected void onDestroy() {
206         super.onDestroy();
207         if (mRefinementResultReceiver != null) {
208             mRefinementResultReceiver.destroy();
209             mRefinementResultReceiver = null;
210         }
211     }
212
213     @Override
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);
221             }
222         }
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));
227         }
228         return result;
229     }
230
231     @Override
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);
237                 try {
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);
242                 }
243             }
244         }
245     }
246
247     @Override
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);
255         }
256     }
257
258     @Override
259     int getLayoutResource() {
260         return R.layout.chooser_grid;
261     }
262
263     @Override
264     boolean shouldGetActivityMetadata() {
265         return true;
266     }
267
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);
274         }
275     }
276
277     @Override
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);
288                     }
289                     fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
290                 }
291                 if (mRefinementResultReceiver != null) {
292                     mRefinementResultReceiver.destroy();
293                 }
294                 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
295                 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
296                         mRefinementResultReceiver);
297                 try {
298                     mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
299                     return false;
300                 } catch (SendIntentException e) {
301                     Log.e(TAG, "Refinement IntentSender failed to send", e);
302                 }
303             }
304         }
305         return super.onTargetSelected(target, alwaysCheck);
306     }
307
308     @Override
309     void startSelected(int which, boolean always, boolean filtered) {
310         super.startSelected(which, always, filtered);
311
312         if (mChooserListAdapter != null) {
313             // Log the index of which type of target the user picked.
314             // Lower values mean the ranking was better.
315             int cat = 0;
316             int value = which;
317             switch (mChooserListAdapter.getPositionTargetType(which)) {
318                 case ChooserListAdapter.TARGET_CALLER:
319                     cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
320                     break;
321                 case ChooserListAdapter.TARGET_SERVICE:
322                     cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
323                     value -= mChooserListAdapter.getCallerTargetCount();
324                     break;
325                 case ChooserListAdapter.TARGET_STANDARD:
326                     cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
327                     value -= mChooserListAdapter.getCallerTargetCount()
328                             + mChooserListAdapter.getServiceTargetCount();
329                     break;
330             }
331
332             if (cat != 0) {
333                 MetricsLogger.action(this, cat, value);
334             }
335         }
336     }
337
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);
352
353                 if (DEBUG) {
354                     Log.d(TAG, "queryTargets found target with service " + serviceComponent);
355                 }
356
357                 try {
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.");
367                         continue;
368                     }
369                 } catch (NameNotFoundException e) {
370                     Log.e(TAG, "Could not look up service " + serviceComponent, e);
371                     continue;
372                 }
373
374                 final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(dri);
375                 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
376                         UserHandle.CURRENT)) {
377                     if (DEBUG) {
378                         Log.d(TAG, "Binding service connection for target " + dri
379                                 + " intent " + serviceIntent);
380                     }
381                     mServiceConnections.add(conn);
382                     targetsToQuery++;
383                 }
384             }
385             if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
386                 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
387                         + QUERY_TARGET_SERVICE_LIMIT);
388                 break;
389             }
390         }
391
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);
397         } else {
398             sendVoiceChoicesIfNeeded();
399         }
400     }
401
402     private String convertServiceName(String packageName, String serviceName) {
403         if (TextUtils.isEmpty(serviceName)) {
404             return null;
405         }
406
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;
414         } else {
415             fullName = null;
416         }
417         return fullName;
418     }
419
420     void unbindRemainingServices() {
421         if (DEBUG) {
422             Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
423         }
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);
427             unbindService(conn);
428         }
429         mServiceConnections.clear();
430         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
431     }
432
433     void onSetupVoiceInteraction() {
434         // Do nothing. We'll send the voice stuff ourselves.
435     }
436
437     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
438         if (mRefinementResultReceiver != null) {
439             mRefinementResultReceiver.destroy();
440             mRefinementResultReceiver = null;
441         }
442
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)) {
449             finish();
450             return;
451         }
452         onRefinementCanceled();
453     }
454
455     void onRefinementCanceled() {
456         if (mRefinementResultReceiver != null) {
457             mRefinementResultReceiver.destroy();
458             mRefinementResultReceiver = null;
459         }
460         finish();
461     }
462
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)) {
468                 return true;
469             }
470         }
471         return false;
472     }
473
474     void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
475         if (targets == null) {
476             return;
477         }
478
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.
485                 continue;
486             }
487
488             boolean remove;
489             try {
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");
495                 remove = true;
496             }
497
498             if (remove) {
499                 targets.remove(i);
500             }
501         }
502     }
503
504     @Override
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);
512         return adapter;
513     }
514
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;
525
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();
533                 if (ri != null) {
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);
539                     }
540                 }
541             }
542             final Icon icon = chooserTarget.getIcon();
543             // TODO do this in the background
544             mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
545
546             if (sourceInfo != null) {
547                 mBackupResolveInfo = null;
548             } else {
549                 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
550             }
551
552             mFillInIntent = null;
553             mFillInFlags = 0;
554         }
555
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;
566         }
567
568         public float getModifiedScore() {
569             return mModifiedScore;
570         }
571
572         @Override
573         public Intent getResolvedIntent() {
574             if (mSourceInfo != null) {
575                 return mSourceInfo.getResolvedIntent();
576             }
577             return getTargetIntent();
578         }
579
580         @Override
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);
587             }
588             return null;
589         }
590
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");
596             } else {
597                 result = new Intent(result);
598                 if (mFillInIntent != null) {
599                     result.fillIn(mFillInIntent, mFillInFlags);
600                 }
601                 result.fillIn(mReferrerFillInIntent, 0);
602             }
603             return result;
604         }
605
606         @Override
607         public boolean start(Activity activity, Bundle options) {
608             throw new RuntimeException("ChooserTargets should be started as caller.");
609         }
610
611         @Override
612         public boolean startAsCaller(Activity activity, Bundle options, int userId) {
613             final Intent intent = getBaseIntentToSend();
614             if (intent == null) {
615                 return false;
616             }
617             intent.setComponent(mChooserTarget.getComponentName());
618             intent.putExtras(mChooserTarget.getIntentExtras());
619             activity.startActivityAsCaller(intent, options, true, userId);
620             return true;
621         }
622
623         @Override
624         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
625             throw new RuntimeException("ChooserTargets should be started as caller.");
626         }
627
628         @Override
629         public ResolveInfo getResolveInfo() {
630             return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
631         }
632
633         @Override
634         public CharSequence getDisplayLabel() {
635             return mChooserTarget.getTitle();
636         }
637
638         @Override
639         public CharSequence getExtendedInfo() {
640             return mSourceInfo != null ? mSourceInfo.getExtendedInfo() : null;
641         }
642
643         @Override
644         public Drawable getDisplayIcon() {
645             return mDisplayIcon;
646         }
647
648         @Override
649         public Drawable getBadgeIcon() {
650             return mBadgeIcon;
651         }
652
653         @Override
654         public CharSequence getBadgeContentDescription() {
655             return mBadgeContentDescription;
656         }
657
658         @Override
659         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
660             return new ChooserTargetInfo(this, fillInIntent, flags);
661         }
662
663         @Override
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));
669             }
670             return results;
671         }
672     }
673
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;
679
680         private static final int MAX_SERVICE_TARGETS = 8;
681
682         private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
683         private final List<TargetInfo> mCallerTargets = new ArrayList<>();
684
685         private float mLateFee = 1.f;
686
687         private final BaseChooserTargetComparator mBaseTargetComparator
688                 = new BaseChooserTargetComparator();
689
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);
696
697             if (initialIntents != null) {
698                 final PackageManager pm = getPackageManager();
699                 for (int i = 0; i < initialIntents.length; i++) {
700                     final Intent ii = initialIntents[i];
701                     if (ii == null) {
702                         continue;
703                     }
704                     final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
705                     if (ai == null) {
706                         Log.w(TAG, "No activity found for " + ii);
707                         continue;
708                     }
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;
720                     }
721                     if (userManager.isManagedProfile()) {
722                         ri.noResourceId = true;
723                         ri.icon = 0;
724                     }
725                     mCallerTargets.add(new DisplayResolveInfo(ii, ri,
726                             ri.loadLabel(pm), null, ii));
727                 }
728             }
729         }
730
731         @Override
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();
736         }
737
738         @Override
739         public View onCreateView(ViewGroup parent) {
740             return mInflater.inflate(
741                     com.android.internal.R.layout.resolve_grid_item, parent, false);
742         }
743
744         @Override
745         public void onListRebuilt() {
746             if (mServiceTargets != null) {
747                 pruneServiceTargets();
748             }
749         }
750
751         @Override
752         public boolean shouldGetResolvedFilter() {
753             return true;
754         }
755
756         @Override
757         public int getCount() {
758             return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
759         }
760
761         @Override
762         public int getUnfilteredCount() {
763             return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
764         }
765
766         public int getCallerTargetCount() {
767             return mCallerTargets.size();
768         }
769
770         public int getServiceTargetCount() {
771             return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
772         }
773
774         public int getStandardTargetCount() {
775             return super.getCount();
776         }
777
778         public int getPositionTargetType(int position) {
779             int offset = 0;
780
781             final int callerTargetCount = getCallerTargetCount();
782             if (position < callerTargetCount) {
783                 return TARGET_CALLER;
784             }
785             offset += callerTargetCount;
786
787             final int serviceTargetCount = getServiceTargetCount();
788             if (position - offset < serviceTargetCount) {
789                 return TARGET_SERVICE;
790             }
791             offset += serviceTargetCount;
792
793             final int standardTargetCount = super.getCount();
794             if (position - offset < standardTargetCount) {
795                 return TARGET_STANDARD;
796             }
797
798             return TARGET_BAD;
799         }
800
801         @Override
802         public TargetInfo getItem(int position) {
803             return targetInfoForPosition(position, true);
804         }
805
806         @Override
807         public TargetInfo targetInfoForPosition(int position, boolean filtered) {
808             int offset = 0;
809
810             final int callerTargetCount = getCallerTargetCount();
811             if (position < callerTargetCount) {
812                 return mCallerTargets.get(position);
813             }
814             offset += callerTargetCount;
815
816             final int serviceTargetCount = getServiceTargetCount();
817             if (position - offset < serviceTargetCount) {
818                 return mServiceTargets.get(position - offset);
819             }
820             offset += serviceTargetCount;
821
822             return filtered ? super.getItem(position - offset)
823                     : getDisplayInfoAt(position - offset);
824         }
825
826         public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
827             if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
828                     + " targets");
829             final float parentScore = getScore(origTarget);
830             Collections.sort(targets, mBaseTargetComparator);
831             float lastScore = 0;
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;
841                 }
842                 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
843
844                 if (DEBUG) {
845                     Log.d(TAG, " => " + target.toString() + " score=" + targetScore
846                             + " base=" + target.getScore()
847                             + " lastScore=" + lastScore
848                             + " parentScore=" + parentScore
849                             + " lateFee=" + mLateFee);
850                 }
851
852                 lastScore = targetScore;
853             }
854
855             mLateFee *= 0.95f;
856
857             notifyDataSetChanged();
858         }
859
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);
866                     return;
867                 }
868             }
869             mServiceTargets.add(chooserTargetInfo);
870         }
871
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);
879                 }
880             }
881         }
882     }
883
884     static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
885         @Override
886         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
887             // Descending order
888             return (int) Math.signum(lhs.getScore() - rhs.getScore());
889         }
890     }
891
892     class ChooserRowAdapter extends BaseAdapter {
893         private ChooserListAdapter mChooserListAdapter;
894         private final LayoutInflater mLayoutInflater;
895         private final int mColumnCount = 4;
896
897         public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
898             mChooserListAdapter = wrappedAdapter;
899             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
900
901             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
902                 @Override
903                 public void onChanged() {
904                     super.onChanged();
905                     notifyDataSetChanged();
906                 }
907
908                 @Override
909                 public void onInvalidated() {
910                     super.onInvalidated();
911                     notifyDataSetInvalidated();
912                 }
913             });
914         }
915
916         @Override
917         public int getCount() {
918             return (int) (
919                     Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
920                     + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
921                     + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
922             );
923         }
924
925         @Override
926         public Object getItem(int position) {
927             // We have nothing useful to return here.
928             return position;
929         }
930
931         @Override
932         public long getItemId(int position) {
933             return position;
934         }
935
936         @Override
937         public View getView(int position, View convertView, ViewGroup parent) {
938             final View[] holder;
939             if (convertView == null) {
940                 holder = createViewHolder(parent);
941             } else {
942                 holder = (View[]) convertView.getTag();
943             }
944             bindViewHolder(position, holder);
945
946             // We keep the actual list item view as the last item in the holder array
947             return holder[mColumnCount];
948         }
949
950         View[] createViewHolder(ViewGroup parent) {
951             final View[] holder = new View[mColumnCount + 1];
952
953             final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
954                     parent, false);
955             for (int i = 0; i < mColumnCount; i++) {
956                 holder[i] = mChooserListAdapter.createView(row);
957                 row.addView(holder[i]);
958             }
959             row.setTag(holder);
960             holder[mColumnCount] = row;
961             return holder;
962         }
963
964         void bindViewHolder(int rowPosition, View[] holder) {
965             final int start = getFirstRowPosition(rowPosition);
966             final int startType = mChooserListAdapter.getPositionTargetType(start);
967
968             int end = start + mColumnCount - 1;
969             while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
970                 end--;
971             }
972
973             final ViewGroup row = (ViewGroup) holder[mColumnCount];
974
975             if (startType == ChooserListAdapter.TARGET_SERVICE) {
976                 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
977             } else {
978                 row.setBackground(null);
979             }
980
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() {
988                         @Override
989                         public void onClick(View v) {
990                             startSelected(itemIndex, false, true);
991                         }
992                     });
993                     v.setOnLongClickListener(new OnLongClickListener() {
994                         @Override
995                         public boolean onLongClick(View v) {
996                             showAppDetails(
997                                     mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
998                             return true;
999                         }
1000                     });
1001                 } else {
1002                     v.setVisibility(View.GONE);
1003                 }
1004             }
1005         }
1006
1007         int getFirstRowPosition(int row) {
1008             final int callerCount = mChooserListAdapter.getCallerTargetCount();
1009             final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1010
1011             if (row < callerRows) {
1012                 return row * mColumnCount;
1013             }
1014
1015             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1016             final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1017
1018             if (row < callerRows + serviceRows) {
1019                 return callerCount + (row - callerRows) * mColumnCount;
1020             }
1021
1022             return callerCount + serviceCount
1023                     + (row - callerRows - serviceRows) * mColumnCount;
1024         }
1025     }
1026
1027     class ChooserTargetServiceConnection implements ServiceConnection {
1028         private final DisplayResolveInfo mOriginalTarget;
1029
1030         private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1031             @Override
1032             public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1033                 filterServiceTargets(mOriginalTarget.getResolveInfo().activityInfo.packageName,
1034                         targets);
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);
1040             }
1041         };
1042
1043         public ChooserTargetServiceConnection(DisplayResolveInfo dri) {
1044             mOriginalTarget = dri;
1045         }
1046
1047         @Override
1048         public void onServiceConnected(ComponentName name, IBinder service) {
1049             if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1050             final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1051             try {
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);
1058             }
1059         }
1060
1061         @Override
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();
1069             }
1070         }
1071
1072         @Override
1073         public String toString() {
1074             return mOriginalTarget.getResolveInfo().activityInfo.toString();
1075         }
1076     }
1077
1078     static class ServiceResultInfo {
1079         public final DisplayResolveInfo originalTarget;
1080         public final List<ChooserTarget> resultTargets;
1081         public final ChooserTargetServiceConnection connection;
1082
1083         public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1084                 ChooserTargetServiceConnection c) {
1085             originalTarget = ot;
1086             resultTargets = rt;
1087             connection = c;
1088         }
1089     }
1090
1091     static class RefinementResultReceiver extends ResultReceiver {
1092         private ChooserActivity mChooserActivity;
1093         private TargetInfo mSelectedTarget;
1094
1095         public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1096                 Handler handler) {
1097             super(handler);
1098             mChooserActivity = host;
1099             mSelectedTarget = target;
1100         }
1101
1102         @Override
1103         protected void onReceiveResult(int resultCode, Bundle resultData) {
1104             if (mChooserActivity == null) {
1105                 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1106                 return;
1107             }
1108             if (resultData == null) {
1109                 Log.e(TAG, "RefinementResultReceiver received null resultData");
1110                 return;
1111             }
1112
1113             switch (resultCode) {
1114                 case RESULT_CANCELED:
1115                     mChooserActivity.onRefinementCanceled();
1116                     break;
1117                 case RESULT_OK:
1118                     Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1119                     if (intentParcelable instanceof Intent) {
1120                         mChooserActivity.onRefinementResult(mSelectedTarget,
1121                                 (Intent) intentParcelable);
1122                     } else {
1123                         Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1124                                 + " in resultData with key Intent.EXTRA_INTENT");
1125                     }
1126                     break;
1127                 default:
1128                     Log.w(TAG, "Unknown result code " + resultCode
1129                             + " sent to RefinementResultReceiver");
1130                     break;
1131             }
1132         }
1133
1134         public void destroy() {
1135             mChooserActivity = null;
1136             mSelectedTarget = null;
1137         }
1138     }
1139 }