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