ChooserActivity.java revision a182e45c6851a8db89e8b0900f0812806ff295d4
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.app.Activity; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentSender; 24import android.content.IntentSender.SendIntentException; 25import android.content.ServiceConnection; 26import android.content.pm.ActivityInfo; 27import android.content.pm.LabeledIntent; 28import android.content.pm.PackageManager; 29import android.content.pm.PackageManager.NameNotFoundException; 30import android.content.pm.ResolveInfo; 31import android.database.DataSetObserver; 32import android.graphics.drawable.Drawable; 33import android.graphics.drawable.Icon; 34import android.os.Bundle; 35import android.os.Handler; 36import android.os.IBinder; 37import android.os.Message; 38import android.os.Parcelable; 39import android.os.RemoteException; 40import android.os.ResultReceiver; 41import android.os.UserHandle; 42import android.os.UserManager; 43import android.service.chooser.ChooserTarget; 44import android.service.chooser.ChooserTargetService; 45import android.service.chooser.IChooserTargetResult; 46import android.service.chooser.IChooserTargetService; 47import android.text.TextUtils; 48import android.util.Log; 49import android.util.Slog; 50import android.view.LayoutInflater; 51import android.view.View; 52import android.view.View.OnClickListener; 53import android.view.View.OnLongClickListener; 54import android.view.ViewGroup; 55import android.widget.AbsListView; 56import android.widget.BaseAdapter; 57import android.widget.ListView; 58import com.android.internal.R; 59import com.android.internal.logging.MetricsLogger; 60 61import java.util.ArrayList; 62import java.util.Collections; 63import java.util.Comparator; 64import java.util.List; 65 66public class ChooserActivity extends ResolverActivity { 67 private static final String TAG = "ChooserActivity"; 68 69 private static final boolean DEBUG = false; 70 71 private static final int QUERY_TARGET_SERVICE_LIMIT = 5; 72 private static final int WATCHDOG_TIMEOUT_MILLIS = 5000; 73 74 private Bundle mReplacementExtras; 75 private IntentSender mChosenComponentSender; 76 private IntentSender mRefinementIntentSender; 77 private RefinementResultReceiver mRefinementResultReceiver; 78 79 private Intent mReferrerFillInIntent; 80 81 private ChooserListAdapter mChooserListAdapter; 82 83 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); 84 85 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1; 86 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2; 87 88 private final Handler mChooserHandler = new Handler() { 89 @Override 90 public void handleMessage(Message msg) { 91 switch (msg.what) { 92 case CHOOSER_TARGET_SERVICE_RESULT: 93 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT"); 94 if (isDestroyed()) break; 95 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; 96 if (!mServiceConnections.contains(sri.connection)) { 97 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection 98 + " returned after being removed from active connections." 99 + " Have you considered returning results faster?"); 100 break; 101 } 102 if (sri.resultTargets != null) { 103 mChooserListAdapter.addServiceResults(sri.originalTarget, 104 sri.resultTargets); 105 } 106 unbindService(sri.connection); 107 mServiceConnections.remove(sri.connection); 108 if (mServiceConnections.isEmpty()) { 109 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 110 sendVoiceChoicesIfNeeded(); 111 } 112 break; 113 114 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT: 115 if (DEBUG) { 116 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services"); 117 } 118 unbindRemainingServices(); 119 sendVoiceChoicesIfNeeded(); 120 break; 121 122 default: 123 super.handleMessage(msg); 124 } 125 } 126 }; 127 128 @Override 129 protected void onCreate(Bundle savedInstanceState) { 130 Intent intent = getIntent(); 131 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT); 132 if (!(targetParcelable instanceof Intent)) { 133 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable); 134 finish(); 135 super.onCreate(null); 136 return; 137 } 138 Intent target = (Intent) targetParcelable; 139 if (target != null) { 140 modifyTargetIntent(target); 141 } 142 Parcelable[] targetsParcelable 143 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS); 144 if (targetsParcelable != null) { 145 final boolean offset = target == null; 146 Intent[] additionalTargets = 147 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length]; 148 for (int i = 0; i < targetsParcelable.length; i++) { 149 if (!(targetsParcelable[i] instanceof Intent)) { 150 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: " 151 + targetsParcelable[i]); 152 finish(); 153 super.onCreate(null); 154 return; 155 } 156 final Intent additionalTarget = (Intent) targetsParcelable[i]; 157 if (i == 0 && target == null) { 158 target = additionalTarget; 159 modifyTargetIntent(target); 160 } else { 161 additionalTargets[offset ? i - 1 : i] = additionalTarget; 162 modifyTargetIntent(additionalTarget); 163 } 164 } 165 setAdditionalTargets(additionalTargets); 166 } 167 168 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); 169 CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); 170 int defaultTitleRes = 0; 171 if (title == null) { 172 defaultTitleRes = com.android.internal.R.string.chooseActivity; 173 } 174 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS); 175 Intent[] initialIntents = null; 176 if (pa != null) { 177 initialIntents = new Intent[pa.length]; 178 for (int i=0; i<pa.length; i++) { 179 if (!(pa[i] instanceof Intent)) { 180 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]); 181 finish(); 182 super.onCreate(null); 183 return; 184 } 185 final Intent in = (Intent) pa[i]; 186 modifyTargetIntent(in); 187 initialIntents[i] = in; 188 } 189 } 190 191 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer()); 192 193 mChosenComponentSender = intent.getParcelableExtra( 194 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); 195 mRefinementIntentSender = intent.getParcelableExtra( 196 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); 197 setSafeForwardingMode(true); 198 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, 199 null, false); 200 201 MetricsLogger.action(this, MetricsLogger.ACTION_ACTIVITY_CHOOSER_SHOWN); 202 } 203 204 @Override 205 protected void onDestroy() { 206 super.onDestroy(); 207 if (mRefinementResultReceiver != null) { 208 mRefinementResultReceiver.destroy(); 209 mRefinementResultReceiver = null; 210 } 211 } 212 213 @Override 214 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 215 Intent result = defIntent; 216 if (mReplacementExtras != null) { 217 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName); 218 if (replExtras != null) { 219 result = new Intent(defIntent); 220 result.putExtras(replExtras); 221 } 222 } 223 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER) 224 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) { 225 result = Intent.createChooser(result, 226 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE)); 227 } 228 return result; 229 } 230 231 @Override 232 void onActivityStarted(TargetInfo cti) { 233 if (mChosenComponentSender != null) { 234 final ComponentName target = cti.getResolvedComponentName(); 235 if (target != null) { 236 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target); 237 try { 238 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null); 239 } catch (IntentSender.SendIntentException e) { 240 Slog.e(TAG, "Unable to launch supplied IntentSender to report " 241 + "the chosen component: " + e); 242 } 243 } 244 } 245 } 246 247 @Override 248 void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, 249 boolean alwaysUseOption) { 250 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; 251 mChooserListAdapter = (ChooserListAdapter) adapter; 252 adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter)); 253 if (listView != null) { 254 listView.setItemsCanFocus(true); 255 } 256 } 257 258 @Override 259 int getLayoutResource() { 260 return R.layout.chooser_grid; 261 } 262 263 @Override 264 boolean shouldGetActivityMetadata() { 265 return true; 266 } 267 268 private void modifyTargetIntent(Intent in) { 269 final String action = in.getAction(); 270 if (Intent.ACTION_SEND.equals(action) || 271 Intent.ACTION_SEND_MULTIPLE.equals(action)) { 272 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | 273 Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 274 } 275 } 276 277 @Override 278 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { 279 if (mRefinementIntentSender != null) { 280 final Intent fillIn = new Intent(); 281 final List<Intent> sourceIntents = target.getAllSourceIntents(); 282 if (!sourceIntents.isEmpty()) { 283 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0)); 284 if (sourceIntents.size() > 1) { 285 final Intent[] alts = new Intent[sourceIntents.size() - 1]; 286 for (int i = 1, N = sourceIntents.size(); i < N; i++) { 287 alts[i - 1] = sourceIntents.get(i); 288 } 289 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts); 290 } 291 if (mRefinementResultReceiver != null) { 292 mRefinementResultReceiver.destroy(); 293 } 294 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null); 295 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER, 296 mRefinementResultReceiver); 297 try { 298 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null); 299 return false; 300 } catch (SendIntentException e) { 301 Log.e(TAG, "Refinement IntentSender failed to send", e); 302 } 303 } 304 } 305 return super.onTargetSelected(target, alwaysCheck); 306 } 307 308 @Override 309 void startSelected(int which, boolean always, boolean filtered) { 310 super.startSelected(which, always, filtered); 311 312 if (mChooserListAdapter != null) { 313 // Log the index of which type of target the user picked. 314 // Lower values mean the ranking was better. 315 int cat = 0; 316 int value = which; 317 switch (mChooserListAdapter.getPositionTargetType(which)) { 318 case ChooserListAdapter.TARGET_CALLER: 319 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET; 320 break; 321 case ChooserListAdapter.TARGET_SERVICE: 322 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET; 323 value -= mChooserListAdapter.getCallerTargetCount(); 324 break; 325 case ChooserListAdapter.TARGET_STANDARD: 326 cat = MetricsLogger.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET; 327 value -= mChooserListAdapter.getCallerTargetCount() 328 + mChooserListAdapter.getServiceTargetCount(); 329 break; 330 } 331 332 if (cat != 0) { 333 MetricsLogger.action(this, cat, value); 334 } 335 } 336 } 337 338 void queryTargetServices(ChooserListAdapter adapter) { 339 final PackageManager pm = getPackageManager(); 340 int targetsToQuery = 0; 341 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) { 342 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); 343 final ActivityInfo ai = dri.getResolveInfo().activityInfo; 344 final Bundle md = ai.metaData; 345 final String serviceName = md != null ? convertServiceName(ai.packageName, 346 md.getString(ChooserTargetService.META_DATA_NAME)) : null; 347 if (serviceName != null) { 348 final ComponentName serviceComponent = new ComponentName( 349 ai.packageName, serviceName); 350 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE) 351 .setComponent(serviceComponent); 352 353 if (DEBUG) { 354 Log.d(TAG, "queryTargets found target with service " + serviceComponent); 355 } 356 357 try { 358 final String perm = pm.getServiceInfo(serviceComponent, 0).permission; 359 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) { 360 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require" 361 + " permission " + ChooserTargetService.BIND_PERMISSION 362 + " - this service will not be queried for ChooserTargets." 363 + " add android:permission=\"" 364 + ChooserTargetService.BIND_PERMISSION + "\"" 365 + " to the <service> tag for " + serviceComponent 366 + " in the manifest."); 367 continue; 368 } 369 } catch (NameNotFoundException e) { 370 Log.e(TAG, "Could not look up service " + serviceComponent, e); 371 continue; 372 } 373 374 final ChooserTargetServiceConnection conn = new ChooserTargetServiceConnection(dri); 375 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND, 376 UserHandle.CURRENT)) { 377 if (DEBUG) { 378 Log.d(TAG, "Binding service connection for target " + dri 379 + " intent " + serviceIntent); 380 } 381 mServiceConnections.add(conn); 382 targetsToQuery++; 383 } 384 } 385 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) { 386 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " 387 + QUERY_TARGET_SERVICE_LIMIT); 388 break; 389 } 390 } 391 392 if (!mServiceConnections.isEmpty()) { 393 if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for " 394 + WATCHDOG_TIMEOUT_MILLIS + "ms"); 395 mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT, 396 WATCHDOG_TIMEOUT_MILLIS); 397 } else { 398 sendVoiceChoicesIfNeeded(); 399 } 400 } 401 402 private String convertServiceName(String packageName, String serviceName) { 403 if (TextUtils.isEmpty(serviceName)) { 404 return null; 405 } 406 407 final String fullName; 408 if (serviceName.startsWith(".")) { 409 // Relative to the app package. Prepend the app package name. 410 fullName = packageName + serviceName; 411 } else if (serviceName.indexOf('.') >= 0) { 412 // Fully qualified package name. 413 fullName = serviceName; 414 } else { 415 fullName = null; 416 } 417 return fullName; 418 } 419 420 void unbindRemainingServices() { 421 if (DEBUG) { 422 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left"); 423 } 424 for (int i = 0, N = mServiceConnections.size(); i < N; i++) { 425 final ChooserTargetServiceConnection conn = mServiceConnections.get(i); 426 if (DEBUG) Log.d(TAG, "unbinding " + conn); 427 unbindService(conn); 428 } 429 mServiceConnections.clear(); 430 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 431 } 432 433 void onSetupVoiceInteraction() { 434 // Do nothing. We'll send the voice stuff ourselves. 435 } 436 437 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { 438 if (mRefinementResultReceiver != null) { 439 mRefinementResultReceiver.destroy(); 440 mRefinementResultReceiver = null; 441 } 442 443 if (selectedTarget == null) { 444 Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); 445 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { 446 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget 447 + " cannot match refined source intent " + matchingIntent); 448 } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) { 449 finish(); 450 return; 451 } 452 onRefinementCanceled(); 453 } 454 455 void onRefinementCanceled() { 456 if (mRefinementResultReceiver != null) { 457 mRefinementResultReceiver.destroy(); 458 mRefinementResultReceiver = null; 459 } 460 finish(); 461 } 462 463 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) { 464 final List<Intent> targetIntents = target.getAllSourceIntents(); 465 for (int i = 0, N = targetIntents.size(); i < N; i++) { 466 final Intent targetIntent = targetIntents.get(i); 467 if (targetIntent.filterEquals(matchingIntent)) { 468 return true; 469 } 470 } 471 return false; 472 } 473 474 @Override 475 ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, 476 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 477 boolean filterLastUsed) { 478 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents, 479 initialIntents, rList, launchedFromUid, filterLastUsed); 480 if (DEBUG) Log.d(TAG, "Adapter created; querying services"); 481 queryTargetServices(adapter); 482 return adapter; 483 } 484 485 final class ChooserTargetInfo implements TargetInfo { 486 private final DisplayResolveInfo mSourceInfo; 487 private final ResolveInfo mBackupResolveInfo; 488 private final ChooserTarget mChooserTarget; 489 private Drawable mBadgeIcon = null; 490 private Drawable mDisplayIcon; 491 private final Intent mFillInIntent; 492 private final int mFillInFlags; 493 private final float mModifiedScore; 494 495 public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget, 496 float modifiedScore) { 497 mSourceInfo = sourceInfo; 498 mChooserTarget = chooserTarget; 499 mModifiedScore = modifiedScore; 500 if (sourceInfo != null) { 501 final ResolveInfo ri = sourceInfo.getResolveInfo(); 502 if (ri != null) { 503 final ActivityInfo ai = ri.activityInfo; 504 if (ai != null && ai.applicationInfo != null) { 505 mBadgeIcon = getPackageManager().getApplicationIcon(ai.applicationInfo); 506 } 507 } 508 } 509 final Icon icon = chooserTarget.getIcon(); 510 // TODO do this in the background 511 mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null; 512 513 if (sourceInfo != null) { 514 mBackupResolveInfo = null; 515 } else { 516 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0); 517 } 518 519 mFillInIntent = null; 520 mFillInFlags = 0; 521 } 522 523 private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) { 524 mSourceInfo = other.mSourceInfo; 525 mBackupResolveInfo = other.mBackupResolveInfo; 526 mChooserTarget = other.mChooserTarget; 527 mBadgeIcon = other.mBadgeIcon; 528 mDisplayIcon = other.mDisplayIcon; 529 mFillInIntent = fillInIntent; 530 mFillInFlags = flags; 531 mModifiedScore = other.mModifiedScore; 532 } 533 534 public float getModifiedScore() { 535 return mModifiedScore; 536 } 537 538 @Override 539 public Intent getResolvedIntent() { 540 if (mSourceInfo != null) { 541 return mSourceInfo.getResolvedIntent(); 542 } 543 return getTargetIntent(); 544 } 545 546 @Override 547 public ComponentName getResolvedComponentName() { 548 if (mSourceInfo != null) { 549 return mSourceInfo.getResolvedComponentName(); 550 } else if (mBackupResolveInfo != null) { 551 return new ComponentName(mBackupResolveInfo.activityInfo.packageName, 552 mBackupResolveInfo.activityInfo.name); 553 } 554 return null; 555 } 556 557 private Intent getFillInIntent() { 558 Intent result = mSourceInfo != null 559 ? mSourceInfo.getResolvedIntent() : getTargetIntent(); 560 if (result == null) { 561 Log.e(TAG, "ChooserTargetInfo#getFillInIntent: no fillIn intent available"); 562 } else { 563 result = new Intent(result); 564 if (mFillInIntent != null) { 565 result.fillIn(mFillInIntent, mFillInFlags); 566 } 567 result.fillIn(mReferrerFillInIntent, 0); 568 } 569 return result; 570 } 571 572 @Override 573 public boolean start(Activity activity, Bundle options) { 574 final Intent intent = getFillInIntent(); 575 if (intent == null) { 576 return false; 577 } 578 return mChooserTarget.sendIntent(activity, intent); 579 } 580 581 @Override 582 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 583 final Intent intent = getFillInIntent(); 584 if (intent == null) { 585 return false; 586 } 587 // ChooserTargets will launch with their IntentSender's identity 588 return mChooserTarget.sendIntent(activity, intent); 589 } 590 591 @Override 592 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 593 final Intent intent = getFillInIntent(); 594 if (intent == null) { 595 return false; 596 } 597 // ChooserTargets will launch with their IntentSender's identity 598 return mChooserTarget.sendIntent(activity, intent); 599 } 600 601 @Override 602 public ResolveInfo getResolveInfo() { 603 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo; 604 } 605 606 @Override 607 public CharSequence getDisplayLabel() { 608 return mChooserTarget.getTitle(); 609 } 610 611 @Override 612 public CharSequence getExtendedInfo() { 613 return mSourceInfo != null ? mSourceInfo.getExtendedInfo() : null; 614 } 615 616 @Override 617 public Drawable getDisplayIcon() { 618 return mDisplayIcon; 619 } 620 621 @Override 622 public Drawable getBadgeIcon() { 623 return mBadgeIcon; 624 } 625 626 @Override 627 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { 628 return new ChooserTargetInfo(this, fillInIntent, flags); 629 } 630 631 @Override 632 public List<Intent> getAllSourceIntents() { 633 final List<Intent> results = new ArrayList<>(); 634 if (mSourceInfo != null) { 635 // We only queried the service for the first one in our sourceinfo. 636 results.add(mSourceInfo.getAllSourceIntents().get(0)); 637 } 638 return results; 639 } 640 } 641 642 public class ChooserListAdapter extends ResolveListAdapter { 643 public static final int TARGET_BAD = -1; 644 public static final int TARGET_CALLER = 0; 645 public static final int TARGET_SERVICE = 1; 646 public static final int TARGET_STANDARD = 2; 647 648 private static final int MAX_SERVICE_TARGETS = 8; 649 650 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); 651 private final List<TargetInfo> mCallerTargets = new ArrayList<>(); 652 653 private float mLateFee = 1.f; 654 655 private final BaseChooserTargetComparator mBaseTargetComparator 656 = new BaseChooserTargetComparator(); 657 658 public ChooserListAdapter(Context context, List<Intent> payloadIntents, 659 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 660 boolean filterLastUsed) { 661 // Don't send the initial intents through the shared ResolverActivity path, 662 // we want to separate them into a different section. 663 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed); 664 665 if (initialIntents != null) { 666 final PackageManager pm = getPackageManager(); 667 for (int i = 0; i < initialIntents.length; i++) { 668 final Intent ii = initialIntents[i]; 669 if (ii == null) { 670 continue; 671 } 672 final ActivityInfo ai = ii.resolveActivityInfo(pm, 0); 673 if (ai == null) { 674 Log.w(TAG, "No activity found for " + ii); 675 continue; 676 } 677 ResolveInfo ri = new ResolveInfo(); 678 ri.activityInfo = ai; 679 UserManager userManager = 680 (UserManager) getSystemService(Context.USER_SERVICE); 681 if (ii instanceof LabeledIntent) { 682 LabeledIntent li = (LabeledIntent)ii; 683 ri.resolvePackageName = li.getSourcePackage(); 684 ri.labelRes = li.getLabelResource(); 685 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 686 ri.icon = li.getIconResource(); 687 ri.iconResourceId = ri.icon; 688 } 689 if (userManager.isManagedProfile()) { 690 ri.noResourceId = true; 691 ri.icon = 0; 692 } 693 mCallerTargets.add(new DisplayResolveInfo(ii, ri, 694 ri.loadLabel(pm), null, ii)); 695 } 696 } 697 } 698 699 @Override 700 public boolean showsExtendedInfo(TargetInfo info) { 701 // Reserve space to show extended info if any one of the items in the adapter has 702 // extended info. This keeps grid item sizes uniform. 703 return hasExtendedInfo(); 704 } 705 706 @Override 707 public View onCreateView(ViewGroup parent) { 708 return mInflater.inflate( 709 com.android.internal.R.layout.resolve_grid_item, parent, false); 710 } 711 712 @Override 713 public void onListRebuilt() { 714 if (mServiceTargets != null) { 715 pruneServiceTargets(); 716 } 717 } 718 719 @Override 720 public boolean shouldGetResolvedFilter() { 721 return true; 722 } 723 724 @Override 725 public int getCount() { 726 return super.getCount() + getServiceTargetCount() + getCallerTargetCount(); 727 } 728 729 @Override 730 public int getUnfilteredCount() { 731 return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount(); 732 } 733 734 public int getCallerTargetCount() { 735 return mCallerTargets.size(); 736 } 737 738 public int getServiceTargetCount() { 739 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS); 740 } 741 742 public int getStandardTargetCount() { 743 return super.getCount(); 744 } 745 746 public int getPositionTargetType(int position) { 747 int offset = 0; 748 749 final int callerTargetCount = getCallerTargetCount(); 750 if (position < callerTargetCount) { 751 return TARGET_CALLER; 752 } 753 offset += callerTargetCount; 754 755 final int serviceTargetCount = getServiceTargetCount(); 756 if (position - offset < serviceTargetCount) { 757 return TARGET_SERVICE; 758 } 759 offset += serviceTargetCount; 760 761 final int standardTargetCount = super.getCount(); 762 if (position - offset < standardTargetCount) { 763 return TARGET_STANDARD; 764 } 765 766 return TARGET_BAD; 767 } 768 769 @Override 770 public TargetInfo getItem(int position) { 771 return targetInfoForPosition(position, true); 772 } 773 774 @Override 775 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 776 int offset = 0; 777 778 final int callerTargetCount = getCallerTargetCount(); 779 if (position < callerTargetCount) { 780 return mCallerTargets.get(position); 781 } 782 offset += callerTargetCount; 783 784 final int serviceTargetCount = getServiceTargetCount(); 785 if (position - offset < serviceTargetCount) { 786 return mServiceTargets.get(position - offset); 787 } 788 offset += serviceTargetCount; 789 790 return filtered ? super.getItem(position - offset) 791 : getDisplayInfoAt(position - offset); 792 } 793 794 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { 795 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() 796 + " targets"); 797 final float parentScore = getScore(origTarget); 798 Collections.sort(targets, mBaseTargetComparator); 799 float lastScore = 0; 800 for (int i = 0, N = targets.size(); i < N; i++) { 801 final ChooserTarget target = targets.get(i); 802 float targetScore = target.getScore(); 803 targetScore *= parentScore; 804 targetScore *= mLateFee; 805 if (i > 0 && targetScore >= lastScore) { 806 // Apply a decay so that the top app can't crowd out everything else. 807 // This incents ChooserTargetServices to define what's truly better. 808 targetScore = lastScore * 0.95f; 809 } 810 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore)); 811 812 if (DEBUG) { 813 Log.d(TAG, " => " + target.toString() + " score=" + targetScore 814 + " base=" + target.getScore() 815 + " lastScore=" + lastScore 816 + " parentScore=" + parentScore 817 + " lateFee=" + mLateFee); 818 } 819 820 lastScore = targetScore; 821 } 822 823 mLateFee *= 0.95f; 824 825 notifyDataSetChanged(); 826 } 827 828 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) { 829 final float newScore = chooserTargetInfo.getModifiedScore(); 830 for (int i = 0, N = mServiceTargets.size(); i < N; i++) { 831 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i); 832 if (newScore > serviceTarget.getModifiedScore()) { 833 mServiceTargets.add(i, chooserTargetInfo); 834 return; 835 } 836 } 837 mServiceTargets.add(chooserTargetInfo); 838 } 839 840 private void pruneServiceTargets() { 841 if (DEBUG) Log.d(TAG, "pruneServiceTargets"); 842 for (int i = mServiceTargets.size() - 1; i >= 0; i--) { 843 final ChooserTargetInfo cti = mServiceTargets.get(i); 844 if (!hasResolvedTarget(cti.getResolveInfo())) { 845 if (DEBUG) Log.d(TAG, " => " + i + " " + cti); 846 mServiceTargets.remove(i); 847 } 848 } 849 } 850 } 851 852 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> { 853 @Override 854 public int compare(ChooserTarget lhs, ChooserTarget rhs) { 855 // Descending order 856 return (int) Math.signum(lhs.getScore() - rhs.getScore()); 857 } 858 } 859 860 class ChooserRowAdapter extends BaseAdapter { 861 private ChooserListAdapter mChooserListAdapter; 862 private final LayoutInflater mLayoutInflater; 863 private final int mColumnCount = 4; 864 865 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) { 866 mChooserListAdapter = wrappedAdapter; 867 mLayoutInflater = LayoutInflater.from(ChooserActivity.this); 868 869 wrappedAdapter.registerDataSetObserver(new DataSetObserver() { 870 @Override 871 public void onChanged() { 872 super.onChanged(); 873 notifyDataSetChanged(); 874 } 875 876 @Override 877 public void onInvalidated() { 878 super.onInvalidated(); 879 notifyDataSetInvalidated(); 880 } 881 }); 882 } 883 884 @Override 885 public int getCount() { 886 return (int) ( 887 Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount) 888 + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount) 889 + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) 890 ); 891 } 892 893 @Override 894 public Object getItem(int position) { 895 // We have nothing useful to return here. 896 return position; 897 } 898 899 @Override 900 public long getItemId(int position) { 901 return position; 902 } 903 904 @Override 905 public View getView(int position, View convertView, ViewGroup parent) { 906 final View[] holder; 907 if (convertView == null) { 908 holder = createViewHolder(parent); 909 } else { 910 holder = (View[]) convertView.getTag(); 911 } 912 bindViewHolder(position, holder); 913 914 // We keep the actual list item view as the last item in the holder array 915 return holder[mColumnCount]; 916 } 917 918 View[] createViewHolder(ViewGroup parent) { 919 final View[] holder = new View[mColumnCount + 1]; 920 921 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, 922 parent, false); 923 for (int i = 0; i < mColumnCount; i++) { 924 holder[i] = mChooserListAdapter.createView(row); 925 row.addView(holder[i]); 926 } 927 row.setTag(holder); 928 holder[mColumnCount] = row; 929 return holder; 930 } 931 932 void bindViewHolder(int rowPosition, View[] holder) { 933 final int start = getFirstRowPosition(rowPosition); 934 final int startType = mChooserListAdapter.getPositionTargetType(start); 935 936 int end = start + mColumnCount - 1; 937 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) { 938 end--; 939 } 940 941 final ViewGroup row = (ViewGroup) holder[mColumnCount]; 942 943 if (startType == ChooserListAdapter.TARGET_SERVICE) { 944 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color)); 945 } else { 946 row.setBackground(null); 947 } 948 949 for (int i = 0; i < mColumnCount; i++) { 950 final View v = holder[i]; 951 if (start + i <= end) { 952 v.setVisibility(View.VISIBLE); 953 final int itemIndex = start + i; 954 mChooserListAdapter.bindView(itemIndex, v); 955 v.setOnClickListener(new OnClickListener() { 956 @Override 957 public void onClick(View v) { 958 startSelected(itemIndex, false, true); 959 } 960 }); 961 v.setOnLongClickListener(new OnLongClickListener() { 962 @Override 963 public boolean onLongClick(View v) { 964 showAppDetails( 965 mChooserListAdapter.resolveInfoForPosition(itemIndex, true)); 966 return true; 967 } 968 }); 969 } else { 970 v.setVisibility(View.GONE); 971 } 972 } 973 } 974 975 int getFirstRowPosition(int row) { 976 final int callerCount = mChooserListAdapter.getCallerTargetCount(); 977 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount); 978 979 if (row < callerRows) { 980 return row * mColumnCount; 981 } 982 983 final int serviceCount = mChooserListAdapter.getServiceTargetCount(); 984 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount); 985 986 if (row < callerRows + serviceRows) { 987 return callerCount + (row - callerRows) * mColumnCount; 988 } 989 990 return callerCount + serviceCount 991 + (row - callerRows - serviceRows) * mColumnCount; 992 } 993 } 994 995 class ChooserTargetServiceConnection implements ServiceConnection { 996 private final DisplayResolveInfo mOriginalTarget; 997 998 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { 999 @Override 1000 public void sendResult(List<ChooserTarget> targets) throws RemoteException { 1001 final Message msg = Message.obtain(); 1002 msg.what = CHOOSER_TARGET_SERVICE_RESULT; 1003 msg.obj = new ServiceResultInfo(mOriginalTarget, targets, 1004 ChooserTargetServiceConnection.this); 1005 mChooserHandler.sendMessage(msg); 1006 } 1007 }; 1008 1009 public ChooserTargetServiceConnection(DisplayResolveInfo dri) { 1010 mOriginalTarget = dri; 1011 } 1012 1013 @Override 1014 public void onServiceConnected(ComponentName name, IBinder service) { 1015 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); 1016 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); 1017 try { 1018 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), 1019 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); 1020 } catch (RemoteException e) { 1021 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); 1022 unbindService(this); 1023 mServiceConnections.remove(this); 1024 } 1025 } 1026 1027 @Override 1028 public void onServiceDisconnected(ComponentName name) { 1029 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); 1030 unbindService(this); 1031 mServiceConnections.remove(this); 1032 if (mServiceConnections.isEmpty()) { 1033 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 1034 sendVoiceChoicesIfNeeded(); 1035 } 1036 } 1037 1038 @Override 1039 public String toString() { 1040 return mOriginalTarget.getResolveInfo().activityInfo.toString(); 1041 } 1042 } 1043 1044 static class ServiceResultInfo { 1045 public final DisplayResolveInfo originalTarget; 1046 public final List<ChooserTarget> resultTargets; 1047 public final ChooserTargetServiceConnection connection; 1048 1049 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, 1050 ChooserTargetServiceConnection c) { 1051 originalTarget = ot; 1052 resultTargets = rt; 1053 connection = c; 1054 } 1055 } 1056 1057 static class RefinementResultReceiver extends ResultReceiver { 1058 private ChooserActivity mChooserActivity; 1059 private TargetInfo mSelectedTarget; 1060 1061 public RefinementResultReceiver(ChooserActivity host, TargetInfo target, 1062 Handler handler) { 1063 super(handler); 1064 mChooserActivity = host; 1065 mSelectedTarget = target; 1066 } 1067 1068 @Override 1069 protected void onReceiveResult(int resultCode, Bundle resultData) { 1070 if (mChooserActivity == null) { 1071 Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); 1072 return; 1073 } 1074 if (resultData == null) { 1075 Log.e(TAG, "RefinementResultReceiver received null resultData"); 1076 return; 1077 } 1078 1079 switch (resultCode) { 1080 case RESULT_CANCELED: 1081 mChooserActivity.onRefinementCanceled(); 1082 break; 1083 case RESULT_OK: 1084 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); 1085 if (intentParcelable instanceof Intent) { 1086 mChooserActivity.onRefinementResult(mSelectedTarget, 1087 (Intent) intentParcelable); 1088 } else { 1089 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" 1090 + " in resultData with key Intent.EXTRA_INTENT"); 1091 } 1092 break; 1093 default: 1094 Log.w(TAG, "Unknown result code " + resultCode 1095 + " sent to RefinementResultReceiver"); 1096 break; 1097 } 1098 } 1099 1100 public void destroy() { 1101 mChooserActivity = null; 1102 mSelectedTarget = null; 1103 } 1104 } 1105} 1106