ChooserActivity.java revision a408061cc7b5efaf090ce9efd5fd0ba1d95e9c11
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 (ii instanceof LabeledIntent) { 617 LabeledIntent li = (LabeledIntent)ii; 618 ri.resolvePackageName = li.getSourcePackage(); 619 ri.labelRes = li.getLabelResource(); 620 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 621 ri.icon = li.getIconResource(); 622 ri.iconResourceId = ri.icon; 623 } 624 if (userManager.isManagedProfile()) { 625 ri.noResourceId = true; 626 ri.icon = 0; 627 } 628 mCallerTargets.add(new DisplayResolveInfo(ii, ri, 629 ri.loadLabel(pm), null, ii)); 630 } 631 } 632 } 633 634 @Override 635 public boolean showsExtendedInfo(TargetInfo info) { 636 // Reserve space to show extended info if any one of the items in the adapter has 637 // extended info. This keeps grid item sizes uniform. 638 return hasExtendedInfo(); 639 } 640 641 @Override 642 public View onCreateView(ViewGroup parent) { 643 return mInflater.inflate( 644 com.android.internal.R.layout.resolve_grid_item, parent, false); 645 } 646 647 @Override 648 public void onListRebuilt() { 649 if (mServiceTargets != null) { 650 pruneServiceTargets(); 651 } 652 } 653 654 @Override 655 public boolean shouldGetResolvedFilter() { 656 return true; 657 } 658 659 @Override 660 public int getCount() { 661 return super.getCount() + mServiceTargets.size() + mCallerTargets.size(); 662 } 663 664 @Override 665 public int getUnfilteredCount() { 666 return super.getUnfilteredCount() + mServiceTargets.size() + mCallerTargets.size(); 667 } 668 669 public int getCallerTargetCount() { 670 return mCallerTargets.size(); 671 } 672 673 public int getServiceTargetCount() { 674 return mServiceTargets.size(); 675 } 676 677 public int getStandardTargetCount() { 678 return super.getCount(); 679 } 680 681 public int getPositionTargetType(int position) { 682 int offset = 0; 683 684 final int callerTargetCount = mCallerTargets.size(); 685 if (position < callerTargetCount) { 686 return TARGET_CALLER; 687 } 688 offset += callerTargetCount; 689 690 final int serviceTargetCount = mServiceTargets.size(); 691 if (position - offset < serviceTargetCount) { 692 return TARGET_SERVICE; 693 } 694 offset += serviceTargetCount; 695 696 final int standardTargetCount = super.getCount(); 697 if (position - offset < standardTargetCount) { 698 return TARGET_STANDARD; 699 } 700 701 return TARGET_BAD; 702 } 703 704 @Override 705 public TargetInfo getItem(int position) { 706 return targetInfoForPosition(position, true); 707 } 708 709 @Override 710 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 711 int offset = 0; 712 713 final int callerTargetCount = mCallerTargets.size(); 714 if (position < callerTargetCount) { 715 return mCallerTargets.get(position); 716 } 717 offset += callerTargetCount; 718 719 final int serviceTargetCount = mServiceTargets.size(); 720 if (position - offset < serviceTargetCount) { 721 return mServiceTargets.get(position - offset); 722 } 723 offset += serviceTargetCount; 724 725 return filtered ? super.getItem(position - offset) 726 : getDisplayInfoAt(position - offset); 727 } 728 729 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { 730 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() 731 + " targets"); 732 for (int i = 0, N = targets.size(); i < N; i++) { 733 mServiceTargets.add(new ChooserTargetInfo(origTarget, targets.get(i))); 734 } 735 736 // TODO: Maintain sort by ranking scores. 737 738 notifyDataSetChanged(); 739 } 740 741 private void pruneServiceTargets() { 742 if (DEBUG) Log.d(TAG, "pruneServiceTargets"); 743 for (int i = mServiceTargets.size() - 1; i >= 0; i--) { 744 final ChooserTargetInfo cti = mServiceTargets.get(i); 745 if (!hasResolvedTarget(cti.getResolveInfo())) { 746 if (DEBUG) Log.d(TAG, " => " + i + " " + cti); 747 mServiceTargets.remove(i); 748 } 749 } 750 } 751 } 752 753 class ChooserRowAdapter extends BaseAdapter { 754 private ChooserListAdapter mChooserListAdapter; 755 private final LayoutInflater mLayoutInflater; 756 private final int mColumnCount = 4; 757 758 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) { 759 mChooserListAdapter = wrappedAdapter; 760 mLayoutInflater = LayoutInflater.from(ChooserActivity.this); 761 762 wrappedAdapter.registerDataSetObserver(new DataSetObserver() { 763 @Override 764 public void onChanged() { 765 super.onChanged(); 766 notifyDataSetChanged(); 767 } 768 769 @Override 770 public void onInvalidated() { 771 super.onInvalidated(); 772 notifyDataSetInvalidated(); 773 } 774 }); 775 } 776 777 @Override 778 public int getCount() { 779 return (int) ( 780 Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount) 781 + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount) 782 + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) 783 ); 784 } 785 786 @Override 787 public Object getItem(int position) { 788 // We have nothing useful to return here. 789 return position; 790 } 791 792 @Override 793 public long getItemId(int position) { 794 return position; 795 } 796 797 @Override 798 public View getView(int position, View convertView, ViewGroup parent) { 799 final View[] holder; 800 if (convertView == null) { 801 holder = createViewHolder(parent); 802 } else { 803 holder = (View[]) convertView.getTag(); 804 } 805 bindViewHolder(position, holder); 806 807 // We keep the actual list item view as the last item in the holder array 808 return holder[mColumnCount]; 809 } 810 811 View[] createViewHolder(ViewGroup parent) { 812 final View[] holder = new View[mColumnCount + 1]; 813 814 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, 815 parent, false); 816 for (int i = 0; i < mColumnCount; i++) { 817 holder[i] = mChooserListAdapter.createView(row); 818 row.addView(holder[i]); 819 } 820 row.setTag(holder); 821 holder[mColumnCount] = row; 822 return holder; 823 } 824 825 void bindViewHolder(int rowPosition, View[] holder) { 826 final int start = getFirstRowPosition(rowPosition); 827 final int startType = mChooserListAdapter.getPositionTargetType(start); 828 829 int end = start + mColumnCount - 1; 830 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) { 831 end--; 832 } 833 834 final ViewGroup row = (ViewGroup) holder[mColumnCount]; 835 836 if (startType == ChooserListAdapter.TARGET_SERVICE) { 837 row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color)); 838 } else { 839 row.setBackground(null); 840 } 841 842 for (int i = 0; i < mColumnCount; i++) { 843 final View v = holder[i]; 844 if (start + i <= end) { 845 v.setVisibility(View.VISIBLE); 846 final int itemIndex = start + i; 847 mChooserListAdapter.bindView(itemIndex, v); 848 v.setOnClickListener(new OnClickListener() { 849 @Override 850 public void onClick(View v) { 851 startSelected(itemIndex, false, true); 852 } 853 }); 854 } else { 855 v.setVisibility(View.GONE); 856 } 857 } 858 } 859 860 int getFirstRowPosition(int row) { 861 final int callerCount = mChooserListAdapter.getCallerTargetCount(); 862 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount); 863 864 if (row < callerRows) { 865 return row * mColumnCount; 866 } 867 868 final int serviceCount = mChooserListAdapter.getServiceTargetCount(); 869 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount); 870 871 if (row < callerRows + serviceRows) { 872 return callerCount + (row - callerRows) * mColumnCount; 873 } 874 875 return callerCount + serviceCount 876 + (row - callerRows - serviceRows) * mColumnCount; 877 } 878 } 879 880 class ChooserTargetServiceConnection implements ServiceConnection { 881 private final DisplayResolveInfo mOriginalTarget; 882 883 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { 884 @Override 885 public void sendResult(List<ChooserTarget> targets) throws RemoteException { 886 final Message msg = Message.obtain(); 887 msg.what = CHOOSER_TARGET_SERVICE_RESULT; 888 msg.obj = new ServiceResultInfo(mOriginalTarget, targets, 889 ChooserTargetServiceConnection.this); 890 mChooserHandler.sendMessage(msg); 891 } 892 }; 893 894 public ChooserTargetServiceConnection(DisplayResolveInfo dri) { 895 mOriginalTarget = dri; 896 } 897 898 @Override 899 public void onServiceConnected(ComponentName name, IBinder service) { 900 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); 901 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); 902 try { 903 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), 904 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); 905 } catch (RemoteException e) { 906 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); 907 unbindService(this); 908 mServiceConnections.remove(this); 909 } 910 } 911 912 @Override 913 public void onServiceDisconnected(ComponentName name) { 914 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); 915 unbindService(this); 916 mServiceConnections.remove(this); 917 } 918 919 @Override 920 public String toString() { 921 return mOriginalTarget.getResolveInfo().activityInfo.toString(); 922 } 923 } 924 925 static class ServiceResultInfo { 926 public final DisplayResolveInfo originalTarget; 927 public final List<ChooserTarget> resultTargets; 928 public final ChooserTargetServiceConnection connection; 929 930 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, 931 ChooserTargetServiceConnection c) { 932 originalTarget = ot; 933 resultTargets = rt; 934 connection = c; 935 } 936 } 937 938 static class RefinementResultReceiver extends ResultReceiver { 939 private ChooserActivity mChooserActivity; 940 private TargetInfo mSelectedTarget; 941 942 public RefinementResultReceiver(ChooserActivity host, TargetInfo target, 943 Handler handler) { 944 super(handler); 945 mChooserActivity = host; 946 mSelectedTarget = target; 947 } 948 949 @Override 950 protected void onReceiveResult(int resultCode, Bundle resultData) { 951 if (mChooserActivity == null) { 952 Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); 953 return; 954 } 955 if (resultData == null) { 956 Log.e(TAG, "RefinementResultReceiver received null resultData"); 957 return; 958 } 959 960 switch (resultCode) { 961 case RESULT_CANCELED: 962 mChooserActivity.onRefinementCanceled(); 963 break; 964 case RESULT_OK: 965 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); 966 if (intentParcelable instanceof Intent) { 967 mChooserActivity.onRefinementResult(mSelectedTarget, 968 (Intent) intentParcelable); 969 } else { 970 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" 971 + " in resultData with key Intent.EXTRA_INTENT"); 972 } 973 break; 974 default: 975 Log.w(TAG, "Unknown result code " + resultCode 976 + " sent to RefinementResultReceiver"); 977 break; 978 } 979 } 980 981 public void destroy() { 982 mChooserActivity = null; 983 mSelectedTarget = null; 984 } 985 } 986} 987