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