ChooserActivity.java revision 666d82a6d5c6a90e87591aea1aabac3d647cd541
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 void filterServiceTargets(String packageName, List<ChooserTarget> targets) { 475 if (targets == null) { 476 return; 477 } 478 479 final PackageManager pm = getPackageManager(); 480 for (int i = targets.size() - 1; i >= 0; i--) { 481 final ChooserTarget target = targets.get(i); 482 final ComponentName targetName = target.getComponentName(); 483 if (packageName != null && packageName.equals(targetName.getPackageName())) { 484 // Anything from the original target's package is fine. 485 continue; 486 } 487 488 boolean remove; 489 try { 490 final ActivityInfo ai = pm.getActivityInfo(targetName, 0); 491 remove = !ai.exported || ai.permission != null; 492 } catch (NameNotFoundException e) { 493 Log.e(TAG, "Target " + target + " returned by " + packageName 494 + " component not found"); 495 remove = true; 496 } 497 498 if (remove) { 499 targets.remove(i); 500 } 501 } 502 } 503 504 @Override 505 ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, 506 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 507 boolean filterLastUsed) { 508 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents, 509 initialIntents, rList, launchedFromUid, filterLastUsed); 510 if (DEBUG) Log.d(TAG, "Adapter created; querying services"); 511 queryTargetServices(adapter); 512 return adapter; 513 } 514 515 final class ChooserTargetInfo implements TargetInfo { 516 private final DisplayResolveInfo mSourceInfo; 517 private final ResolveInfo mBackupResolveInfo; 518 private final ChooserTarget mChooserTarget; 519 private Drawable mBadgeIcon = null; 520 private Drawable mDisplayIcon; 521 private final Intent mFillInIntent; 522 private final int mFillInFlags; 523 private final float mModifiedScore; 524 525 public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget, 526 float modifiedScore) { 527 mSourceInfo = sourceInfo; 528 mChooserTarget = chooserTarget; 529 mModifiedScore = modifiedScore; 530 if (sourceInfo != null) { 531 final ResolveInfo ri = sourceInfo.getResolveInfo(); 532 if (ri != null) { 533 final ActivityInfo ai = ri.activityInfo; 534 if (ai != null && ai.applicationInfo != null) { 535 mBadgeIcon = getPackageManager().getApplicationIcon(ai.applicationInfo); 536 } 537 } 538 } 539 final Icon icon = chooserTarget.getIcon(); 540 // TODO do this in the background 541 mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null; 542 543 if (sourceInfo != null) { 544 mBackupResolveInfo = null; 545 } else { 546 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0); 547 } 548 549 mFillInIntent = null; 550 mFillInFlags = 0; 551 } 552 553 private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) { 554 mSourceInfo = other.mSourceInfo; 555 mBackupResolveInfo = other.mBackupResolveInfo; 556 mChooserTarget = other.mChooserTarget; 557 mBadgeIcon = other.mBadgeIcon; 558 mDisplayIcon = other.mDisplayIcon; 559 mFillInIntent = fillInIntent; 560 mFillInFlags = flags; 561 mModifiedScore = other.mModifiedScore; 562 } 563 564 public float getModifiedScore() { 565 return mModifiedScore; 566 } 567 568 @Override 569 public Intent getResolvedIntent() { 570 if (mSourceInfo != null) { 571 return mSourceInfo.getResolvedIntent(); 572 } 573 return getTargetIntent(); 574 } 575 576 @Override 577 public ComponentName getResolvedComponentName() { 578 if (mSourceInfo != null) { 579 return mSourceInfo.getResolvedComponentName(); 580 } else if (mBackupResolveInfo != null) { 581 return new ComponentName(mBackupResolveInfo.activityInfo.packageName, 582 mBackupResolveInfo.activityInfo.name); 583 } 584 return null; 585 } 586 587 private Intent getBaseIntentToSend() { 588 Intent result = mSourceInfo != null 589 ? mSourceInfo.getResolvedIntent() : getTargetIntent(); 590 if (result == null) { 591 Log.e(TAG, "ChooserTargetInfo: no base intent available to send"); 592 } else { 593 result = new Intent(result); 594 if (mFillInIntent != null) { 595 result.fillIn(mFillInIntent, mFillInFlags); 596 } 597 result.fillIn(mReferrerFillInIntent, 0); 598 } 599 return result; 600 } 601 602 @Override 603 public boolean start(Activity activity, Bundle options) { 604 throw new RuntimeException("ChooserTargets should be started as caller."); 605 } 606 607 @Override 608 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 609 final Intent intent = getBaseIntentToSend(); 610 if (intent == null) { 611 return false; 612 } 613 intent.setComponent(mChooserTarget.getComponentName()); 614 intent.putExtras(mChooserTarget.getIntentExtras()); 615 activity.startActivityAsCaller(intent, options, true, userId); 616 return true; 617 } 618 619 @Override 620 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 621 throw new RuntimeException("ChooserTargets should be started as caller."); 622 } 623 624 @Override 625 public ResolveInfo getResolveInfo() { 626 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo; 627 } 628 629 @Override 630 public CharSequence getDisplayLabel() { 631 return mChooserTarget.getTitle(); 632 } 633 634 @Override 635 public CharSequence getExtendedInfo() { 636 return mSourceInfo != null ? mSourceInfo.getExtendedInfo() : null; 637 } 638 639 @Override 640 public Drawable getDisplayIcon() { 641 return mDisplayIcon; 642 } 643 644 @Override 645 public Drawable getBadgeIcon() { 646 return mBadgeIcon; 647 } 648 649 @Override 650 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { 651 return new ChooserTargetInfo(this, fillInIntent, flags); 652 } 653 654 @Override 655 public List<Intent> getAllSourceIntents() { 656 final List<Intent> results = new ArrayList<>(); 657 if (mSourceInfo != null) { 658 // We only queried the service for the first one in our sourceinfo. 659 results.add(mSourceInfo.getAllSourceIntents().get(0)); 660 } 661 return results; 662 } 663 } 664 665 public class ChooserListAdapter extends ResolveListAdapter { 666 public static final int TARGET_BAD = -1; 667 public static final int TARGET_CALLER = 0; 668 public static final int TARGET_SERVICE = 1; 669 public static final int TARGET_STANDARD = 2; 670 671 private static final int MAX_SERVICE_TARGETS = 8; 672 673 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); 674 private final List<TargetInfo> mCallerTargets = new ArrayList<>(); 675 676 private float mLateFee = 1.f; 677 678 private final BaseChooserTargetComparator mBaseTargetComparator 679 = new BaseChooserTargetComparator(); 680 681 public ChooserListAdapter(Context context, List<Intent> payloadIntents, 682 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 683 boolean filterLastUsed) { 684 // Don't send the initial intents through the shared ResolverActivity path, 685 // we want to separate them into a different section. 686 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed); 687 688 if (initialIntents != null) { 689 final PackageManager pm = getPackageManager(); 690 for (int i = 0; i < initialIntents.length; i++) { 691 final Intent ii = initialIntents[i]; 692 if (ii == null) { 693 continue; 694 } 695 final ActivityInfo ai = ii.resolveActivityInfo(pm, 0); 696 if (ai == null) { 697 Log.w(TAG, "No activity found for " + ii); 698 continue; 699 } 700 ResolveInfo ri = new ResolveInfo(); 701 ri.activityInfo = ai; 702 UserManager userManager = 703 (UserManager) getSystemService(Context.USER_SERVICE); 704 if (ii instanceof LabeledIntent) { 705 LabeledIntent li = (LabeledIntent)ii; 706 ri.resolvePackageName = li.getSourcePackage(); 707 ri.labelRes = li.getLabelResource(); 708 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 709 ri.icon = li.getIconResource(); 710 ri.iconResourceId = ri.icon; 711 } 712 if (userManager.isManagedProfile()) { 713 ri.noResourceId = true; 714 ri.icon = 0; 715 } 716 mCallerTargets.add(new DisplayResolveInfo(ii, ri, 717 ri.loadLabel(pm), null, ii)); 718 } 719 } 720 } 721 722 @Override 723 public boolean showsExtendedInfo(TargetInfo info) { 724 // Reserve space to show extended info if any one of the items in the adapter has 725 // extended info. This keeps grid item sizes uniform. 726 return hasExtendedInfo(); 727 } 728 729 @Override 730 public View onCreateView(ViewGroup parent) { 731 return mInflater.inflate( 732 com.android.internal.R.layout.resolve_grid_item, parent, false); 733 } 734 735 @Override 736 public void onListRebuilt() { 737 if (mServiceTargets != null) { 738 pruneServiceTargets(); 739 } 740 } 741 742 @Override 743 public boolean shouldGetResolvedFilter() { 744 return true; 745 } 746 747 @Override 748 public int getCount() { 749 return super.getCount() + getServiceTargetCount() + getCallerTargetCount(); 750 } 751 752 @Override 753 public int getUnfilteredCount() { 754 return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount(); 755 } 756 757 public int getCallerTargetCount() { 758 return mCallerTargets.size(); 759 } 760 761 public int getServiceTargetCount() { 762 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS); 763 } 764 765 public int getStandardTargetCount() { 766 return super.getCount(); 767 } 768 769 public int getPositionTargetType(int position) { 770 int offset = 0; 771 772 final int callerTargetCount = getCallerTargetCount(); 773 if (position < callerTargetCount) { 774 return TARGET_CALLER; 775 } 776 offset += callerTargetCount; 777 778 final int serviceTargetCount = getServiceTargetCount(); 779 if (position - offset < serviceTargetCount) { 780 return TARGET_SERVICE; 781 } 782 offset += serviceTargetCount; 783 784 final int standardTargetCount = super.getCount(); 785 if (position - offset < standardTargetCount) { 786 return TARGET_STANDARD; 787 } 788 789 return TARGET_BAD; 790 } 791 792 @Override 793 public TargetInfo getItem(int position) { 794 return targetInfoForPosition(position, true); 795 } 796 797 @Override 798 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 799 int offset = 0; 800 801 final int callerTargetCount = getCallerTargetCount(); 802 if (position < callerTargetCount) { 803 return mCallerTargets.get(position); 804 } 805 offset += callerTargetCount; 806 807 final int serviceTargetCount = getServiceTargetCount(); 808 if (position - offset < serviceTargetCount) { 809 return mServiceTargets.get(position - offset); 810 } 811 offset += serviceTargetCount; 812 813 return filtered ? super.getItem(position - offset) 814 : getDisplayInfoAt(position - offset); 815 } 816 817 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { 818 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() 819 + " targets"); 820 final float parentScore = getScore(origTarget); 821 Collections.sort(targets, mBaseTargetComparator); 822 float lastScore = 0; 823 for (int i = 0, N = targets.size(); i < N; i++) { 824 final ChooserTarget target = targets.get(i); 825 float targetScore = target.getScore(); 826 targetScore *= parentScore; 827 targetScore *= mLateFee; 828 if (i > 0 && targetScore >= lastScore) { 829 // Apply a decay so that the top app can't crowd out everything else. 830 // This incents ChooserTargetServices to define what's truly better. 831 targetScore = lastScore * 0.95f; 832 } 833 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore)); 834 835 if (DEBUG) { 836 Log.d(TAG, " => " + target.toString() + " score=" + targetScore 837 + " base=" + target.getScore() 838 + " lastScore=" + lastScore 839 + " parentScore=" + parentScore 840 + " lateFee=" + mLateFee); 841 } 842 843 lastScore = targetScore; 844 } 845 846 mLateFee *= 0.95f; 847 848 notifyDataSetChanged(); 849 } 850 851 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) { 852 final float newScore = chooserTargetInfo.getModifiedScore(); 853 for (int i = 0, N = mServiceTargets.size(); i < N; i++) { 854 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i); 855 if (newScore > serviceTarget.getModifiedScore()) { 856 mServiceTargets.add(i, chooserTargetInfo); 857 return; 858 } 859 } 860 mServiceTargets.add(chooserTargetInfo); 861 } 862 863 private void pruneServiceTargets() { 864 if (DEBUG) Log.d(TAG, "pruneServiceTargets"); 865 for (int i = mServiceTargets.size() - 1; i >= 0; i--) { 866 final ChooserTargetInfo cti = mServiceTargets.get(i); 867 if (!hasResolvedTarget(cti.getResolveInfo())) { 868 if (DEBUG) Log.d(TAG, " => " + i + " " + cti); 869 mServiceTargets.remove(i); 870 } 871 } 872 } 873 } 874 875 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> { 876 @Override 877 public int compare(ChooserTarget lhs, ChooserTarget rhs) { 878 // Descending order 879 return (int) Math.signum(lhs.getScore() - rhs.getScore()); 880 } 881 } 882 883 class ChooserRowAdapter extends BaseAdapter { 884 private ChooserListAdapter mChooserListAdapter; 885 private final LayoutInflater mLayoutInflater; 886 private final int mColumnCount = 4; 887 888 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) { 889 mChooserListAdapter = wrappedAdapter; 890 mLayoutInflater = LayoutInflater.from(ChooserActivity.this); 891 892 wrappedAdapter.registerDataSetObserver(new DataSetObserver() { 893 @Override 894 public void onChanged() { 895 super.onChanged(); 896 notifyDataSetChanged(); 897 } 898 899 @Override 900 public void onInvalidated() { 901 super.onInvalidated(); 902 notifyDataSetInvalidated(); 903 } 904 }); 905 } 906 907 @Override 908 public int getCount() { 909 return (int) ( 910 Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount) 911 + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount) 912 + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) 913 ); 914 } 915 916 @Override 917 public Object getItem(int position) { 918 // We have nothing useful to return here. 919 return position; 920 } 921 922 @Override 923 public long getItemId(int position) { 924 return position; 925 } 926 927 @Override 928 public View getView(int position, View convertView, ViewGroup parent) { 929 final View[] holder; 930 if (convertView == null) { 931 holder = createViewHolder(parent); 932 } else { 933 holder = (View[]) convertView.getTag(); 934 } 935 bindViewHolder(position, holder); 936 937 // We keep the actual list item view as the last item in the holder array 938 return holder[mColumnCount]; 939 } 940 941 View[] createViewHolder(ViewGroup parent) { 942 final View[] holder = new View[mColumnCount + 1]; 943 944 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, 945 parent, false); 946 for (int i = 0; i < mColumnCount; i++) { 947 holder[i] = mChooserListAdapter.createView(row); 948 row.addView(holder[i]); 949 } 950 row.setTag(holder); 951 holder[mColumnCount] = row; 952 return holder; 953 } 954 955 void bindViewHolder(int rowPosition, View[] holder) { 956 final int start = getFirstRowPosition(rowPosition); 957 final int startType = mChooserListAdapter.getPositionTargetType(start); 958 959 int end = start + mColumnCount - 1; 960 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) { 961 end--; 962 } 963 964 final ViewGroup row = (ViewGroup) holder[mColumnCount]; 965 966 if (startType == ChooserListAdapter.TARGET_SERVICE) { 967 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color)); 968 } else { 969 row.setBackground(null); 970 } 971 972 for (int i = 0; i < mColumnCount; i++) { 973 final View v = holder[i]; 974 if (start + i <= end) { 975 v.setVisibility(View.VISIBLE); 976 final int itemIndex = start + i; 977 mChooserListAdapter.bindView(itemIndex, v); 978 v.setOnClickListener(new OnClickListener() { 979 @Override 980 public void onClick(View v) { 981 startSelected(itemIndex, false, true); 982 } 983 }); 984 v.setOnLongClickListener(new OnLongClickListener() { 985 @Override 986 public boolean onLongClick(View v) { 987 showAppDetails( 988 mChooserListAdapter.resolveInfoForPosition(itemIndex, true)); 989 return true; 990 } 991 }); 992 } else { 993 v.setVisibility(View.GONE); 994 } 995 } 996 } 997 998 int getFirstRowPosition(int row) { 999 final int callerCount = mChooserListAdapter.getCallerTargetCount(); 1000 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount); 1001 1002 if (row < callerRows) { 1003 return row * mColumnCount; 1004 } 1005 1006 final int serviceCount = mChooserListAdapter.getServiceTargetCount(); 1007 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount); 1008 1009 if (row < callerRows + serviceRows) { 1010 return callerCount + (row - callerRows) * mColumnCount; 1011 } 1012 1013 return callerCount + serviceCount 1014 + (row - callerRows - serviceRows) * mColumnCount; 1015 } 1016 } 1017 1018 class ChooserTargetServiceConnection implements ServiceConnection { 1019 private final DisplayResolveInfo mOriginalTarget; 1020 1021 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { 1022 @Override 1023 public void sendResult(List<ChooserTarget> targets) throws RemoteException { 1024 filterServiceTargets(mOriginalTarget.getResolveInfo().activityInfo.packageName, 1025 targets); 1026 final Message msg = Message.obtain(); 1027 msg.what = CHOOSER_TARGET_SERVICE_RESULT; 1028 msg.obj = new ServiceResultInfo(mOriginalTarget, targets, 1029 ChooserTargetServiceConnection.this); 1030 mChooserHandler.sendMessage(msg); 1031 } 1032 }; 1033 1034 public ChooserTargetServiceConnection(DisplayResolveInfo dri) { 1035 mOriginalTarget = dri; 1036 } 1037 1038 @Override 1039 public void onServiceConnected(ComponentName name, IBinder service) { 1040 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); 1041 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); 1042 try { 1043 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), 1044 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); 1045 } catch (RemoteException e) { 1046 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); 1047 unbindService(this); 1048 mServiceConnections.remove(this); 1049 } 1050 } 1051 1052 @Override 1053 public void onServiceDisconnected(ComponentName name) { 1054 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); 1055 unbindService(this); 1056 mServiceConnections.remove(this); 1057 if (mServiceConnections.isEmpty()) { 1058 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 1059 sendVoiceChoicesIfNeeded(); 1060 } 1061 } 1062 1063 @Override 1064 public String toString() { 1065 return mOriginalTarget.getResolveInfo().activityInfo.toString(); 1066 } 1067 } 1068 1069 static class ServiceResultInfo { 1070 public final DisplayResolveInfo originalTarget; 1071 public final List<ChooserTarget> resultTargets; 1072 public final ChooserTargetServiceConnection connection; 1073 1074 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, 1075 ChooserTargetServiceConnection c) { 1076 originalTarget = ot; 1077 resultTargets = rt; 1078 connection = c; 1079 } 1080 } 1081 1082 static class RefinementResultReceiver extends ResultReceiver { 1083 private ChooserActivity mChooserActivity; 1084 private TargetInfo mSelectedTarget; 1085 1086 public RefinementResultReceiver(ChooserActivity host, TargetInfo target, 1087 Handler handler) { 1088 super(handler); 1089 mChooserActivity = host; 1090 mSelectedTarget = target; 1091 } 1092 1093 @Override 1094 protected void onReceiveResult(int resultCode, Bundle resultData) { 1095 if (mChooserActivity == null) { 1096 Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); 1097 return; 1098 } 1099 if (resultData == null) { 1100 Log.e(TAG, "RefinementResultReceiver received null resultData"); 1101 return; 1102 } 1103 1104 switch (resultCode) { 1105 case RESULT_CANCELED: 1106 mChooserActivity.onRefinementCanceled(); 1107 break; 1108 case RESULT_OK: 1109 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); 1110 if (intentParcelable instanceof Intent) { 1111 mChooserActivity.onRefinementResult(mSelectedTarget, 1112 (Intent) intentParcelable); 1113 } else { 1114 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" 1115 + " in resultData with key Intent.EXTRA_INTENT"); 1116 } 1117 break; 1118 default: 1119 Log.w(TAG, "Unknown result code " + resultCode 1120 + " sent to RefinementResultReceiver"); 1121 break; 1122 } 1123 } 1124 1125 public void destroy() { 1126 mChooserActivity = null; 1127 mSelectedTarget = null; 1128 } 1129 } 1130} 1131