OSDN Git Service

Relax auto-launch checks for GET_CONTENT.
[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.provider.DocumentsContract;
44 import android.service.chooser.ChooserTarget;
45 import android.service.chooser.ChooserTargetService;
46 import android.service.chooser.IChooserTargetResult;
47 import android.service.chooser.IChooserTargetService;
48 import android.text.TextUtils;
49 import android.util.Log;
50 import android.util.Slog;
51 import android.view.LayoutInflater;
52 import android.view.View;
53 import android.view.View.OnClickListener;
54 import android.view.View.OnLongClickListener;
55 import android.view.ViewGroup;
56 import android.widget.AbsListView;
57 import android.widget.BaseAdapter;
58 import android.widget.ListView;
59 import com.android.internal.R;
60 import com.android.internal.logging.MetricsLogger;
61
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.Comparator;
65 import java.util.List;
66
67 public class ChooserActivity extends ResolverActivity {
68     private static final String TAG = "ChooserActivity";
69
70     private static final boolean DEBUG = false;
71
72     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
73     private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
74
75     private Bundle mReplacementExtras;
76     private IntentSender mChosenComponentSender;
77     private IntentSender mRefinementIntentSender;
78     private RefinementResultReceiver mRefinementResultReceiver;
79
80     private Intent mReferrerFillInIntent;
81
82     private ChooserListAdapter mChooserListAdapter;
83
84     private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
85
86     private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
87     private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
88
89     private final Handler mChooserHandler = new Handler() {
90         @Override
91         public void handleMessage(Message msg) {
92             switch (msg.what) {
93                 case CHOOSER_TARGET_SERVICE_RESULT:
94                     if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
95                     if (isDestroyed()) break;
96                     final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
97                     if (!mServiceConnections.contains(sri.connection)) {
98                         Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
99                                 + " returned after being removed from active connections."
100                                 + " Have you considered returning results faster?");
101                         break;
102                     }
103                     if (sri.resultTargets != null) {
104                         mChooserListAdapter.addServiceResults(sri.originalTarget,
105                                 sri.resultTargets);
106                     }
107                     unbindService(sri.connection);
108                     sri.connection.destroy();
109                     mServiceConnections.remove(sri.connection);
110                     if (mServiceConnections.isEmpty()) {
111                         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
112                         sendVoiceChoicesIfNeeded();
113                     }
114                     break;
115
116                 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
117                     if (DEBUG) {
118                         Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
119                     }
120                     unbindRemainingServices();
121                     sendVoiceChoicesIfNeeded();
122                     break;
123
124                 default:
125                     super.handleMessage(msg);
126             }
127         }
128     };
129
130     @Override
131     protected void onCreate(Bundle savedInstanceState) {
132         Intent intent = getIntent();
133         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
134         if (!(targetParcelable instanceof Intent)) {
135             Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
136             finish();
137             super.onCreate(null);
138             return;
139         }
140         Intent target = (Intent) targetParcelable;
141         if (target != null) {
142             modifyTargetIntent(target);
143         }
144         Parcelable[] targetsParcelable
145                 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
146         if (targetsParcelable != null) {
147             final boolean offset = target == null;
148             Intent[] additionalTargets =
149                     new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
150             for (int i = 0; i < targetsParcelable.length; i++) {
151                 if (!(targetsParcelable[i] instanceof Intent)) {
152                     Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
153                             + targetsParcelable[i]);
154                     finish();
155                     super.onCreate(null);
156                     return;
157                 }
158                 final Intent additionalTarget = (Intent) targetsParcelable[i];
159                 if (i == 0 && target == null) {
160                     target = additionalTarget;
161                     modifyTargetIntent(target);
162                 } else {
163                     additionalTargets[offset ? i - 1 : i] = additionalTarget;
164                     modifyTargetIntent(additionalTarget);
165                 }
166             }
167             setAdditionalTargets(additionalTargets);
168         }
169
170         mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
171         CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
172         int defaultTitleRes = 0;
173         if (title == null) {
174             defaultTitleRes = com.android.internal.R.string.chooseActivity;
175         }
176         Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
177         Intent[] initialIntents = null;
178         if (pa != null) {
179             initialIntents = new Intent[pa.length];
180             for (int i=0; i<pa.length; i++) {
181                 if (!(pa[i] instanceof Intent)) {
182                     Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
183                     finish();
184                     super.onCreate(null);
185                     return;
186                 }
187                 final Intent in = (Intent) pa[i];
188                 modifyTargetIntent(in);
189                 initialIntents[i] = in;
190             }
191         }
192
193         mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
194
195         mChosenComponentSender = intent.getParcelableExtra(
196                 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
197         mRefinementIntentSender = intent.getParcelableExtra(
198                 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
199         setSafeForwardingMode(true);
200         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
201                 null, false);
202
203         MetricsLogger.action(this, MetricsLogger.ACTION_ACTIVITY_CHOOSER_SHOWN);
204     }
205
206     @Override
207     protected void onDestroy() {
208         super.onDestroy();
209         if (mRefinementResultReceiver != null) {
210             mRefinementResultReceiver.destroy();
211             mRefinementResultReceiver = null;
212         }
213         unbindRemainingServices();
214         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
215     }
216
217     @Override
218     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
219         Intent result = defIntent;
220         if (mReplacementExtras != null) {
221             final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
222             if (replExtras != null) {
223                 result = new Intent(defIntent);
224                 result.putExtras(replExtras);
225             }
226         }
227         if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER)
228                 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
229             result = Intent.createChooser(result,
230                     getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
231         }
232         return result;
233     }
234
235     @Override
236     void onActivityStarted(TargetInfo cti) {
237         if (mChosenComponentSender != null) {
238             final ComponentName target = cti.getResolvedComponentName();
239             if (target != null) {
240                 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
241                 try {
242                     mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
243                 } catch (IntentSender.SendIntentException e) {
244                     Slog.e(TAG, "Unable to launch supplied IntentSender to report "
245                             + "the chosen component: " + e);
246                 }
247             }
248         }
249     }
250
251     @Override
252     void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
253             boolean alwaysUseOption) {
254         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
255         mChooserListAdapter = (ChooserListAdapter) adapter;
256         adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
257         if (listView != null) {
258             listView.setItemsCanFocus(true);
259         }
260     }
261
262     @Override
263     int getLayoutResource() {
264         return R.layout.chooser_grid;
265     }
266
267     @Override
268     boolean shouldGetActivityMetadata() {
269         return true;
270     }
271
272     @Override
273     boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
274         final Intent intent = target.getResolvedIntent();
275         final ResolveInfo resolve = target.getResolveInfo();
276
277         // When GET_CONTENT is handled by the DocumentsUI system component,
278         // we're okay automatically launching it, since it offers it's own
279         // intent disambiguation UI.
280         if (intent != null && Intent.ACTION_GET_CONTENT.equals(intent.getAction())
281                 && resolve != null && resolve.priority > 0
282                 && resolve.activityInfo != null && DocumentsContract.PACKAGE_DOCUMENTS_UI
283                         .equals(resolve.activityInfo.packageName)) {
284             return true;
285         }
286
287         return false;
288     }
289
290     private void modifyTargetIntent(Intent in) {
291         final String action = in.getAction();
292         if (Intent.ACTION_SEND.equals(action) ||
293                 Intent.ACTION_SEND_MULTIPLE.equals(action)) {
294             in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
295                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
296         }
297     }
298
299     @Override
300     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
301         if (mRefinementIntentSender != null) {
302             final Intent fillIn = new Intent();
303             final List<Intent> sourceIntents = target.getAllSourceIntents();
304             if (!sourceIntents.isEmpty()) {
305                 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
306                 if (sourceIntents.size() > 1) {
307                     final Intent[] alts = new Intent[sourceIntents.size() - 1];
308                     for (int i = 1, N = sourceIntents.size(); i < N; i++) {
309                         alts[i - 1] = sourceIntents.get(i);
310                     }
311                     fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
312                 }
313                 if (mRefinementResultReceiver != null) {
314                     mRefinementResultReceiver.destroy();
315                 }
316                 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
317                 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
318                         mRefinementResultReceiver);
319                 try {
320                     mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
321                     return false;
322                 } catch (SendIntentException e) {
323                     Log.e(TAG, "Refinement IntentSender failed to send", e);
324                 }
325             }
326         }
327         return super.onTargetSelected(target, alwaysCheck);
328     }
329
330     @Override
331     void startSelected(int which, boolean always, boolean filtered) {
332         super.startSelected(which, always, filtered);
333
334         if (mChooserListAdapter != null) {
335             // Log the index of which type of target the user picked.
336             // Lower values mean the ranking was better.
337             int cat = 0;
338             int value = which;
339             switch (mChooserListAdapter.getPositionTargetType(which)) {
340                 case ChooserListAdapter.TARGET_CALLER:
341                     cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
342                     break;
343                 case ChooserListAdapter.TARGET_SERVICE:
344                     cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
345                     value -= mChooserListAdapter.getCallerTargetCount();
346                     break;
347                 case ChooserListAdapter.TARGET_STANDARD:
348                     cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
349                     value -= mChooserListAdapter.getCallerTargetCount()
350                             + mChooserListAdapter.getServiceTargetCount();
351                     break;
352             }
353
354             if (cat != 0) {
355                 MetricsLogger.action(this, cat, value);
356             }
357         }
358     }
359
360     void queryTargetServices(ChooserListAdapter adapter) {
361         final PackageManager pm = getPackageManager();
362         int targetsToQuery = 0;
363         for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
364             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
365             final ActivityInfo ai = dri.getResolveInfo().activityInfo;
366             final Bundle md = ai.metaData;
367             final String serviceName = md != null ? convertServiceName(ai.packageName,
368                     md.getString(ChooserTargetService.META_DATA_NAME)) : null;
369             if (serviceName != null) {
370                 final ComponentName serviceComponent = new ComponentName(
371                         ai.packageName, serviceName);
372                 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
373                         .setComponent(serviceComponent);
374
375                 if (DEBUG) {
376                     Log.d(TAG, "queryTargets found target with service " + serviceComponent);
377                 }
378
379                 try {
380                     final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
381                     if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
382                         Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
383                                 + " permission " + ChooserTargetService.BIND_PERMISSION
384                                 + " - this service will not be queried for ChooserTargets."
385                                 + " add android:permission=\""
386                                 + ChooserTargetService.BIND_PERMISSION + "\""
387                                 + " to the <service> tag for " + serviceComponent
388                                 + " in the manifest.");
389                         continue;
390                     }
391                 } catch (NameNotFoundException e) {
392                     Log.e(TAG, "Could not look up service " + serviceComponent, e);
393                     continue;
394                 }
395
396                 final ChooserTargetServiceConnection conn =
397                         new ChooserTargetServiceConnection(this, dri);
398                 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
399                         UserHandle.CURRENT)) {
400                     if (DEBUG) {
401                         Log.d(TAG, "Binding service connection for target " + dri
402                                 + " intent " + serviceIntent);
403                     }
404                     mServiceConnections.add(conn);
405                     targetsToQuery++;
406                 }
407             }
408             if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
409                 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
410                         + QUERY_TARGET_SERVICE_LIMIT);
411                 break;
412             }
413         }
414
415         if (!mServiceConnections.isEmpty()) {
416             if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for "
417                     + WATCHDOG_TIMEOUT_MILLIS + "ms");
418             mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
419                     WATCHDOG_TIMEOUT_MILLIS);
420         } else {
421             sendVoiceChoicesIfNeeded();
422         }
423     }
424
425     private String convertServiceName(String packageName, String serviceName) {
426         if (TextUtils.isEmpty(serviceName)) {
427             return null;
428         }
429
430         final String fullName;
431         if (serviceName.startsWith(".")) {
432             // Relative to the app package. Prepend the app package name.
433             fullName = packageName + serviceName;
434         } else if (serviceName.indexOf('.') >= 0) {
435             // Fully qualified package name.
436             fullName = serviceName;
437         } else {
438             fullName = null;
439         }
440         return fullName;
441     }
442
443     void unbindRemainingServices() {
444         if (DEBUG) {
445             Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
446         }
447         for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
448             final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
449             if (DEBUG) Log.d(TAG, "unbinding " + conn);
450             unbindService(conn);
451             conn.destroy();
452         }
453         mServiceConnections.clear();
454         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
455     }
456
457     void onSetupVoiceInteraction() {
458         // Do nothing. We'll send the voice stuff ourselves.
459     }
460
461     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
462         if (mRefinementResultReceiver != null) {
463             mRefinementResultReceiver.destroy();
464             mRefinementResultReceiver = null;
465         }
466
467         if (selectedTarget == null) {
468             Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
469         } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
470             Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
471                     + " cannot match refined source intent " + matchingIntent);
472         } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) {
473             finish();
474             return;
475         }
476         onRefinementCanceled();
477     }
478
479     void onRefinementCanceled() {
480         if (mRefinementResultReceiver != null) {
481             mRefinementResultReceiver.destroy();
482             mRefinementResultReceiver = null;
483         }
484         finish();
485     }
486
487     boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
488         final List<Intent> targetIntents = target.getAllSourceIntents();
489         for (int i = 0, N = targetIntents.size(); i < N; i++) {
490             final Intent targetIntent = targetIntents.get(i);
491             if (targetIntent.filterEquals(matchingIntent)) {
492                 return true;
493             }
494         }
495         return false;
496     }
497
498     void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
499         if (targets == null) {
500             return;
501         }
502
503         final PackageManager pm = getPackageManager();
504         for (int i = targets.size() - 1; i >= 0; i--) {
505             final ChooserTarget target = targets.get(i);
506             final ComponentName targetName = target.getComponentName();
507             if (packageName != null && packageName.equals(targetName.getPackageName())) {
508                 // Anything from the original target's package is fine.
509                 continue;
510             }
511
512             boolean remove;
513             try {
514                 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
515                 remove = !ai.exported || ai.permission != null;
516             } catch (NameNotFoundException e) {
517                 Log.e(TAG, "Target " + target + " returned by " + packageName
518                         + " component not found");
519                 remove = true;
520             }
521
522             if (remove) {
523                 targets.remove(i);
524             }
525         }
526     }
527
528     @Override
529     ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
530             Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
531             boolean filterLastUsed) {
532         final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
533                 initialIntents, rList, launchedFromUid, filterLastUsed);
534         if (DEBUG) Log.d(TAG, "Adapter created; querying services");
535         queryTargetServices(adapter);
536         return adapter;
537     }
538
539     final class ChooserTargetInfo implements TargetInfo {
540         private final DisplayResolveInfo mSourceInfo;
541         private final ResolveInfo mBackupResolveInfo;
542         private final ChooserTarget mChooserTarget;
543         private Drawable mBadgeIcon = null;
544         private CharSequence mBadgeContentDescription;
545         private Drawable mDisplayIcon;
546         private final Intent mFillInIntent;
547         private final int mFillInFlags;
548         private final float mModifiedScore;
549
550         public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
551                 float modifiedScore) {
552             mSourceInfo = sourceInfo;
553             mChooserTarget = chooserTarget;
554             mModifiedScore = modifiedScore;
555             if (sourceInfo != null) {
556                 final ResolveInfo ri = sourceInfo.getResolveInfo();
557                 if (ri != null) {
558                     final ActivityInfo ai = ri.activityInfo;
559                     if (ai != null && ai.applicationInfo != null) {
560                         final PackageManager pm = getPackageManager();
561                         mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
562                         mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
563                     }
564                 }
565             }
566             final Icon icon = chooserTarget.getIcon();
567             // TODO do this in the background
568             mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
569
570             if (sourceInfo != null) {
571                 mBackupResolveInfo = null;
572             } else {
573                 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
574             }
575
576             mFillInIntent = null;
577             mFillInFlags = 0;
578         }
579
580         private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
581             mSourceInfo = other.mSourceInfo;
582             mBackupResolveInfo = other.mBackupResolveInfo;
583             mChooserTarget = other.mChooserTarget;
584             mBadgeIcon = other.mBadgeIcon;
585             mBadgeContentDescription = other.mBadgeContentDescription;
586             mDisplayIcon = other.mDisplayIcon;
587             mFillInIntent = fillInIntent;
588             mFillInFlags = flags;
589             mModifiedScore = other.mModifiedScore;
590         }
591
592         public float getModifiedScore() {
593             return mModifiedScore;
594         }
595
596         @Override
597         public Intent getResolvedIntent() {
598             if (mSourceInfo != null) {
599                 return mSourceInfo.getResolvedIntent();
600             }
601             return getTargetIntent();
602         }
603
604         @Override
605         public ComponentName getResolvedComponentName() {
606             if (mSourceInfo != null) {
607                 return mSourceInfo.getResolvedComponentName();
608             } else if (mBackupResolveInfo != null) {
609                 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
610                         mBackupResolveInfo.activityInfo.name);
611             }
612             return null;
613         }
614
615         private Intent getBaseIntentToSend() {
616             Intent result = mSourceInfo != null
617                     ? mSourceInfo.getResolvedIntent() : getTargetIntent();
618             if (result == null) {
619                 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
620             } else {
621                 result = new Intent(result);
622                 if (mFillInIntent != null) {
623                     result.fillIn(mFillInIntent, mFillInFlags);
624                 }
625                 result.fillIn(mReferrerFillInIntent, 0);
626             }
627             return result;
628         }
629
630         @Override
631         public boolean start(Activity activity, Bundle options) {
632             throw new RuntimeException("ChooserTargets should be started as caller.");
633         }
634
635         @Override
636         public boolean startAsCaller(Activity activity, Bundle options, int userId) {
637             final Intent intent = getBaseIntentToSend();
638             if (intent == null) {
639                 return false;
640             }
641             intent.setComponent(mChooserTarget.getComponentName());
642             intent.putExtras(mChooserTarget.getIntentExtras());
643             activity.startActivityAsCaller(intent, options, true, userId);
644             return true;
645         }
646
647         @Override
648         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
649             throw new RuntimeException("ChooserTargets should be started as caller.");
650         }
651
652         @Override
653         public ResolveInfo getResolveInfo() {
654             return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
655         }
656
657         @Override
658         public CharSequence getDisplayLabel() {
659             return mChooserTarget.getTitle();
660         }
661
662         @Override
663         public CharSequence getExtendedInfo() {
664             // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
665             return null;
666         }
667
668         @Override
669         public Drawable getDisplayIcon() {
670             return mDisplayIcon;
671         }
672
673         @Override
674         public Drawable getBadgeIcon() {
675             return mBadgeIcon;
676         }
677
678         @Override
679         public CharSequence getBadgeContentDescription() {
680             return mBadgeContentDescription;
681         }
682
683         @Override
684         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
685             return new ChooserTargetInfo(this, fillInIntent, flags);
686         }
687
688         @Override
689         public List<Intent> getAllSourceIntents() {
690             final List<Intent> results = new ArrayList<>();
691             if (mSourceInfo != null) {
692                 // We only queried the service for the first one in our sourceinfo.
693                 results.add(mSourceInfo.getAllSourceIntents().get(0));
694             }
695             return results;
696         }
697     }
698
699     public class ChooserListAdapter extends ResolveListAdapter {
700         public static final int TARGET_BAD = -1;
701         public static final int TARGET_CALLER = 0;
702         public static final int TARGET_SERVICE = 1;
703         public static final int TARGET_STANDARD = 2;
704
705         private static final int MAX_SERVICE_TARGETS = 8;
706
707         private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
708         private final List<TargetInfo> mCallerTargets = new ArrayList<>();
709
710         private float mLateFee = 1.f;
711
712         private final BaseChooserTargetComparator mBaseTargetComparator
713                 = new BaseChooserTargetComparator();
714
715         public ChooserListAdapter(Context context, List<Intent> payloadIntents,
716                 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
717                 boolean filterLastUsed) {
718             // Don't send the initial intents through the shared ResolverActivity path,
719             // we want to separate them into a different section.
720             super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed);
721
722             if (initialIntents != null) {
723                 final PackageManager pm = getPackageManager();
724                 for (int i = 0; i < initialIntents.length; i++) {
725                     final Intent ii = initialIntents[i];
726                     if (ii == null) {
727                         continue;
728                     }
729                     final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
730                     if (ai == null) {
731                         Log.w(TAG, "No activity found for " + ii);
732                         continue;
733                     }
734                     ResolveInfo ri = new ResolveInfo();
735                     ri.activityInfo = ai;
736                     UserManager userManager =
737                             (UserManager) getSystemService(Context.USER_SERVICE);
738                     if (ii instanceof LabeledIntent) {
739                         LabeledIntent li = (LabeledIntent)ii;
740                         ri.resolvePackageName = li.getSourcePackage();
741                         ri.labelRes = li.getLabelResource();
742                         ri.nonLocalizedLabel = li.getNonLocalizedLabel();
743                         ri.icon = li.getIconResource();
744                         ri.iconResourceId = ri.icon;
745                     }
746                     if (userManager.isManagedProfile()) {
747                         ri.noResourceId = true;
748                         ri.icon = 0;
749                     }
750                     mCallerTargets.add(new DisplayResolveInfo(ii, ri,
751                             ri.loadLabel(pm), null, ii));
752                 }
753             }
754         }
755
756         @Override
757         public boolean showsExtendedInfo(TargetInfo info) {
758             // We have badges so we don't need this text shown.
759             return false;
760         }
761
762         @Override
763         public View onCreateView(ViewGroup parent) {
764             return mInflater.inflate(
765                     com.android.internal.R.layout.resolve_grid_item, parent, false);
766         }
767
768         @Override
769         public void onListRebuilt() {
770             if (mServiceTargets != null) {
771                 pruneServiceTargets();
772             }
773         }
774
775         @Override
776         public boolean shouldGetResolvedFilter() {
777             return true;
778         }
779
780         @Override
781         public int getCount() {
782             return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
783         }
784
785         @Override
786         public int getUnfilteredCount() {
787             return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
788         }
789
790         public int getCallerTargetCount() {
791             return mCallerTargets.size();
792         }
793
794         public int getServiceTargetCount() {
795             return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
796         }
797
798         public int getStandardTargetCount() {
799             return super.getCount();
800         }
801
802         public int getPositionTargetType(int position) {
803             int offset = 0;
804
805             final int callerTargetCount = getCallerTargetCount();
806             if (position < callerTargetCount) {
807                 return TARGET_CALLER;
808             }
809             offset += callerTargetCount;
810
811             final int serviceTargetCount = getServiceTargetCount();
812             if (position - offset < serviceTargetCount) {
813                 return TARGET_SERVICE;
814             }
815             offset += serviceTargetCount;
816
817             final int standardTargetCount = super.getCount();
818             if (position - offset < standardTargetCount) {
819                 return TARGET_STANDARD;
820             }
821
822             return TARGET_BAD;
823         }
824
825         @Override
826         public TargetInfo getItem(int position) {
827             return targetInfoForPosition(position, true);
828         }
829
830         @Override
831         public TargetInfo targetInfoForPosition(int position, boolean filtered) {
832             int offset = 0;
833
834             final int callerTargetCount = getCallerTargetCount();
835             if (position < callerTargetCount) {
836                 return mCallerTargets.get(position);
837             }
838             offset += callerTargetCount;
839
840             final int serviceTargetCount = getServiceTargetCount();
841             if (position - offset < serviceTargetCount) {
842                 return mServiceTargets.get(position - offset);
843             }
844             offset += serviceTargetCount;
845
846             return filtered ? super.getItem(position - offset)
847                     : getDisplayInfoAt(position - offset);
848         }
849
850         public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
851             if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
852                     + " targets");
853             final float parentScore = getScore(origTarget);
854             Collections.sort(targets, mBaseTargetComparator);
855             float lastScore = 0;
856             for (int i = 0, N = targets.size(); i < N; i++) {
857                 final ChooserTarget target = targets.get(i);
858                 float targetScore = target.getScore();
859                 targetScore *= parentScore;
860                 targetScore *= mLateFee;
861                 if (i > 0 && targetScore >= lastScore) {
862                     // Apply a decay so that the top app can't crowd out everything else.
863                     // This incents ChooserTargetServices to define what's truly better.
864                     targetScore = lastScore * 0.95f;
865                 }
866                 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
867
868                 if (DEBUG) {
869                     Log.d(TAG, " => " + target.toString() + " score=" + targetScore
870                             + " base=" + target.getScore()
871                             + " lastScore=" + lastScore
872                             + " parentScore=" + parentScore
873                             + " lateFee=" + mLateFee);
874                 }
875
876                 lastScore = targetScore;
877             }
878
879             mLateFee *= 0.95f;
880
881             notifyDataSetChanged();
882         }
883
884         private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
885             final float newScore = chooserTargetInfo.getModifiedScore();
886             for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
887                 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
888                 if (newScore > serviceTarget.getModifiedScore()) {
889                     mServiceTargets.add(i, chooserTargetInfo);
890                     return;
891                 }
892             }
893             mServiceTargets.add(chooserTargetInfo);
894         }
895
896         private void pruneServiceTargets() {
897             if (DEBUG) Log.d(TAG, "pruneServiceTargets");
898             for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
899                 final ChooserTargetInfo cti = mServiceTargets.get(i);
900                 if (!hasResolvedTarget(cti.getResolveInfo())) {
901                     if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
902                     mServiceTargets.remove(i);
903                 }
904             }
905         }
906     }
907
908     static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
909         @Override
910         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
911             // Descending order
912             return (int) Math.signum(lhs.getScore() - rhs.getScore());
913         }
914     }
915
916     class ChooserRowAdapter extends BaseAdapter {
917         private ChooserListAdapter mChooserListAdapter;
918         private final LayoutInflater mLayoutInflater;
919         private final int mColumnCount = 4;
920
921         public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
922             mChooserListAdapter = wrappedAdapter;
923             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
924
925             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
926                 @Override
927                 public void onChanged() {
928                     super.onChanged();
929                     notifyDataSetChanged();
930                 }
931
932                 @Override
933                 public void onInvalidated() {
934                     super.onInvalidated();
935                     notifyDataSetInvalidated();
936                 }
937             });
938         }
939
940         @Override
941         public int getCount() {
942             return (int) (
943                     Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
944                     + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
945                     + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
946             );
947         }
948
949         @Override
950         public Object getItem(int position) {
951             // We have nothing useful to return here.
952             return position;
953         }
954
955         @Override
956         public long getItemId(int position) {
957             return position;
958         }
959
960         @Override
961         public View getView(int position, View convertView, ViewGroup parent) {
962             final View[] holder;
963             if (convertView == null) {
964                 holder = createViewHolder(parent);
965             } else {
966                 holder = (View[]) convertView.getTag();
967             }
968             bindViewHolder(position, holder);
969
970             // We keep the actual list item view as the last item in the holder array
971             return holder[mColumnCount];
972         }
973
974         View[] createViewHolder(ViewGroup parent) {
975             final View[] holder = new View[mColumnCount + 1];
976
977             final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
978                     parent, false);
979             for (int i = 0; i < mColumnCount; i++) {
980                 holder[i] = mChooserListAdapter.createView(row);
981                 row.addView(holder[i]);
982             }
983             row.setTag(holder);
984             holder[mColumnCount] = row;
985             return holder;
986         }
987
988         void bindViewHolder(int rowPosition, View[] holder) {
989             final int start = getFirstRowPosition(rowPosition);
990             final int startType = mChooserListAdapter.getPositionTargetType(start);
991
992             int end = start + mColumnCount - 1;
993             while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
994                 end--;
995             }
996
997             final ViewGroup row = (ViewGroup) holder[mColumnCount];
998
999             if (startType == ChooserListAdapter.TARGET_SERVICE) {
1000                 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
1001             } else {
1002                 row.setBackground(null);
1003             }
1004
1005             for (int i = 0; i < mColumnCount; i++) {
1006                 final View v = holder[i];
1007                 if (start + i <= end) {
1008                     v.setVisibility(View.VISIBLE);
1009                     final int itemIndex = start + i;
1010                     mChooserListAdapter.bindView(itemIndex, v);
1011                     v.setOnClickListener(new OnClickListener() {
1012                         @Override
1013                         public void onClick(View v) {
1014                             startSelected(itemIndex, false, true);
1015                         }
1016                     });
1017                     v.setOnLongClickListener(new OnLongClickListener() {
1018                         @Override
1019                         public boolean onLongClick(View v) {
1020                             showAppDetails(
1021                                     mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
1022                             return true;
1023                         }
1024                     });
1025                 } else {
1026                     v.setVisibility(View.GONE);
1027                 }
1028             }
1029         }
1030
1031         int getFirstRowPosition(int row) {
1032             final int callerCount = mChooserListAdapter.getCallerTargetCount();
1033             final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1034
1035             if (row < callerRows) {
1036                 return row * mColumnCount;
1037             }
1038
1039             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1040             final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1041
1042             if (row < callerRows + serviceRows) {
1043                 return callerCount + (row - callerRows) * mColumnCount;
1044             }
1045
1046             return callerCount + serviceCount
1047                     + (row - callerRows - serviceRows) * mColumnCount;
1048         }
1049     }
1050
1051     static class ChooserTargetServiceConnection implements ServiceConnection {
1052         private final DisplayResolveInfo mOriginalTarget;
1053         private ComponentName mConnectedComponent;
1054         private ChooserActivity mChooserActivity;
1055         private final Object mLock = new Object();
1056
1057         private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1058             @Override
1059             public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1060                 synchronized (mLock) {
1061                     if (mChooserActivity == null) {
1062                         Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
1063                                 + mConnectedComponent + "; ignoring...");
1064                         return;
1065                     }
1066                     mChooserActivity.filterServiceTargets(
1067                             mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
1068                     final Message msg = Message.obtain();
1069                     msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1070                     msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1071                             ChooserTargetServiceConnection.this);
1072                     mChooserActivity.mChooserHandler.sendMessage(msg);
1073                 }
1074             }
1075         };
1076
1077         public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
1078                 DisplayResolveInfo dri) {
1079             mChooserActivity = chooserActivity;
1080             mOriginalTarget = dri;
1081         }
1082
1083         @Override
1084         public void onServiceConnected(ComponentName name, IBinder service) {
1085             if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1086             synchronized (mLock) {
1087                 if (mChooserActivity == null) {
1088                     Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
1089                     return;
1090                 }
1091
1092                 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1093                 try {
1094                     icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1095                             mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1096                 } catch (RemoteException e) {
1097                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1098                     mChooserActivity.unbindService(this);
1099                     destroy();
1100                     mChooserActivity.mServiceConnections.remove(this);
1101                 }
1102             }
1103         }
1104
1105         @Override
1106         public void onServiceDisconnected(ComponentName name) {
1107             if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1108             synchronized (mLock) {
1109                 if (mChooserActivity == null) {
1110                     Log.e(TAG,
1111                             "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
1112                     return;
1113                 }
1114
1115                 mChooserActivity.unbindService(this);
1116                 destroy();
1117                 mChooserActivity.mServiceConnections.remove(this);
1118                 if (mChooserActivity.mServiceConnections.isEmpty()) {
1119                     mChooserActivity.mChooserHandler.removeMessages(
1120                             CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1121                     mChooserActivity.sendVoiceChoicesIfNeeded();
1122                 }
1123                 mConnectedComponent = null;
1124             }
1125         }
1126
1127         public void destroy() {
1128             synchronized (mLock) {
1129                 mChooserActivity = null;
1130             }
1131         }
1132
1133         @Override
1134         public String toString() {
1135             return "ChooserTargetServiceConnection{service="
1136                     + mConnectedComponent + ", activity="
1137                     + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}";
1138         }
1139     }
1140
1141     static class ServiceResultInfo {
1142         public final DisplayResolveInfo originalTarget;
1143         public final List<ChooserTarget> resultTargets;
1144         public final ChooserTargetServiceConnection connection;
1145
1146         public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1147                 ChooserTargetServiceConnection c) {
1148             originalTarget = ot;
1149             resultTargets = rt;
1150             connection = c;
1151         }
1152     }
1153
1154     static class RefinementResultReceiver extends ResultReceiver {
1155         private ChooserActivity mChooserActivity;
1156         private TargetInfo mSelectedTarget;
1157
1158         public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1159                 Handler handler) {
1160             super(handler);
1161             mChooserActivity = host;
1162             mSelectedTarget = target;
1163         }
1164
1165         @Override
1166         protected void onReceiveResult(int resultCode, Bundle resultData) {
1167             if (mChooserActivity == null) {
1168                 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1169                 return;
1170             }
1171             if (resultData == null) {
1172                 Log.e(TAG, "RefinementResultReceiver received null resultData");
1173                 return;
1174             }
1175
1176             switch (resultCode) {
1177                 case RESULT_CANCELED:
1178                     mChooserActivity.onRefinementCanceled();
1179                     break;
1180                 case RESULT_OK:
1181                     Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1182                     if (intentParcelable instanceof Intent) {
1183                         mChooserActivity.onRefinementResult(mSelectedTarget,
1184                                 (Intent) intentParcelable);
1185                     } else {
1186                         Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1187                                 + " in resultData with key Intent.EXTRA_INTENT");
1188                     }
1189                     break;
1190                 default:
1191                     Log.w(TAG, "Unknown result code " + resultCode
1192                             + " sent to RefinementResultReceiver");
1193                     break;
1194             }
1195         }
1196
1197         public void destroy() {
1198             mChooserActivity = null;
1199             mSelectedTarget = null;
1200         }
1201     }
1202 }