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