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