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