ChooserActivity.java revision 7317e8abcc1d691b77386b9ff5852360dc40cdd4
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.animation.ObjectAnimator; 20import android.annotation.NonNull; 21import android.app.Activity; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentSender; 26import android.content.IntentSender.SendIntentException; 27import android.content.ServiceConnection; 28import android.content.SharedPreferences; 29import android.content.pm.ActivityInfo; 30import android.content.pm.LabeledIntent; 31import android.content.pm.PackageManager; 32import android.content.pm.PackageManager.NameNotFoundException; 33import android.content.pm.ResolveInfo; 34import android.database.DataSetObserver; 35import android.graphics.Color; 36import android.graphics.drawable.Drawable; 37import android.graphics.drawable.Icon; 38import android.os.Bundle; 39import android.os.Environment; 40import android.os.Handler; 41import android.os.IBinder; 42import android.os.Message; 43import android.os.Parcelable; 44import android.os.Process; 45import android.os.RemoteException; 46import android.os.ResultReceiver; 47import android.os.UserHandle; 48import android.os.UserManager; 49import android.os.storage.StorageManager; 50import android.service.chooser.ChooserTarget; 51import android.service.chooser.ChooserTargetService; 52import android.service.chooser.IChooserTargetResult; 53import android.service.chooser.IChooserTargetService; 54import android.text.TextUtils; 55import android.util.FloatProperty; 56import android.util.Log; 57import android.util.Slog; 58import android.view.LayoutInflater; 59import android.view.View; 60import android.view.View.MeasureSpec; 61import android.view.View.OnClickListener; 62import android.view.View.OnLongClickListener; 63import android.view.ViewGroup; 64import android.view.ViewGroup.LayoutParams; 65import android.view.animation.AnimationUtils; 66import android.view.animation.Interpolator; 67import android.widget.AbsListView; 68import android.widget.BaseAdapter; 69import android.widget.ListView; 70import com.android.internal.R; 71import com.android.internal.app.ResolverActivity.TargetInfo; 72import com.android.internal.logging.MetricsLogger; 73import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 74import com.google.android.collect.Lists; 75 76import java.io.File; 77import java.util.ArrayList; 78import java.util.Collections; 79import java.util.Comparator; 80import java.util.List; 81 82public class ChooserActivity extends ResolverActivity { 83 private static final String TAG = "ChooserActivity"; 84 85 private static final boolean DEBUG = false; 86 87 private static final int QUERY_TARGET_SERVICE_LIMIT = 5; 88 private static final int WATCHDOG_TIMEOUT_MILLIS = 5000; 89 90 private Bundle mReplacementExtras; 91 private IntentSender mChosenComponentSender; 92 private IntentSender mRefinementIntentSender; 93 private RefinementResultReceiver mRefinementResultReceiver; 94 private ChooserTarget[] mCallerChooserTargets; 95 96 private Intent mReferrerFillInIntent; 97 98 private ChooserListAdapter mChooserListAdapter; 99 private ChooserRowAdapter mChooserRowAdapter; 100 101 private SharedPreferences mPinnedSharedPrefs; 102 private static final float PINNED_TARGET_SCORE_BOOST = 1000.f; 103 private static final float CALLER_TARGET_SCORE_BOOST = 900.f; 104 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings"; 105 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment"; 106 107 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); 108 109 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1; 110 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2; 111 112 private final Handler mChooserHandler = new Handler() { 113 @Override 114 public void handleMessage(Message msg) { 115 switch (msg.what) { 116 case CHOOSER_TARGET_SERVICE_RESULT: 117 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT"); 118 if (isDestroyed()) break; 119 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; 120 if (!mServiceConnections.contains(sri.connection)) { 121 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection 122 + " returned after being removed from active connections." 123 + " Have you considered returning results faster?"); 124 break; 125 } 126 if (sri.resultTargets != null) { 127 mChooserListAdapter.addServiceResults(sri.originalTarget, 128 sri.resultTargets); 129 } 130 unbindService(sri.connection); 131 sri.connection.destroy(); 132 mServiceConnections.remove(sri.connection); 133 if (mServiceConnections.isEmpty()) { 134 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 135 sendVoiceChoicesIfNeeded(); 136 mChooserListAdapter.setShowServiceTargets(true); 137 } 138 break; 139 140 case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT: 141 if (DEBUG) { 142 Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services"); 143 } 144 unbindRemainingServices(); 145 sendVoiceChoicesIfNeeded(); 146 mChooserListAdapter.setShowServiceTargets(true); 147 break; 148 149 default: 150 super.handleMessage(msg); 151 } 152 } 153 }; 154 155 @Override 156 protected void onCreate(Bundle savedInstanceState) { 157 Intent intent = getIntent(); 158 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT); 159 if (!(targetParcelable instanceof Intent)) { 160 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable); 161 finish(); 162 super.onCreate(null); 163 return; 164 } 165 Intent target = (Intent) targetParcelable; 166 if (target != null) { 167 modifyTargetIntent(target); 168 } 169 Parcelable[] targetsParcelable 170 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS); 171 if (targetsParcelable != null) { 172 final boolean offset = target == null; 173 Intent[] additionalTargets = 174 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length]; 175 for (int i = 0; i < targetsParcelable.length; i++) { 176 if (!(targetsParcelable[i] instanceof Intent)) { 177 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: " 178 + targetsParcelable[i]); 179 finish(); 180 super.onCreate(null); 181 return; 182 } 183 final Intent additionalTarget = (Intent) targetsParcelable[i]; 184 if (i == 0 && target == null) { 185 target = additionalTarget; 186 modifyTargetIntent(target); 187 } else { 188 additionalTargets[offset ? i - 1 : i] = additionalTarget; 189 modifyTargetIntent(additionalTarget); 190 } 191 } 192 setAdditionalTargets(additionalTargets); 193 } 194 195 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); 196 CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); 197 int defaultTitleRes = 0; 198 if (title == null) { 199 defaultTitleRes = com.android.internal.R.string.chooseActivity; 200 } 201 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS); 202 Intent[] initialIntents = null; 203 if (pa != null) { 204 initialIntents = new Intent[pa.length]; 205 for (int i=0; i<pa.length; i++) { 206 if (!(pa[i] instanceof Intent)) { 207 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]); 208 finish(); 209 super.onCreate(null); 210 return; 211 } 212 final Intent in = (Intent) pa[i]; 213 modifyTargetIntent(in); 214 initialIntents[i] = in; 215 } 216 } 217 218 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer()); 219 220 mChosenComponentSender = intent.getParcelableExtra( 221 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); 222 mRefinementIntentSender = intent.getParcelableExtra( 223 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); 224 setSafeForwardingMode(true); 225 226 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS); 227 if (pa != null) { 228 ComponentName[] names = new ComponentName[pa.length]; 229 for (int i = 0; i < pa.length; i++) { 230 if (!(pa[i] instanceof ComponentName)) { 231 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]); 232 names = null; 233 break; 234 } 235 names[i] = (ComponentName) pa[i]; 236 } 237 setFilteredComponents(names); 238 } 239 240 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS); 241 if (pa != null) { 242 ChooserTarget[] targets = new ChooserTarget[pa.length]; 243 for (int i = 0; i < pa.length; i++) { 244 if (!(pa[i] instanceof ChooserTarget)) { 245 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]); 246 targets = null; 247 break; 248 } 249 targets[i] = (ChooserTarget) pa[i]; 250 } 251 mCallerChooserTargets = targets; 252 } 253 254 mPinnedSharedPrefs = getPinnedSharedPrefs(this); 255 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, 256 null, false); 257 258 MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN); 259 } 260 261 static SharedPreferences getPinnedSharedPrefs(Context context) { 262 // The code below is because in the android:ui process, no one can hear you scream. 263 // The package info in the context isn't initialized in the way it is for normal apps, 264 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we 265 // build the path manually below using the same policy that appears in ContextImpl. 266 // This fails silently under the hood if there's a problem, so if we find ourselves in 267 // the case where we don't have access to credential encrypted storage we just won't 268 // have our pinned target info. 269 final File prefsFile = new File(new File( 270 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL, 271 context.getUserId(), context.getPackageName()), 272 "shared_prefs"), 273 PINNED_SHARED_PREFS_NAME + ".xml"); 274 return context.getSharedPreferences(prefsFile, MODE_PRIVATE); 275 } 276 277 @Override 278 protected void onDestroy() { 279 super.onDestroy(); 280 if (mRefinementResultReceiver != null) { 281 mRefinementResultReceiver.destroy(); 282 mRefinementResultReceiver = null; 283 } 284 unbindRemainingServices(); 285 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); 286 } 287 288 @Override 289 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 290 Intent result = defIntent; 291 if (mReplacementExtras != null) { 292 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName); 293 if (replExtras != null) { 294 result = new Intent(defIntent); 295 result.putExtras(replExtras); 296 } 297 } 298 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT) 299 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) { 300 result = Intent.createChooser(result, 301 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE)); 302 303 // Don't auto-launch single intents if the intent is being forwarded. This is done 304 // because automatically launching a resolving application as a response to the user 305 // action of switching accounts is pretty unexpected. 306 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false); 307 } 308 return result; 309 } 310 311 @Override 312 public void onActivityStarted(TargetInfo cti) { 313 if (mChosenComponentSender != null) { 314 final ComponentName target = cti.getResolvedComponentName(); 315 if (target != null) { 316 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target); 317 try { 318 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null); 319 } catch (IntentSender.SendIntentException e) { 320 Slog.e(TAG, "Unable to launch supplied IntentSender to report " 321 + "the chosen component: " + e); 322 } 323 } 324 } 325 } 326 327 @Override 328 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, 329 boolean alwaysUseOption) { 330 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; 331 mChooserListAdapter = (ChooserListAdapter) adapter; 332 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) { 333 mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets)); 334 } 335 mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter); 336 mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView)); 337 adapterView.setAdapter(mChooserRowAdapter); 338 if (listView != null) { 339 listView.setItemsCanFocus(true); 340 } 341 } 342 343 @Override 344 public int getLayoutResource() { 345 return R.layout.chooser_grid; 346 } 347 348 @Override 349 public boolean shouldGetActivityMetadata() { 350 return true; 351 } 352 353 @Override 354 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { 355 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, 356 super.shouldAutoLaunchSingleChoice(target)); 357 } 358 359 @Override 360 public void showTargetDetails(ResolveInfo ri) { 361 ComponentName name = ri.activityInfo.getComponentName(); 362 boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false); 363 ResolverTargetActionsDialogFragment f = 364 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()), 365 name, pinned); 366 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); 367 } 368 369 private void modifyTargetIntent(Intent in) { 370 final String action = in.getAction(); 371 if (Intent.ACTION_SEND.equals(action) || 372 Intent.ACTION_SEND_MULTIPLE.equals(action)) { 373 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | 374 Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 375 } 376 } 377 378 @Override 379 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { 380 if (mRefinementIntentSender != null) { 381 final Intent fillIn = new Intent(); 382 final List<Intent> sourceIntents = target.getAllSourceIntents(); 383 if (!sourceIntents.isEmpty()) { 384 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0)); 385 if (sourceIntents.size() > 1) { 386 final Intent[] alts = new Intent[sourceIntents.size() - 1]; 387 for (int i = 1, N = sourceIntents.size(); i < N; i++) { 388 alts[i - 1] = sourceIntents.get(i); 389 } 390 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts); 391 } 392 if (mRefinementResultReceiver != null) { 393 mRefinementResultReceiver.destroy(); 394 } 395 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null); 396 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER, 397 mRefinementResultReceiver); 398 try { 399 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null); 400 return false; 401 } catch (SendIntentException e) { 402 Log.e(TAG, "Refinement IntentSender failed to send", e); 403 } 404 } 405 } 406 return super.onTargetSelected(target, alwaysCheck); 407 } 408 409 @Override 410 public void startSelected(int which, boolean always, boolean filtered) { 411 super.startSelected(which, always, filtered); 412 413 if (mChooserListAdapter != null) { 414 // Log the index of which type of target the user picked. 415 // Lower values mean the ranking was better. 416 int cat = 0; 417 int value = which; 418 switch (mChooserListAdapter.getPositionTargetType(which)) { 419 case ChooserListAdapter.TARGET_CALLER: 420 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET; 421 break; 422 case ChooserListAdapter.TARGET_SERVICE: 423 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET; 424 value -= mChooserListAdapter.getCallerTargetCount(); 425 break; 426 case ChooserListAdapter.TARGET_STANDARD: 427 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET; 428 value -= mChooserListAdapter.getCallerTargetCount() 429 + mChooserListAdapter.getServiceTargetCount(); 430 break; 431 } 432 433 if (cat != 0) { 434 MetricsLogger.action(this, cat, value); 435 } 436 } 437 } 438 439 void queryTargetServices(ChooserListAdapter adapter) { 440 final PackageManager pm = getPackageManager(); 441 int targetsToQuery = 0; 442 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) { 443 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); 444 if (adapter.getScore(dri) == 0) { 445 // A score of 0 means the app hasn't been used in some time; 446 // don't query it as it's not likely to be relevant. 447 continue; 448 } 449 final ActivityInfo ai = dri.getResolveInfo().activityInfo; 450 final Bundle md = ai.metaData; 451 final String serviceName = md != null ? convertServiceName(ai.packageName, 452 md.getString(ChooserTargetService.META_DATA_NAME)) : null; 453 if (serviceName != null) { 454 final ComponentName serviceComponent = new ComponentName( 455 ai.packageName, serviceName); 456 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE) 457 .setComponent(serviceComponent); 458 459 if (DEBUG) { 460 Log.d(TAG, "queryTargets found target with service " + serviceComponent); 461 } 462 463 try { 464 final String perm = pm.getServiceInfo(serviceComponent, 0).permission; 465 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) { 466 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require" 467 + " permission " + ChooserTargetService.BIND_PERMISSION 468 + " - this service will not be queried for ChooserTargets." 469 + " add android:permission=\"" 470 + ChooserTargetService.BIND_PERMISSION + "\"" 471 + " to the <service> tag for " + serviceComponent 472 + " in the manifest."); 473 continue; 474 } 475 } catch (NameNotFoundException e) { 476 Log.e(TAG, "Could not look up service " + serviceComponent 477 + "; component name not found"); 478 continue; 479 } 480 481 final ChooserTargetServiceConnection conn = 482 new ChooserTargetServiceConnection(this, dri); 483 484 // Explicitly specify Process.myUserHandle instead of calling bindService 485 // to avoid the warning from calling from the system process without an explicit 486 // user handle 487 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND, 488 Process.myUserHandle())) { 489 if (DEBUG) { 490 Log.d(TAG, "Binding service connection for target " + dri 491 + " intent " + serviceIntent); 492 } 493 mServiceConnections.add(conn); 494 targetsToQuery++; 495 } 496 } 497 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) { 498 if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " 499 + QUERY_TARGET_SERVICE_LIMIT); 500 break; 501 } 502 } 503 504 if (!mServiceConnections.isEmpty()) { 505 if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for " 506 + WATCHDOG_TIMEOUT_MILLIS + "ms"); 507 mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT, 508 WATCHDOG_TIMEOUT_MILLIS); 509 } else { 510 sendVoiceChoicesIfNeeded(); 511 } 512 } 513 514 private String convertServiceName(String packageName, String serviceName) { 515 if (TextUtils.isEmpty(serviceName)) { 516 return null; 517 } 518 519 final String fullName; 520 if (serviceName.startsWith(".")) { 521 // Relative to the app package. Prepend the app package name. 522 fullName = packageName + serviceName; 523 } else if (serviceName.indexOf('.') >= 0) { 524 // Fully qualified package name. 525 fullName = serviceName; 526 } else { 527 fullName = null; 528 } 529 return fullName; 530 } 531 532 void unbindRemainingServices() { 533 if (DEBUG) { 534 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left"); 535 } 536 for (int i = 0, N = mServiceConnections.size(); i < N; i++) { 537 final ChooserTargetServiceConnection conn = mServiceConnections.get(i); 538 if (DEBUG) Log.d(TAG, "unbinding " + conn); 539 unbindService(conn); 540 conn.destroy(); 541 } 542 mServiceConnections.clear(); 543 mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 544 } 545 546 public void onSetupVoiceInteraction() { 547 // Do nothing. We'll send the voice stuff ourselves. 548 } 549 550 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { 551 if (mRefinementResultReceiver != null) { 552 mRefinementResultReceiver.destroy(); 553 mRefinementResultReceiver = null; 554 } 555 556 if (selectedTarget == null) { 557 Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); 558 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { 559 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget 560 + " cannot match refined source intent " + matchingIntent); 561 } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) { 562 finish(); 563 return; 564 } 565 onRefinementCanceled(); 566 } 567 568 void onRefinementCanceled() { 569 if (mRefinementResultReceiver != null) { 570 mRefinementResultReceiver.destroy(); 571 mRefinementResultReceiver = null; 572 } 573 finish(); 574 } 575 576 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) { 577 final List<Intent> targetIntents = target.getAllSourceIntents(); 578 for (int i = 0, N = targetIntents.size(); i < N; i++) { 579 final Intent targetIntent = targetIntents.get(i); 580 if (targetIntent.filterEquals(matchingIntent)) { 581 return true; 582 } 583 } 584 return false; 585 } 586 587 void filterServiceTargets(String packageName, List<ChooserTarget> targets) { 588 if (targets == null) { 589 return; 590 } 591 592 final PackageManager pm = getPackageManager(); 593 for (int i = targets.size() - 1; i >= 0; i--) { 594 final ChooserTarget target = targets.get(i); 595 final ComponentName targetName = target.getComponentName(); 596 if (packageName != null && packageName.equals(targetName.getPackageName())) { 597 // Anything from the original target's package is fine. 598 continue; 599 } 600 601 boolean remove; 602 try { 603 final ActivityInfo ai = pm.getActivityInfo(targetName, 0); 604 remove = !ai.exported || ai.permission != null; 605 } catch (NameNotFoundException e) { 606 Log.e(TAG, "Target " + target + " returned by " + packageName 607 + " component not found"); 608 remove = true; 609 } 610 611 if (remove) { 612 targets.remove(i); 613 } 614 } 615 } 616 617 @Override 618 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, 619 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 620 boolean filterLastUsed) { 621 final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents, 622 initialIntents, rList, launchedFromUid, filterLastUsed); 623 if (DEBUG) Log.d(TAG, "Adapter created; querying services"); 624 queryTargetServices(adapter); 625 return adapter; 626 } 627 628 final class ChooserTargetInfo implements TargetInfo { 629 private final DisplayResolveInfo mSourceInfo; 630 private final ResolveInfo mBackupResolveInfo; 631 private final ChooserTarget mChooserTarget; 632 private Drawable mBadgeIcon = null; 633 private CharSequence mBadgeContentDescription; 634 private Drawable mDisplayIcon; 635 private final Intent mFillInIntent; 636 private final int mFillInFlags; 637 private final float mModifiedScore; 638 639 public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget, 640 float modifiedScore) { 641 mSourceInfo = sourceInfo; 642 mChooserTarget = chooserTarget; 643 mModifiedScore = modifiedScore; 644 if (sourceInfo != null) { 645 final ResolveInfo ri = sourceInfo.getResolveInfo(); 646 if (ri != null) { 647 final ActivityInfo ai = ri.activityInfo; 648 if (ai != null && ai.applicationInfo != null) { 649 final PackageManager pm = getPackageManager(); 650 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo); 651 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo); 652 } 653 } 654 } 655 final Icon icon = chooserTarget.getIcon(); 656 // TODO do this in the background 657 mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null; 658 659 if (sourceInfo != null) { 660 mBackupResolveInfo = null; 661 } else { 662 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0); 663 } 664 665 mFillInIntent = null; 666 mFillInFlags = 0; 667 } 668 669 private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) { 670 mSourceInfo = other.mSourceInfo; 671 mBackupResolveInfo = other.mBackupResolveInfo; 672 mChooserTarget = other.mChooserTarget; 673 mBadgeIcon = other.mBadgeIcon; 674 mBadgeContentDescription = other.mBadgeContentDescription; 675 mDisplayIcon = other.mDisplayIcon; 676 mFillInIntent = fillInIntent; 677 mFillInFlags = flags; 678 mModifiedScore = other.mModifiedScore; 679 } 680 681 public float getModifiedScore() { 682 return mModifiedScore; 683 } 684 685 @Override 686 public Intent getResolvedIntent() { 687 if (mSourceInfo != null) { 688 return mSourceInfo.getResolvedIntent(); 689 } 690 691 final Intent targetIntent = new Intent(getTargetIntent()); 692 targetIntent.setComponent(mChooserTarget.getComponentName()); 693 targetIntent.putExtras(mChooserTarget.getIntentExtras()); 694 return targetIntent; 695 } 696 697 @Override 698 public ComponentName getResolvedComponentName() { 699 if (mSourceInfo != null) { 700 return mSourceInfo.getResolvedComponentName(); 701 } else if (mBackupResolveInfo != null) { 702 return new ComponentName(mBackupResolveInfo.activityInfo.packageName, 703 mBackupResolveInfo.activityInfo.name); 704 } 705 return null; 706 } 707 708 private Intent getBaseIntentToSend() { 709 Intent result = getResolvedIntent(); 710 if (result == null) { 711 Log.e(TAG, "ChooserTargetInfo: no base intent available to send"); 712 } else { 713 result = new Intent(result); 714 if (mFillInIntent != null) { 715 result.fillIn(mFillInIntent, mFillInFlags); 716 } 717 result.fillIn(mReferrerFillInIntent, 0); 718 } 719 return result; 720 } 721 722 @Override 723 public boolean start(Activity activity, Bundle options) { 724 throw new RuntimeException("ChooserTargets should be started as caller."); 725 } 726 727 @Override 728 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 729 final Intent intent = getBaseIntentToSend(); 730 if (intent == null) { 731 return false; 732 } 733 intent.setComponent(mChooserTarget.getComponentName()); 734 intent.putExtras(mChooserTarget.getIntentExtras()); 735 736 // Important: we will ignore the target security checks in ActivityManager 737 // if and only if the ChooserTarget's target package is the same package 738 // where we got the ChooserTargetService that provided it. This lets a 739 // ChooserTargetService provide a non-exported or permission-guarded target 740 // to the chooser for the user to pick. 741 // 742 // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere 743 // so we'll obey the caller's normal security checks. 744 final boolean ignoreTargetSecurity = mSourceInfo != null 745 && mSourceInfo.getResolvedComponentName().getPackageName() 746 .equals(mChooserTarget.getComponentName().getPackageName()); 747 activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId); 748 return true; 749 } 750 751 @Override 752 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 753 throw new RuntimeException("ChooserTargets should be started as caller."); 754 } 755 756 @Override 757 public ResolveInfo getResolveInfo() { 758 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo; 759 } 760 761 @Override 762 public CharSequence getDisplayLabel() { 763 return mChooserTarget.getTitle(); 764 } 765 766 @Override 767 public CharSequence getExtendedInfo() { 768 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate. 769 return null; 770 } 771 772 @Override 773 public Drawable getDisplayIcon() { 774 return mDisplayIcon; 775 } 776 777 @Override 778 public Drawable getBadgeIcon() { 779 return mBadgeIcon; 780 } 781 782 @Override 783 public CharSequence getBadgeContentDescription() { 784 return mBadgeContentDescription; 785 } 786 787 @Override 788 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { 789 return new ChooserTargetInfo(this, fillInIntent, flags); 790 } 791 792 @Override 793 public List<Intent> getAllSourceIntents() { 794 final List<Intent> results = new ArrayList<>(); 795 if (mSourceInfo != null) { 796 // We only queried the service for the first one in our sourceinfo. 797 results.add(mSourceInfo.getAllSourceIntents().get(0)); 798 } 799 return results; 800 } 801 802 @Override 803 public boolean isPinned() { 804 return mSourceInfo != null ? mSourceInfo.isPinned() : false; 805 } 806 } 807 808 public class ChooserListAdapter extends ResolveListAdapter { 809 public static final int TARGET_BAD = -1; 810 public static final int TARGET_CALLER = 0; 811 public static final int TARGET_SERVICE = 1; 812 public static final int TARGET_STANDARD = 2; 813 814 private static final int MAX_SERVICE_TARGETS = 8; 815 private static final int MAX_TARGETS_PER_SERVICE = 4; 816 817 private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>(); 818 private final List<TargetInfo> mCallerTargets = new ArrayList<>(); 819 private boolean mShowServiceTargets; 820 821 private float mLateFee = 1.f; 822 823 private final BaseChooserTargetComparator mBaseTargetComparator 824 = new BaseChooserTargetComparator(); 825 826 public ChooserListAdapter(Context context, List<Intent> payloadIntents, 827 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 828 boolean filterLastUsed) { 829 // Don't send the initial intents through the shared ResolverActivity path, 830 // we want to separate them into a different section. 831 super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed); 832 833 if (initialIntents != null) { 834 final PackageManager pm = getPackageManager(); 835 for (int i = 0; i < initialIntents.length; i++) { 836 final Intent ii = initialIntents[i]; 837 if (ii == null) { 838 continue; 839 } 840 841 // We reimplement Intent#resolveActivityInfo here because if we have an 842 // implicit intent, we want the ResolveInfo returned by PackageManager 843 // instead of one we reconstruct ourselves. The ResolveInfo returned might 844 // have extra metadata and resolvePackageName set and we want to respect that. 845 ResolveInfo ri = null; 846 ActivityInfo ai = null; 847 final ComponentName cn = ii.getComponent(); 848 if (cn != null) { 849 try { 850 ai = pm.getActivityInfo(ii.getComponent(), 0); 851 ri = new ResolveInfo(); 852 ri.activityInfo = ai; 853 } catch (PackageManager.NameNotFoundException ignored) { 854 // ai will == null below 855 } 856 } 857 if (ai == null) { 858 ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY); 859 ai = ri != null ? ri.activityInfo : null; 860 } 861 if (ai == null) { 862 Log.w(TAG, "No activity found for " + ii); 863 continue; 864 } 865 UserManager userManager = 866 (UserManager) getSystemService(Context.USER_SERVICE); 867 if (ii instanceof LabeledIntent) { 868 LabeledIntent li = (LabeledIntent)ii; 869 ri.resolvePackageName = li.getSourcePackage(); 870 ri.labelRes = li.getLabelResource(); 871 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 872 ri.icon = li.getIconResource(); 873 ri.iconResourceId = ri.icon; 874 } 875 if (userManager.isManagedProfile()) { 876 ri.noResourceId = true; 877 ri.icon = 0; 878 } 879 mCallerTargets.add(new DisplayResolveInfo(ii, ri, 880 ri.loadLabel(pm), null, ii)); 881 } 882 } 883 } 884 885 @Override 886 public boolean showsExtendedInfo(TargetInfo info) { 887 // We have badges so we don't need this text shown. 888 return false; 889 } 890 891 @Override 892 public boolean isComponentPinned(ComponentName name) { 893 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false); 894 } 895 896 @Override 897 public float getScore(DisplayResolveInfo target) { 898 if (target == null) { 899 return CALLER_TARGET_SCORE_BOOST; 900 } 901 float score = super.getScore(target); 902 if (target.isPinned()) { 903 score += PINNED_TARGET_SCORE_BOOST; 904 } 905 return score; 906 } 907 908 @Override 909 public View onCreateView(ViewGroup parent) { 910 return mInflater.inflate( 911 com.android.internal.R.layout.resolve_grid_item, parent, false); 912 } 913 914 @Override 915 public void onListRebuilt() { 916 if (mServiceTargets != null) { 917 pruneServiceTargets(); 918 } 919 } 920 921 @Override 922 public boolean shouldGetResolvedFilter() { 923 return true; 924 } 925 926 @Override 927 public int getCount() { 928 return super.getCount() + getServiceTargetCount() + getCallerTargetCount(); 929 } 930 931 @Override 932 public int getUnfilteredCount() { 933 return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount(); 934 } 935 936 public int getCallerTargetCount() { 937 return mCallerTargets.size(); 938 } 939 940 public int getServiceTargetCount() { 941 if (!mShowServiceTargets) { 942 return 0; 943 } 944 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS); 945 } 946 947 public int getStandardTargetCount() { 948 return super.getCount(); 949 } 950 951 public int getPositionTargetType(int position) { 952 int offset = 0; 953 954 final int callerTargetCount = getCallerTargetCount(); 955 if (position < callerTargetCount) { 956 return TARGET_CALLER; 957 } 958 offset += callerTargetCount; 959 960 final int serviceTargetCount = getServiceTargetCount(); 961 if (position - offset < serviceTargetCount) { 962 return TARGET_SERVICE; 963 } 964 offset += serviceTargetCount; 965 966 final int standardTargetCount = super.getCount(); 967 if (position - offset < standardTargetCount) { 968 return TARGET_STANDARD; 969 } 970 971 return TARGET_BAD; 972 } 973 974 @Override 975 public TargetInfo getItem(int position) { 976 return targetInfoForPosition(position, true); 977 } 978 979 @Override 980 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 981 int offset = 0; 982 983 final int callerTargetCount = getCallerTargetCount(); 984 if (position < callerTargetCount) { 985 return mCallerTargets.get(position); 986 } 987 offset += callerTargetCount; 988 989 final int serviceTargetCount = getServiceTargetCount(); 990 if (position - offset < serviceTargetCount) { 991 return mServiceTargets.get(position - offset); 992 } 993 offset += serviceTargetCount; 994 995 return filtered ? super.getItem(position - offset) 996 : getDisplayInfoAt(position - offset); 997 } 998 999 public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { 1000 if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size() 1001 + " targets"); 1002 final float parentScore = getScore(origTarget); 1003 Collections.sort(targets, mBaseTargetComparator); 1004 float lastScore = 0; 1005 for (int i = 0, N = Math.min(targets.size(), MAX_TARGETS_PER_SERVICE); i < N; i++) { 1006 final ChooserTarget target = targets.get(i); 1007 float targetScore = target.getScore(); 1008 targetScore *= parentScore; 1009 targetScore *= mLateFee; 1010 if (i > 0 && targetScore >= lastScore) { 1011 // Apply a decay so that the top app can't crowd out everything else. 1012 // This incents ChooserTargetServices to define what's truly better. 1013 targetScore = lastScore * 0.95f; 1014 } 1015 insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore)); 1016 1017 if (DEBUG) { 1018 Log.d(TAG, " => " + target.toString() + " score=" + targetScore 1019 + " base=" + target.getScore() 1020 + " lastScore=" + lastScore 1021 + " parentScore=" + parentScore 1022 + " lateFee=" + mLateFee); 1023 } 1024 1025 lastScore = targetScore; 1026 } 1027 1028 mLateFee *= 0.95f; 1029 1030 notifyDataSetChanged(); 1031 } 1032 1033 /** 1034 * Set to true to reveal all service targets at once. 1035 */ 1036 public void setShowServiceTargets(boolean show) { 1037 mShowServiceTargets = show; 1038 notifyDataSetChanged(); 1039 } 1040 1041 private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) { 1042 final float newScore = chooserTargetInfo.getModifiedScore(); 1043 for (int i = 0, N = mServiceTargets.size(); i < N; i++) { 1044 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i); 1045 if (newScore > serviceTarget.getModifiedScore()) { 1046 mServiceTargets.add(i, chooserTargetInfo); 1047 return; 1048 } 1049 } 1050 mServiceTargets.add(chooserTargetInfo); 1051 } 1052 1053 private void pruneServiceTargets() { 1054 if (DEBUG) Log.d(TAG, "pruneServiceTargets"); 1055 for (int i = mServiceTargets.size() - 1; i >= 0; i--) { 1056 final ChooserTargetInfo cti = mServiceTargets.get(i); 1057 if (!hasResolvedTarget(cti.getResolveInfo())) { 1058 if (DEBUG) Log.d(TAG, " => " + i + " " + cti); 1059 mServiceTargets.remove(i); 1060 } 1061 } 1062 } 1063 } 1064 1065 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> { 1066 @Override 1067 public int compare(ChooserTarget lhs, ChooserTarget rhs) { 1068 // Descending order 1069 return (int) Math.signum(rhs.getScore() - lhs.getScore()); 1070 } 1071 } 1072 1073 static class RowScale { 1074 private static final int DURATION = 400; 1075 1076 float mScale; 1077 ChooserRowAdapter mAdapter; 1078 private final ObjectAnimator mAnimator; 1079 1080 public static final FloatProperty<RowScale> PROPERTY = 1081 new FloatProperty<RowScale>("scale") { 1082 @Override 1083 public void setValue(RowScale object, float value) { 1084 object.mScale = value; 1085 object.mAdapter.notifyDataSetChanged(); 1086 } 1087 1088 @Override 1089 public Float get(RowScale object) { 1090 return object.mScale; 1091 } 1092 }; 1093 1094 public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) { 1095 mAdapter = adapter; 1096 mScale = from; 1097 if (from == to) { 1098 mAnimator = null; 1099 return; 1100 } 1101 1102 mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION); 1103 } 1104 1105 public RowScale setInterpolator(Interpolator interpolator) { 1106 if (mAnimator != null) { 1107 mAnimator.setInterpolator(interpolator); 1108 } 1109 return this; 1110 } 1111 1112 public float get() { 1113 return mScale; 1114 } 1115 1116 public void startAnimation() { 1117 if (mAnimator != null) { 1118 mAnimator.start(); 1119 } 1120 } 1121 1122 public void cancelAnimation() { 1123 if (mAnimator != null) { 1124 mAnimator.cancel(); 1125 } 1126 } 1127 } 1128 1129 class ChooserRowAdapter extends BaseAdapter { 1130 private ChooserListAdapter mChooserListAdapter; 1131 private final LayoutInflater mLayoutInflater; 1132 private final int mColumnCount = 4; 1133 private RowScale[] mServiceTargetScale; 1134 private final Interpolator mInterpolator; 1135 1136 public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) { 1137 mChooserListAdapter = wrappedAdapter; 1138 mLayoutInflater = LayoutInflater.from(ChooserActivity.this); 1139 1140 mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this, 1141 android.R.interpolator.decelerate_quint); 1142 1143 wrappedAdapter.registerDataSetObserver(new DataSetObserver() { 1144 @Override 1145 public void onChanged() { 1146 super.onChanged(); 1147 final int rcount = getServiceTargetRowCount(); 1148 if (mServiceTargetScale == null 1149 || mServiceTargetScale.length != rcount) { 1150 RowScale[] old = mServiceTargetScale; 1151 int oldRCount = old != null ? old.length : 0; 1152 mServiceTargetScale = new RowScale[rcount]; 1153 if (old != null && rcount > 0) { 1154 System.arraycopy(old, 0, mServiceTargetScale, 0, 1155 Math.min(old.length, rcount)); 1156 } 1157 1158 for (int i = rcount; i < oldRCount; i++) { 1159 old[i].cancelAnimation(); 1160 } 1161 1162 for (int i = oldRCount; i < rcount; i++) { 1163 final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f) 1164 .setInterpolator(mInterpolator); 1165 mServiceTargetScale[i] = rs; 1166 } 1167 1168 // Start the animations in a separate loop. 1169 // The process of starting animations will result in 1170 // binding views to set up initial values, and we must 1171 // have ALL of the new RowScale objects created above before 1172 // we get started. 1173 for (int i = oldRCount; i < rcount; i++) { 1174 mServiceTargetScale[i].startAnimation(); 1175 } 1176 } 1177 1178 notifyDataSetChanged(); 1179 } 1180 1181 @Override 1182 public void onInvalidated() { 1183 super.onInvalidated(); 1184 notifyDataSetInvalidated(); 1185 if (mServiceTargetScale != null) { 1186 for (RowScale rs : mServiceTargetScale) { 1187 rs.cancelAnimation(); 1188 } 1189 } 1190 } 1191 }); 1192 } 1193 1194 private float getRowScale(int rowPosition) { 1195 final int start = getCallerTargetRowCount(); 1196 final int end = start + getServiceTargetRowCount(); 1197 if (rowPosition >= start && rowPosition < end) { 1198 return mServiceTargetScale[rowPosition - start].get(); 1199 } 1200 return 1.f; 1201 } 1202 1203 @Override 1204 public int getCount() { 1205 return (int) ( 1206 getCallerTargetRowCount() 1207 + getServiceTargetRowCount() 1208 + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount) 1209 ); 1210 } 1211 1212 public int getCallerTargetRowCount() { 1213 return (int) Math.ceil( 1214 (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount); 1215 } 1216 1217 public int getServiceTargetRowCount() { 1218 return (int) Math.ceil( 1219 (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount); 1220 } 1221 1222 @Override 1223 public Object getItem(int position) { 1224 // We have nothing useful to return here. 1225 return position; 1226 } 1227 1228 @Override 1229 public long getItemId(int position) { 1230 return position; 1231 } 1232 1233 @Override 1234 public View getView(int position, View convertView, ViewGroup parent) { 1235 final RowViewHolder holder; 1236 if (convertView == null) { 1237 holder = createViewHolder(parent); 1238 } else { 1239 holder = (RowViewHolder) convertView.getTag(); 1240 } 1241 bindViewHolder(position, holder); 1242 1243 return holder.row; 1244 } 1245 1246 RowViewHolder createViewHolder(ViewGroup parent) { 1247 final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, 1248 parent, false); 1249 final RowViewHolder holder = new RowViewHolder(row, mColumnCount); 1250 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1251 1252 for (int i = 0; i < mColumnCount; i++) { 1253 final View v = mChooserListAdapter.createView(row); 1254 final int column = i; 1255 v.setOnClickListener(new OnClickListener() { 1256 @Override 1257 public void onClick(View v) { 1258 startSelected(holder.itemIndices[column], false, true); 1259 } 1260 }); 1261 v.setOnLongClickListener(new OnLongClickListener() { 1262 @Override 1263 public boolean onLongClick(View v) { 1264 showTargetDetails( 1265 mChooserListAdapter.resolveInfoForPosition( 1266 holder.itemIndices[column], true)); 1267 return true; 1268 } 1269 }); 1270 row.addView(v); 1271 holder.cells[i] = v; 1272 1273 // Force height to be a given so we don't have visual disruption during scaling. 1274 LayoutParams lp = v.getLayoutParams(); 1275 v.measure(spec, spec); 1276 if (lp == null) { 1277 lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight()); 1278 row.setLayoutParams(lp); 1279 } else { 1280 lp.height = v.getMeasuredHeight(); 1281 } 1282 } 1283 1284 // Pre-measure so we can scale later. 1285 holder.measure(); 1286 LayoutParams lp = row.getLayoutParams(); 1287 if (lp == null) { 1288 lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight); 1289 row.setLayoutParams(lp); 1290 } else { 1291 lp.height = holder.measuredRowHeight; 1292 } 1293 row.setTag(holder); 1294 return holder; 1295 } 1296 1297 void bindViewHolder(int rowPosition, RowViewHolder holder) { 1298 final int start = getFirstRowPosition(rowPosition); 1299 final int startType = mChooserListAdapter.getPositionTargetType(start); 1300 1301 int end = start + mColumnCount - 1; 1302 while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) { 1303 end--; 1304 } 1305 1306 if (startType == ChooserListAdapter.TARGET_SERVICE) { 1307 holder.row.setBackgroundColor( 1308 getColor(R.color.chooser_service_row_background_color)); 1309 } else { 1310 holder.row.setBackgroundColor(Color.TRANSPARENT); 1311 } 1312 1313 final int oldHeight = holder.row.getLayoutParams().height; 1314 holder.row.getLayoutParams().height = Math.max(1, 1315 (int) (holder.measuredRowHeight * getRowScale(rowPosition))); 1316 if (holder.row.getLayoutParams().height != oldHeight) { 1317 holder.row.requestLayout(); 1318 } 1319 1320 for (int i = 0; i < mColumnCount; i++) { 1321 final View v = holder.cells[i]; 1322 if (start + i <= end) { 1323 v.setVisibility(View.VISIBLE); 1324 holder.itemIndices[i] = start + i; 1325 mChooserListAdapter.bindView(holder.itemIndices[i], v); 1326 } else { 1327 v.setVisibility(View.GONE); 1328 } 1329 } 1330 } 1331 1332 int getFirstRowPosition(int row) { 1333 final int callerCount = mChooserListAdapter.getCallerTargetCount(); 1334 final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount); 1335 1336 if (row < callerRows) { 1337 return row * mColumnCount; 1338 } 1339 1340 final int serviceCount = mChooserListAdapter.getServiceTargetCount(); 1341 final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount); 1342 1343 if (row < callerRows + serviceRows) { 1344 return callerCount + (row - callerRows) * mColumnCount; 1345 } 1346 1347 return callerCount + serviceCount 1348 + (row - callerRows - serviceRows) * mColumnCount; 1349 } 1350 } 1351 1352 static class RowViewHolder { 1353 final View[] cells; 1354 final ViewGroup row; 1355 int measuredRowHeight; 1356 int[] itemIndices; 1357 1358 public RowViewHolder(ViewGroup row, int cellCount) { 1359 this.row = row; 1360 this.cells = new View[cellCount]; 1361 this.itemIndices = new int[cellCount]; 1362 } 1363 1364 public void measure() { 1365 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1366 row.measure(spec, spec); 1367 measuredRowHeight = row.getMeasuredHeight(); 1368 } 1369 } 1370 1371 static class ChooserTargetServiceConnection implements ServiceConnection { 1372 private DisplayResolveInfo mOriginalTarget; 1373 private ComponentName mConnectedComponent; 1374 private ChooserActivity mChooserActivity; 1375 private final Object mLock = new Object(); 1376 1377 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { 1378 @Override 1379 public void sendResult(List<ChooserTarget> targets) throws RemoteException { 1380 synchronized (mLock) { 1381 if (mChooserActivity == null) { 1382 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from " 1383 + mConnectedComponent + "; ignoring..."); 1384 return; 1385 } 1386 mChooserActivity.filterServiceTargets( 1387 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets); 1388 final Message msg = Message.obtain(); 1389 msg.what = CHOOSER_TARGET_SERVICE_RESULT; 1390 msg.obj = new ServiceResultInfo(mOriginalTarget, targets, 1391 ChooserTargetServiceConnection.this); 1392 mChooserActivity.mChooserHandler.sendMessage(msg); 1393 } 1394 } 1395 }; 1396 1397 public ChooserTargetServiceConnection(ChooserActivity chooserActivity, 1398 DisplayResolveInfo dri) { 1399 mChooserActivity = chooserActivity; 1400 mOriginalTarget = dri; 1401 } 1402 1403 @Override 1404 public void onServiceConnected(ComponentName name, IBinder service) { 1405 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); 1406 synchronized (mLock) { 1407 if (mChooserActivity == null) { 1408 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected"); 1409 return; 1410 } 1411 1412 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); 1413 try { 1414 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), 1415 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); 1416 } catch (RemoteException e) { 1417 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); 1418 mChooserActivity.unbindService(this); 1419 destroy(); 1420 mChooserActivity.mServiceConnections.remove(this); 1421 } 1422 } 1423 } 1424 1425 @Override 1426 public void onServiceDisconnected(ComponentName name) { 1427 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); 1428 synchronized (mLock) { 1429 if (mChooserActivity == null) { 1430 Log.e(TAG, 1431 "destroyed ChooserTargetServiceConnection got onServiceDisconnected"); 1432 return; 1433 } 1434 1435 mChooserActivity.unbindService(this); 1436 destroy(); 1437 mChooserActivity.mServiceConnections.remove(this); 1438 if (mChooserActivity.mServiceConnections.isEmpty()) { 1439 mChooserActivity.mChooserHandler.removeMessages( 1440 CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); 1441 mChooserActivity.sendVoiceChoicesIfNeeded(); 1442 } 1443 mConnectedComponent = null; 1444 } 1445 } 1446 1447 public void destroy() { 1448 synchronized (mLock) { 1449 mChooserActivity = null; 1450 mOriginalTarget = null; 1451 } 1452 } 1453 1454 @Override 1455 public String toString() { 1456 return "ChooserTargetServiceConnection{service=" 1457 + mConnectedComponent + ", activity=" 1458 + (mOriginalTarget != null 1459 ? mOriginalTarget.getResolveInfo().activityInfo.toString() 1460 : "<connection destroyed>") + "}"; 1461 } 1462 } 1463 1464 static class ServiceResultInfo { 1465 public final DisplayResolveInfo originalTarget; 1466 public final List<ChooserTarget> resultTargets; 1467 public final ChooserTargetServiceConnection connection; 1468 1469 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, 1470 ChooserTargetServiceConnection c) { 1471 originalTarget = ot; 1472 resultTargets = rt; 1473 connection = c; 1474 } 1475 } 1476 1477 static class RefinementResultReceiver extends ResultReceiver { 1478 private ChooserActivity mChooserActivity; 1479 private TargetInfo mSelectedTarget; 1480 1481 public RefinementResultReceiver(ChooserActivity host, TargetInfo target, 1482 Handler handler) { 1483 super(handler); 1484 mChooserActivity = host; 1485 mSelectedTarget = target; 1486 } 1487 1488 @Override 1489 protected void onReceiveResult(int resultCode, Bundle resultData) { 1490 if (mChooserActivity == null) { 1491 Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); 1492 return; 1493 } 1494 if (resultData == null) { 1495 Log.e(TAG, "RefinementResultReceiver received null resultData"); 1496 return; 1497 } 1498 1499 switch (resultCode) { 1500 case RESULT_CANCELED: 1501 mChooserActivity.onRefinementCanceled(); 1502 break; 1503 case RESULT_OK: 1504 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); 1505 if (intentParcelable instanceof Intent) { 1506 mChooserActivity.onRefinementResult(mSelectedTarget, 1507 (Intent) intentParcelable); 1508 } else { 1509 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" 1510 + " in resultData with key Intent.EXTRA_INTENT"); 1511 } 1512 break; 1513 default: 1514 Log.w(TAG, "Unknown result code " + resultCode 1515 + " sent to RefinementResultReceiver"); 1516 break; 1517 } 1518 } 1519 1520 public void destroy() { 1521 mChooserActivity = null; 1522 mSelectedTarget = null; 1523 } 1524 } 1525 1526 class OffsetDataSetObserver extends DataSetObserver { 1527 private final AbsListView mListView; 1528 private int mCachedViewType = -1; 1529 private View mCachedView; 1530 1531 public OffsetDataSetObserver(AbsListView listView) { 1532 mListView = listView; 1533 } 1534 1535 @Override 1536 public void onChanged() { 1537 if (mResolverDrawerLayout == null) { 1538 return; 1539 } 1540 1541 final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount(); 1542 int offset = 0; 1543 for (int i = 0; i < chooserTargetRows; i++) { 1544 final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i; 1545 final int vt = mChooserRowAdapter.getItemViewType(pos); 1546 if (vt != mCachedViewType) { 1547 mCachedView = null; 1548 } 1549 final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView); 1550 int height = ((RowViewHolder) (v.getTag())).measuredRowHeight; 1551 1552 offset += (int) (height * mChooserRowAdapter.getRowScale(pos)); 1553 1554 if (vt >= 0) { 1555 mCachedViewType = vt; 1556 mCachedView = v; 1557 } else { 1558 mCachedViewType = -1; 1559 } 1560 } 1561 1562 mResolverDrawerLayout.setCollapsibleHeightReserved(offset); 1563 } 1564 } 1565} 1566