ChooserActivity.java revision a182e45c6851a8db89e8b0900f0812806ff295d4
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
17package com.android.internal.app;
18
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentSender;
24import android.content.IntentSender.SendIntentException;
25import android.content.ServiceConnection;
26import android.content.pm.ActivityInfo;
27import android.content.pm.LabeledIntent;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.content.pm.ResolveInfo;
31import android.database.DataSetObserver;
32import android.graphics.drawable.Drawable;
33import android.graphics.drawable.Icon;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.Message;
38import android.os.Parcelable;
39import android.os.RemoteException;
40import android.os.ResultReceiver;
41import android.os.UserHandle;
42import android.os.UserManager;
43import android.service.chooser.ChooserTarget;
44import android.service.chooser.ChooserTargetService;
45import android.service.chooser.IChooserTargetResult;
46import android.service.chooser.IChooserTargetService;
47import android.text.TextUtils;
48import android.util.Log;
49import android.util.Slog;
50import android.view.LayoutInflater;
51import android.view.View;
52import android.view.View.OnClickListener;
53import android.view.View.OnLongClickListener;
54import android.view.ViewGroup;
55import android.widget.AbsListView;
56import android.widget.BaseAdapter;
57import android.widget.ListView;
58import com.android.internal.R;
59import com.android.internal.logging.MetricsLogger;
60
61import java.util.ArrayList;
62import java.util.Collections;
63import java.util.Comparator;
64import java.util.List;
65
66public 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    @Override
475    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
476            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
477            boolean filterLastUsed) {
478        final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
479                initialIntents, rList, launchedFromUid, filterLastUsed);
480        if (DEBUG) Log.d(TAG, "Adapter created; querying services");
481        queryTargetServices(adapter);
482        return adapter;
483    }
484
485    final class ChooserTargetInfo implements TargetInfo {
486        private final DisplayResolveInfo mSourceInfo;
487        private final ResolveInfo mBackupResolveInfo;
488        private final ChooserTarget mChooserTarget;
489        private Drawable mBadgeIcon = null;
490        private Drawable mDisplayIcon;
491        private final Intent mFillInIntent;
492        private final int mFillInFlags;
493        private final float mModifiedScore;
494
495        public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
496                float modifiedScore) {
497            mSourceInfo = sourceInfo;
498            mChooserTarget = chooserTarget;
499            mModifiedScore = modifiedScore;
500            if (sourceInfo != null) {
501                final ResolveInfo ri = sourceInfo.getResolveInfo();
502                if (ri != null) {
503                    final ActivityInfo ai = ri.activityInfo;
504                    if (ai != null && ai.applicationInfo != null) {
505                        mBadgeIcon = getPackageManager().getApplicationIcon(ai.applicationInfo);
506                    }
507                }
508            }
509            final Icon icon = chooserTarget.getIcon();
510            // TODO do this in the background
511            mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
512
513            if (sourceInfo != null) {
514                mBackupResolveInfo = null;
515            } else {
516                mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
517            }
518
519            mFillInIntent = null;
520            mFillInFlags = 0;
521        }
522
523        private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
524            mSourceInfo = other.mSourceInfo;
525            mBackupResolveInfo = other.mBackupResolveInfo;
526            mChooserTarget = other.mChooserTarget;
527            mBadgeIcon = other.mBadgeIcon;
528            mDisplayIcon = other.mDisplayIcon;
529            mFillInIntent = fillInIntent;
530            mFillInFlags = flags;
531            mModifiedScore = other.mModifiedScore;
532        }
533
534        public float getModifiedScore() {
535            return mModifiedScore;
536        }
537
538        @Override
539        public Intent getResolvedIntent() {
540            if (mSourceInfo != null) {
541                return mSourceInfo.getResolvedIntent();
542            }
543            return getTargetIntent();
544        }
545
546        @Override
547        public ComponentName getResolvedComponentName() {
548            if (mSourceInfo != null) {
549                return mSourceInfo.getResolvedComponentName();
550            } else if (mBackupResolveInfo != null) {
551                return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
552                        mBackupResolveInfo.activityInfo.name);
553            }
554            return null;
555        }
556
557        private Intent getFillInIntent() {
558            Intent result = mSourceInfo != null
559                    ? mSourceInfo.getResolvedIntent() : getTargetIntent();
560            if (result == null) {
561                Log.e(TAG, "ChooserTargetInfo#getFillInIntent: no fillIn intent available");
562            } else {
563                result = new Intent(result);
564                if (mFillInIntent != null) {
565                    result.fillIn(mFillInIntent, mFillInFlags);
566                }
567                result.fillIn(mReferrerFillInIntent, 0);
568            }
569            return result;
570        }
571
572        @Override
573        public boolean start(Activity activity, Bundle options) {
574            final Intent intent = getFillInIntent();
575            if (intent == null) {
576                return false;
577            }
578            return mChooserTarget.sendIntent(activity, intent);
579        }
580
581        @Override
582        public boolean startAsCaller(Activity activity, Bundle options, int userId) {
583            final Intent intent = getFillInIntent();
584            if (intent == null) {
585                return false;
586            }
587            // ChooserTargets will launch with their IntentSender's identity
588            return mChooserTarget.sendIntent(activity, intent);
589        }
590
591        @Override
592        public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
593            final Intent intent = getFillInIntent();
594            if (intent == null) {
595                return false;
596            }
597            // ChooserTargets will launch with their IntentSender's identity
598            return mChooserTarget.sendIntent(activity, intent);
599        }
600
601        @Override
602        public ResolveInfo getResolveInfo() {
603            return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
604        }
605
606        @Override
607        public CharSequence getDisplayLabel() {
608            return mChooserTarget.getTitle();
609        }
610
611        @Override
612        public CharSequence getExtendedInfo() {
613            return mSourceInfo != null ? mSourceInfo.getExtendedInfo() : null;
614        }
615
616        @Override
617        public Drawable getDisplayIcon() {
618            return mDisplayIcon;
619        }
620
621        @Override
622        public Drawable getBadgeIcon() {
623            return mBadgeIcon;
624        }
625
626        @Override
627        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
628            return new ChooserTargetInfo(this, fillInIntent, flags);
629        }
630
631        @Override
632        public List<Intent> getAllSourceIntents() {
633            final List<Intent> results = new ArrayList<>();
634            if (mSourceInfo != null) {
635                // We only queried the service for the first one in our sourceinfo.
636                results.add(mSourceInfo.getAllSourceIntents().get(0));
637            }
638            return results;
639        }
640    }
641
642    public class ChooserListAdapter extends ResolveListAdapter {
643        public static final int TARGET_BAD = -1;
644        public static final int TARGET_CALLER = 0;
645        public static final int TARGET_SERVICE = 1;
646        public static final int TARGET_STANDARD = 2;
647
648        private static final int MAX_SERVICE_TARGETS = 8;
649
650        private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
651        private final List<TargetInfo> mCallerTargets = new ArrayList<>();
652
653        private float mLateFee = 1.f;
654
655        private final BaseChooserTargetComparator mBaseTargetComparator
656                = new BaseChooserTargetComparator();
657
658        public ChooserListAdapter(Context context, List<Intent> payloadIntents,
659                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
660                boolean filterLastUsed) {
661            // Don't send the initial intents through the shared ResolverActivity path,
662            // we want to separate them into a different section.
663            super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed);
664
665            if (initialIntents != null) {
666                final PackageManager pm = getPackageManager();
667                for (int i = 0; i < initialIntents.length; i++) {
668                    final Intent ii = initialIntents[i];
669                    if (ii == null) {
670                        continue;
671                    }
672                    final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
673                    if (ai == null) {
674                        Log.w(TAG, "No activity found for " + ii);
675                        continue;
676                    }
677                    ResolveInfo ri = new ResolveInfo();
678                    ri.activityInfo = ai;
679                    UserManager userManager =
680                            (UserManager) getSystemService(Context.USER_SERVICE);
681                    if (ii instanceof LabeledIntent) {
682                        LabeledIntent li = (LabeledIntent)ii;
683                        ri.resolvePackageName = li.getSourcePackage();
684                        ri.labelRes = li.getLabelResource();
685                        ri.nonLocalizedLabel = li.getNonLocalizedLabel();
686                        ri.icon = li.getIconResource();
687                        ri.iconResourceId = ri.icon;
688                    }
689                    if (userManager.isManagedProfile()) {
690                        ri.noResourceId = true;
691                        ri.icon = 0;
692                    }
693                    mCallerTargets.add(new DisplayResolveInfo(ii, ri,
694                            ri.loadLabel(pm), null, ii));
695                }
696            }
697        }
698
699        @Override
700        public boolean showsExtendedInfo(TargetInfo info) {
701            // Reserve space to show extended info if any one of the items in the adapter has
702            // extended info. This keeps grid item sizes uniform.
703            return hasExtendedInfo();
704        }
705
706        @Override
707        public View onCreateView(ViewGroup parent) {
708            return mInflater.inflate(
709                    com.android.internal.R.layout.resolve_grid_item, parent, false);
710        }
711
712        @Override
713        public void onListRebuilt() {
714            if (mServiceTargets != null) {
715                pruneServiceTargets();
716            }
717        }
718
719        @Override
720        public boolean shouldGetResolvedFilter() {
721            return true;
722        }
723
724        @Override
725        public int getCount() {
726            return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
727        }
728
729        @Override
730        public int getUnfilteredCount() {
731            return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
732        }
733
734        public int getCallerTargetCount() {
735            return mCallerTargets.size();
736        }
737
738        public int getServiceTargetCount() {
739            return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
740        }
741
742        public int getStandardTargetCount() {
743            return super.getCount();
744        }
745
746        public int getPositionTargetType(int position) {
747            int offset = 0;
748
749            final int callerTargetCount = getCallerTargetCount();
750            if (position < callerTargetCount) {
751                return TARGET_CALLER;
752            }
753            offset += callerTargetCount;
754
755            final int serviceTargetCount = getServiceTargetCount();
756            if (position - offset < serviceTargetCount) {
757                return TARGET_SERVICE;
758            }
759            offset += serviceTargetCount;
760
761            final int standardTargetCount = super.getCount();
762            if (position - offset < standardTargetCount) {
763                return TARGET_STANDARD;
764            }
765
766            return TARGET_BAD;
767        }
768
769        @Override
770        public TargetInfo getItem(int position) {
771            return targetInfoForPosition(position, true);
772        }
773
774        @Override
775        public TargetInfo targetInfoForPosition(int position, boolean filtered) {
776            int offset = 0;
777
778            final int callerTargetCount = getCallerTargetCount();
779            if (position < callerTargetCount) {
780                return mCallerTargets.get(position);
781            }
782            offset += callerTargetCount;
783
784            final int serviceTargetCount = getServiceTargetCount();
785            if (position - offset < serviceTargetCount) {
786                return mServiceTargets.get(position - offset);
787            }
788            offset += serviceTargetCount;
789
790            return filtered ? super.getItem(position - offset)
791                    : getDisplayInfoAt(position - offset);
792        }
793
794        public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
795            if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
796                    + " targets");
797            final float parentScore = getScore(origTarget);
798            Collections.sort(targets, mBaseTargetComparator);
799            float lastScore = 0;
800            for (int i = 0, N = targets.size(); i < N; i++) {
801                final ChooserTarget target = targets.get(i);
802                float targetScore = target.getScore();
803                targetScore *= parentScore;
804                targetScore *= mLateFee;
805                if (i > 0 && targetScore >= lastScore) {
806                    // Apply a decay so that the top app can't crowd out everything else.
807                    // This incents ChooserTargetServices to define what's truly better.
808                    targetScore = lastScore * 0.95f;
809                }
810                insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
811
812                if (DEBUG) {
813                    Log.d(TAG, " => " + target.toString() + " score=" + targetScore
814                            + " base=" + target.getScore()
815                            + " lastScore=" + lastScore
816                            + " parentScore=" + parentScore
817                            + " lateFee=" + mLateFee);
818                }
819
820                lastScore = targetScore;
821            }
822
823            mLateFee *= 0.95f;
824
825            notifyDataSetChanged();
826        }
827
828        private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
829            final float newScore = chooserTargetInfo.getModifiedScore();
830            for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
831                final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
832                if (newScore > serviceTarget.getModifiedScore()) {
833                    mServiceTargets.add(i, chooserTargetInfo);
834                    return;
835                }
836            }
837            mServiceTargets.add(chooserTargetInfo);
838        }
839
840        private void pruneServiceTargets() {
841            if (DEBUG) Log.d(TAG, "pruneServiceTargets");
842            for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
843                final ChooserTargetInfo cti = mServiceTargets.get(i);
844                if (!hasResolvedTarget(cti.getResolveInfo())) {
845                    if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
846                    mServiceTargets.remove(i);
847                }
848            }
849        }
850    }
851
852    static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
853        @Override
854        public int compare(ChooserTarget lhs, ChooserTarget rhs) {
855            // Descending order
856            return (int) Math.signum(lhs.getScore() - rhs.getScore());
857        }
858    }
859
860    class ChooserRowAdapter extends BaseAdapter {
861        private ChooserListAdapter mChooserListAdapter;
862        private final LayoutInflater mLayoutInflater;
863        private final int mColumnCount = 4;
864
865        public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
866            mChooserListAdapter = wrappedAdapter;
867            mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
868
869            wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
870                @Override
871                public void onChanged() {
872                    super.onChanged();
873                    notifyDataSetChanged();
874                }
875
876                @Override
877                public void onInvalidated() {
878                    super.onInvalidated();
879                    notifyDataSetInvalidated();
880                }
881            });
882        }
883
884        @Override
885        public int getCount() {
886            return (int) (
887                    Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
888                    + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
889                    + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
890            );
891        }
892
893        @Override
894        public Object getItem(int position) {
895            // We have nothing useful to return here.
896            return position;
897        }
898
899        @Override
900        public long getItemId(int position) {
901            return position;
902        }
903
904        @Override
905        public View getView(int position, View convertView, ViewGroup parent) {
906            final View[] holder;
907            if (convertView == null) {
908                holder = createViewHolder(parent);
909            } else {
910                holder = (View[]) convertView.getTag();
911            }
912            bindViewHolder(position, holder);
913
914            // We keep the actual list item view as the last item in the holder array
915            return holder[mColumnCount];
916        }
917
918        View[] createViewHolder(ViewGroup parent) {
919            final View[] holder = new View[mColumnCount + 1];
920
921            final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
922                    parent, false);
923            for (int i = 0; i < mColumnCount; i++) {
924                holder[i] = mChooserListAdapter.createView(row);
925                row.addView(holder[i]);
926            }
927            row.setTag(holder);
928            holder[mColumnCount] = row;
929            return holder;
930        }
931
932        void bindViewHolder(int rowPosition, View[] holder) {
933            final int start = getFirstRowPosition(rowPosition);
934            final int startType = mChooserListAdapter.getPositionTargetType(start);
935
936            int end = start + mColumnCount - 1;
937            while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
938                end--;
939            }
940
941            final ViewGroup row = (ViewGroup) holder[mColumnCount];
942
943            if (startType == ChooserListAdapter.TARGET_SERVICE) {
944                row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
945            } else {
946                row.setBackground(null);
947            }
948
949            for (int i = 0; i < mColumnCount; i++) {
950                final View v = holder[i];
951                if (start + i <= end) {
952                    v.setVisibility(View.VISIBLE);
953                    final int itemIndex = start + i;
954                    mChooserListAdapter.bindView(itemIndex, v);
955                    v.setOnClickListener(new OnClickListener() {
956                        @Override
957                        public void onClick(View v) {
958                            startSelected(itemIndex, false, true);
959                        }
960                    });
961                    v.setOnLongClickListener(new OnLongClickListener() {
962                        @Override
963                        public boolean onLongClick(View v) {
964                            showAppDetails(
965                                    mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
966                            return true;
967                        }
968                    });
969                } else {
970                    v.setVisibility(View.GONE);
971                }
972            }
973        }
974
975        int getFirstRowPosition(int row) {
976            final int callerCount = mChooserListAdapter.getCallerTargetCount();
977            final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
978
979            if (row < callerRows) {
980                return row * mColumnCount;
981            }
982
983            final int serviceCount = mChooserListAdapter.getServiceTargetCount();
984            final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
985
986            if (row < callerRows + serviceRows) {
987                return callerCount + (row - callerRows) * mColumnCount;
988            }
989
990            return callerCount + serviceCount
991                    + (row - callerRows - serviceRows) * mColumnCount;
992        }
993    }
994
995    class ChooserTargetServiceConnection implements ServiceConnection {
996        private final DisplayResolveInfo mOriginalTarget;
997
998        private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
999            @Override
1000            public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1001                final Message msg = Message.obtain();
1002                msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1003                msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1004                        ChooserTargetServiceConnection.this);
1005                mChooserHandler.sendMessage(msg);
1006            }
1007        };
1008
1009        public ChooserTargetServiceConnection(DisplayResolveInfo dri) {
1010            mOriginalTarget = dri;
1011        }
1012
1013        @Override
1014        public void onServiceConnected(ComponentName name, IBinder service) {
1015            if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1016            final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1017            try {
1018                icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1019                        mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1020            } catch (RemoteException e) {
1021                Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1022                unbindService(this);
1023                mServiceConnections.remove(this);
1024            }
1025        }
1026
1027        @Override
1028        public void onServiceDisconnected(ComponentName name) {
1029            if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1030            unbindService(this);
1031            mServiceConnections.remove(this);
1032            if (mServiceConnections.isEmpty()) {
1033                mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1034                sendVoiceChoicesIfNeeded();
1035            }
1036        }
1037
1038        @Override
1039        public String toString() {
1040            return mOriginalTarget.getResolveInfo().activityInfo.toString();
1041        }
1042    }
1043
1044    static class ServiceResultInfo {
1045        public final DisplayResolveInfo originalTarget;
1046        public final List<ChooserTarget> resultTargets;
1047        public final ChooserTargetServiceConnection connection;
1048
1049        public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1050                ChooserTargetServiceConnection c) {
1051            originalTarget = ot;
1052            resultTargets = rt;
1053            connection = c;
1054        }
1055    }
1056
1057    static class RefinementResultReceiver extends ResultReceiver {
1058        private ChooserActivity mChooserActivity;
1059        private TargetInfo mSelectedTarget;
1060
1061        public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1062                Handler handler) {
1063            super(handler);
1064            mChooserActivity = host;
1065            mSelectedTarget = target;
1066        }
1067
1068        @Override
1069        protected void onReceiveResult(int resultCode, Bundle resultData) {
1070            if (mChooserActivity == null) {
1071                Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1072                return;
1073            }
1074            if (resultData == null) {
1075                Log.e(TAG, "RefinementResultReceiver received null resultData");
1076                return;
1077            }
1078
1079            switch (resultCode) {
1080                case RESULT_CANCELED:
1081                    mChooserActivity.onRefinementCanceled();
1082                    break;
1083                case RESULT_OK:
1084                    Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1085                    if (intentParcelable instanceof Intent) {
1086                        mChooserActivity.onRefinementResult(mSelectedTarget,
1087                                (Intent) intentParcelable);
1088                    } else {
1089                        Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1090                                + " in resultData with key Intent.EXTRA_INTENT");
1091                    }
1092                    break;
1093                default:
1094                    Log.w(TAG, "Unknown result code " + resultCode
1095                            + " sent to RefinementResultReceiver");
1096                    break;
1097            }
1098        }
1099
1100        public void destroy() {
1101            mChooserActivity = null;
1102            mSelectedTarget = null;
1103        }
1104    }
1105}
1106