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