ChooserActivity.java revision 2388251fcd8def03a6bbc91382e84db085cf4253
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
765        private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
766        private final List<TargetInfo> mCallerTargets = new ArrayList<>();
767
768        private float mLateFee = 1.f;
769
770        private final BaseChooserTargetComparator mBaseTargetComparator
771                = new BaseChooserTargetComparator();
772
773        public ChooserListAdapter(Context context, List<Intent> payloadIntents,
774                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
775                boolean filterLastUsed) {
776            // Don't send the initial intents through the shared ResolverActivity path,
777            // we want to separate them into a different section.
778            super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed);
779
780            if (initialIntents != null) {
781                final PackageManager pm = getPackageManager();
782                for (int i = 0; i < initialIntents.length; i++) {
783                    final Intent ii = initialIntents[i];
784                    if (ii == null) {
785                        continue;
786                    }
787                    final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
788                    if (ai == null) {
789                        Log.w(TAG, "No activity found for " + ii);
790                        continue;
791                    }
792                    ResolveInfo ri = new ResolveInfo();
793                    ri.activityInfo = ai;
794                    UserManager userManager =
795                            (UserManager) getSystemService(Context.USER_SERVICE);
796                    if (ii instanceof LabeledIntent) {
797                        LabeledIntent li = (LabeledIntent)ii;
798                        ri.resolvePackageName = li.getSourcePackage();
799                        ri.labelRes = li.getLabelResource();
800                        ri.nonLocalizedLabel = li.getNonLocalizedLabel();
801                        ri.icon = li.getIconResource();
802                        ri.iconResourceId = ri.icon;
803                    }
804                    if (userManager.isManagedProfile()) {
805                        ri.noResourceId = true;
806                        ri.icon = 0;
807                    }
808                    mCallerTargets.add(new DisplayResolveInfo(ii, ri,
809                            ri.loadLabel(pm), null, ii));
810                }
811            }
812        }
813
814        @Override
815        public boolean showsExtendedInfo(TargetInfo info) {
816            // We have badges so we don't need this text shown.
817            return false;
818        }
819
820        @Override
821        public boolean isComponentPinned(ComponentName name) {
822            return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
823        }
824
825        @Override
826        public float getScore(DisplayResolveInfo target) {
827            float score = super.getScore(target);
828            if (target.isPinned()) {
829                score += PINNED_TARGET_SCORE_BOOST;
830            }
831            return score;
832        }
833
834        @Override
835        public View onCreateView(ViewGroup parent) {
836            return mInflater.inflate(
837                    com.android.internal.R.layout.resolve_grid_item, parent, false);
838        }
839
840        @Override
841        public void onListRebuilt() {
842            if (mServiceTargets != null) {
843                pruneServiceTargets();
844            }
845        }
846
847        @Override
848        public boolean shouldGetResolvedFilter() {
849            return true;
850        }
851
852        @Override
853        public int getCount() {
854            return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
855        }
856
857        @Override
858        public int getUnfilteredCount() {
859            return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
860        }
861
862        public int getCallerTargetCount() {
863            return mCallerTargets.size();
864        }
865
866        public int getServiceTargetCount() {
867            return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
868        }
869
870        public int getStandardTargetCount() {
871            return super.getCount();
872        }
873
874        public int getPositionTargetType(int position) {
875            int offset = 0;
876
877            final int callerTargetCount = getCallerTargetCount();
878            if (position < callerTargetCount) {
879                return TARGET_CALLER;
880            }
881            offset += callerTargetCount;
882
883            final int serviceTargetCount = getServiceTargetCount();
884            if (position - offset < serviceTargetCount) {
885                return TARGET_SERVICE;
886            }
887            offset += serviceTargetCount;
888
889            final int standardTargetCount = super.getCount();
890            if (position - offset < standardTargetCount) {
891                return TARGET_STANDARD;
892            }
893
894            return TARGET_BAD;
895        }
896
897        @Override
898        public TargetInfo getItem(int position) {
899            return targetInfoForPosition(position, true);
900        }
901
902        @Override
903        public TargetInfo targetInfoForPosition(int position, boolean filtered) {
904            int offset = 0;
905
906            final int callerTargetCount = getCallerTargetCount();
907            if (position < callerTargetCount) {
908                return mCallerTargets.get(position);
909            }
910            offset += callerTargetCount;
911
912            final int serviceTargetCount = getServiceTargetCount();
913            if (position - offset < serviceTargetCount) {
914                return mServiceTargets.get(position - offset);
915            }
916            offset += serviceTargetCount;
917
918            return filtered ? super.getItem(position - offset)
919                    : getDisplayInfoAt(position - offset);
920        }
921
922        public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
923            if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
924                    + " targets");
925            final float parentScore = getScore(origTarget);
926            Collections.sort(targets, mBaseTargetComparator);
927            float lastScore = 0;
928            for (int i = 0, N = targets.size(); i < N; i++) {
929                final ChooserTarget target = targets.get(i);
930                float targetScore = target.getScore();
931                targetScore *= parentScore;
932                targetScore *= mLateFee;
933                if (i > 0 && targetScore >= lastScore) {
934                    // Apply a decay so that the top app can't crowd out everything else.
935                    // This incents ChooserTargetServices to define what's truly better.
936                    targetScore = lastScore * 0.95f;
937                }
938                insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
939
940                if (DEBUG) {
941                    Log.d(TAG, " => " + target.toString() + " score=" + targetScore
942                            + " base=" + target.getScore()
943                            + " lastScore=" + lastScore
944                            + " parentScore=" + parentScore
945                            + " lateFee=" + mLateFee);
946                }
947
948                lastScore = targetScore;
949            }
950
951            mLateFee *= 0.95f;
952
953            notifyDataSetChanged();
954        }
955
956        private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
957            final float newScore = chooserTargetInfo.getModifiedScore();
958            for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
959                final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
960                if (newScore > serviceTarget.getModifiedScore()) {
961                    mServiceTargets.add(i, chooserTargetInfo);
962                    return;
963                }
964            }
965            mServiceTargets.add(chooserTargetInfo);
966        }
967
968        private void pruneServiceTargets() {
969            if (DEBUG) Log.d(TAG, "pruneServiceTargets");
970            for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
971                final ChooserTargetInfo cti = mServiceTargets.get(i);
972                if (!hasResolvedTarget(cti.getResolveInfo())) {
973                    if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
974                    mServiceTargets.remove(i);
975                }
976            }
977        }
978    }
979
980    static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
981        @Override
982        public int compare(ChooserTarget lhs, ChooserTarget rhs) {
983            // Descending order
984            return (int) Math.signum(rhs.getScore() - lhs.getScore());
985        }
986    }
987
988    static class RowScale {
989        private static final int DURATION = 400;
990
991        float mScale;
992        ChooserRowAdapter mAdapter;
993        private final ObjectAnimator mAnimator;
994
995        public static final FloatProperty<RowScale> PROPERTY =
996                new FloatProperty<RowScale>("scale") {
997            @Override
998            public void setValue(RowScale object, float value) {
999                object.mScale = value;
1000                object.mAdapter.notifyDataSetChanged();
1001            }
1002
1003            @Override
1004            public Float get(RowScale object) {
1005                return object.mScale;
1006            }
1007        };
1008
1009        public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) {
1010            mAdapter = adapter;
1011            mScale = from;
1012            if (from == to) {
1013                mAnimator = null;
1014                return;
1015            }
1016
1017            mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
1018        }
1019
1020        public RowScale setInterpolator(Interpolator interpolator) {
1021            if (mAnimator != null) {
1022                mAnimator.setInterpolator(interpolator);
1023            }
1024            return this;
1025        }
1026
1027        public float get() {
1028            return mScale;
1029        }
1030
1031        public void startAnimation() {
1032            if (mAnimator != null) {
1033                mAnimator.start();
1034            }
1035        }
1036
1037        public void cancelAnimation() {
1038            if (mAnimator != null) {
1039                mAnimator.cancel();
1040            }
1041        }
1042    }
1043
1044    class ChooserRowAdapter extends BaseAdapter {
1045        private ChooserListAdapter mChooserListAdapter;
1046        private final LayoutInflater mLayoutInflater;
1047        private final int mColumnCount = 4;
1048        private RowScale[] mServiceTargetScale;
1049        private final Interpolator mInterpolator;
1050
1051        public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
1052            mChooserListAdapter = wrappedAdapter;
1053            mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
1054
1055            mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this,
1056                    android.R.interpolator.decelerate_quint);
1057
1058            wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
1059                @Override
1060                public void onChanged() {
1061                    super.onChanged();
1062                    final int rcount = getServiceTargetRowCount();
1063                    if (mServiceTargetScale == null
1064                            || mServiceTargetScale.length != rcount) {
1065                        RowScale[] old = mServiceTargetScale;
1066                        int oldRCount = old != null ? old.length : 0;
1067                        mServiceTargetScale = new RowScale[rcount];
1068                        if (old != null && rcount > 0) {
1069                            System.arraycopy(old, 0, mServiceTargetScale, 0,
1070                                    Math.min(old.length, rcount));
1071                        }
1072
1073                        for (int i = rcount; i < oldRCount; i++) {
1074                            old[i].cancelAnimation();
1075                        }
1076
1077                        for (int i = oldRCount; i < rcount; i++) {
1078                            final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
1079                                    .setInterpolator(mInterpolator);
1080                            mServiceTargetScale[i] = rs;
1081                        }
1082
1083                        // Start the animations in a separate loop.
1084                        // The process of starting animations will result in
1085                        // binding views to set up initial values, and we must
1086                        // have ALL of the new RowScale objects created above before
1087                        // we get started.
1088                        for (int i = oldRCount; i < rcount; i++) {
1089                            mServiceTargetScale[i].startAnimation();
1090                        }
1091                    }
1092
1093                    notifyDataSetChanged();
1094                }
1095
1096                @Override
1097                public void onInvalidated() {
1098                    super.onInvalidated();
1099                    notifyDataSetInvalidated();
1100                    if (mServiceTargetScale != null) {
1101                        for (RowScale rs : mServiceTargetScale) {
1102                            rs.cancelAnimation();
1103                        }
1104                    }
1105                }
1106            });
1107        }
1108
1109        private float getRowScale(int rowPosition) {
1110            final int start = getCallerTargetRowCount();
1111            final int end = start + getServiceTargetRowCount();
1112            if (rowPosition >= start && rowPosition < end) {
1113                return mServiceTargetScale[rowPosition - start].get();
1114            }
1115            return 1.f;
1116        }
1117
1118        @Override
1119        public int getCount() {
1120            return (int) (
1121                    getCallerTargetRowCount()
1122                    + getServiceTargetRowCount()
1123                    + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
1124            );
1125        }
1126
1127        public int getCallerTargetRowCount() {
1128            return (int) Math.ceil(
1129                    (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
1130        }
1131
1132        public int getServiceTargetRowCount() {
1133            return (int) Math.ceil(
1134                    (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
1135        }
1136
1137        @Override
1138        public Object getItem(int position) {
1139            // We have nothing useful to return here.
1140            return position;
1141        }
1142
1143        @Override
1144        public long getItemId(int position) {
1145            return position;
1146        }
1147
1148        @Override
1149        public View getView(int position, View convertView, ViewGroup parent) {
1150            final RowViewHolder holder;
1151            if (convertView == null) {
1152                holder = createViewHolder(parent);
1153            } else {
1154                holder = (RowViewHolder) convertView.getTag();
1155            }
1156            bindViewHolder(position, holder);
1157
1158            return holder.row;
1159        }
1160
1161        RowViewHolder createViewHolder(ViewGroup parent) {
1162            final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
1163                    parent, false);
1164            final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
1165            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1166
1167            for (int i = 0; i < mColumnCount; i++) {
1168                final View v = mChooserListAdapter.createView(row);
1169                final int column = i;
1170                v.setOnClickListener(new OnClickListener() {
1171                    @Override
1172                    public void onClick(View v) {
1173                        startSelected(holder.itemIndices[column], false, true);
1174                    }
1175                });
1176                v.setOnLongClickListener(new OnLongClickListener() {
1177                    @Override
1178                    public boolean onLongClick(View v) {
1179                        showTargetDetails(
1180                                mChooserListAdapter.resolveInfoForPosition(
1181                                        holder.itemIndices[column], true));
1182                        return true;
1183                    }
1184                });
1185                row.addView(v);
1186                holder.cells[i] = v;
1187
1188                // Force height to be a given so we don't have visual disruption during scaling.
1189                LayoutParams lp = v.getLayoutParams();
1190                v.measure(spec, spec);
1191                if (lp == null) {
1192                    lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
1193                    row.setLayoutParams(lp);
1194                } else {
1195                    lp.height = v.getMeasuredHeight();
1196                }
1197            }
1198
1199            // Pre-measure so we can scale later.
1200            holder.measure();
1201            LayoutParams lp = row.getLayoutParams();
1202            if (lp == null) {
1203                lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
1204                row.setLayoutParams(lp);
1205            } else {
1206                lp.height = holder.measuredRowHeight;
1207            }
1208            row.setTag(holder);
1209            return holder;
1210        }
1211
1212        void bindViewHolder(int rowPosition, RowViewHolder holder) {
1213            final int start = getFirstRowPosition(rowPosition);
1214            final int startType = mChooserListAdapter.getPositionTargetType(start);
1215
1216            int end = start + mColumnCount - 1;
1217            while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
1218                end--;
1219            }
1220
1221            if (startType == ChooserListAdapter.TARGET_SERVICE) {
1222                holder.row.setBackgroundColor(
1223                        getColor(R.color.chooser_service_row_background_color));
1224            } else {
1225                holder.row.setBackgroundColor(Color.TRANSPARENT);
1226            }
1227
1228            final int oldHeight = holder.row.getLayoutParams().height;
1229            holder.row.getLayoutParams().height = Math.max(1,
1230                    (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
1231            if (holder.row.getLayoutParams().height != oldHeight) {
1232                holder.row.requestLayout();
1233            }
1234
1235            for (int i = 0; i < mColumnCount; i++) {
1236                final View v = holder.cells[i];
1237                if (start + i <= end) {
1238                    v.setVisibility(View.VISIBLE);
1239                    holder.itemIndices[i] = start + i;
1240                    mChooserListAdapter.bindView(holder.itemIndices[i], v);
1241                } else {
1242                    v.setVisibility(View.GONE);
1243                }
1244            }
1245        }
1246
1247        int getFirstRowPosition(int row) {
1248            final int callerCount = mChooserListAdapter.getCallerTargetCount();
1249            final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1250
1251            if (row < callerRows) {
1252                return row * mColumnCount;
1253            }
1254
1255            final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1256            final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1257
1258            if (row < callerRows + serviceRows) {
1259                return callerCount + (row - callerRows) * mColumnCount;
1260            }
1261
1262            return callerCount + serviceCount
1263                    + (row - callerRows - serviceRows) * mColumnCount;
1264        }
1265    }
1266
1267    static class RowViewHolder {
1268        final View[] cells;
1269        final ViewGroup row;
1270        int measuredRowHeight;
1271        int[] itemIndices;
1272
1273        public RowViewHolder(ViewGroup row, int cellCount) {
1274            this.row = row;
1275            this.cells = new View[cellCount];
1276            this.itemIndices = new int[cellCount];
1277        }
1278
1279        public void measure() {
1280            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1281            row.measure(spec, spec);
1282            measuredRowHeight = row.getMeasuredHeight();
1283        }
1284    }
1285
1286    static class ChooserTargetServiceConnection implements ServiceConnection {
1287        private final DisplayResolveInfo mOriginalTarget;
1288        private ComponentName mConnectedComponent;
1289        private ChooserActivity mChooserActivity;
1290        private final Object mLock = new Object();
1291
1292        private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1293            @Override
1294            public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1295                synchronized (mLock) {
1296                    if (mChooserActivity == null) {
1297                        Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
1298                                + mConnectedComponent + "; ignoring...");
1299                        return;
1300                    }
1301                    mChooserActivity.filterServiceTargets(
1302                            mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
1303                    final Message msg = Message.obtain();
1304                    msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1305                    msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1306                            ChooserTargetServiceConnection.this);
1307                    mChooserActivity.mChooserHandler.sendMessage(msg);
1308                }
1309            }
1310        };
1311
1312        public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
1313                DisplayResolveInfo dri) {
1314            mChooserActivity = chooserActivity;
1315            mOriginalTarget = dri;
1316        }
1317
1318        @Override
1319        public void onServiceConnected(ComponentName name, IBinder service) {
1320            if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1321            synchronized (mLock) {
1322                if (mChooserActivity == null) {
1323                    Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
1324                    return;
1325                }
1326
1327                final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1328                try {
1329                    icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1330                            mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1331                } catch (RemoteException e) {
1332                    Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1333                    mChooserActivity.unbindService(this);
1334                    destroy();
1335                    mChooserActivity.mServiceConnections.remove(this);
1336                }
1337            }
1338        }
1339
1340        @Override
1341        public void onServiceDisconnected(ComponentName name) {
1342            if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1343            synchronized (mLock) {
1344                if (mChooserActivity == null) {
1345                    Log.e(TAG,
1346                            "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
1347                    return;
1348                }
1349
1350                mChooserActivity.unbindService(this);
1351                destroy();
1352                mChooserActivity.mServiceConnections.remove(this);
1353                if (mChooserActivity.mServiceConnections.isEmpty()) {
1354                    mChooserActivity.mChooserHandler.removeMessages(
1355                            CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1356                    mChooserActivity.sendVoiceChoicesIfNeeded();
1357                }
1358                mConnectedComponent = null;
1359            }
1360        }
1361
1362        public void destroy() {
1363            synchronized (mLock) {
1364                mChooserActivity = null;
1365            }
1366        }
1367
1368        @Override
1369        public String toString() {
1370            return "ChooserTargetServiceConnection{service="
1371                    + mConnectedComponent + ", activity="
1372                    + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}";
1373        }
1374    }
1375
1376    static class ServiceResultInfo {
1377        public final DisplayResolveInfo originalTarget;
1378        public final List<ChooserTarget> resultTargets;
1379        public final ChooserTargetServiceConnection connection;
1380
1381        public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1382                ChooserTargetServiceConnection c) {
1383            originalTarget = ot;
1384            resultTargets = rt;
1385            connection = c;
1386        }
1387    }
1388
1389    static class RefinementResultReceiver extends ResultReceiver {
1390        private ChooserActivity mChooserActivity;
1391        private TargetInfo mSelectedTarget;
1392
1393        public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1394                Handler handler) {
1395            super(handler);
1396            mChooserActivity = host;
1397            mSelectedTarget = target;
1398        }
1399
1400        @Override
1401        protected void onReceiveResult(int resultCode, Bundle resultData) {
1402            if (mChooserActivity == null) {
1403                Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1404                return;
1405            }
1406            if (resultData == null) {
1407                Log.e(TAG, "RefinementResultReceiver received null resultData");
1408                return;
1409            }
1410
1411            switch (resultCode) {
1412                case RESULT_CANCELED:
1413                    mChooserActivity.onRefinementCanceled();
1414                    break;
1415                case RESULT_OK:
1416                    Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1417                    if (intentParcelable instanceof Intent) {
1418                        mChooserActivity.onRefinementResult(mSelectedTarget,
1419                                (Intent) intentParcelable);
1420                    } else {
1421                        Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1422                                + " in resultData with key Intent.EXTRA_INTENT");
1423                    }
1424                    break;
1425                default:
1426                    Log.w(TAG, "Unknown result code " + resultCode
1427                            + " sent to RefinementResultReceiver");
1428                    break;
1429            }
1430        }
1431
1432        public void destroy() {
1433            mChooserActivity = null;
1434            mSelectedTarget = null;
1435        }
1436    }
1437
1438    class OffsetDataSetObserver extends DataSetObserver {
1439        private final AbsListView mListView;
1440        private int mCachedViewType = -1;
1441        private View mCachedView;
1442
1443        public OffsetDataSetObserver(AbsListView listView) {
1444            mListView = listView;
1445        }
1446
1447        @Override
1448        public void onChanged() {
1449            if (mResolverDrawerLayout == null) {
1450                return;
1451            }
1452
1453            final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
1454            int offset = 0;
1455            for (int i = 0; i < chooserTargetRows; i++)  {
1456                final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
1457                final int vt = mChooserRowAdapter.getItemViewType(pos);
1458                if (vt != mCachedViewType) {
1459                    mCachedView = null;
1460                }
1461                final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
1462                int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
1463
1464                offset += (int) (height * mChooserRowAdapter.getRowScale(pos));
1465
1466                if (vt >= 0) {
1467                    mCachedViewType = vt;
1468                    mCachedView = v;
1469                } else {
1470                    mCachedViewType = -1;
1471                }
1472            }
1473
1474            mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
1475        }
1476    }
1477}
1478