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