• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = 2000;
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
onCreate(Bundle savedInstanceState)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 
getPinnedSharedPrefs(Context context)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
onDestroy()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
getReplacementIntent(ActivityInfo aInfo, Intent defIntent)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
onActivityStarted(TargetInfo cti)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
onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter)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
getLayoutResource()372     public int getLayoutResource() {
373         return R.layout.chooser_grid;
374     }
375 
376     @Override
shouldGetActivityMetadata()377     public boolean shouldGetActivityMetadata() {
378         return true;
379     }
380 
381     @Override
shouldAutoLaunchSingleChoice(TargetInfo target)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
showTargetDetails(ResolveInfo ri)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 
modifyTargetIntent(Intent in)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
onTargetSelected(TargetInfo target, boolean alwaysCheck)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
startSelected(int which, boolean always, boolean filtered)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 
queryTargetServices(ChooserListAdapter adapter)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 
convertServiceName(String packageName, String serviceName)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 
unbindRemainingServices()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 
onSetupVoiceInteraction()590     public void onSetupVoiceInteraction() {
591         // Do nothing. We'll send the voice stuff ourselves.
592     }
593 
updateModelAndChooserCounts(TargetInfo info)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 
onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent)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 
onRefinementCanceled()636     void onRefinementCanceled() {
637         if (mRefinementResultReceiver != null) {
638             mRefinementResultReceiver.destroy();
639             mRefinementResultReceiver = null;
640         }
641         finish();
642     }
643 
checkTargetSourceIntent(TargetInfo target, Intent matchingIntent)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 
filterServiceTargets(String packageName, List<ChooserTarget> targets)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 {
ChooserListController(Context context, PackageManager pm, Intent targetIntent, String referrerPackageName, int launchedFromUid)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
isComponentPinned(ComponentName name)695         boolean isComponentPinned(ComponentName name) {
696             return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
697         }
698 
699         @Override
isComponentFiltered(ComponentName name)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
getScore(DisplayResolveInfo target)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
createAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed)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
createListController()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 
ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget, float modifiedScore)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 
ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags)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 
getModifiedScore()797         public float getModifiedScore() {
798             return mModifiedScore;
799         }
800 
801         @Override
getResolvedIntent()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
getResolvedComponentName()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 
getBaseIntentToSend()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
start(Activity activity, Bundle options)839         public boolean start(Activity activity, Bundle options) {
840             throw new RuntimeException("ChooserTargets should be started as caller.");
841         }
842 
843         @Override
startAsCaller(Activity activity, Bundle options, int userId)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
startAsUser(Activity activity, Bundle options, UserHandle user)868         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
869             throw new RuntimeException("ChooserTargets should be started as caller.");
870         }
871 
872         @Override
getResolveInfo()873         public ResolveInfo getResolveInfo() {
874             return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
875         }
876 
877         @Override
getDisplayLabel()878         public CharSequence getDisplayLabel() {
879             return mChooserTarget.getTitle();
880         }
881 
882         @Override
getExtendedInfo()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
getDisplayIcon()889         public Drawable getDisplayIcon() {
890             return mDisplayIcon;
891         }
892 
893         @Override
getBadgeIcon()894         public Drawable getBadgeIcon() {
895             return mBadgeIcon;
896         }
897 
898         @Override
getBadgeContentDescription()899         public CharSequence getBadgeContentDescription() {
900             return mBadgeContentDescription;
901         }
902 
903         @Override
cloneFilledIn(Intent fillInIntent, int flags)904         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
905             return new ChooserTargetInfo(this, fillInIntent, flags);
906         }
907 
908         @Override
getAllSourceIntents()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
isPinned()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 
ChooserListAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed, ResolverListController resolverListController)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
showsExtendedInfo(TargetInfo info)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
isComponentPinned(ComponentName name)1009         public boolean isComponentPinned(ComponentName name) {
1010             return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
1011         }
1012 
1013         @Override
onCreateView(ViewGroup parent)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
onListRebuilt()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
shouldGetResolvedFilter()1029         public boolean shouldGetResolvedFilter() {
1030             return true;
1031         }
1032 
1033         @Override
getCount()1034         public int getCount() {
1035             return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
1036         }
1037 
1038         @Override
getUnfilteredCount()1039         public int getUnfilteredCount() {
1040             return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
1041         }
1042 
getCallerTargetCount()1043         public int getCallerTargetCount() {
1044             return mCallerTargets.size();
1045         }
1046 
getServiceTargetCount()1047         public int getServiceTargetCount() {
1048             if (!mShowServiceTargets) {
1049                 return 0;
1050             }
1051             return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
1052         }
1053 
getStandardTargetCount()1054         public int getStandardTargetCount() {
1055             return super.getCount();
1056         }
1057 
getPositionTargetType(int position)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
getItem(int position)1082         public TargetInfo getItem(int position) {
1083             return targetInfoForPosition(position, true);
1084         }
1085 
1086         @Override
targetInfoForPosition(int position, boolean filtered)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 
addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets)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          */
setShowServiceTargets(boolean show)1143         public void setShowServiceTargets(boolean show) {
1144             if (show != mShowServiceTargets) {
1145                 mShowServiceTargets = show;
1146                 notifyDataSetChanged();
1147             }
1148         }
1149 
insertServiceTarget(ChooserTargetInfo chooserTargetInfo)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 
pruneServiceTargets()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
compare(ChooserTarget lhs, ChooserTarget rhs)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 
RowScale(@onNull ChooserRowAdapter adapter, float from, float to)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 
setInterpolator(Interpolator interpolator)1225         public RowScale setInterpolator(Interpolator interpolator) {
1226             if (mAnimator != null) {
1227                 mAnimator.setInterpolator(interpolator);
1228             }
1229             return this;
1230         }
1231 
get()1232         public float get() {
1233             return mScale;
1234         }
1235 
startAnimation()1236         public void startAnimation() {
1237             if (mAnimator != null) {
1238                 mAnimator.start();
1239             }
1240         }
1241 
cancelAnimation()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 
ChooserRowAdapter(ChooserListAdapter wrappedAdapter)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 
getRowScale(int rowPosition)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 
onAnimationStart()1324         public void onAnimationStart() {
1325             final boolean lock = mAnimationCount == 0;
1326             mAnimationCount++;
1327             if (lock) {
1328                 mResolverDrawerLayout.setDismissLocked(true);
1329             }
1330         }
1331 
onAnimationEnd()1332         public void onAnimationEnd() {
1333             mAnimationCount--;
1334             if (mAnimationCount == 0) {
1335                 mResolverDrawerLayout.setDismissLocked(false);
1336             }
1337         }
1338 
1339         @Override
getCount()1340         public int getCount() {
1341             return (int) (
1342                     getCallerTargetRowCount()
1343                     + getServiceTargetRowCount()
1344                     + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
1345             );
1346         }
1347 
getCallerTargetRowCount()1348         public int getCallerTargetRowCount() {
1349             return (int) Math.ceil(
1350                     (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
1351         }
1352 
getServiceTargetRowCount()1353         public int getServiceTargetRowCount() {
1354             return (int) Math.ceil(
1355                     (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
1356         }
1357 
1358         @Override
getItem(int position)1359         public Object getItem(int position) {
1360             // We have nothing useful to return here.
1361             return position;
1362         }
1363 
1364         @Override
getItemId(int position)1365         public long getItemId(int position) {
1366             return position;
1367         }
1368 
1369         @Override
getView(int position, View convertView, ViewGroup parent)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 
createViewHolder(ViewGroup parent)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 
bindViewHolder(int rowPosition, RowViewHolder holder)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                 if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
1454                     // if the row is the only row for target service
1455                     setVertPadding(holder, 0, 0);
1456                 } else {
1457                     int top = rowPosition == 0 ? serviceSpacing : 0;
1458                     if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
1459                         setVertPadding(holder, top, serviceSpacing);
1460                     } else {
1461                         setVertPadding(holder, top, 0);
1462                     }
1463                 }
1464             } else {
1465                 holder.row.setBackgroundColor(Color.TRANSPARENT);
1466                 int lastStartType = mChooserListAdapter.getPositionTargetType(
1467                         getFirstRowPosition(rowPosition - 1));
1468                 if (lastStartType == ChooserListAdapter.TARGET_SERVICE || rowPosition == 0) {
1469                     int serviceSpacing = holder.row.getContext().getResources()
1470                             .getDimensionPixelSize(R.dimen.chooser_service_spacing);
1471                     setVertPadding(holder, serviceSpacing, 0);
1472                 } else {
1473                     setVertPadding(holder, 0, 0);
1474                 }
1475             }
1476 
1477             final int oldHeight = holder.row.getLayoutParams().height;
1478             holder.row.getLayoutParams().height = Math.max(1,
1479                     (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
1480             if (holder.row.getLayoutParams().height != oldHeight) {
1481                 holder.row.requestLayout();
1482             }
1483 
1484             for (int i = 0; i < mColumnCount; i++) {
1485                 final View v = holder.cells[i];
1486                 if (start + i <= end) {
1487                     v.setVisibility(View.VISIBLE);
1488                     holder.itemIndices[i] = start + i;
1489                     mChooserListAdapter.bindView(holder.itemIndices[i], v);
1490                 } else {
1491                     v.setVisibility(View.INVISIBLE);
1492                 }
1493             }
1494         }
1495 
setVertPadding(RowViewHolder holder, int top, int bottom)1496         private void setVertPadding(RowViewHolder holder, int top, int bottom) {
1497             holder.row.setPadding(holder.row.getPaddingLeft(), top,
1498                     holder.row.getPaddingRight(), bottom);
1499         }
1500 
getFirstRowPosition(int row)1501         int getFirstRowPosition(int row) {
1502             final int callerCount = mChooserListAdapter.getCallerTargetCount();
1503             final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
1504 
1505             if (row < callerRows) {
1506                 return row * mColumnCount;
1507             }
1508 
1509             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
1510             final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
1511 
1512             if (row < callerRows + serviceRows) {
1513                 return callerCount + (row - callerRows) * mColumnCount;
1514             }
1515 
1516             return callerCount + serviceCount
1517                     + (row - callerRows - serviceRows) * mColumnCount;
1518         }
1519     }
1520 
1521     static class RowViewHolder {
1522         final View[] cells;
1523         final ViewGroup row;
1524         int measuredRowHeight;
1525         int[] itemIndices;
1526 
RowViewHolder(ViewGroup row, int cellCount)1527         public RowViewHolder(ViewGroup row, int cellCount) {
1528             this.row = row;
1529             this.cells = new View[cellCount];
1530             this.itemIndices = new int[cellCount];
1531         }
1532 
measure()1533         public void measure() {
1534             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1535             row.measure(spec, spec);
1536             measuredRowHeight = row.getMeasuredHeight();
1537         }
1538     }
1539 
1540     static class ChooserTargetServiceConnection implements ServiceConnection {
1541         private DisplayResolveInfo mOriginalTarget;
1542         private ComponentName mConnectedComponent;
1543         private ChooserActivity mChooserActivity;
1544         private final Object mLock = new Object();
1545 
1546         private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
1547             @Override
1548             public void sendResult(List<ChooserTarget> targets) throws RemoteException {
1549                 synchronized (mLock) {
1550                     if (mChooserActivity == null) {
1551                         Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
1552                                 + mConnectedComponent + "; ignoring...");
1553                         return;
1554                     }
1555                     mChooserActivity.filterServiceTargets(
1556                             mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
1557                     final Message msg = Message.obtain();
1558                     msg.what = CHOOSER_TARGET_SERVICE_RESULT;
1559                     msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
1560                             ChooserTargetServiceConnection.this);
1561                     mChooserActivity.mChooserHandler.sendMessage(msg);
1562                 }
1563             }
1564         };
1565 
ChooserTargetServiceConnection(ChooserActivity chooserActivity, DisplayResolveInfo dri)1566         public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
1567                 DisplayResolveInfo dri) {
1568             mChooserActivity = chooserActivity;
1569             mOriginalTarget = dri;
1570         }
1571 
1572         @Override
onServiceConnected(ComponentName name, IBinder service)1573         public void onServiceConnected(ComponentName name, IBinder service) {
1574             if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
1575             synchronized (mLock) {
1576                 if (mChooserActivity == null) {
1577                     Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
1578                     return;
1579                 }
1580 
1581                 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
1582                 try {
1583                     icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
1584                             mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
1585                 } catch (RemoteException e) {
1586                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
1587                     mChooserActivity.unbindService(this);
1588                     mChooserActivity.mServiceConnections.remove(this);
1589                     destroy();
1590                 }
1591             }
1592         }
1593 
1594         @Override
onServiceDisconnected(ComponentName name)1595         public void onServiceDisconnected(ComponentName name) {
1596             if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
1597             synchronized (mLock) {
1598                 if (mChooserActivity == null) {
1599                     Log.e(TAG,
1600                             "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
1601                     return;
1602                 }
1603 
1604                 mChooserActivity.unbindService(this);
1605                 mChooserActivity.mServiceConnections.remove(this);
1606                 if (mChooserActivity.mServiceConnections.isEmpty()) {
1607                     mChooserActivity.mChooserHandler.removeMessages(
1608                             CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
1609                     mChooserActivity.sendVoiceChoicesIfNeeded();
1610                 }
1611                 mConnectedComponent = null;
1612                 destroy();
1613             }
1614         }
1615 
destroy()1616         public void destroy() {
1617             synchronized (mLock) {
1618                 mChooserActivity = null;
1619                 mOriginalTarget = null;
1620             }
1621         }
1622 
1623         @Override
toString()1624         public String toString() {
1625             return "ChooserTargetServiceConnection{service="
1626                     + mConnectedComponent + ", activity="
1627                     + (mOriginalTarget != null
1628                     ? mOriginalTarget.getResolveInfo().activityInfo.toString()
1629                     : "<connection destroyed>") + "}";
1630         }
1631     }
1632 
1633     static class ServiceResultInfo {
1634         public final DisplayResolveInfo originalTarget;
1635         public final List<ChooserTarget> resultTargets;
1636         public final ChooserTargetServiceConnection connection;
1637 
ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, ChooserTargetServiceConnection c)1638         public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
1639                 ChooserTargetServiceConnection c) {
1640             originalTarget = ot;
1641             resultTargets = rt;
1642             connection = c;
1643         }
1644     }
1645 
1646     static class RefinementResultReceiver extends ResultReceiver {
1647         private ChooserActivity mChooserActivity;
1648         private TargetInfo mSelectedTarget;
1649 
RefinementResultReceiver(ChooserActivity host, TargetInfo target, Handler handler)1650         public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
1651                 Handler handler) {
1652             super(handler);
1653             mChooserActivity = host;
1654             mSelectedTarget = target;
1655         }
1656 
1657         @Override
onReceiveResult(int resultCode, Bundle resultData)1658         protected void onReceiveResult(int resultCode, Bundle resultData) {
1659             if (mChooserActivity == null) {
1660                 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
1661                 return;
1662             }
1663             if (resultData == null) {
1664                 Log.e(TAG, "RefinementResultReceiver received null resultData");
1665                 return;
1666             }
1667 
1668             switch (resultCode) {
1669                 case RESULT_CANCELED:
1670                     mChooserActivity.onRefinementCanceled();
1671                     break;
1672                 case RESULT_OK:
1673                     Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
1674                     if (intentParcelable instanceof Intent) {
1675                         mChooserActivity.onRefinementResult(mSelectedTarget,
1676                                 (Intent) intentParcelable);
1677                     } else {
1678                         Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
1679                                 + " in resultData with key Intent.EXTRA_INTENT");
1680                     }
1681                     break;
1682                 default:
1683                     Log.w(TAG, "Unknown result code " + resultCode
1684                             + " sent to RefinementResultReceiver");
1685                     break;
1686             }
1687         }
1688 
destroy()1689         public void destroy() {
1690             mChooserActivity = null;
1691             mSelectedTarget = null;
1692         }
1693     }
1694 
1695     class OffsetDataSetObserver extends DataSetObserver {
1696         private final AbsListView mListView;
1697         private int mCachedViewType = -1;
1698         private View mCachedView;
1699 
OffsetDataSetObserver(AbsListView listView)1700         public OffsetDataSetObserver(AbsListView listView) {
1701             mListView = listView;
1702         }
1703 
1704         @Override
onChanged()1705         public void onChanged() {
1706             if (mResolverDrawerLayout == null) {
1707                 return;
1708             }
1709 
1710             final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
1711             int offset = 0;
1712             for (int i = 0; i < chooserTargetRows; i++)  {
1713                 final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
1714                 final int vt = mChooserRowAdapter.getItemViewType(pos);
1715                 if (vt != mCachedViewType) {
1716                     mCachedView = null;
1717                 }
1718                 final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
1719                 int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
1720 
1721                 offset += (int) (height * mChooserRowAdapter.getRowScale(pos));
1722 
1723                 if (vt >= 0) {
1724                     mCachedViewType = vt;
1725                     mCachedView = v;
1726                 } else {
1727                     mCachedViewType = -1;
1728                 }
1729             }
1730 
1731             mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
1732         }
1733     }
1734 }
1735