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