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