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