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