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