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