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