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