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