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