ChooserActivity.java revision bba0030a8529fd904056cbb7cfbbe3afbc1c8a95
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.animation.ObjectAnimator;
20import android.annotation.NonNull;
21import android.app.Activity;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentSender;
26import android.content.IntentSender.SendIntentException;
27import android.content.ServiceConnection;
28import android.content.SharedPreferences;
29import android.content.pm.ActivityInfo;
30import android.content.pm.LabeledIntent;
31import android.content.pm.PackageManager;
32import android.content.pm.PackageManager.NameNotFoundException;
33import android.content.pm.ResolveInfo;
34import android.database.DataSetObserver;
35import android.graphics.Color;
36import android.graphics.drawable.Drawable;
37import android.graphics.drawable.Icon;
38import android.os.Bundle;
39import android.os.Environment;
40import android.os.Handler;
41import android.os.IBinder;
42import android.os.Message;
43import android.os.Parcelable;
44import android.os.RemoteException;
45import android.os.ResultReceiver;
46import android.os.UserHandle;
47import android.os.UserManager;
48import android.provider.DocumentsContract;
49import android.service.chooser.ChooserTarget;
50import android.service.chooser.ChooserTargetService;
51import android.service.chooser.IChooserTargetResult;
52import android.service.chooser.IChooserTargetService;
53import android.text.TextUtils;
54import android.util.FloatProperty;
55import android.util.Log;
56import android.util.Slog;
57import android.view.LayoutInflater;
58import android.view.View;
59import android.view.View.MeasureSpec;
60import android.view.View.OnClickListener;
61import android.view.View.OnLongClickListener;
62import android.view.ViewGroup;
63import android.view.ViewGroup.LayoutParams;
64import android.view.animation.AnimationUtils;
65import android.view.animation.Interpolator;
66import android.widget.AbsListView;
67import android.widget.BaseAdapter;
68import android.widget.ListView;
69import com.android.internal.R;
70import com.android.internal.logging.MetricsLogger;
71import com.android.internal.logging.MetricsProto.MetricsEvent;
72
73import java.io.File;
74import java.util.ArrayList;
75import java.util.Collections;
76import java.util.Comparator;
77import java.util.List;
78
79public class ChooserActivity extends ResolverActivity {
80    private static final String TAG = "ChooserActivity";
81
82    private static final boolean DEBUG = false;
83
84    private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
85    private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
86
87    private Bundle mReplacementExtras;
88    private IntentSender mChosenComponentSender;
89    private IntentSender mRefinementIntentSender;
90    private RefinementResultReceiver mRefinementResultReceiver;
91
92    private Intent mReferrerFillInIntent;
93
94    private ChooserListAdapter mChooserListAdapter;
95    private ChooserRowAdapter mChooserRowAdapter;
96
97    private SharedPreferences mPinnedSharedPrefs;
98    private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
99    private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
100    private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
101
102    private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
103
104    private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
105    private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
106
107    private final Handler mChooserHandler = new Handler() {
108        @Override
109        public void handleMessage(Message msg) {
110            switch (msg.what) {
111                case CHOOSER_TARGET_SERVICE_RESULT:
112                    if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
113                    if (isDestroyed()) break;
114                    final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
115                    if (!mServiceConnections.contains(sri.connection)) {
116                        Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
117                                + " returned after being removed from active connections."
118                                + " Have you considered returning results faster?");
119                        break;
120                    }
121                    if (sri.resultTargets != null) {
122                        mChooserListAdapter.addServiceResults(sri.originalTarget,
123                                sri.resultTargets);
124                    }
125                    unbindService(sri.connection);
126                    sri.connection.destroy();
127                    mServiceConnections.remove(sri.connection);
128                    if (mServiceConnections.isEmpty()) {
129                        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
130                        sendVoiceChoicesIfNeeded();
131                    }
132                    break;
133
134                case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
135                    if (DEBUG) {
136                        Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
137                    }
138                    unbindRemainingServices();
139                    sendVoiceChoicesIfNeeded();
140                    break;
141
142                default:
143                    super.handleMessage(msg);
144            }
145        }
146    };
147
148    @Override
149    protected void onCreate(Bundle savedInstanceState) {
150        Intent intent = getIntent();
151        Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
152        if (!(targetParcelable instanceof Intent)) {
153            Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
154            finish();
155            super.onCreate(null);
156            return;
157        }
158        Intent target = (Intent) targetParcelable;
159        if (target != null) {
160            modifyTargetIntent(target);
161        }
162        Parcelable[] targetsParcelable
163                = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
164        if (targetsParcelable != null) {
165            final boolean offset = target == null;
166            Intent[] additionalTargets =
167                    new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
168            for (int i = 0; i < targetsParcelable.length; i++) {
169                if (!(targetsParcelable[i] instanceof Intent)) {
170                    Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
171                            + targetsParcelable[i]);
172                    finish();
173                    super.onCreate(null);
174                    return;
175                }
176                final Intent additionalTarget = (Intent) targetsParcelable[i];
177                if (i == 0 && target == null) {
178                    target = additionalTarget;
179                    modifyTargetIntent(target);
180                } else {
181                    additionalTargets[offset ? i - 1 : i] = additionalTarget;
182                    modifyTargetIntent(additionalTarget);
183                }
184            }
185            setAdditionalTargets(additionalTargets);
186        }
187
188        mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
189        CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
190        int defaultTitleRes = 0;
191        if (title == null) {
192            defaultTitleRes = com.android.internal.R.string.chooseActivity;
193        }
194        Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
195        Intent[] initialIntents = null;
196        if (pa != null) {
197            initialIntents = new Intent[pa.length];
198            for (int i=0; i<pa.length; i++) {
199                if (!(pa[i] instanceof Intent)) {
200                    Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
201                    finish();
202                    super.onCreate(null);
203                    return;
204                }
205                final Intent in = (Intent) pa[i];
206                modifyTargetIntent(in);
207                initialIntents[i] = in;
208            }
209        }
210
211        mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
212
213        mChosenComponentSender = intent.getParcelableExtra(
214                Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
215        mRefinementIntentSender = intent.getParcelableExtra(
216                Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
217        setSafeForwardingMode(true);
218
219        mPinnedSharedPrefs = getPinnedSharedPrefs(this);
220        super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
221                null, false);
222
223        MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
224    }
225
226    static SharedPreferences getPinnedSharedPrefs(Context context) {
227        // The code below is because in the android:ui process, no one can hear you scream.
228        // The package info in the context isn't initialized in the way it is for normal apps,
229        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
230        // build the path manually below using the same policy that appears in ContextImpl.
231        // This fails silently under the hood if there's a problem, so if we find ourselves in
232        // the case where we don't have access to credential encrypted storage we just won't
233        // have our pinned target info.
234        final File prefsFile = new File(new File(
235                Environment.getDataUserCredentialEncryptedPackageDirectory(null,
236                        context.getUserId(), context.getPackageName()),
237                "shared_prefs"),
238                PINNED_SHARED_PREFS_NAME + ".xml");
239        return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
240    }
241
242    @Override
243    protected void onDestroy() {
244        super.onDestroy();
245        if (mRefinementResultReceiver != null) {
246            mRefinementResultReceiver.destroy();
247            mRefinementResultReceiver = null;
248        }
249        unbindRemainingServices();
250        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
251    }
252
253    @Override
254    public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
255        Intent result = defIntent;
256        if (mReplacementExtras != null) {
257            final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
258            if (replExtras != null) {
259                result = new Intent(defIntent);
260                result.putExtras(replExtras);
261            }
262        }
263        if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
264                || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
265            result = Intent.createChooser(result,
266                    getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
267        }
268        return result;
269    }
270
271    @Override
272    public void onActivityStarted(TargetInfo cti) {
273        if (mChosenComponentSender != null) {
274            final ComponentName target = cti.getResolvedComponentName();
275            if (target != null) {
276                final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
277                try {
278                    mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
279                } catch (IntentSender.SendIntentException e) {
280                    Slog.e(TAG, "Unable to launch supplied IntentSender to report "
281                            + "the chosen component: " + e);
282                }
283            }
284        }
285    }
286
287    @Override
288    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
289            boolean alwaysUseOption) {
290        final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
291        mChooserListAdapter = (ChooserListAdapter) adapter;
292        mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
293        mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
294        adapterView.setAdapter(mChooserRowAdapter);
295        if (listView != null) {
296            listView.setItemsCanFocus(true);
297        }
298    }
299
300    @Override
301    public int getLayoutResource() {
302        return R.layout.chooser_grid;
303    }
304
305    @Override
306    public boolean shouldGetActivityMetadata() {
307        return true;
308    }
309
310    @Override
311    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
312        final Intent intent = target.getResolvedIntent();
313        final ResolveInfo resolve = target.getResolveInfo();
314
315        // When GET_CONTENT is handled by the DocumentsUI system component,
316        // we're okay automatically launching it, since it offers it's own
317        // intent disambiguation UI.
318        if (intent != null && Intent.ACTION_GET_CONTENT.equals(intent.getAction())
319                && resolve != null && resolve.priority > 0
320                && resolve.activityInfo != null && DocumentsContract.PACKAGE_DOCUMENTS_UI
321                        .equals(resolve.activityInfo.packageName)) {
322            return true;
323        }
324
325        return false;
326    }
327
328    @Override
329    public void showTargetDetails(ResolveInfo ri) {
330        ComponentName name = ri.activityInfo.getComponentName();
331        boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
332        ResolverTargetActionsDialogFragment f =
333                new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
334                        name, pinned);
335        f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
336    }
337
338    private void modifyTargetIntent(Intent in) {
339        final String action = in.getAction();
340        if (Intent.ACTION_SEND.equals(action) ||
341                Intent.ACTION_SEND_MULTIPLE.equals(action)) {
342            in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
343                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
344        }
345    }
346
347    @Override
348    protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
349        if (mRefinementIntentSender != null) {
350            final Intent fillIn = new Intent();
351            final List<Intent> sourceIntents = target.getAllSourceIntents();
352            if (!sourceIntents.isEmpty()) {
353                fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
354                if (sourceIntents.size() > 1) {
355                    final Intent[] alts = new Intent[sourceIntents.size() - 1];
356                    for (int i = 1, N = sourceIntents.size(); i < N; i++) {
357                        alts[i - 1] = sourceIntents.get(i);
358                    }
359                    fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
360                }
361                if (mRefinementResultReceiver != null) {
362                    mRefinementResultReceiver.destroy();
363                }
364                mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
365                fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
366                        mRefinementResultReceiver);
367                try {
368                    mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
369                    return false;
370                } catch (SendIntentException e) {
371                    Log.e(TAG, "Refinement IntentSender failed to send", e);
372                }
373            }
374        }
375        return super.onTargetSelected(target, alwaysCheck);
376    }
377
378    @Override
379    public void startSelected(int which, boolean always, boolean filtered) {
380        super.startSelected(which, always, filtered);
381
382        if (mChooserListAdapter != null) {
383            // Log the index of which type of target the user picked.
384            // Lower values mean the ranking was better.
385            int cat = 0;
386            int value = which;
387            switch (mChooserListAdapter.getPositionTargetType(which)) {
388                case ChooserListAdapter.TARGET_CALLER:
389                    cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
390                    break;
391                case ChooserListAdapter.TARGET_SERVICE:
392                    cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
393                    value -= mChooserListAdapter.getCallerTargetCount();
394                    break;
395                case ChooserListAdapter.TARGET_STANDARD:
396                    cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
397                    value -= mChooserListAdapter.getCallerTargetCount()
398                            + mChooserListAdapter.getServiceTargetCount();
399                    break;
400            }
401
402            if (cat != 0) {
403                MetricsLogger.action(this, cat, value);
404            }
405        }
406    }
407
408    void queryTargetServices(ChooserListAdapter adapter) {
409        final PackageManager pm = getPackageManager();
410        int targetsToQuery = 0;
411        for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
412            final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
413            if (adapter.getScore(dri) == 0) {
414                // A score of 0 means the app hasn't been used in some time;
415                // don't query it as it's not likely to be relevant.
416                continue;
417            }
418            final ActivityInfo ai = dri.getResolveInfo().activityInfo;
419            final Bundle md = ai.metaData;
420            final String serviceName = md != null ? convertServiceName(ai.packageName,
421                    md.getString(ChooserTargetService.META_DATA_NAME)) : null;
422            if (serviceName != null) {
423                final ComponentName serviceComponent = new ComponentName(
424                        ai.packageName, serviceName);
425                final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
426                        .setComponent(serviceComponent);
427
428                if (DEBUG) {
429                    Log.d(TAG, "queryTargets found target with service " + serviceComponent);
430                }
431
432                try {
433                    final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
434                    if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
435                        Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
436                                + " permission " + ChooserTargetService.BIND_PERMISSION
437                                + " - this service will not be queried for ChooserTargets."
438                                + " add android:permission=\""
439                                + ChooserTargetService.BIND_PERMISSION + "\""
440                                + " to the <service> tag for " + serviceComponent
441                                + " in the manifest.");
442                        continue;
443                    }
444                } catch (NameNotFoundException e) {
445                    Log.e(TAG, "Could not look up service " + serviceComponent, e);
446                    continue;
447                }
448
449                final ChooserTargetServiceConnection conn =
450                        new ChooserTargetServiceConnection(this, dri);
451                if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
452                        UserHandle.CURRENT)) {
453                    if (DEBUG) {
454                        Log.d(TAG, "Binding service connection for target " + dri
455                                + " intent " + serviceIntent);
456                    }
457                    mServiceConnections.add(conn);
458                    targetsToQuery++;
459                }
460            }
461            if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
462                if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
463                        + QUERY_TARGET_SERVICE_LIMIT);
464                break;
465            }
466        }
467
468        if (!mServiceConnections.isEmpty()) {
469            if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for "
470                    + WATCHDOG_TIMEOUT_MILLIS + "ms");
471            mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
472                    WATCHDOG_TIMEOUT_MILLIS);
473        } else {
474            sendVoiceChoicesIfNeeded();
475        }
476    }
477
478    private String convertServiceName(String packageName, String serviceName) {
479        if (TextUtils.isEmpty(serviceName)) {
480            return null;
481        }
482
483        final String fullName;
484        if (serviceName.startsWith(".")) {
485            // Relative to the app package. Prepend the app package name.
486            fullName = packageName + serviceName;
487        } else if (serviceName.indexOf('.') >= 0) {
488            // Fully qualified package name.
489            fullName = serviceName;
490        } else {
491            fullName = null;
492        }
493        return fullName;
494    }
495
496    void unbindRemainingServices() {
497        if (DEBUG) {
498            Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
499        }
500        for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
501            final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
502            if (DEBUG) Log.d(TAG, "unbinding " + conn);
503            unbindService(conn);
504            conn.destroy();
505        }
506        mServiceConnections.clear();
507        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
508    }
509
510    public void onSetupVoiceInteraction() {
511        // Do nothing. We'll send the voice stuff ourselves.
512    }
513
514    void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
515        if (mRefinementResultReceiver != null) {
516            mRefinementResultReceiver.destroy();
517            mRefinementResultReceiver = null;
518        }
519
520        if (selectedTarget == null) {
521            Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
522        } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
523            Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
524                    + " cannot match refined source intent " + matchingIntent);
525        } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) {
526            finish();
527            return;
528        }
529        onRefinementCanceled();
530    }
531
532    void onRefinementCanceled() {
533        if (mRefinementResultReceiver != null) {
534            mRefinementResultReceiver.destroy();
535            mRefinementResultReceiver = null;
536        }
537        finish();
538    }
539
540    boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
541        final List<Intent> targetIntents = target.getAllSourceIntents();
542        for (int i = 0, N = targetIntents.size(); i < N; i++) {
543            final Intent targetIntent = targetIntents.get(i);
544            if (targetIntent.filterEquals(matchingIntent)) {
545                return true;
546            }
547        }
548        return false;
549    }
550
551    void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
552        if (targets == null) {
553            return;
554        }
555
556        final PackageManager pm = getPackageManager();
557        for (int i = targets.size() - 1; i >= 0; i--) {
558            final ChooserTarget target = targets.get(i);
559            final ComponentName targetName = target.getComponentName();
560            if (packageName != null && packageName.equals(targetName.getPackageName())) {
561                // Anything from the original target's package is fine.
562                continue;
563            }
564
565            boolean remove;
566            try {
567                final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
568                remove = !ai.exported || ai.permission != null;
569            } catch (NameNotFoundException e) {
570                Log.e(TAG, "Target " + target + " returned by " + packageName
571                        + " component not found");
572                remove = true;
573            }
574
575            if (remove) {
576                targets.remove(i);
577            }
578        }
579    }
580
581    @Override
582    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
583            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
584            boolean filterLastUsed) {
585        final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
586                initialIntents, rList, launchedFromUid, filterLastUsed);
587        if (DEBUG) Log.d(TAG, "Adapter created; querying services");
588        queryTargetServices(adapter);
589        return adapter;
590    }
591
592    final class ChooserTargetInfo implements TargetInfo {
593        private final DisplayResolveInfo mSourceInfo;
594        private final ResolveInfo mBackupResolveInfo;
595        private final ChooserTarget mChooserTarget;
596        private Drawable mBadgeIcon = null;
597        private CharSequence mBadgeContentDescription;
598        private Drawable mDisplayIcon;
599        private final Intent mFillInIntent;
600        private final int mFillInFlags;
601        private final float mModifiedScore;
602
603        public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
604                float modifiedScore) {
605            mSourceInfo = sourceInfo;
606            mChooserTarget = chooserTarget;
607            mModifiedScore = modifiedScore;
608            if (sourceInfo != null) {
609                final ResolveInfo ri = sourceInfo.getResolveInfo();
610                if (ri != null) {
611                    final ActivityInfo ai = ri.activityInfo;
612                    if (ai != null && ai.applicationInfo != null) {
613                        final PackageManager pm = getPackageManager();
614                        mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
615                        mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
616                    }
617                }
618            }
619            final Icon icon = chooserTarget.getIcon();
620            // TODO do this in the background
621            mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
622
623            if (sourceInfo != null) {
624                mBackupResolveInfo = null;
625            } else {
626                mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
627            }
628
629            mFillInIntent = null;
630            mFillInFlags = 0;
631        }
632
633        private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
634            mSourceInfo = other.mSourceInfo;
635            mBackupResolveInfo = other.mBackupResolveInfo;
636            mChooserTarget = other.mChooserTarget;
637            mBadgeIcon = other.mBadgeIcon;
638            mBadgeContentDescription = other.mBadgeContentDescription;
639            mDisplayIcon = other.mDisplayIcon;
640            mFillInIntent = fillInIntent;
641            mFillInFlags = flags;
642            mModifiedScore = other.mModifiedScore;
643        }
644
645        public float getModifiedScore() {
646            return mModifiedScore;
647        }
648
649        @Override
650        public Intent getResolvedIntent() {
651            if (mSourceInfo != null) {
652                return mSourceInfo.getResolvedIntent();
653            }
654            return getTargetIntent();
655        }
656
657        @Override
658        public ComponentName getResolvedComponentName() {
659            if (mSourceInfo != null) {
660                return mSourceInfo.getResolvedComponentName();
661            } else if (mBackupResolveInfo != null) {
662                return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
663                        mBackupResolveInfo.activityInfo.name);
664            }
665            return null;
666        }
667
668        private Intent getBaseIntentToSend() {
669            Intent result = mSourceInfo != null
670                    ? mSourceInfo.getResolvedIntent() : getTargetIntent();
671            if (result == null) {
672                Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
673            } else {
674                result = new Intent(result);
675                if (mFillInIntent != null) {
676                    result.fillIn(mFillInIntent, mFillInFlags);
677                }
678                result.fillIn(mReferrerFillInIntent, 0);
679            }
680            return result;
681        }
682
683        @Override
684        public boolean start(Activity activity, Bundle options) {
685            throw new RuntimeException("ChooserTargets should be started as caller.");
686        }
687
688        @Override
689        public boolean startAsCaller(Activity activity, Bundle options, int userId) {
690            final Intent intent = getBaseIntentToSend();
691            if (intent == null) {
692                return false;
693            }
694            intent.setComponent(mChooserTarget.getComponentName());
695            intent.putExtras(mChooserTarget.getIntentExtras());
696            activity.startActivityAsCaller(intent, options, true, userId);
697            return true;
698        }
699
700        @Override
701        public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
702            throw new RuntimeException("ChooserTargets should be started as caller.");
703        }
704
705        @Override
706        public ResolveInfo getResolveInfo() {
707            return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
708        }
709
710        @Override
711        public CharSequence getDisplayLabel() {
712            return mChooserTarget.getTitle();
713        }
714
715        @Override
716        public CharSequence getExtendedInfo() {
717            // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
718            return null;
719        }
720
721        @Override
722        public Drawable getDisplayIcon() {
723            return mDisplayIcon;
724        }
725
726        @Override
727        public Drawable getBadgeIcon() {
728            return mBadgeIcon;
729        }
730
731        @Override
732        public CharSequence getBadgeContentDescription() {
733            return mBadgeContentDescription;
734        }
735
736        @Override
737        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
738            return new ChooserTargetInfo(this, fillInIntent, flags);
739        }
740
741        @Override
742        public List<Intent> getAllSourceIntents() {
743            final List<Intent> results = new ArrayList<>();
744            if (mSourceInfo != null) {
745                // We only queried the service for the first one in our sourceinfo.
746                results.add(mSourceInfo.getAllSourceIntents().get(0));
747            }
748            return results;
749        }
750
751        @Override
752        public boolean isPinned() {
753            return mSourceInfo != null ? mSourceInfo.isPinned() : false;
754        }
755    }
756
757    public class ChooserListAdapter extends ResolveListAdapter {
758        public static final int TARGET_BAD = -1;
759        public static final int TARGET_CALLER = 0;
760        public static final int TARGET_SERVICE = 1;
761        public static final int TARGET_STANDARD = 2;
762
763        private static final int MAX_SERVICE_TARGETS = 8;
764        private static final int MAX_TARGETS_PER_SERVICE = 4;
765
766        private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
767        private final List<TargetInfo> mCallerTargets = new ArrayList<>();
768
769        private float mLateFee = 1.f;
770
771        private final BaseChooserTargetComparator mBaseTargetComparator
772                = new BaseChooserTargetComparator();
773
774        public ChooserListAdapter(Context context, List<Intent> payloadIntents,
775                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
776                boolean filterLastUsed) {
777            // Don't send the initial intents through the shared ResolverActivity path,
778            // we want to separate them into a different section.
779            super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed);
780
781            if (initialIntents != null) {
782                final PackageManager pm = getPackageManager();
783                for (int i = 0; i < initialIntents.length; i++) {
784                    final Intent ii = initialIntents[i];
785                    if (ii == null) {
786                        continue;
787                    }
788                    final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
789                    if (ai == null) {
790                        Log.w(TAG, "No activity found for " + ii);
791                        continue;
792                    }
793                    ResolveInfo ri = new ResolveInfo();
794                    ri.activityInfo = ai;
795                    UserManager userManager =
796                            (UserManager) getSystemService(Context.USER_SERVICE);
797                    if (ii instanceof LabeledIntent) {
798                        LabeledIntent li = (LabeledIntent)ii;
799                        ri.resolvePackageName = li.getSourcePackage();
800                        ri.labelRes = li.getLabelResource();
801                        ri.nonLocalizedLabel = li.getNonLocalizedLabel();
802                        ri.icon = li.getIconResource();
803                        ri.iconResourceId = ri.icon;
804                    }
805                    if (userManager.isManagedProfile()) {
806                        ri.noResourceId = true;
807                        ri.icon = 0;
808                    }
809                    mCallerTargets.add(new DisplayResolveInfo(ii, ri,
810                            ri.loadLabel(pm), null, ii));
811                }
812            }
813        }
814
815        @Override
816        public boolean showsExtendedInfo(TargetInfo info) {
817            // We have badges so we don't need this text shown.
818            return false;
819        }
820
821        @Override
822        public boolean isComponentPinned(ComponentName name) {
823            return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
824        }
825
826        @Override
827        public float getScore(DisplayResolveInfo target) {
828            float score = super.getScore(target);
829            if (target.isPinned()) {
830                score += PINNED_TARGET_SCORE_BOOST;
831            }
832            return score;
833        }
834
835        @Override
836        public View onCreateView(ViewGroup parent) {
837            return mInflater.inflate(
838                    com.android.internal.R.layout.resolve_grid_item, parent, false);
839        }
840
841        @Override
842        public void onListRebuilt() {
843            if (mServiceTargets != null) {
844                pruneServiceTargets();
845            }
846        }
847
848        @Override
849        public boolean shouldGetResolvedFilter() {
850            return true;
851        }
852
853        @Override
854        public int getCount() {
855            return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
856        }
857
858        @Override
859        public int getUnfilteredCount() {
860            return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
861        }
862
863        public int getCallerTargetCount() {
864            return mCallerTargets.size();
865        }
866
867        public int getServiceTargetCount() {
868            return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
869        }
870
871        public int getStandardTargetCount() {
872            return super.getCount();
873        }
874
875        public int getPositionTargetType(int position) {
876            int offset = 0;
877
878            final int callerTargetCount = getCallerTargetCount();
879            if (position < callerTargetCount) {
880                return TARGET_CALLER;
881            }
882            offset += callerTargetCount;
883
884            final int serviceTargetCount = getServiceTargetCount();
885            if (position - offset < serviceTargetCount) {
886                return TARGET_SERVICE;
887            }
888            offset += serviceTargetCount;
889
890            final int standardTargetCount = super.getCount();
891            if (position - offset < standardTargetCount) {
892                return TARGET_STANDARD;
893            }
894
895            return TARGET_BAD;
896        }
897
898        @Override
899        public TargetInfo getItem(int position) {
900            return targetInfoForPosition(position, true);
901        }
902
903        @Override
904        public TargetInfo targetInfoForPosition(int position, boolean filtered) {
905            int offset = 0;
906
907            final int callerTargetCount = getCallerTargetCount();
908            if (position < callerTargetCount) {
909                return mCallerTargets.get(position);
910            }
911            offset += callerTargetCount;
912
913            final int serviceTargetCount = getServiceTargetCount();
914            if (position - offset < serviceTargetCount) {
915                return mServiceTargets.get(position - offset);
916            }
917            offset += serviceTargetCount;
918
919            return filtered ? super.getItem(position - offset)
920                    : getDisplayInfoAt(position - offset);
921        }
922
923        public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
924            if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
925                    + " targets");
926            final float parentScore = getScore(origTarget);
927            Collections.sort(targets, mBaseTargetComparator);
928            float lastScore = 0;
929            for (int i = 0, N = Math.min(targets.size(), MAX_TARGETS_PER_SERVICE); i < N; i++) {
930                final ChooserTarget target = targets.get(i);
931                float targetScore = target.getScore();
932                targetScore *= parentScore;
933                targetScore *= mLateFee;
934                if (i > 0 && targetScore >= lastScore) {
935                    // Apply a decay so that the top app can't crowd out everything else.
936                    // This incents ChooserTargetServices to define what's truly better.
937                    targetScore = lastScore * 0.95f;
938                }
939                insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
940
941                if (DEBUG) {
942                    Log.d(TAG, " => " + target.toString() + " score=" + targetScore
943                            + " base=" + target.getScore()
944                            + " lastScore=" + lastScore
945                            + " parentScore=" + parentScore
946                            + " lateFee=" + mLateFee);
947                }
948
949                lastScore = targetScore;
950            }
951
952            mLateFee *= 0.95f;
953
954            notifyDataSetChanged();
955        }
956
957        private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
958            final float newScore = chooserTargetInfo.getModifiedScore();
959            for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
960                final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
961                if (newScore > serviceTarget.getModifiedScore()) {
962                    mServiceTargets.add(i, chooserTargetInfo);
963                    return;
964                }
965            }
966            mServiceTargets.add(chooserTargetInfo);
967        }
968
969        private void pruneServiceTargets() {
970            if (DEBUG) Log.d(TAG, "pruneServiceTargets");
971            for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
972                final ChooserTargetInfo cti = mServiceTargets.get(i);
973                if (!hasResolvedTarget(cti.getResolveInfo())) {
974                    if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
975                    mServiceTargets.remove(i);
976                }
977            }
978        }
979    }
980
981    static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
982        @Override
983        public int compare(ChooserTarget lhs, ChooserTarget rhs) {
984            // Descending order
985            return (int) Math.signum(rhs.getScore() - lhs.getScore());
986        }
987    }
988
989    static class RowScale {
990        private static final int DURATION = 400;
991
992        float mScale;
993        ChooserRowAdapter mAdapter;
994        private final ObjectAnimator mAnimator;
995
996        public static final FloatProperty<RowScale> PROPERTY =
997                new FloatProperty<RowScale>("scale") {
998            @Override
999            public void setValue(RowScale object, float value) {
1000                object.mScale = value;
1001                object.mAdapter.notifyDataSetChanged();
1002            }
1003
1004            @Override
1005            public Float get(RowScale object) {
1006                return object.mScale;
1007            }
1008        };
1009
1010        public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) {
1011            mAdapter = adapter;
1012            mScale = from;
1013            if (from == to) {
1014                mAnimator = null;
1015                return;
1016            }
1017
1018            mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
1019        }
1020
1021        public RowScale setInterpolator(Interpolator interpolator) {
1022            if (mAnimator != null) {
1023                mAnimator.setInterpolator(interpolator);
1024            }
1025            return this;
1026        }
1027
1028        public float get() {
1029            return mScale;
1030        }
1031
1032        public void startAnimation() {
1033            if (mAnimator != null) {
1034                mAnimator.start();
1035            }
1036        }
1037
1038        public void cancelAnimation() {
1039            if (mAnimator != null) {
1040                mAnimator.cancel();
1041            }
1042        }
1043    }
1044
1045    class ChooserRowAdapter extends BaseAdapter {
1046        private ChooserListAdapter mChooserListAdapter;
1047        private final LayoutInflater mLayoutInflater;
1048        private final int mColumnCount = 4;
1049        private RowScale[] mServiceTargetScale;
1050        private final Interpolator mInterpolator;
1051
1052        public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
1053            mChooserListAdapter = wrappedAdapter;
1054            mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
1055
1056            mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this,
1057                    android.R.interpolator.decelerate_quint);
1058
1059            wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
1060                @Override
1061                public void onChanged() {
1062                    super.onChanged();
1063                    final int rcount = getServiceTargetRowCount();
1064                    if (mServiceTargetScale == null
1065                            || mServiceTargetScale.length != rcount) {
1066                        RowScale[] old = mServiceTargetScale;
1067                        int oldRCount = old != null ? old.length : 0;
1068                        mServiceTargetScale = new RowScale[rcount];
1069                        if (old != null && rcount > 0) {
1070                            System.arraycopy(old, 0, mServiceTargetScale, 0,
1071                                    Math.min(old.length, rcount));
1072                        }
1073
1074                        for (int i = rcount; i < oldRCount; i++) {
1075                            old[i].cancelAnimation();
1076                        }
1077
1078                        for (int i = oldRCount; i < rcount; i++) {
1079                            final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
1080                                    .setInterpolator(mInterpolator);
1081                            mServiceTargetScale[i] = rs;
1082                        }
1083
1084                        // Start the animations in a separate loop.
1085                        // The process of starting animations will result in
1086                        // binding views to set up initial values, and we must
1087                        // have ALL of the new RowScale objects created above before
1088                        // we get started.
1089                        for (int i = oldRCount; i < rcount; i++) {
1090                            mServiceTargetScale[i].startAnimation();
1091                        }
1092                    }
1093
1094                    notifyDataSetChanged();
1095                }
1096
1097                @Override
1098                public void onInvalidated() {
1099                    super.onInvalidated();
1100                    notifyDataSetInvalidated();
1101                    if (mServiceTargetScale != null) {
1102                        for (RowScale rs : mServiceTargetScale) {
1103                            rs.cancelAnimation();
1104                        }
1105                    }
1106                }
1107            });
1108        }
1109
1110        private float getRowScale(int rowPosition) {
1111            final int start = getCallerTargetRowCount();
1112            final int end = start + getServiceTargetRowCount();
1113            if (rowPosition >= start && rowPosition < end) {
1114                return mServiceTargetScale[rowPosition - start].get();
1115            }
1116            return 1.f;
1117        }
1118
1119        @Override
1120        public int getCount() {
1121            return (int) (
1122                    getCallerTargetRowCount()
1123                    + getServiceTargetRowCount()
1124                    + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
1125            );
1126        }
1127
1128        public int getCallerTargetRowCount() {
1129            return (int) Math.ceil(
1130                    (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
1131        }
1132
1133        public int getServiceTargetRowCount() {
1134            return (int) Math.ceil(
1135                    (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
1136        }
1137
1138        @Override
1139        public Object getItem(int position) {
1140            // We have nothing useful to return here.
1141            return position;
1142        }
1143
1144        @Override
1145        public long getItemId(int position) {
1146            return position;
1147        }
1148
1149        @Override
1150        public View getView(int position, View convertView, ViewGroup parent) {
1151            final RowViewHolder holder;
1152            if (convertView == null) {
1153                holder = createViewHolder(parent);
1154            } else {
1155                holder = (RowViewHolder) convertView.getTag();
1156            }
1157            bindViewHolder(position, holder);
1158
1159            return holder.row;
1160        }
1161
1162        RowViewHolder createViewHolder(ViewGroup parent) {
1163            final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
1164                    parent, false);
1165            final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
1166            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1167
1168            for (int i = 0; i < mColumnCount; i++) {
1169                final View v = mChooserListAdapter.createView(row);
1170                final int column = i;
1171                v.setOnClickListener(new OnClickListener() {
1172                    @Override
1173                    public void onClick(View v) {
1174                        startSelected(holder.itemIndices[column], false, true);
1175                    }
1176                });
1177                v.setOnLongClickListener(new OnLongClickListener() {
1178                    @Override
1179                    public boolean onLongClick(View v) {
1180                        showTargetDetails(
1181                                mChooserListAdapter.resolveInfoForPosition(
1182                                        holder.itemIndices[column], true));
1183                        return true;
1184                    }
1185                });
1186                row.addView(v);
1187                holder.cells[i] = v;
1188
1189                // Force height to be a given so we don't have visual disruption during scaling.
1190                LayoutParams lp = v.getLayoutParams();
1191                v.measure(spec, spec);
1192                if (lp == null) {
1193                    lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
1194                    row.setLayoutParams(lp);
1195                } else {
1196                    lp.height = v.getMeasuredHeight();
1197                }
1198            }
1199
1200            // Pre-measure so we can scale later.
1201            holder.measure();
1202            LayoutParams lp = row.getLayoutParams();
1203            if (lp == null) {
1204                lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
1205                row.setLayoutParams(lp);
1206            } else {
1207                lp.height = holder.measuredRowHeight;
1208            }
1209            row.setTag(holder);
1210            return holder;
1211        }
1212
1213        void bindViewHolder(int rowPosition, RowViewHolder holder) {
1214            final int start = getFirstRowPosition(rowPosition);
1215            final int startType = mChooserListAdapter.getPositionTargetType(start);
1216
1217            int end = start + mColumnCount - 1;
1218            while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
1219                end--;
1220            }
1221
1222            if (startType == ChooserListAdapter.TARGET_SERVICE) {
1223                holder.row.setBackgroundColor(
1224                        getColor(R.color.chooser_service_row_background_color));
1225            } else {
1226                holder.row.setBackgroundColor(Color.TRANSPARENT);
1227            }
1228
1229            final int oldHeight = holder.row.getLayoutParams().height;
1230            holder.row.getLayoutParams().height = Math.max(1,
1231                    (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
1232            if (holder.row.getLayoutParams().height != oldHeight) {
1233                holder.row.requestLayout();
1234            }
1235
1236            for (int i = 0; i < mColumnCount; i++) {
1237                final View v = holder.cells[i];
1238                if (start + i <= end) {
1239                    v.setVisibility(View.VISIBLE);
1240                    holder.itemIndices[i] = start + i;
1241                    mChooserListAdapter.bindView(holder.itemIndices[i], v);
1242                } else {
1243                    v.setVisibility(View.GONE);
1244                }
1245            }
1246        }
1247
1248        int getFirstRowPosition(int row) {
1249            final int callerCount = mChooserListAdapter.getCallerTargetCount();
1250            final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1251
1252            if (row < callerRows) {
1253                return row * mColumnCount;
1254            }
1255
1256            final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1257            final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1258
1259            if (row < callerRows + serviceRows) {
1260                return callerCount + (row - callerRows) * mColumnCount;
1261            }
1262
1263            return callerCount + serviceCount
1264                    + (row - callerRows - serviceRows) * mColumnCount;
1265        }
1266    }
1267
1268    static class RowViewHolder {
1269        final View[] cells;
1270        final ViewGroup row;
1271        int measuredRowHeight;
1272        int[] itemIndices;
1273
1274        public RowViewHolder(ViewGroup row, int cellCount) {
1275            this.row = row;
1276            this.cells = new View[cellCount];
1277            this.itemIndices = new int[cellCount];
1278        }
1279
1280        public void measure() {
1281            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1282            row.measure(spec, spec);
1283            measuredRowHeight = row.getMeasuredHeight();
1284        }
1285    }
1286
1287    static class ChooserTargetServiceConnection implements ServiceConnection {
1288        private final DisplayResolveInfo mOriginalTarget;
1289        private ComponentName mConnectedComponent;
1290        private ChooserActivity mChooserActivity;
1291        private final Object mLock = new Object();
1292
1293        private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1294            @Override
1295            public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1296                synchronized (mLock) {
1297                    if (mChooserActivity == null) {
1298                        Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
1299                                + mConnectedComponent + "; ignoring...");
1300                        return;
1301                    }
1302                    mChooserActivity.filterServiceTargets(
1303                            mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
1304                    final Message msg = Message.obtain();
1305                    msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1306                    msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1307                            ChooserTargetServiceConnection.this);
1308                    mChooserActivity.mChooserHandler.sendMessage(msg);
1309                }
1310            }
1311        };
1312
1313        public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
1314                DisplayResolveInfo dri) {
1315            mChooserActivity = chooserActivity;
1316            mOriginalTarget = dri;
1317        }
1318
1319        @Override
1320        public void onServiceConnected(ComponentName name, IBinder service) {
1321            if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1322            synchronized (mLock) {
1323                if (mChooserActivity == null) {
1324                    Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
1325                    return;
1326                }
1327
1328                final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1329                try {
1330                    icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1331                            mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1332                } catch (RemoteException e) {
1333                    Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1334                    mChooserActivity.unbindService(this);
1335                    destroy();
1336                    mChooserActivity.mServiceConnections.remove(this);
1337                }
1338            }
1339        }
1340
1341        @Override
1342        public void onServiceDisconnected(ComponentName name) {
1343            if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1344            synchronized (mLock) {
1345                if (mChooserActivity == null) {
1346                    Log.e(TAG,
1347                            "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
1348                    return;
1349                }
1350
1351                mChooserActivity.unbindService(this);
1352                destroy();
1353                mChooserActivity.mServiceConnections.remove(this);
1354                if (mChooserActivity.mServiceConnections.isEmpty()) {
1355                    mChooserActivity.mChooserHandler.removeMessages(
1356                            CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1357                    mChooserActivity.sendVoiceChoicesIfNeeded();
1358                }
1359                mConnectedComponent = null;
1360            }
1361        }
1362
1363        public void destroy() {
1364            synchronized (mLock) {
1365                mChooserActivity = null;
1366            }
1367        }
1368
1369        @Override
1370        public String toString() {
1371            return "ChooserTargetServiceConnection{service="
1372                    + mConnectedComponent + ", activity="
1373                    + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}";
1374        }
1375    }
1376
1377    static class ServiceResultInfo {
1378        public final DisplayResolveInfo originalTarget;
1379        public final List<ChooserTarget> resultTargets;
1380        public final ChooserTargetServiceConnection connection;
1381
1382        public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1383                ChooserTargetServiceConnection c) {
1384            originalTarget = ot;
1385            resultTargets = rt;
1386            connection = c;
1387        }
1388    }
1389
1390    static class RefinementResultReceiver extends ResultReceiver {
1391        private ChooserActivity mChooserActivity;
1392        private TargetInfo mSelectedTarget;
1393
1394        public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1395                Handler handler) {
1396            super(handler);
1397            mChooserActivity = host;
1398            mSelectedTarget = target;
1399        }
1400
1401        @Override
1402        protected void onReceiveResult(int resultCode, Bundle resultData) {
1403            if (mChooserActivity == null) {
1404                Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1405                return;
1406            }
1407            if (resultData == null) {
1408                Log.e(TAG, "RefinementResultReceiver received null resultData");
1409                return;
1410            }
1411
1412            switch (resultCode) {
1413                case RESULT_CANCELED:
1414                    mChooserActivity.onRefinementCanceled();
1415                    break;
1416                case RESULT_OK:
1417                    Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1418                    if (intentParcelable instanceof Intent) {
1419                        mChooserActivity.onRefinementResult(mSelectedTarget,
1420                                (Intent) intentParcelable);
1421                    } else {
1422                        Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1423                                + " in resultData with key Intent.EXTRA_INTENT");
1424                    }
1425                    break;
1426                default:
1427                    Log.w(TAG, "Unknown result code " + resultCode
1428                            + " sent to RefinementResultReceiver");
1429                    break;
1430            }
1431        }
1432
1433        public void destroy() {
1434            mChooserActivity = null;
1435            mSelectedTarget = null;
1436        }
1437    }
1438
1439    class OffsetDataSetObserver extends DataSetObserver {
1440        private final AbsListView mListView;
1441        private int mCachedViewType = -1;
1442        private View mCachedView;
1443
1444        public OffsetDataSetObserver(AbsListView listView) {
1445            mListView = listView;
1446        }
1447
1448        @Override
1449        public void onChanged() {
1450            if (mResolverDrawerLayout == null) {
1451                return;
1452            }
1453
1454            final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
1455            int offset = 0;
1456            for (int i = 0; i < chooserTargetRows; i++)  {
1457                final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
1458                final int vt = mChooserRowAdapter.getItemViewType(pos);
1459                if (vt != mCachedViewType) {
1460                    mCachedView = null;
1461                }
1462                final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
1463                int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
1464
1465                offset += (int) (height * mChooserRowAdapter.getRowScale(pos));
1466
1467                if (vt >= 0) {
1468                    mCachedViewType = vt;
1469                    mCachedView = v;
1470                } else {
1471                    mCachedViewType = -1;
1472                }
1473            }
1474
1475            mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
1476        }
1477    }
1478}
1479