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