OSDN Git Service

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