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