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