ChooserActivity.java revision a408061cc7b5efaf090ce9efd5fd0ba1d95e9c11
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 (ii instanceof LabeledIntent) {
617                        LabeledIntent li = (LabeledIntent)ii;
618                        ri.resolvePackageName = li.getSourcePackage();
619                        ri.labelRes = li.getLabelResource();
620                        ri.nonLocalizedLabel = li.getNonLocalizedLabel();
621                        ri.icon = li.getIconResource();
622                        ri.iconResourceId = ri.icon;
623                    }
624                    if (userManager.isManagedProfile()) {
625                        ri.noResourceId = true;
626                        ri.icon = 0;
627                    }
628                    mCallerTargets.add(new DisplayResolveInfo(ii, ri,
629                            ri.loadLabel(pm), null, ii));
630                }
631            }
632        }
633
634        @Override
635        public boolean showsExtendedInfo(TargetInfo info) {
636            // Reserve space to show extended info if any one of the items in the adapter has
637            // extended info. This keeps grid item sizes uniform.
638            return hasExtendedInfo();
639        }
640
641        @Override
642        public View onCreateView(ViewGroup parent) {
643            return mInflater.inflate(
644                    com.android.internal.R.layout.resolve_grid_item, parent, false);
645        }
646
647        @Override
648        public void onListRebuilt() {
649            if (mServiceTargets != null) {
650                pruneServiceTargets();
651            }
652        }
653
654        @Override
655        public boolean shouldGetResolvedFilter() {
656            return true;
657        }
658
659        @Override
660        public int getCount() {
661            return super.getCount() + mServiceTargets.size() + mCallerTargets.size();
662        }
663
664        @Override
665        public int getUnfilteredCount() {
666            return super.getUnfilteredCount() + mServiceTargets.size() + mCallerTargets.size();
667        }
668
669        public int getCallerTargetCount() {
670            return mCallerTargets.size();
671        }
672
673        public int getServiceTargetCount() {
674            return mServiceTargets.size();
675        }
676
677        public int getStandardTargetCount() {
678            return super.getCount();
679        }
680
681        public int getPositionTargetType(int position) {
682            int offset = 0;
683
684            final int callerTargetCount = mCallerTargets.size();
685            if (position < callerTargetCount) {
686                return TARGET_CALLER;
687            }
688            offset += callerTargetCount;
689
690            final int serviceTargetCount = mServiceTargets.size();
691            if (position - offset < serviceTargetCount) {
692                return TARGET_SERVICE;
693            }
694            offset += serviceTargetCount;
695
696            final int standardTargetCount = super.getCount();
697            if (position - offset < standardTargetCount) {
698                return TARGET_STANDARD;
699            }
700
701            return TARGET_BAD;
702        }
703
704        @Override
705        public TargetInfo getItem(int position) {
706            return targetInfoForPosition(position, true);
707        }
708
709        @Override
710        public TargetInfo targetInfoForPosition(int position, boolean filtered) {
711            int offset = 0;
712
713            final int callerTargetCount = mCallerTargets.size();
714            if (position < callerTargetCount) {
715                return mCallerTargets.get(position);
716            }
717            offset += callerTargetCount;
718
719            final int serviceTargetCount = mServiceTargets.size();
720            if (position - offset < serviceTargetCount) {
721                return mServiceTargets.get(position - offset);
722            }
723            offset += serviceTargetCount;
724
725            return filtered ? super.getItem(position - offset)
726                    : getDisplayInfoAt(position - offset);
727        }
728
729        public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
730            if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
731                    + " targets");
732            for (int i = 0, N = targets.size(); i < N; i++) {
733                mServiceTargets.add(new ChooserTargetInfo(origTarget, targets.get(i)));
734            }
735
736            // TODO: Maintain sort by ranking scores.
737
738            notifyDataSetChanged();
739        }
740
741        private void pruneServiceTargets() {
742            if (DEBUG) Log.d(TAG, "pruneServiceTargets");
743            for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
744                final ChooserTargetInfo cti = mServiceTargets.get(i);
745                if (!hasResolvedTarget(cti.getResolveInfo())) {
746                    if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
747                    mServiceTargets.remove(i);
748                }
749            }
750        }
751    }
752
753    class ChooserRowAdapter extends BaseAdapter {
754        private ChooserListAdapter mChooserListAdapter;
755        private final LayoutInflater mLayoutInflater;
756        private final int mColumnCount = 4;
757
758        public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
759            mChooserListAdapter = wrappedAdapter;
760            mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
761
762            wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
763                @Override
764                public void onChanged() {
765                    super.onChanged();
766                    notifyDataSetChanged();
767                }
768
769                @Override
770                public void onInvalidated() {
771                    super.onInvalidated();
772                    notifyDataSetInvalidated();
773                }
774            });
775        }
776
777        @Override
778        public int getCount() {
779            return (int) (
780                    Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
781                    + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
782                    + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
783            );
784        }
785
786        @Override
787        public Object getItem(int position) {
788            // We have nothing useful to return here.
789            return position;
790        }
791
792        @Override
793        public long getItemId(int position) {
794            return position;
795        }
796
797        @Override
798        public View getView(int position, View convertView, ViewGroup parent) {
799            final View[] holder;
800            if (convertView == null) {
801                holder = createViewHolder(parent);
802            } else {
803                holder = (View[]) convertView.getTag();
804            }
805            bindViewHolder(position, holder);
806
807            // We keep the actual list item view as the last item in the holder array
808            return holder[mColumnCount];
809        }
810
811        View[] createViewHolder(ViewGroup parent) {
812            final View[] holder = new View[mColumnCount + 1];
813
814            final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
815                    parent, false);
816            for (int i = 0; i < mColumnCount; i++) {
817                holder[i] = mChooserListAdapter.createView(row);
818                row.addView(holder[i]);
819            }
820            row.setTag(holder);
821            holder[mColumnCount] = row;
822            return holder;
823        }
824
825        void bindViewHolder(int rowPosition, View[] holder) {
826            final int start = getFirstRowPosition(rowPosition);
827            final int startType = mChooserListAdapter.getPositionTargetType(start);
828
829            int end = start + mColumnCount - 1;
830            while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
831                end--;
832            }
833
834            final ViewGroup row = (ViewGroup) holder[mColumnCount];
835
836            if (startType == ChooserListAdapter.TARGET_SERVICE) {
837                row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
838            } else {
839                row.setBackground(null);
840            }
841
842            for (int i = 0; i < mColumnCount; i++) {
843                final View v = holder[i];
844                if (start + i <= end) {
845                    v.setVisibility(View.VISIBLE);
846                    final int itemIndex = start + i;
847                    mChooserListAdapter.bindView(itemIndex, v);
848                    v.setOnClickListener(new OnClickListener() {
849                        @Override
850                        public void onClick(View v) {
851                            startSelected(itemIndex, false, true);
852                        }
853                    });
854                } else {
855                    v.setVisibility(View.GONE);
856                }
857            }
858        }
859
860        int getFirstRowPosition(int row) {
861            final int callerCount = mChooserListAdapter.getCallerTargetCount();
862            final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
863
864            if (row < callerRows) {
865                return row * mColumnCount;
866            }
867
868            final int serviceCount = mChooserListAdapter.getServiceTargetCount();
869            final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
870
871            if (row < callerRows + serviceRows) {
872                return callerCount + (row - callerRows) * mColumnCount;
873            }
874
875            return callerCount + serviceCount
876                    + (row - callerRows - serviceRows) * mColumnCount;
877        }
878    }
879
880    class ChooserTargetServiceConnection implements ServiceConnection {
881        private final DisplayResolveInfo mOriginalTarget;
882
883        private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
884            @Override
885            public void sendResult(List<ChooserTarget> targets) throws RemoteException {
886                final Message msg = Message.obtain();
887                msg.what = CHOOSER_TARGET_SERVICE_RESULT;
888                msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
889                        ChooserTargetServiceConnection.this);
890                mChooserHandler.sendMessage(msg);
891            }
892        };
893
894        public ChooserTargetServiceConnection(DisplayResolveInfo dri) {
895            mOriginalTarget = dri;
896        }
897
898        @Override
899        public void onServiceConnected(ComponentName name, IBinder service) {
900            if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
901            final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
902            try {
903                icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
904                        mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
905            } catch (RemoteException e) {
906                Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
907                unbindService(this);
908                mServiceConnections.remove(this);
909            }
910        }
911
912        @Override
913        public void onServiceDisconnected(ComponentName name) {
914            if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
915            unbindService(this);
916            mServiceConnections.remove(this);
917        }
918
919        @Override
920        public String toString() {
921            return mOriginalTarget.getResolveInfo().activityInfo.toString();
922        }
923    }
924
925    static class ServiceResultInfo {
926        public final DisplayResolveInfo originalTarget;
927        public final List<ChooserTarget> resultTargets;
928        public final ChooserTargetServiceConnection connection;
929
930        public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
931                ChooserTargetServiceConnection c) {
932            originalTarget = ot;
933            resultTargets = rt;
934            connection = c;
935        }
936    }
937
938    static class RefinementResultReceiver extends ResultReceiver {
939        private ChooserActivity mChooserActivity;
940        private TargetInfo mSelectedTarget;
941
942        public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
943                Handler handler) {
944            super(handler);
945            mChooserActivity = host;
946            mSelectedTarget = target;
947        }
948
949        @Override
950        protected void onReceiveResult(int resultCode, Bundle resultData) {
951            if (mChooserActivity == null) {
952                Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
953                return;
954            }
955            if (resultData == null) {
956                Log.e(TAG, "RefinementResultReceiver received null resultData");
957                return;
958            }
959
960            switch (resultCode) {
961                case RESULT_CANCELED:
962                    mChooserActivity.onRefinementCanceled();
963                    break;
964                case RESULT_OK:
965                    Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
966                    if (intentParcelable instanceof Intent) {
967                        mChooserActivity.onRefinementResult(mSelectedTarget,
968                                (Intent) intentParcelable);
969                    } else {
970                        Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
971                                + " in resultData with key Intent.EXTRA_INTENT");
972                    }
973                    break;
974                default:
975                    Log.w(TAG, "Unknown result code " + resultCode
976                            + " sent to RefinementResultReceiver");
977                    break;
978            }
979        }
980
981        public void destroy() {
982            mChooserActivity = null;
983            mSelectedTarget = null;
984        }
985    }
986}
987