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