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