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