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