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