OSDN Git Service

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