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