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