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