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 static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.AnimatorSet; 24 import android.animation.ObjectAnimator; 25 import android.animation.ValueAnimator; 26 import android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.app.Activity; 30 import android.app.ActivityManager; 31 import android.app.SharedElementCallback; 32 import android.app.prediction.AppPredictionContext; 33 import android.app.prediction.AppPredictionManager; 34 import android.app.prediction.AppPredictor; 35 import android.app.prediction.AppTarget; 36 import android.app.prediction.AppTargetEvent; 37 import android.app.prediction.AppTargetId; 38 import android.compat.annotation.UnsupportedAppUsage; 39 import android.content.ClipData; 40 import android.content.ClipboardManager; 41 import android.content.ComponentName; 42 import android.content.ContentResolver; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.IntentFilter; 46 import android.content.IntentSender; 47 import android.content.IntentSender.SendIntentException; 48 import android.content.ServiceConnection; 49 import android.content.SharedPreferences; 50 import android.content.pm.ActivityInfo; 51 import android.content.pm.ApplicationInfo; 52 import android.content.pm.PackageManager; 53 import android.content.pm.PackageManager.NameNotFoundException; 54 import android.content.pm.ResolveInfo; 55 import android.content.pm.ShortcutInfo; 56 import android.content.pm.ShortcutManager; 57 import android.content.res.Configuration; 58 import android.content.res.Resources; 59 import android.database.Cursor; 60 import android.database.DataSetObserver; 61 import android.graphics.Bitmap; 62 import android.graphics.Canvas; 63 import android.graphics.Color; 64 import android.graphics.Paint; 65 import android.graphics.Path; 66 import android.graphics.drawable.AnimatedVectorDrawable; 67 import android.graphics.drawable.Drawable; 68 import android.metrics.LogMaker; 69 import android.net.Uri; 70 import android.os.AsyncTask; 71 import android.os.Bundle; 72 import android.os.Environment; 73 import android.os.Handler; 74 import android.os.IBinder; 75 import android.os.Message; 76 import android.os.Parcelable; 77 import android.os.PatternMatcher; 78 import android.os.RemoteException; 79 import android.os.ResultReceiver; 80 import android.os.UserHandle; 81 import android.os.UserManager; 82 import android.os.storage.StorageManager; 83 import android.provider.DeviceConfig; 84 import android.provider.DocumentsContract; 85 import android.provider.Downloads; 86 import android.provider.OpenableColumns; 87 import android.provider.Settings; 88 import android.service.chooser.ChooserTarget; 89 import android.service.chooser.ChooserTargetService; 90 import android.service.chooser.IChooserTargetResult; 91 import android.service.chooser.IChooserTargetService; 92 import android.text.TextUtils; 93 import android.util.AttributeSet; 94 import android.util.HashedStringCache; 95 import android.util.Log; 96 import android.util.Pair; 97 import android.util.Size; 98 import android.util.Slog; 99 import android.view.LayoutInflater; 100 import android.view.View; 101 import android.view.View.MeasureSpec; 102 import android.view.View.OnClickListener; 103 import android.view.ViewGroup; 104 import android.view.ViewGroup.LayoutParams; 105 import android.view.ViewTreeObserver; 106 import android.view.WindowInsets; 107 import android.view.animation.AccelerateInterpolator; 108 import android.view.animation.DecelerateInterpolator; 109 import android.widget.Button; 110 import android.widget.ImageView; 111 import android.widget.Space; 112 import android.widget.TextView; 113 import android.widget.Toast; 114 115 import com.android.internal.R; 116 import com.android.internal.annotations.VisibleForTesting; 117 import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter; 118 import com.android.internal.app.ResolverListAdapter.ViewHolder; 119 import com.android.internal.app.chooser.ChooserTargetInfo; 120 import com.android.internal.app.chooser.DisplayResolveInfo; 121 import com.android.internal.app.chooser.MultiDisplayResolveInfo; 122 import com.android.internal.app.chooser.NotSelectableTargetInfo; 123 import com.android.internal.app.chooser.SelectableTargetInfo; 124 import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator; 125 import com.android.internal.app.chooser.TargetInfo; 126 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 127 import com.android.internal.content.PackageMonitor; 128 import com.android.internal.logging.MetricsLogger; 129 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 130 import com.android.internal.util.FrameworkStatsLog; 131 import com.android.internal.widget.GridLayoutManager; 132 import com.android.internal.widget.RecyclerView; 133 import com.android.internal.widget.ResolverDrawerLayout; 134 import com.android.internal.widget.ViewPager; 135 136 import com.google.android.collect.Lists; 137 138 import java.io.File; 139 import java.io.IOException; 140 import java.lang.annotation.Retention; 141 import java.lang.annotation.RetentionPolicy; 142 import java.net.URISyntaxException; 143 import java.text.Collator; 144 import java.util.ArrayList; 145 import java.util.Collections; 146 import java.util.Comparator; 147 import java.util.HashMap; 148 import java.util.HashSet; 149 import java.util.List; 150 import java.util.Map; 151 import java.util.Set; 152 153 /** 154 * The Chooser Activity handles intent resolution specifically for sharing intents - 155 * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence). 156 * 157 */ 158 public class ChooserActivity extends ResolverActivity implements 159 ChooserListAdapter.ChooserListCommunicator, 160 SelectableTargetInfoCommunicator { 161 private static final String TAG = "ChooserActivity"; 162 private AppPredictor mPersonalAppPredictor; 163 private AppPredictor mWorkAppPredictor; 164 private boolean mShouldDisplayLandscape; 165 166 private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4; 167 private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8; 168 169 @UnsupportedAppUsage ChooserActivity()170 public ChooserActivity() { 171 } 172 /** 173 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself 174 * in onStop when launched in a new task. If this extra is set to true, we do not finish 175 * ourselves when onStop gets called. 176 */ 177 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP 178 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP"; 179 180 /** 181 * Transition name for the first image preview. 182 * To be used for shared element transition into this activity. 183 * @hide 184 */ 185 public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image"; 186 187 private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions"; 188 189 private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label"; 190 private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon"; 191 192 private static final boolean DEBUG = true; 193 194 private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true; 195 // TODO(b/123088566) Share these in a better way. 196 private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; 197 public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share"; 198 public static final String CHOOSER_TARGET = "chooser_target"; 199 private static final String SHORTCUT_TARGET = "shortcut_target"; 200 private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; 201 public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter"; 202 203 @VisibleForTesting 204 public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250; 205 206 private boolean mIsAppPredictorComponentAvailable; 207 private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache; 208 private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache; 209 private Map<ComponentName, ComponentName> mChooserTargetComponentNameCache; 210 211 public static final int TARGET_TYPE_DEFAULT = 0; 212 public static final int TARGET_TYPE_CHOOSER_TARGET = 1; 213 public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2; 214 public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3; 215 216 public static final int SELECTION_TYPE_SERVICE = 1; 217 public static final int SELECTION_TYPE_APP = 2; 218 public static final int SELECTION_TYPE_STANDARD = 3; 219 public static final int SELECTION_TYPE_COPY = 4; 220 public static final int SELECTION_TYPE_NEARBY = 5; 221 public static final int SELECTION_TYPE_EDIT = 6; 222 223 private static final int SCROLL_STATUS_IDLE = 0; 224 private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1; 225 private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2; 226 227 // statsd logger wrapper 228 protected ChooserActivityLogger mChooserActivityLogger; 229 230 private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true; 231 232 @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = { 233 TARGET_TYPE_DEFAULT, 234 TARGET_TYPE_CHOOSER_TARGET, 235 TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER, 236 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE 237 }) 238 @Retention(RetentionPolicy.SOURCE) 239 public @interface ShareTargetType {} 240 241 /** 242 * The transition time between placeholders for direct share to a message 243 * indicating that non are available. 244 */ 245 private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200; 246 247 private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f; 248 249 // TODO(b/121287224): Re-evaluate this limit 250 private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; 251 252 private static final int QUERY_TARGET_SERVICE_LIMIT = 5; 253 254 private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7; 255 private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI, 256 SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS, 257 DEFAULT_SALT_EXPIRATION_DAYS); 258 259 private Bundle mReplacementExtras; 260 private IntentSender mChosenComponentSender; 261 private IntentSender mRefinementIntentSender; 262 private RefinementResultReceiver mRefinementResultReceiver; 263 private ChooserTarget[] mCallerChooserTargets; 264 private ComponentName[] mFilteredComponentNames; 265 266 private Intent mReferrerFillInIntent; 267 268 private long mChooserShownTime; 269 protected boolean mIsSuccessfullySelected; 270 271 private long mQueriedTargetServicesTimeMs; 272 private long mQueriedSharingShortcutsTimeMs; 273 274 private int mChooserRowServiceSpacing; 275 276 private int mCurrAvailableWidth = 0; 277 private int mLastNumberOfChildren = -1; 278 279 private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment"; 280 281 private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); 282 private final Set<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>(); 283 284 private static final int MAX_LOG_RANK_POSITION = 12; 285 286 private static final int MAX_EXTRA_INITIAL_INTENTS = 2; 287 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2; 288 289 private SharedPreferences mPinnedSharedPrefs; 290 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings"; 291 292 @Retention(SOURCE) 293 @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT}) 294 private @interface ContentPreviewType { 295 } 296 297 // Starting at 1 since 0 is considered "undefined" for some of the database transformations 298 // of tron logs. 299 protected static final int CONTENT_PREVIEW_IMAGE = 1; 300 protected static final int CONTENT_PREVIEW_FILE = 2; 301 protected static final int CONTENT_PREVIEW_TEXT = 3; 302 protected MetricsLogger mMetricsLogger; 303 304 private ContentPreviewCoordinator mPreviewCoord; 305 private int mScrollStatus = SCROLL_STATUS_IDLE; 306 307 @VisibleForTesting 308 protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; 309 310 private boolean mRemoveSharedElements = false; 311 312 private class ContentPreviewCoordinator { 313 private static final int IMAGE_FADE_IN_MILLIS = 150; 314 private static final int IMAGE_LOAD_TIMEOUT = 1; 315 private static final int IMAGE_LOAD_INTO_VIEW = 2; 316 317 private final int mImageLoadTimeoutMillis = 318 getResources().getInteger(R.integer.config_shortAnimTime); 319 320 private final View mParentView; 321 private boolean mHideParentOnFail; 322 private boolean mAtLeastOneLoaded = false; 323 324 class LoadUriTask { 325 public final Uri mUri; 326 public final int mImageResourceId; 327 public final int mExtraCount; 328 public final Bitmap mBmp; 329 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp)330 LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) { 331 this.mImageResourceId = imageResourceId; 332 this.mUri = uri; 333 this.mExtraCount = extraCount; 334 this.mBmp = bmp; 335 } 336 } 337 338 // If at least one image loads within the timeout period, allow other 339 // loads to continue. Otherwise terminate and optionally hide 340 // the parent area 341 private final Handler mHandler = new Handler() { 342 @Override 343 public void handleMessage(Message msg) { 344 switch (msg.what) { 345 case IMAGE_LOAD_TIMEOUT: 346 maybeHideContentPreview(); 347 break; 348 349 case IMAGE_LOAD_INTO_VIEW: 350 if (isFinishing()) break; 351 352 LoadUriTask task = (LoadUriTask) msg.obj; 353 RoundedRectImageView imageView = mParentView.findViewById( 354 task.mImageResourceId); 355 if (task.mBmp == null) { 356 imageView.setVisibility(View.GONE); 357 maybeHideContentPreview(); 358 return; 359 } 360 361 mAtLeastOneLoaded = true; 362 imageView.setVisibility(View.VISIBLE); 363 imageView.setAlpha(0.0f); 364 imageView.setImageBitmap(task.mBmp); 365 366 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f, 367 1.0f); 368 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f)); 369 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS); 370 fadeAnim.start(); 371 372 if (task.mExtraCount > 0) { 373 imageView.setExtraImageCount(task.mExtraCount); 374 } 375 376 setupPreDrawForSharedElementTransition(imageView); 377 } 378 } 379 }; 380 setupPreDrawForSharedElementTransition(View v)381 private void setupPreDrawForSharedElementTransition(View v) { 382 v.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 383 @Override 384 public boolean onPreDraw() { 385 v.getViewTreeObserver().removeOnPreDrawListener(this); 386 387 if (!mRemoveSharedElements && isActivityTransitionRunning()) { 388 // Disable the window animations as it interferes with the 389 // transition animation. 390 getWindow().setWindowAnimations(0); 391 } 392 startPostponedEnterTransition(); 393 return true; 394 } 395 }); 396 } 397 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail)398 ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) { 399 super(); 400 401 this.mParentView = parentView; 402 this.mHideParentOnFail = hideParentOnFail; 403 } 404 loadUriIntoView(final int imageResourceId, final Uri uri, final int extraImages)405 private void loadUriIntoView(final int imageResourceId, final Uri uri, 406 final int extraImages) { 407 mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis); 408 409 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { 410 int size = getResources().getDimensionPixelSize( 411 R.dimen.chooser_preview_image_max_dimen); 412 final Bitmap bmp = loadThumbnail(uri, new Size(size, size)); 413 final Message msg = Message.obtain(); 414 msg.what = IMAGE_LOAD_INTO_VIEW; 415 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp); 416 mHandler.sendMessage(msg); 417 }); 418 } 419 cancelLoads()420 private void cancelLoads() { 421 mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW); 422 mHandler.removeMessages(IMAGE_LOAD_TIMEOUT); 423 } 424 maybeHideContentPreview()425 private void maybeHideContentPreview() { 426 if (!mAtLeastOneLoaded) { 427 if (mHideParentOnFail) { 428 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load" 429 + " within " + mImageLoadTimeoutMillis + "ms."); 430 collapseParentView(); 431 if (shouldShowTabs()) { 432 hideStickyContentPreview(); 433 } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) { 434 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() 435 .hideContentPreview(); 436 } 437 mHideParentOnFail = false; 438 } 439 mRemoveSharedElements = true; 440 startPostponedEnterTransition(); 441 } 442 } 443 collapseParentView()444 private void collapseParentView() { 445 // This will effectively hide the content preview row by forcing the height 446 // to zero. It is faster than forcing a relayout of the listview 447 final View v = mParentView; 448 int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY); 449 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY); 450 v.measure(widthSpec, heightSpec); 451 v.getLayoutParams().height = 0; 452 v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop()); 453 v.invalidate(); 454 } 455 } 456 457 private final ChooserHandler mChooserHandler = new ChooserHandler(); 458 459 private class ChooserHandler extends Handler { 460 private static final int CHOOSER_TARGET_SERVICE_RESULT = 1; 461 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2; 462 private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3; 463 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4; 464 private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5; 465 private static final int LIST_VIEW_UPDATE_MESSAGE = 6; 466 467 private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 1000; 468 private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 300; 469 470 private boolean mMinTimeoutPassed = false; 471 removeAllMessages()472 private void removeAllMessages() { 473 removeMessages(LIST_VIEW_UPDATE_MESSAGE); 474 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT); 475 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT); 476 removeMessages(CHOOSER_TARGET_SERVICE_RESULT); 477 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT); 478 removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED); 479 } 480 restartServiceRequestTimer()481 private void restartServiceRequestTimer() { 482 mMinTimeoutPassed = false; 483 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT); 484 removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT); 485 486 if (DEBUG) { 487 Log.d(TAG, "queryTargets setting watchdog timer for " 488 + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms"); 489 } 490 491 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT, 492 WATCHDOG_TIMEOUT_MIN_MILLIS); 493 sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT, 494 WATCHDOG_TIMEOUT_MAX_MILLIS); 495 } 496 maybeStopServiceRequestTimer()497 private void maybeStopServiceRequestTimer() { 498 // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts 499 // and older-style direct share services, have had time to load, otherwise 500 // just checking mServiceConnections could force us to end prematurely 501 if (mMinTimeoutPassed && mServiceConnections.isEmpty()) { 502 logDirectShareTargetReceived( 503 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE); 504 sendVoiceChoicesIfNeeded(); 505 mChooserMultiProfilePagerAdapter.getActiveListAdapter() 506 .completeServiceTargetLoading(); 507 } 508 } 509 510 @Override handleMessage(Message msg)511 public void handleMessage(Message msg) { 512 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null || isDestroyed()) { 513 return; 514 } 515 516 switch (msg.what) { 517 case CHOOSER_TARGET_SERVICE_RESULT: 518 if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT"); 519 final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; 520 if (!mServiceConnections.contains(sri.connection)) { 521 Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection 522 + sri.originalTarget.getResolveInfo().activityInfo.packageName 523 + " returned after being removed from active connections." 524 + " Have you considered returning results faster?"); 525 break; 526 } 527 if (sri.resultTargets != null) { 528 ChooserListAdapter adapterForUserHandle = 529 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle( 530 sri.userHandle); 531 if (adapterForUserHandle != null) { 532 adapterForUserHandle.addServiceResults(sri.originalTarget, 533 sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, 534 /* directShareShortcutInfoCache */ null, mServiceConnections); 535 if (!sri.resultTargets.isEmpty() && sri.originalTarget != null) { 536 mChooserTargetComponentNameCache.put( 537 sri.resultTargets.get(0).getComponentName(), 538 sri.originalTarget.getResolvedComponentName()); 539 } 540 } 541 } 542 unbindService(sri.connection); 543 sri.connection.destroy(); 544 mServiceConnections.remove(sri.connection); 545 maybeStopServiceRequestTimer(); 546 break; 547 548 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT: 549 mMinTimeoutPassed = true; 550 maybeStopServiceRequestTimer(); 551 break; 552 553 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT: 554 mMinTimeoutPassed = true; 555 if (!mServiceConnections.isEmpty()) { 556 getChooserActivityLogger().logSharesheetDirectLoadTimeout(); 557 } 558 unbindRemainingServices(); 559 maybeStopServiceRequestTimer(); 560 break; 561 562 case LIST_VIEW_UPDATE_MESSAGE: 563 if (DEBUG) { 564 Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; "); 565 } 566 567 UserHandle userHandle = (UserHandle) msg.obj; 568 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle) 569 .refreshListView(); 570 break; 571 572 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT: 573 if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT"); 574 final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj; 575 if (resultInfo.resultTargets != null) { 576 ChooserListAdapter adapterForUserHandle = 577 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle( 578 resultInfo.userHandle); 579 if (adapterForUserHandle != null) { 580 adapterForUserHandle.addServiceResults( 581 resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1, 582 mDirectShareShortcutInfoCache, mServiceConnections); 583 } 584 } 585 break; 586 587 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED: 588 logDirectShareTargetReceived( 589 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER); 590 sendVoiceChoicesIfNeeded(); 591 getChooserActivityLogger().logSharesheetDirectLoadComplete(); 592 break; 593 594 default: 595 super.handleMessage(msg); 596 } 597 } 598 }; 599 600 @Override onCreate(Bundle savedInstanceState)601 protected void onCreate(Bundle savedInstanceState) { 602 final long intentReceivedTime = System.currentTimeMillis(); 603 getChooserActivityLogger().logSharesheetTriggered(); 604 // This is the only place this value is being set. Effectively final. 605 mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable(); 606 607 mIsSuccessfullySelected = false; 608 Intent intent = getIntent(); 609 Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT); 610 if (targetParcelable instanceof Uri) { 611 try { 612 targetParcelable = Intent.parseUri(targetParcelable.toString(), 613 Intent.URI_INTENT_SCHEME); 614 } catch (URISyntaxException ex) { 615 // doesn't parse as an intent; let the next test fail and error out 616 } 617 } 618 619 if (!(targetParcelable instanceof Intent)) { 620 Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable); 621 finish(); 622 super.onCreate(null); 623 return; 624 } 625 Intent target = (Intent) targetParcelable; 626 if (target != null) { 627 modifyTargetIntent(target); 628 } 629 Parcelable[] targetsParcelable 630 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS); 631 if (targetsParcelable != null) { 632 final boolean offset = target == null; 633 Intent[] additionalTargets = 634 new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length]; 635 for (int i = 0; i < targetsParcelable.length; i++) { 636 if (!(targetsParcelable[i] instanceof Intent)) { 637 Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: " 638 + targetsParcelable[i]); 639 finish(); 640 super.onCreate(null); 641 return; 642 } 643 final Intent additionalTarget = (Intent) targetsParcelable[i]; 644 if (i == 0 && target == null) { 645 target = additionalTarget; 646 modifyTargetIntent(target); 647 } else { 648 additionalTargets[offset ? i - 1 : i] = additionalTarget; 649 modifyTargetIntent(additionalTarget); 650 } 651 } 652 setAdditionalTargets(additionalTargets); 653 } 654 655 mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); 656 657 // Do not allow the title to be changed when sharing content 658 CharSequence title = null; 659 if (target != null) { 660 if (!isSendAction(target)) { 661 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); 662 } else { 663 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a" 664 + " preview title by using EXTRA_TITLE property of the wrapped" 665 + " EXTRA_INTENT."); 666 } 667 } 668 669 int defaultTitleRes = 0; 670 if (title == null) { 671 defaultTitleRes = com.android.internal.R.string.chooseActivity; 672 } 673 674 Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS); 675 Intent[] initialIntents = null; 676 if (pa != null) { 677 int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS); 678 initialIntents = new Intent[count]; 679 for (int i = 0; i < count; i++) { 680 if (!(pa[i] instanceof Intent)) { 681 Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]); 682 finish(); 683 super.onCreate(null); 684 return; 685 } 686 final Intent in = (Intent) pa[i]; 687 modifyTargetIntent(in); 688 initialIntents[i] = in; 689 } 690 } 691 692 mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer()); 693 694 mChosenComponentSender = intent.getParcelableExtra( 695 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); 696 mRefinementIntentSender = intent.getParcelableExtra( 697 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); 698 setSafeForwardingMode(true); 699 700 mPinnedSharedPrefs = getPinnedSharedPrefs(this); 701 702 pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS); 703 704 705 // Exclude out Nearby from main list if chip is present, to avoid duplication 706 ComponentName nearbySharingComponent = getNearbySharingComponent(); 707 boolean hasNearby = nearbySharingComponent != null; 708 709 if (pa != null) { 710 ComponentName[] names = new ComponentName[pa.length + (hasNearby ? 1 : 0)]; 711 for (int i = 0; i < pa.length; i++) { 712 if (!(pa[i] instanceof ComponentName)) { 713 Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]); 714 names = null; 715 break; 716 } 717 names[i] = (ComponentName) pa[i]; 718 } 719 if (hasNearby) { 720 names[names.length - 1] = nearbySharingComponent; 721 } 722 723 mFilteredComponentNames = names; 724 } else if (hasNearby) { 725 mFilteredComponentNames = new ComponentName[1]; 726 mFilteredComponentNames[0] = nearbySharingComponent; 727 } 728 729 pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS); 730 if (pa != null) { 731 int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS); 732 ChooserTarget[] targets = new ChooserTarget[count]; 733 for (int i = 0; i < count; i++) { 734 if (!(pa[i] instanceof ChooserTarget)) { 735 Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]); 736 targets = null; 737 break; 738 } 739 targets[i] = (ChooserTarget) pa[i]; 740 } 741 mCallerChooserTargets = targets; 742 } 743 744 mShouldDisplayLandscape = shouldDisplayLandscape( 745 getResources().getConfiguration().orientation); 746 setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false)); 747 super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, 748 null, false); 749 750 mChooserShownTime = System.currentTimeMillis(); 751 final long systemCost = mChooserShownTime - intentReceivedTime; 752 753 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN) 754 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE : 755 MetricsEvent.PARENT_PROFILE) 756 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType()) 757 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost)); 758 759 mChooserRowServiceSpacing = getResources() 760 .getDimensionPixelSize(R.dimen.chooser_service_spacing); 761 762 if (mResolverDrawerLayout != null) { 763 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange); 764 765 // expand/shrink direct share 4 -> 8 viewgroup 766 if (isSendAction(target)) { 767 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll); 768 } 769 770 mResolverDrawerLayout.setOnCollapsedChangedListener( 771 new ResolverDrawerLayout.OnCollapsedChangedListener() { 772 773 // Only consider one expansion per activity creation 774 private boolean mWrittenOnce = false; 775 776 @Override 777 public void onCollapsedChanged(boolean isCollapsed) { 778 if (!isCollapsed && !mWrittenOnce) { 779 incrementNumSheetExpansions(); 780 mWrittenOnce = true; 781 } 782 getChooserActivityLogger() 783 .logSharesheetExpansionChanged(isCollapsed); 784 } 785 }); 786 } 787 788 if (DEBUG) { 789 Log.d(TAG, "System Time Cost is " + systemCost); 790 } 791 792 getChooserActivityLogger().logShareStarted( 793 FrameworkStatsLog.SHARESHEET_STARTED, 794 getReferrerPackageName(), 795 target.getType(), 796 mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length, 797 initialIntents == null ? 0 : initialIntents.length, 798 isWorkProfile(), 799 findPreferredContentPreview(getTargetIntent(), getContentResolver()), 800 target.getAction() 801 ); 802 mDirectShareShortcutInfoCache = new HashMap<>(); 803 mChooserTargetComponentNameCache = new HashMap<>(); 804 805 setEnterSharedElementCallback(new SharedElementCallback() { 806 @Override 807 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { 808 if (mRemoveSharedElements) { 809 names.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME); 810 sharedElements.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME); 811 } 812 super.onMapSharedElements(names, sharedElements); 813 mRemoveSharedElements = false; 814 } 815 }); 816 postponeEnterTransition(); 817 } 818 819 @Override appliedThemeResId()820 protected int appliedThemeResId() { 821 return R.style.Theme_DeviceDefault_Chooser; 822 } 823 setupAppPredictorForUser(UserHandle userHandle, AppPredictor.Callback appPredictorCallback)824 private AppPredictor setupAppPredictorForUser(UserHandle userHandle, 825 AppPredictor.Callback appPredictorCallback) { 826 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle); 827 if (appPredictor == null) { 828 return null; 829 } 830 mDirectShareAppTargetCache = new HashMap<>(); 831 appPredictor.registerPredictionUpdates(this.getMainExecutor(), appPredictorCallback); 832 return appPredictor; 833 } 834 createAppPredictorCallback( ChooserListAdapter chooserListAdapter)835 private AppPredictor.Callback createAppPredictorCallback( 836 ChooserListAdapter chooserListAdapter) { 837 return resultList -> { 838 if (isFinishing() || isDestroyed()) { 839 return; 840 } 841 if (chooserListAdapter.getCount() == 0) { 842 return; 843 } 844 if (resultList.isEmpty() 845 && shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) { 846 // APS may be disabled, so try querying targets ourselves. 847 queryDirectShareTargets(chooserListAdapter, true); 848 return; 849 } 850 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos = 851 new ArrayList<>(); 852 853 List<AppTarget> shortcutResults = new ArrayList<>(); 854 for (AppTarget appTarget : resultList) { 855 if (appTarget.getShortcutInfo() == null) { 856 continue; 857 } 858 shortcutResults.add(appTarget); 859 } 860 resultList = shortcutResults; 861 for (AppTarget appTarget : resultList) { 862 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo( 863 appTarget.getShortcutInfo(), 864 new ComponentName( 865 appTarget.getPackageName(), appTarget.getClassName()))); 866 } 867 sendShareShortcutInfoList(shareShortcutInfos, chooserListAdapter, resultList, 868 chooserListAdapter.getUserHandle()); 869 }; 870 } 871 872 static SharedPreferences getPinnedSharedPrefs(Context context) { 873 // The code below is because in the android:ui process, no one can hear you scream. 874 // The package info in the context isn't initialized in the way it is for normal apps, 875 // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we 876 // build the path manually below using the same policy that appears in ContextImpl. 877 // This fails silently under the hood if there's a problem, so if we find ourselves in 878 // the case where we don't have access to credential encrypted storage we just won't 879 // have our pinned target info. 880 final File prefsFile = new File(new File( 881 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL, 882 context.getUserId(), context.getPackageName()), 883 "shared_prefs"), 884 PINNED_SHARED_PREFS_NAME + ".xml"); 885 return context.getSharedPreferences(prefsFile, MODE_PRIVATE); 886 } 887 888 @Override 889 protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( 890 Intent[] initialIntents, 891 List<ResolveInfo> rList, 892 boolean filterLastUsed) { 893 if (shouldShowTabs()) { 894 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles( 895 initialIntents, rList, filterLastUsed); 896 } else { 897 mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile( 898 initialIntents, rList, filterLastUsed); 899 } 900 return mChooserMultiProfilePagerAdapter; 901 } 902 903 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile( 904 Intent[] initialIntents, 905 List<ResolveInfo> rList, 906 boolean filterLastUsed) { 907 ChooserGridAdapter adapter = createChooserGridAdapter( 908 /* context */ this, 909 /* payloadIntents */ mIntents, 910 initialIntents, 911 rList, 912 filterLastUsed, 913 /* userHandle */ UserHandle.of(UserHandle.myUserId())); 914 return new ChooserMultiProfilePagerAdapter( 915 /* context */ this, 916 adapter, 917 getPersonalProfileUserHandle(), 918 /* workProfileUserHandle= */ null, 919 isSendAction(getTargetIntent()), getMaxTargetsPerRow()); 920 } 921 922 private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles( 923 Intent[] initialIntents, 924 List<ResolveInfo> rList, 925 boolean filterLastUsed) { 926 int selectedProfile = findSelectedProfile(); 927 ChooserGridAdapter personalAdapter = createChooserGridAdapter( 928 /* context */ this, 929 /* payloadIntents */ mIntents, 930 selectedProfile == PROFILE_PERSONAL ? initialIntents : null, 931 rList, 932 filterLastUsed, 933 /* userHandle */ getPersonalProfileUserHandle()); 934 ChooserGridAdapter workAdapter = createChooserGridAdapter( 935 /* context */ this, 936 /* payloadIntents */ mIntents, 937 selectedProfile == PROFILE_WORK ? initialIntents : null, 938 rList, 939 filterLastUsed, 940 /* userHandle */ getWorkProfileUserHandle()); 941 return new ChooserMultiProfilePagerAdapter( 942 /* context */ this, 943 personalAdapter, 944 workAdapter, 945 selectedProfile, 946 getPersonalProfileUserHandle(), 947 getWorkProfileUserHandle(), 948 isSendAction(getTargetIntent()), getMaxTargetsPerRow()); 949 } 950 951 private int findSelectedProfile() { 952 int selectedProfile = getSelectedProfileExtra(); 953 if (selectedProfile == -1) { 954 selectedProfile = getProfileForUser(getUser()); 955 } 956 return selectedProfile; 957 } 958 959 @Override 960 protected boolean postRebuildList(boolean rebuildCompleted) { 961 updateStickyContentPreview(); 962 if (shouldShowStickyContentPreview() 963 || mChooserMultiProfilePagerAdapter 964 .getCurrentRootAdapter().getSystemRowCount() != 0) { 965 logActionShareWithPreview(); 966 } 967 return postRebuildListInternal(rebuildCompleted); 968 } 969 970 /** 971 * Returns true if app prediction service is defined and the component exists on device. 972 */ 973 @VisibleForTesting 974 public boolean isAppPredictionServiceAvailable() { 975 if (getPackageManager().getAppPredictionServicePackageName() == null) { 976 // Default AppPredictionService is not defined. 977 return false; 978 } 979 980 final String appPredictionServiceName = 981 getString(R.string.config_defaultAppPredictionService); 982 if (appPredictionServiceName == null) { 983 return false; 984 } 985 final ComponentName appPredictionComponentName = 986 ComponentName.unflattenFromString(appPredictionServiceName); 987 if (appPredictionComponentName == null) { 988 return false; 989 } 990 991 // Check if the app prediction component actually exists on the device. 992 Intent intent = new Intent(); 993 intent.setComponent(appPredictionComponentName); 994 if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) { 995 Log.e(TAG, "App prediction service is defined, but does not exist: " 996 + appPredictionServiceName); 997 return false; 998 } 999 return true; 1000 } 1001 1002 /** 1003 * Check if the profile currently used is a work profile. 1004 * @return true if it is work profile, false if it is parent profile (or no work profile is 1005 * set up) 1006 */ 1007 protected boolean isWorkProfile() { 1008 return getSystemService(UserManager.class) 1009 .getUserInfo(UserHandle.myUserId()).isManagedProfile(); 1010 } 1011 1012 @Override 1013 protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) { 1014 return new PackageMonitor() { 1015 @Override 1016 public void onSomePackagesChanged() { 1017 handlePackagesChanged(listAdapter); 1018 } 1019 }; 1020 } 1021 1022 /** 1023 * Update UI to reflect changes in data. 1024 */ 1025 public void handlePackagesChanged() { 1026 handlePackagesChanged(/* listAdapter */ null); 1027 } 1028 1029 /** 1030 * Update UI to reflect changes in data. 1031 * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if 1032 * available. 1033 */ 1034 private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) { 1035 // Refresh pinned items 1036 mPinnedSharedPrefs = getPinnedSharedPrefs(this); 1037 if (listAdapter == null) { 1038 mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); 1039 if (mChooserMultiProfilePagerAdapter.getCount() > 1) { 1040 mChooserMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged(); 1041 } 1042 } else { 1043 listAdapter.handlePackagesChanged(); 1044 } 1045 updateProfileViewButton(); 1046 } 1047 1048 private void onCopyButtonClicked(View v) { 1049 Intent targetIntent = getTargetIntent(); 1050 if (targetIntent == null) { 1051 finish(); 1052 } else { 1053 final String action = targetIntent.getAction(); 1054 1055 ClipData clipData = null; 1056 if (Intent.ACTION_SEND.equals(action)) { 1057 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT); 1058 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); 1059 1060 if (extraText != null) { 1061 clipData = ClipData.newPlainText(null, extraText); 1062 } else if (extraStream != null) { 1063 clipData = ClipData.newUri(getContentResolver(), null, extraStream); 1064 } else { 1065 Log.w(TAG, "No data available to copy to clipboard"); 1066 return; 1067 } 1068 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { 1069 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra( 1070 Intent.EXTRA_STREAM); 1071 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0)); 1072 for (int i = 1; i < streams.size(); i++) { 1073 clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i))); 1074 } 1075 } else { 1076 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE 1077 // so warn about unexpected action 1078 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard"); 1079 return; 1080 } 1081 1082 ClipboardManager clipboardManager = (ClipboardManager) getSystemService( 1083 Context.CLIPBOARD_SERVICE); 1084 clipboardManager.setPrimaryClipAsPackage(clipData, getReferrerPackageName()); 1085 Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show(); 1086 1087 // Log share completion via copy 1088 LogMaker targetLogMaker = new LogMaker( 1089 MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1); 1090 getMetricsLogger().write(targetLogMaker); 1091 getChooserActivityLogger().logShareTargetSelected( 1092 SELECTION_TYPE_COPY, 1093 "", 1094 -1); 1095 1096 setResult(RESULT_OK); 1097 finish(); 1098 } 1099 } 1100 1101 @Override 1102 public void onConfigurationChanged(Configuration newConfig) { 1103 super.onConfigurationChanged(newConfig); 1104 ViewPager viewPager = findViewById(R.id.profile_pager); 1105 if (viewPager.isLayoutRtl()) { 1106 mMultiProfilePagerAdapter.setupViewPager(viewPager); 1107 } 1108 1109 mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); 1110 adjustPreviewWidth(newConfig.orientation, null); 1111 updateStickyContentPreview(); 1112 } 1113 1114 private boolean shouldDisplayLandscape(int orientation) { 1115 // Sharesheet fixes the # of items per row and therefore can not correctly lay out 1116 // when in the restricted size of multi-window mode. In the future, would be nice 1117 // to use minimum dp size requirements instead 1118 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode(); 1119 } 1120 1121 private void adjustPreviewWidth(int orientation, View parent) { 1122 int width = -1; 1123 if (mShouldDisplayLandscape) { 1124 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width); 1125 } 1126 1127 parent = parent == null ? getWindow().getDecorView() : parent; 1128 1129 updateLayoutWidth(R.id.content_preview_text_layout, width, parent); 1130 updateLayoutWidth(R.id.content_preview_title_layout, width, parent); 1131 updateLayoutWidth(R.id.content_preview_file_layout, width, parent); 1132 } 1133 1134 private void updateLayoutWidth(int layoutResourceId, int width, View parent) { 1135 View view = parent.findViewById(layoutResourceId); 1136 if (view != null && view.getLayoutParams() != null) { 1137 LayoutParams params = view.getLayoutParams(); 1138 params.width = width; 1139 view.setLayoutParams(params); 1140 } 1141 } 1142 1143 private ViewGroup createContentPreviewView(ViewGroup parent) { 1144 Intent targetIntent = getTargetIntent(); 1145 int previewType = findPreferredContentPreview(targetIntent, getContentResolver()); 1146 return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent); 1147 } 1148 1149 @VisibleForTesting 1150 protected ComponentName getNearbySharingComponent() { 1151 String nearbyComponent = Settings.Secure.getString( 1152 getContentResolver(), 1153 Settings.Secure.NEARBY_SHARING_COMPONENT); 1154 if (TextUtils.isEmpty(nearbyComponent)) { 1155 nearbyComponent = getString(R.string.config_defaultNearbySharingComponent); 1156 } 1157 if (TextUtils.isEmpty(nearbyComponent)) { 1158 return null; 1159 } 1160 return ComponentName.unflattenFromString(nearbyComponent); 1161 } 1162 1163 @VisibleForTesting 1164 protected @Nullable ComponentName getEditSharingComponent() { 1165 String editorPackage = getApplicationContext().getString(R.string.config_systemImageEditor); 1166 if (editorPackage == null || TextUtils.isEmpty(editorPackage)) { 1167 return null; 1168 } 1169 return ComponentName.unflattenFromString(editorPackage); 1170 } 1171 1172 @VisibleForTesting 1173 protected TargetInfo getEditSharingTarget(Intent originalIntent) { 1174 final ComponentName cn = getEditSharingComponent(); 1175 1176 final Intent resolveIntent = new Intent(originalIntent); 1177 resolveIntent.setComponent(cn); 1178 resolveIntent.setAction(Intent.ACTION_EDIT); 1179 final ResolveInfo ri = getPackageManager().resolveActivity( 1180 resolveIntent, PackageManager.GET_META_DATA); 1181 if (ri == null || ri.activityInfo == null) { 1182 Log.e(TAG, "Device-specified image edit component (" + cn 1183 + ") not available"); 1184 return null; 1185 } 1186 1187 final DisplayResolveInfo dri = new DisplayResolveInfo( 1188 originalIntent, ri, getString(R.string.screenshot_edit), "", resolveIntent, null); 1189 dri.setDisplayIcon(getDrawable(R.drawable.ic_screenshot_edit)); 1190 return dri; 1191 } 1192 1193 1194 @VisibleForTesting 1195 protected TargetInfo getNearbySharingTarget(Intent originalIntent) { 1196 final ComponentName cn = getNearbySharingComponent(); 1197 if (cn == null) return null; 1198 1199 final Intent resolveIntent = new Intent(originalIntent); 1200 resolveIntent.setComponent(cn); 1201 final ResolveInfo ri = getPackageManager().resolveActivity( 1202 resolveIntent, PackageManager.GET_META_DATA); 1203 if (ri == null || ri.activityInfo == null) { 1204 Log.e(TAG, "Device-specified nearby sharing component (" + cn 1205 + ") not available"); 1206 return null; 1207 } 1208 1209 // Allow the nearby sharing component to provide a more appropriate icon and label 1210 // for the chip. 1211 CharSequence name = null; 1212 Drawable icon = null; 1213 final Bundle metaData = ri.activityInfo.metaData; 1214 if (metaData != null) { 1215 try { 1216 final Resources pkgRes = getPackageManager().getResourcesForActivity(cn); 1217 final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY); 1218 name = pkgRes.getString(nameResId); 1219 final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY); 1220 icon = pkgRes.getDrawable(resId); 1221 } catch (Resources.NotFoundException ex) { 1222 } catch (NameNotFoundException ex) { 1223 } 1224 } 1225 if (TextUtils.isEmpty(name)) { 1226 name = ri.loadLabel(getPackageManager()); 1227 } 1228 if (icon == null) { 1229 icon = ri.loadIcon(getPackageManager()); 1230 } 1231 1232 final DisplayResolveInfo dri = new DisplayResolveInfo( 1233 originalIntent, ri, name, "", resolveIntent, null); 1234 dri.setDisplayIcon(icon); 1235 return dri; 1236 } 1237 1238 private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) { 1239 Button b = (Button) LayoutInflater.from(this).inflate(R.layout.chooser_action_button, null); 1240 if (icon != null) { 1241 final int size = getResources() 1242 .getDimensionPixelSize(R.dimen.chooser_action_button_icon_size); 1243 icon.setBounds(0, 0, size, size); 1244 b.setCompoundDrawablesRelative(icon, null, null, null); 1245 } 1246 b.setText(title); 1247 b.setOnClickListener(r); 1248 return b; 1249 } 1250 1251 private Button createCopyButton() { 1252 final Button b = createActionButton( 1253 getDrawable(R.drawable.ic_menu_copy_material), 1254 getString(R.string.copy), this::onCopyButtonClicked); 1255 b.setId(R.id.chooser_copy_button); 1256 return b; 1257 } 1258 1259 private @Nullable Button createNearbyButton(Intent originalIntent) { 1260 final TargetInfo ti = getNearbySharingTarget(originalIntent); 1261 if (ti == null) return null; 1262 1263 final Button b = createActionButton( 1264 ti.getDisplayIcon(this), 1265 ti.getDisplayLabel(), 1266 (View unused) -> { 1267 // Log share completion via nearby 1268 getChooserActivityLogger().logShareTargetSelected( 1269 SELECTION_TYPE_NEARBY, 1270 "", 1271 -1); 1272 // Action bar is user-independent, always start as primary 1273 safelyStartActivityAsUser(ti, getPersonalProfileUserHandle()); 1274 finish(); 1275 } 1276 ); 1277 b.setId(R.id.chooser_nearby_button); 1278 return b; 1279 } 1280 1281 private @Nullable Button createEditButton(Intent originalIntent) { 1282 final TargetInfo ti = getEditSharingTarget(originalIntent); 1283 if (ti == null) return null; 1284 1285 final Button b = createActionButton( 1286 ti.getDisplayIcon(this), 1287 ti.getDisplayLabel(), 1288 (View unused) -> { 1289 // Log share completion via edit 1290 getChooserActivityLogger().logShareTargetSelected( 1291 SELECTION_TYPE_EDIT, 1292 "", 1293 -1); 1294 // Action bar is user-independent, always start as primary 1295 safelyStartActivityAsUser(ti, getPersonalProfileUserHandle()); 1296 finish(); 1297 } 1298 ); 1299 b.setId(R.id.chooser_edit_button); 1300 return b; 1301 } 1302 1303 private void addActionButton(ViewGroup parent, Button b) { 1304 if (b == null) return; 1305 final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams( 1306 LayoutParams.WRAP_CONTENT, 1307 LayoutParams.WRAP_CONTENT 1308 ); 1309 final int gap = getResources().getDimensionPixelSize(R.dimen.resolver_icon_margin) / 2; 1310 lp.setMarginsRelative(gap, 0, gap, 0); 1311 parent.addView(b, lp); 1312 } 1313 1314 private ViewGroup displayContentPreview(@ContentPreviewType int previewType, 1315 Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) { 1316 ViewGroup layout = null; 1317 1318 switch (previewType) { 1319 case CONTENT_PREVIEW_TEXT: 1320 layout = displayTextContentPreview(targetIntent, layoutInflater, parent); 1321 break; 1322 case CONTENT_PREVIEW_IMAGE: 1323 layout = displayImageContentPreview(targetIntent, layoutInflater, parent); 1324 break; 1325 case CONTENT_PREVIEW_FILE: 1326 layout = displayFileContentPreview(targetIntent, layoutInflater, parent); 1327 break; 1328 default: 1329 Log.e(TAG, "Unexpected content preview type: " + previewType); 1330 } 1331 1332 if (layout != null) { 1333 adjustPreviewWidth(getResources().getConfiguration().orientation, layout); 1334 } 1335 1336 return layout; 1337 } 1338 1339 private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater, 1340 ViewGroup parent) { 1341 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate( 1342 R.layout.chooser_grid_preview_text, parent, false); 1343 1344 final ViewGroup actionRow = 1345 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row); 1346 addActionButton(actionRow, createCopyButton()); 1347 addActionButton(actionRow, createNearbyButton(targetIntent)); 1348 1349 CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); 1350 if (sharingText == null) { 1351 contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility( 1352 View.GONE); 1353 } else { 1354 TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text); 1355 textView.setText(sharingText); 1356 } 1357 1358 String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE); 1359 if (TextUtils.isEmpty(previewTitle)) { 1360 contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility( 1361 View.GONE); 1362 } else { 1363 TextView previewTitleView = contentPreviewLayout.findViewById( 1364 R.id.content_preview_title); 1365 previewTitleView.setText(previewTitle); 1366 1367 ClipData previewData = targetIntent.getClipData(); 1368 Uri previewThumbnail = null; 1369 if (previewData != null) { 1370 if (previewData.getItemCount() > 0) { 1371 ClipData.Item previewDataItem = previewData.getItemAt(0); 1372 previewThumbnail = previewDataItem.getUri(); 1373 } 1374 } 1375 1376 ImageView previewThumbnailView = contentPreviewLayout.findViewById( 1377 R.id.content_preview_thumbnail); 1378 if (previewThumbnail == null) { 1379 previewThumbnailView.setVisibility(View.GONE); 1380 } else { 1381 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false); 1382 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0); 1383 } 1384 } 1385 1386 return contentPreviewLayout; 1387 } 1388 1389 private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater, 1390 ViewGroup parent) { 1391 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate( 1392 R.layout.chooser_grid_preview_image, parent, false); 1393 ViewGroup imagePreview = contentPreviewLayout.findViewById(R.id.content_preview_image_area); 1394 1395 final ViewGroup actionRow = 1396 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row); 1397 //TODO: addActionButton(actionRow, createCopyButton()); 1398 addActionButton(actionRow, createNearbyButton(targetIntent)); 1399 addActionButton(actionRow, createEditButton(targetIntent)); 1400 1401 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false); 1402 1403 String action = targetIntent.getAction(); 1404 if (Intent.ACTION_SEND.equals(action)) { 1405 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); 1406 imagePreview.findViewById(R.id.content_preview_image_1_large) 1407 .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); 1408 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0); 1409 } else { 1410 ContentResolver resolver = getContentResolver(); 1411 1412 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 1413 List<Uri> imageUris = new ArrayList<>(); 1414 for (Uri uri : uris) { 1415 if (isImageType(resolver.getType(uri))) { 1416 imageUris.add(uri); 1417 } 1418 } 1419 1420 if (imageUris.size() == 0) { 1421 Log.i(TAG, "Attempted to display image preview area with zero" 1422 + " available images detected in EXTRA_STREAM list"); 1423 imagePreview.setVisibility(View.GONE); 1424 return contentPreviewLayout; 1425 } 1426 1427 imagePreview.findViewById(R.id.content_preview_image_1_large) 1428 .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); 1429 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0); 1430 1431 if (imageUris.size() == 2) { 1432 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large, 1433 imageUris.get(1), 0); 1434 } else if (imageUris.size() > 2) { 1435 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small, 1436 imageUris.get(1), 0); 1437 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small, 1438 imageUris.get(2), imageUris.size() - 3); 1439 } 1440 } 1441 1442 return contentPreviewLayout; 1443 } 1444 1445 private static class FileInfo { 1446 public final String name; 1447 public final boolean hasThumbnail; 1448 1449 FileInfo(String name, boolean hasThumbnail) { 1450 this.name = name; 1451 this.hasThumbnail = hasThumbnail; 1452 } 1453 } 1454 1455 /** 1456 * Wrapping the ContentResolver call to expose for easier mocking, 1457 * and to avoid mocking Android core classes. 1458 */ 1459 @VisibleForTesting 1460 public Cursor queryResolver(ContentResolver resolver, Uri uri) { 1461 return resolver.query(uri, null, null, null, null); 1462 } 1463 1464 private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) { 1465 String fileName = null; 1466 boolean hasThumbnail = false; 1467 1468 try (Cursor cursor = queryResolver(resolver, uri)) { 1469 if (cursor != null && cursor.getCount() > 0) { 1470 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 1471 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE); 1472 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS); 1473 1474 cursor.moveToFirst(); 1475 if (nameIndex != -1) { 1476 fileName = cursor.getString(nameIndex); 1477 } else if (titleIndex != -1) { 1478 fileName = cursor.getString(titleIndex); 1479 } 1480 1481 if (flagsIndex != -1) { 1482 hasThumbnail = (cursor.getInt(flagsIndex) 1483 & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0; 1484 } 1485 } 1486 } catch (SecurityException | NullPointerException e) { 1487 logContentPreviewWarning(uri); 1488 } 1489 1490 if (TextUtils.isEmpty(fileName)) { 1491 fileName = uri.getPath(); 1492 int index = fileName.lastIndexOf('/'); 1493 if (index != -1) { 1494 fileName = fileName.substring(index + 1); 1495 } 1496 } 1497 1498 return new FileInfo(fileName, hasThumbnail); 1499 } 1500 1501 private void logContentPreviewWarning(Uri uri) { 1502 // The ContentResolver already logs the exception. Log something more informative. 1503 Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If " 1504 + "desired, consider using Intent#createChooser to launch the ChooserActivity, " 1505 + "and set your Intent's clipData and flags in accordance with that method's " 1506 + "documentation"); 1507 } 1508 1509 private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater, 1510 ViewGroup parent) { 1511 1512 ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate( 1513 R.layout.chooser_grid_preview_file, parent, false); 1514 1515 final ViewGroup actionRow = 1516 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row); 1517 //TODO(b/120417119): addActionButton(actionRow, createCopyButton()); 1518 addActionButton(actionRow, createNearbyButton(targetIntent)); 1519 1520 1521 String action = targetIntent.getAction(); 1522 if (Intent.ACTION_SEND.equals(action)) { 1523 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); 1524 loadFileUriIntoView(uri, contentPreviewLayout); 1525 } else { 1526 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 1527 int uriCount = uris.size(); 1528 1529 if (uriCount == 0) { 1530 contentPreviewLayout.setVisibility(View.GONE); 1531 Log.i(TAG, 1532 "Appears to be no uris available in EXTRA_STREAM, removing " 1533 + "preview area"); 1534 return contentPreviewLayout; 1535 } else if (uriCount == 1) { 1536 loadFileUriIntoView(uris.get(0), contentPreviewLayout); 1537 } else { 1538 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver()); 1539 int remUriCount = uriCount - 1; 1540 String fileName = getResources().getQuantityString(R.plurals.file_count, 1541 remUriCount, fileInfo.name, remUriCount); 1542 1543 TextView fileNameView = contentPreviewLayout.findViewById( 1544 R.id.content_preview_filename); 1545 fileNameView.setText(fileName); 1546 1547 View thumbnailView = contentPreviewLayout.findViewById( 1548 R.id.content_preview_file_thumbnail); 1549 thumbnailView.setVisibility(View.GONE); 1550 1551 ImageView fileIconView = contentPreviewLayout.findViewById( 1552 R.id.content_preview_file_icon); 1553 fileIconView.setVisibility(View.VISIBLE); 1554 fileIconView.setImageResource(R.drawable.ic_file_copy); 1555 } 1556 } 1557 1558 return contentPreviewLayout; 1559 } 1560 1561 private void loadFileUriIntoView(final Uri uri, final View parent) { 1562 FileInfo fileInfo = extractFileInfo(uri, getContentResolver()); 1563 1564 TextView fileNameView = parent.findViewById(R.id.content_preview_filename); 1565 fileNameView.setText(fileInfo.name); 1566 1567 if (fileInfo.hasThumbnail) { 1568 mPreviewCoord = new ContentPreviewCoordinator(parent, false); 1569 mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0); 1570 } else { 1571 View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail); 1572 thumbnailView.setVisibility(View.GONE); 1573 1574 ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon); 1575 fileIconView.setVisibility(View.VISIBLE); 1576 fileIconView.setImageResource(R.drawable.chooser_file_generic); 1577 } 1578 } 1579 1580 @VisibleForTesting 1581 protected boolean isImageType(String mimeType) { 1582 return mimeType != null && mimeType.startsWith("image/"); 1583 } 1584 1585 @ContentPreviewType 1586 private int findPreferredContentPreview(Uri uri, ContentResolver resolver) { 1587 if (uri == null) { 1588 return CONTENT_PREVIEW_TEXT; 1589 } 1590 1591 String mimeType = resolver.getType(uri); 1592 return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE; 1593 } 1594 1595 /** 1596 * In {@link android.content.Intent#getType}, the app may specify a very general 1597 * mime-type that broadly covers all data being shared, such as {@literal *}/* 1598 * when sending an image and text. We therefore should inspect each item for the 1599 * the preferred type, in order of IMAGE, FILE, TEXT. 1600 */ 1601 @ContentPreviewType 1602 private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) { 1603 String action = targetIntent.getAction(); 1604 if (Intent.ACTION_SEND.equals(action)) { 1605 Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); 1606 return findPreferredContentPreview(uri, resolver); 1607 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { 1608 List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 1609 if (uris == null || uris.isEmpty()) { 1610 return CONTENT_PREVIEW_TEXT; 1611 } 1612 1613 for (Uri uri : uris) { 1614 // Defaulting to file preview when there are mixed image/file types is 1615 // preferable, as it shows the user the correct number of items being shared 1616 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) { 1617 return CONTENT_PREVIEW_FILE; 1618 } 1619 } 1620 1621 return CONTENT_PREVIEW_IMAGE; 1622 } 1623 1624 return CONTENT_PREVIEW_TEXT; 1625 } 1626 1627 private int getNumSheetExpansions() { 1628 return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0); 1629 } 1630 1631 private void incrementNumSheetExpansions() { 1632 getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS, 1633 getNumSheetExpansions() + 1).apply(); 1634 } 1635 1636 @Override 1637 protected void onDestroy() { 1638 super.onDestroy(); 1639 if (mRefinementResultReceiver != null) { 1640 mRefinementResultReceiver.destroy(); 1641 mRefinementResultReceiver = null; 1642 } 1643 unbindRemainingServices(); 1644 mChooserHandler.removeAllMessages(); 1645 1646 if (mPreviewCoord != null) mPreviewCoord.cancelLoads(); 1647 1648 mChooserMultiProfilePagerAdapter.getActiveListAdapter().destroyAppPredictor(); 1649 if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) { 1650 mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor(); 1651 } 1652 } 1653 1654 @Override // ResolverListCommunicator 1655 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 1656 Intent result = defIntent; 1657 if (mReplacementExtras != null) { 1658 final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName); 1659 if (replExtras != null) { 1660 result = new Intent(defIntent); 1661 result.putExtras(replExtras); 1662 } 1663 } 1664 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT) 1665 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) { 1666 result = Intent.createChooser(result, 1667 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE)); 1668 1669 // Don't auto-launch single intents if the intent is being forwarded. This is done 1670 // because automatically launching a resolving application as a response to the user 1671 // action of switching accounts is pretty unexpected. 1672 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false); 1673 } 1674 return result; 1675 } 1676 1677 @Override 1678 public void onActivityStarted(TargetInfo cti) { 1679 if (mChosenComponentSender != null) { 1680 final ComponentName target = cti.getResolvedComponentName(); 1681 if (target != null) { 1682 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target); 1683 try { 1684 mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null); 1685 } catch (IntentSender.SendIntentException e) { 1686 Slog.e(TAG, "Unable to launch supplied IntentSender to report " 1687 + "the chosen component: " + e); 1688 } 1689 } 1690 } 1691 } 1692 1693 @Override 1694 public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) { 1695 if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) { 1696 mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( 1697 /* origTarget */ null, 1698 Lists.newArrayList(mCallerChooserTargets), 1699 TARGET_TYPE_DEFAULT, 1700 /* directShareShortcutInfoCache */ null, mServiceConnections); 1701 } 1702 } 1703 1704 @Override 1705 public int getLayoutResource() { 1706 return R.layout.chooser_grid; 1707 } 1708 1709 @Override // ResolverListCommunicator 1710 public boolean shouldGetActivityMetadata() { 1711 return true; 1712 } 1713 1714 @Override 1715 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { 1716 // Note that this is only safe because the Intent handled by the ChooserActivity is 1717 // guaranteed to contain no extras unknown to the local ClassLoader. That is why this 1718 // method can not be replaced in the ResolverActivity whole hog. 1719 if (!super.shouldAutoLaunchSingleChoice(target)) { 1720 return false; 1721 } 1722 1723 return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true); 1724 } 1725 1726 private void showTargetDetails(DisplayResolveInfo ti) { 1727 if (ti == null) return; 1728 1729 ArrayList<DisplayResolveInfo> targetList; 1730 1731 // For multiple targets, include info on all targets 1732 if (ti instanceof MultiDisplayResolveInfo) { 1733 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti; 1734 targetList = mti.getTargets(); 1735 } else { 1736 targetList = new ArrayList<DisplayResolveInfo>(); 1737 targetList.add(ti); 1738 } 1739 1740 ChooserTargetActionsDialogFragment f = new ChooserTargetActionsDialogFragment(); 1741 Bundle b = new Bundle(); 1742 b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY, 1743 mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); 1744 b.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY, 1745 targetList); 1746 f.setArguments(b); 1747 1748 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); 1749 } 1750 1751 private void modifyTargetIntent(Intent in) { 1752 if (isSendAction(in)) { 1753 in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | 1754 Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 1755 } 1756 } 1757 1758 @Override 1759 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { 1760 if (mRefinementIntentSender != null) { 1761 final Intent fillIn = new Intent(); 1762 final List<Intent> sourceIntents = target.getAllSourceIntents(); 1763 if (!sourceIntents.isEmpty()) { 1764 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0)); 1765 if (sourceIntents.size() > 1) { 1766 final Intent[] alts = new Intent[sourceIntents.size() - 1]; 1767 for (int i = 1, N = sourceIntents.size(); i < N; i++) { 1768 alts[i - 1] = sourceIntents.get(i); 1769 } 1770 fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts); 1771 } 1772 if (mRefinementResultReceiver != null) { 1773 mRefinementResultReceiver.destroy(); 1774 } 1775 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null); 1776 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER, 1777 mRefinementResultReceiver); 1778 try { 1779 mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null); 1780 return false; 1781 } catch (SendIntentException e) { 1782 Log.e(TAG, "Refinement IntentSender failed to send", e); 1783 } 1784 } 1785 } 1786 updateModelAndChooserCounts(target); 1787 return super.onTargetSelected(target, alwaysCheck); 1788 } 1789 1790 @Override 1791 public void startSelected(int which, boolean always, boolean filtered) { 1792 ChooserListAdapter currentListAdapter = 1793 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 1794 TargetInfo targetInfo = currentListAdapter 1795 .targetInfoForPosition(which, filtered); 1796 if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) { 1797 return; 1798 } 1799 1800 final long selectionCost = System.currentTimeMillis() - mChooserShownTime; 1801 1802 if (targetInfo instanceof MultiDisplayResolveInfo) { 1803 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo; 1804 if (!mti.hasSelected()) { 1805 ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(); 1806 Bundle b = new Bundle(); 1807 b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY, 1808 mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); 1809 b.putObject(ChooserStackedAppDialogFragment.MULTI_DRI_KEY, 1810 mti); 1811 b.putInt(ChooserStackedAppDialogFragment.WHICH_KEY, which); 1812 f.setArguments(b); 1813 1814 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); 1815 return; 1816 } 1817 } 1818 1819 super.startSelected(which, always, filtered); 1820 1821 1822 if (currentListAdapter.getCount() > 0) { 1823 // Log the index of which type of target the user picked. 1824 // Lower values mean the ranking was better. 1825 int cat = 0; 1826 int value = which; 1827 int directTargetAlsoRanked = -1; 1828 int numCallerProvided = 0; 1829 HashedStringCache.HashResult directTargetHashed = null; 1830 switch (currentListAdapter.getPositionTargetType(which)) { 1831 case ChooserListAdapter.TARGET_SERVICE: 1832 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET; 1833 // Log the package name + target name to answer the question if most users 1834 // share to mostly the same person or to a bunch of different people. 1835 ChooserTarget target = currentListAdapter.getChooserTargetForValue(value); 1836 directTargetHashed = HashedStringCache.getInstance().hashString( 1837 this, 1838 TAG, 1839 target.getComponentName().getPackageName() 1840 + target.getTitle().toString(), 1841 mMaxHashSaltDays); 1842 directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo); 1843 1844 if (mCallerChooserTargets != null) { 1845 numCallerProvided = mCallerChooserTargets.length; 1846 } 1847 getChooserActivityLogger().logShareTargetSelected( 1848 SELECTION_TYPE_SERVICE, 1849 targetInfo.getResolveInfo().activityInfo.processName, 1850 value 1851 ); 1852 break; 1853 case ChooserListAdapter.TARGET_CALLER: 1854 case ChooserListAdapter.TARGET_STANDARD: 1855 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET; 1856 value -= currentListAdapter.getSurfacedTargetInfo().size(); 1857 numCallerProvided = currentListAdapter.getCallerTargetCount(); 1858 getChooserActivityLogger().logShareTargetSelected( 1859 SELECTION_TYPE_APP, 1860 targetInfo.getResolveInfo().activityInfo.processName, 1861 value 1862 ); 1863 break; 1864 case ChooserListAdapter.TARGET_STANDARD_AZ: 1865 // A-Z targets are unranked standard targets; we use -1 to mark that they 1866 // are from the alphabetical pool. 1867 value = -1; 1868 cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET; 1869 getChooserActivityLogger().logShareTargetSelected( 1870 SELECTION_TYPE_STANDARD, 1871 targetInfo.getResolveInfo().activityInfo.processName, 1872 value 1873 ); 1874 break; 1875 } 1876 1877 if (cat != 0) { 1878 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value); 1879 if (directTargetHashed != null) { 1880 targetLogMaker.addTaggedData( 1881 MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString); 1882 targetLogMaker.addTaggedData( 1883 MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN, 1884 directTargetHashed.saltGeneration); 1885 targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, 1886 directTargetAlsoRanked); 1887 } 1888 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, 1889 numCallerProvided); 1890 getMetricsLogger().write(targetLogMaker); 1891 } 1892 1893 if (mIsSuccessfullySelected) { 1894 if (DEBUG) { 1895 Log.d(TAG, "User Selection Time Cost is " + selectionCost); 1896 Log.d(TAG, "position of selected app/service/caller is " + 1897 Integer.toString(value)); 1898 } 1899 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing", 1900 (int) selectionCost); 1901 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value); 1902 } 1903 } 1904 } 1905 1906 private int getRankedPosition(SelectableTargetInfo targetInfo) { 1907 String targetPackageName = 1908 targetInfo.getChooserTarget().getComponentName().getPackageName(); 1909 ChooserListAdapter currentListAdapter = 1910 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 1911 int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(), 1912 MAX_LOG_RANK_POSITION); 1913 1914 for (int i = 0; i < maxRankedResults; i++) { 1915 if (currentListAdapter.mDisplayList.get(i) 1916 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) { 1917 return i; 1918 } 1919 } 1920 return -1; 1921 } 1922 1923 @Override 1924 protected boolean shouldAddFooterView() { 1925 // To accommodate for window insets 1926 return true; 1927 } 1928 1929 @Override 1930 protected void applyFooterView(int height) { 1931 int count = mChooserMultiProfilePagerAdapter.getItemCount(); 1932 1933 for (int i = 0; i < count; i++) { 1934 mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height); 1935 } 1936 } 1937 1938 @VisibleForTesting 1939 protected void queryTargetServices(ChooserListAdapter adapter) { 1940 1941 mQueriedTargetServicesTimeMs = System.currentTimeMillis(); 1942 1943 Context selectedProfileContext = createContextAsUser( 1944 adapter.getUserHandle(), 0 /* flags */); 1945 final PackageManager pm = selectedProfileContext.getPackageManager(); 1946 ShortcutManager sm = selectedProfileContext.getSystemService(ShortcutManager.class); 1947 int targetsToQuery = 0; 1948 1949 for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) { 1950 final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i); 1951 if (adapter.getScore(dri) == 0) { 1952 // A score of 0 means the app hasn't been used in some time; 1953 // don't query it as it's not likely to be relevant. 1954 continue; 1955 } 1956 final ActivityInfo ai = dri.getResolveInfo().activityInfo; 1957 if (sm.hasShareTargets(ai.packageName)) { 1958 // Share targets will be queried from ShortcutManager 1959 continue; 1960 } 1961 final Bundle md = ai.metaData; 1962 final String serviceName = md != null ? convertServiceName(ai.packageName, 1963 md.getString(ChooserTargetService.META_DATA_NAME)) : null; 1964 if (serviceName != null && ChooserFlags.USE_SERVICE_TARGETS_FOR_DIRECT_TARGETS) { 1965 final ComponentName serviceComponent = new ComponentName( 1966 ai.packageName, serviceName); 1967 1968 UserHandle userHandle = adapter.getUserHandle(); 1969 Pair<ComponentName, UserHandle> requestedItem = 1970 new Pair<>(serviceComponent, userHandle); 1971 if (mServicesRequested.contains(requestedItem)) { 1972 continue; 1973 } 1974 mServicesRequested.add(requestedItem); 1975 1976 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE) 1977 .setComponent(serviceComponent); 1978 1979 if (DEBUG) { 1980 Log.d(TAG, "queryTargets found target with service " + serviceComponent); 1981 } 1982 1983 try { 1984 final String perm = pm.getServiceInfo(serviceComponent, 0).permission; 1985 if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) { 1986 Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require" 1987 + " permission " + ChooserTargetService.BIND_PERMISSION 1988 + " - this service will not be queried for ChooserTargets." 1989 + " add android:permission=\"" 1990 + ChooserTargetService.BIND_PERMISSION + "\"" 1991 + " to the <service> tag for " + serviceComponent 1992 + " in the manifest."); 1993 continue; 1994 } 1995 } catch (NameNotFoundException e) { 1996 Log.e(TAG, "Could not look up service " + serviceComponent 1997 + "; component name not found"); 1998 continue; 1999 } 2000 2001 final ChooserTargetServiceConnection conn = 2002 new ChooserTargetServiceConnection(this, dri, 2003 adapter.getUserHandle()); 2004 2005 // Explicitly specify the user handle instead of calling bindService 2006 // to avoid the warning from calling from the system process without an explicit 2007 // user handle 2008 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND, 2009 adapter.getUserHandle())) { 2010 if (DEBUG) { 2011 Log.d(TAG, "Binding service connection for target " + dri 2012 + " intent " + serviceIntent); 2013 } 2014 mServiceConnections.add(conn); 2015 targetsToQuery++; 2016 } 2017 } 2018 if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) { 2019 if (DEBUG) { 2020 Log.d(TAG, "queryTargets hit query target limit " 2021 + QUERY_TARGET_SERVICE_LIMIT); 2022 } 2023 break; 2024 } 2025 } 2026 2027 mChooserHandler.restartServiceRequestTimer(); 2028 } 2029 2030 private IntentFilter getTargetIntentFilter() { 2031 try { 2032 final Intent intent = getTargetIntent(); 2033 String dataString = intent.getDataString(); 2034 if (!TextUtils.isEmpty(dataString)) { 2035 return new IntentFilter(intent.getAction(), dataString); 2036 } 2037 if (intent.getType() == null) { 2038 Log.e(TAG, "Failed to get target intent filter: intent data and type are null"); 2039 return null; 2040 } 2041 IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType()); 2042 List<Uri> contentUris = new ArrayList<>(); 2043 if (Intent.ACTION_SEND.equals(intent.getAction())) { 2044 Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); 2045 if (uri != null) { 2046 contentUris.add(uri); 2047 } 2048 } else { 2049 List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 2050 if (uris != null) { 2051 contentUris.addAll(uris); 2052 } 2053 } 2054 for (Uri uri : contentUris) { 2055 intentFilter.addDataScheme(uri.getScheme()); 2056 intentFilter.addDataAuthority(uri.getAuthority(), null); 2057 intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL); 2058 } 2059 return intentFilter; 2060 } catch (Exception e) { 2061 Log.e(TAG, "Failed to get target intent filter", e); 2062 return null; 2063 } 2064 } 2065 2066 @VisibleForTesting 2067 protected void queryDirectShareTargets( 2068 ChooserListAdapter adapter, boolean skipAppPredictionService) { 2069 mQueriedSharingShortcutsTimeMs = System.currentTimeMillis(); 2070 UserHandle userHandle = adapter.getUserHandle(); 2071 if (!skipAppPredictionService) { 2072 AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle); 2073 if (appPredictor != null) { 2074 appPredictor.requestPredictionUpdate(); 2075 return; 2076 } 2077 } 2078 // Default to just querying ShortcutManager if AppPredictor not present. 2079 final IntentFilter filter = getTargetIntentFilter(); 2080 if (filter == null) { 2081 return; 2082 } 2083 2084 AsyncTask.execute(() -> { 2085 Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */); 2086 ShortcutManager sm = (ShortcutManager) selectedProfileContext 2087 .getSystemService(Context.SHORTCUT_SERVICE); 2088 List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter); 2089 sendShareShortcutInfoList(resultList, adapter, null, userHandle); 2090 }); 2091 } 2092 2093 /** 2094 * Returns {@code false} if {@code userHandle} is the work profile and it's either 2095 * in quiet mode or not running. 2096 */ 2097 private boolean shouldQueryShortcutManager(UserHandle userHandle) { 2098 if (!shouldShowTabs()) { 2099 return true; 2100 } 2101 if (!getWorkProfileUserHandle().equals(userHandle)) { 2102 return true; 2103 } 2104 if (!isUserRunning(userHandle)) { 2105 return false; 2106 } 2107 if (!isUserUnlocked(userHandle)) { 2108 return false; 2109 } 2110 if (isQuietModeEnabled(userHandle)) { 2111 return false; 2112 } 2113 return true; 2114 } 2115 2116 private void sendShareShortcutInfoList( 2117 List<ShortcutManager.ShareShortcutInfo> resultList, 2118 ChooserListAdapter chooserListAdapter, 2119 @Nullable List<AppTarget> appTargets, UserHandle userHandle) { 2120 if (appTargets != null && appTargets.size() != resultList.size()) { 2121 throw new RuntimeException("resultList and appTargets must have the same size." 2122 + " resultList.size()=" + resultList.size() 2123 + " appTargets.size()=" + appTargets.size()); 2124 } 2125 Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */); 2126 for (int i = resultList.size() - 1; i >= 0; i--) { 2127 final String packageName = resultList.get(i).getTargetComponent().getPackageName(); 2128 if (!isPackageEnabled(selectedProfileContext, packageName)) { 2129 resultList.remove(i); 2130 if (appTargets != null) { 2131 appTargets.remove(i); 2132 } 2133 } 2134 } 2135 2136 // If |appTargets| is not null, results are from AppPredictionService and already sorted. 2137 final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER : 2138 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE); 2139 2140 // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path 2141 // for direct share targets. After ShareSheet is refactored we should use the 2142 // ShareShortcutInfos directly. 2143 boolean resultMessageSent = false; 2144 for (int i = 0; i < chooserListAdapter.getDisplayResolveInfoCount(); i++) { 2145 List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>(); 2146 for (int j = 0; j < resultList.size(); j++) { 2147 if (chooserListAdapter.getDisplayResolveInfo(i).getResolvedComponentName().equals( 2148 resultList.get(j).getTargetComponent())) { 2149 matchingShortcuts.add(resultList.get(j)); 2150 } 2151 } 2152 if (matchingShortcuts.isEmpty()) { 2153 continue; 2154 } 2155 List<ChooserTarget> chooserTargets = convertToChooserTarget( 2156 matchingShortcuts, resultList, appTargets, shortcutType); 2157 2158 2159 2160 final Message msg = Message.obtain(); 2161 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT; 2162 msg.obj = new ServiceResultInfo(chooserListAdapter.getDisplayResolveInfo(i), 2163 chooserTargets, null, userHandle); 2164 msg.arg1 = shortcutType; 2165 mChooserHandler.sendMessage(msg); 2166 resultMessageSent = true; 2167 } 2168 2169 if (resultMessageSent) { 2170 sendShortcutManagerShareTargetResultCompleted(); 2171 } 2172 } 2173 2174 private void sendShortcutManagerShareTargetResultCompleted() { 2175 final Message msg = Message.obtain(); 2176 msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED; 2177 mChooserHandler.sendMessage(msg); 2178 } 2179 2180 private boolean isPackageEnabled(Context context, String packageName) { 2181 if (TextUtils.isEmpty(packageName)) { 2182 return false; 2183 } 2184 ApplicationInfo appInfo; 2185 try { 2186 appInfo = context.getPackageManager().getApplicationInfo(packageName, 0); 2187 } catch (NameNotFoundException e) { 2188 return false; 2189 } 2190 2191 if (appInfo != null && appInfo.enabled 2192 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) { 2193 return true; 2194 } 2195 return false; 2196 } 2197 2198 /** 2199 * Converts a list of ShareShortcutInfos to ChooserTargets. 2200 * @param matchingShortcuts List of shortcuts, all from the same package, that match the current 2201 * share intent filter. 2202 * @param allShortcuts List of all the shortcuts from all the packages on the device that are 2203 * returned for the current sharing action. 2204 * @param allAppTargets List of AppTargets. Null if the results are not from prediction service. 2205 * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or 2206 * TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE 2207 * @return A list of ChooserTargets sorted by score in descending order. 2208 */ 2209 @VisibleForTesting 2210 @NonNull 2211 public List<ChooserTarget> convertToChooserTarget( 2212 @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts, 2213 @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts, 2214 @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) { 2215 // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted 2216 // list instead of the actual rank value when converting a rank to a score. 2217 List<Integer> scoreList = new ArrayList<>(); 2218 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) { 2219 for (int i = 0; i < matchingShortcuts.size(); i++) { 2220 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank(); 2221 if (!scoreList.contains(shortcutRank)) { 2222 scoreList.add(shortcutRank); 2223 } 2224 } 2225 Collections.sort(scoreList); 2226 } 2227 2228 List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size()); 2229 for (int i = 0; i < matchingShortcuts.size(); i++) { 2230 ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo(); 2231 int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i)); 2232 2233 float score; 2234 if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) { 2235 // Incoming results are ordered. Create a score based on index in the original list. 2236 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f); 2237 } else { 2238 // Create a score based on the rank of the shortcut. 2239 int rankIndex = scoreList.indexOf(shortcutInfo.getRank()); 2240 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f); 2241 } 2242 2243 Bundle extras = new Bundle(); 2244 extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId()); 2245 2246 ChooserTarget chooserTarget = new ChooserTarget( 2247 shortcutInfo.getLabel(), 2248 null, // Icon will be loaded later if this target is selected to be shown. 2249 score, matchingShortcuts.get(i).getTargetComponent().clone(), extras); 2250 2251 chooserTargetList.add(chooserTarget); 2252 if (mDirectShareAppTargetCache != null && allAppTargets != null) { 2253 mDirectShareAppTargetCache.put(chooserTarget, 2254 allAppTargets.get(indexInAllShortcuts)); 2255 } 2256 if (mDirectShareShortcutInfoCache != null) { 2257 mDirectShareShortcutInfoCache.put(chooserTarget, shortcutInfo); 2258 } 2259 } 2260 // Sort ChooserTargets by score in descending order 2261 Comparator<ChooserTarget> byScore = 2262 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore()); 2263 Collections.sort(chooserTargetList, byScore); 2264 return chooserTargetList; 2265 } 2266 2267 private String convertServiceName(String packageName, String serviceName) { 2268 if (TextUtils.isEmpty(serviceName)) { 2269 return null; 2270 } 2271 2272 final String fullName; 2273 if (serviceName.startsWith(".")) { 2274 // Relative to the app package. Prepend the app package name. 2275 fullName = packageName + serviceName; 2276 } else if (serviceName.indexOf('.') >= 0) { 2277 // Fully qualified package name. 2278 fullName = serviceName; 2279 } else { 2280 fullName = null; 2281 } 2282 return fullName; 2283 } 2284 2285 void unbindRemainingServices() { 2286 if (DEBUG) { 2287 Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left"); 2288 } 2289 for (int i = 0, N = mServiceConnections.size(); i < N; i++) { 2290 final ChooserTargetServiceConnection conn = mServiceConnections.get(i); 2291 if (DEBUG) Log.d(TAG, "unbinding " + conn); 2292 unbindService(conn); 2293 conn.destroy(); 2294 } 2295 mServicesRequested.clear(); 2296 mServiceConnections.clear(); 2297 } 2298 2299 private void logDirectShareTargetReceived(int logCategory) { 2300 final long queryTime = 2301 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER 2302 ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs; 2303 final int apiLatency = (int) (System.currentTimeMillis() - queryTime); 2304 getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency)); 2305 } 2306 2307 void updateModelAndChooserCounts(TargetInfo info) { 2308 if (info != null && info instanceof MultiDisplayResolveInfo) { 2309 info = ((MultiDisplayResolveInfo) info).getSelectedTarget(); 2310 } 2311 if (info != null) { 2312 sendClickToAppPredictor(info); 2313 final ResolveInfo ri = info.getResolveInfo(); 2314 Intent targetIntent = getTargetIntent(); 2315 if (ri != null && ri.activityInfo != null && targetIntent != null) { 2316 ChooserListAdapter currentListAdapter = 2317 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 2318 if (currentListAdapter != null) { 2319 sendImpressionToAppPredictor(info, currentListAdapter); 2320 currentListAdapter.updateModel(info.getResolvedComponentName()); 2321 currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, 2322 targetIntent.getAction()); 2323 } 2324 if (DEBUG) { 2325 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName); 2326 Log.d(TAG, "Action to be updated is " + targetIntent.getAction()); 2327 } 2328 } else if (DEBUG) { 2329 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo"); 2330 } 2331 } 2332 mIsSuccessfullySelected = true; 2333 } 2334 2335 private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) { 2336 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled( 2337 mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); 2338 if (directShareAppPredictor == null) { 2339 return; 2340 } 2341 // Send DS target impression info to AppPredictor, only when user chooses app share. 2342 if (targetInfo instanceof ChooserTargetInfo) { 2343 return; 2344 } 2345 List<ChooserTargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo(); 2346 List<AppTargetId> targetIds = new ArrayList<>(); 2347 for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) { 2348 ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget(); 2349 ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( 2350 chooserTarget.getComponentName(), chooserTarget.getComponentName()); 2351 if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) { 2352 String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId(); 2353 targetIds.add(new AppTargetId( 2354 String.format("%s/%s/%s", shortcutId, componentName.flattenToString(), 2355 SHORTCUT_TARGET))); 2356 } 2357 } 2358 directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds); 2359 } 2360 2361 private void sendClickToAppPredictor(TargetInfo targetInfo) { 2362 AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled( 2363 mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); 2364 if (directShareAppPredictor == null) { 2365 return; 2366 } 2367 if (!(targetInfo instanceof ChooserTargetInfo)) { 2368 return; 2369 } 2370 ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget(); 2371 AppTarget appTarget = null; 2372 if (mDirectShareAppTargetCache != null) { 2373 appTarget = mDirectShareAppTargetCache.get(chooserTarget); 2374 } 2375 // This is a direct share click that was provided by the APS 2376 if (appTarget != null) { 2377 directShareAppPredictor.notifyAppTargetEvent( 2378 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) 2379 .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE) 2380 .build()); 2381 } 2382 } 2383 2384 @Nullable 2385 private AppPredictor createAppPredictor(UserHandle userHandle) { 2386 if (!mIsAppPredictorComponentAvailable) { 2387 return null; 2388 } 2389 2390 if (getPersonalProfileUserHandle().equals(userHandle)) { 2391 if (mPersonalAppPredictor != null) { 2392 return mPersonalAppPredictor; 2393 } 2394 } else { 2395 if (mWorkAppPredictor != null) { 2396 return mWorkAppPredictor; 2397 } 2398 } 2399 2400 // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets. 2401 // Make AppPredictor work cross-profile. 2402 Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */); 2403 final IntentFilter filter = getTargetIntentFilter(); 2404 Bundle extras = new Bundle(); 2405 extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter); 2406 AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser) 2407 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) 2408 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT) 2409 .setExtras(extras) 2410 .build(); 2411 AppPredictionManager appPredictionManager = 2412 contextAsUser 2413 .getSystemService(AppPredictionManager.class); 2414 AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession( 2415 appPredictionContext); 2416 if (getPersonalProfileUserHandle().equals(userHandle)) { 2417 mPersonalAppPredictor = appPredictionSession; 2418 } else { 2419 mWorkAppPredictor = appPredictionSession; 2420 } 2421 return appPredictionSession; 2422 } 2423 2424 /** 2425 * This will return an app predictor if it is enabled for direct share sorting 2426 * and if one exists. Otherwise, it returns null. 2427 * @param userHandle 2428 */ 2429 @Nullable 2430 private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) { 2431 return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS 2432 && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : null; 2433 } 2434 2435 /** 2436 * This will return an app predictor if it is enabled for share activity sorting 2437 * and if one exists. Otherwise, it returns null. 2438 */ 2439 @Nullable 2440 private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) { 2441 return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null; 2442 } 2443 2444 void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { 2445 if (mRefinementResultReceiver != null) { 2446 mRefinementResultReceiver.destroy(); 2447 mRefinementResultReceiver = null; 2448 } 2449 if (selectedTarget == null) { 2450 Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); 2451 } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { 2452 Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget 2453 + " cannot match refined source intent " + matchingIntent); 2454 } else { 2455 TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0); 2456 if (super.onTargetSelected(clonedTarget, false)) { 2457 updateModelAndChooserCounts(clonedTarget); 2458 finish(); 2459 return; 2460 } 2461 } 2462 onRefinementCanceled(); 2463 } 2464 2465 void onRefinementCanceled() { 2466 if (mRefinementResultReceiver != null) { 2467 mRefinementResultReceiver.destroy(); 2468 mRefinementResultReceiver = null; 2469 } 2470 finish(); 2471 } 2472 2473 boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) { 2474 final List<Intent> targetIntents = target.getAllSourceIntents(); 2475 for (int i = 0, N = targetIntents.size(); i < N; i++) { 2476 final Intent targetIntent = targetIntents.get(i); 2477 if (targetIntent.filterEquals(matchingIntent)) { 2478 return true; 2479 } 2480 } 2481 return false; 2482 } 2483 2484 void filterServiceTargets(Context contextAsUser, String packageName, 2485 List<ChooserTarget> targets) { 2486 if (targets == null) { 2487 return; 2488 } 2489 2490 final PackageManager pm = contextAsUser.getPackageManager(); 2491 for (int i = targets.size() - 1; i >= 0; i--) { 2492 final ChooserTarget target = targets.get(i); 2493 final ComponentName targetName = target.getComponentName(); 2494 if (packageName != null && packageName.equals(targetName.getPackageName())) { 2495 // Anything from the original target's package is fine. 2496 continue; 2497 } 2498 2499 boolean remove; 2500 try { 2501 final ActivityInfo ai = pm.getActivityInfo(targetName, 0); 2502 remove = !ai.exported || ai.permission != null; 2503 } catch (NameNotFoundException e) { 2504 Log.e(TAG, "Target " + target + " returned by " + packageName 2505 + " component not found"); 2506 remove = true; 2507 } 2508 2509 if (remove) { 2510 targets.remove(i); 2511 } 2512 } 2513 } 2514 2515 /** 2516 * Sort intents alphabetically based on display label. 2517 */ 2518 static class AzInfoComparator implements Comparator<DisplayResolveInfo> { 2519 Collator mCollator; 2520 AzInfoComparator(Context context) { 2521 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); 2522 } 2523 2524 @Override 2525 public int compare( 2526 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) { 2527 return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel()); 2528 } 2529 } 2530 2531 protected MetricsLogger getMetricsLogger() { 2532 if (mMetricsLogger == null) { 2533 mMetricsLogger = new MetricsLogger(); 2534 } 2535 return mMetricsLogger; 2536 } 2537 2538 protected ChooserActivityLogger getChooserActivityLogger() { 2539 if (mChooserActivityLogger == null) { 2540 mChooserActivityLogger = new ChooserActivityLoggerImpl(); 2541 } 2542 return mChooserActivityLogger; 2543 } 2544 2545 public class ChooserListController extends ResolverListController { 2546 public ChooserListController(Context context, 2547 PackageManager pm, 2548 Intent targetIntent, 2549 String referrerPackageName, 2550 int launchedFromUid, 2551 UserHandle userId, 2552 AbstractResolverComparator resolverComparator) { 2553 super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId, 2554 resolverComparator); 2555 } 2556 2557 @Override 2558 boolean isComponentFiltered(ComponentName name) { 2559 if (mFilteredComponentNames == null) { 2560 return false; 2561 } 2562 for (ComponentName filteredComponentName : mFilteredComponentNames) { 2563 if (name.equals(filteredComponentName)) { 2564 return true; 2565 } 2566 } 2567 return false; 2568 } 2569 2570 @Override 2571 public boolean isComponentPinned(ComponentName name) { 2572 return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false); 2573 } 2574 } 2575 2576 @VisibleForTesting 2577 public ChooserGridAdapter createChooserGridAdapter(Context context, 2578 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, 2579 boolean filterLastUsed, UserHandle userHandle) { 2580 ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents, 2581 initialIntents, rList, filterLastUsed, 2582 createListController(userHandle)); 2583 AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter); 2584 AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback); 2585 chooserListAdapter.setAppPredictor(appPredictor); 2586 chooserListAdapter.setAppPredictorCallback(appPredictorCallback); 2587 return new ChooserGridAdapter(chooserListAdapter); 2588 } 2589 2590 @VisibleForTesting 2591 public ChooserListAdapter createChooserListAdapter(Context context, 2592 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, 2593 boolean filterLastUsed, ResolverListController resolverListController) { 2594 return new ChooserListAdapter(context, payloadIntents, initialIntents, rList, 2595 filterLastUsed, resolverListController, this, 2596 this, context.getPackageManager(), 2597 getChooserActivityLogger()); 2598 } 2599 2600 @VisibleForTesting 2601 protected ResolverListController createListController(UserHandle userHandle) { 2602 AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle); 2603 AbstractResolverComparator resolverComparator; 2604 if (appPredictor != null) { 2605 resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(), 2606 getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger()); 2607 } else { 2608 resolverComparator = 2609 new ResolverRankerServiceResolverComparator(this, getTargetIntent(), 2610 getReferrerPackageName(), null, getChooserActivityLogger()); 2611 } 2612 2613 return new ChooserListController( 2614 this, 2615 mPm, 2616 getTargetIntent(), 2617 getReferrerPackageName(), 2618 mLaunchedFromUid, 2619 userHandle, 2620 resolverComparator); 2621 } 2622 2623 @VisibleForTesting 2624 protected Bitmap loadThumbnail(Uri uri, Size size) { 2625 if (uri == null || size == null) { 2626 return null; 2627 } 2628 2629 try { 2630 return getContentResolver().loadThumbnail(uri, size, null); 2631 } catch (IOException | NullPointerException | SecurityException ex) { 2632 logContentPreviewWarning(uri); 2633 } 2634 return null; 2635 } 2636 2637 static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo { 2638 public Drawable getDisplayIcon(Context context) { 2639 AnimatedVectorDrawable avd = (AnimatedVectorDrawable) 2640 context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder); 2641 avd.start(); // Start animation after generation 2642 return avd; 2643 } 2644 } 2645 2646 protected static final class EmptyTargetInfo extends NotSelectableTargetInfo { 2647 public EmptyTargetInfo() {} 2648 2649 public Drawable getDisplayIcon(Context context) { 2650 return null; 2651 } 2652 } 2653 2654 private void handleScroll(View view, int x, int y, int oldx, int oldy) { 2655 if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) { 2656 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy); 2657 } 2658 } 2659 2660 /* 2661 * Need to dynamically adjust how many icons can fit per row before we add them, 2662 * which also means setting the correct offset to initially show the content 2663 * preview area + 2 rows of targets 2664 */ 2665 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 2666 int oldTop, int oldRight, int oldBottom) { 2667 if (mChooserMultiProfilePagerAdapter == null) { 2668 return; 2669 } 2670 RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView(); 2671 ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter(); 2672 // Skip height calculation if recycler view was scrolled to prevent it inaccurately 2673 // calculating the height, as the logic below does not account for the scrolled offset. 2674 if (gridAdapter == null || recyclerView == null 2675 || recyclerView.computeVerticalScrollOffset() != 0) { 2676 return; 2677 } 2678 2679 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight(); 2680 boolean isLayoutUpdated = gridAdapter.consumeLayoutRequest() 2681 || gridAdapter.calculateChooserTargetWidth(availableWidth) 2682 || recyclerView.getAdapter() == null 2683 || availableWidth != mCurrAvailableWidth; 2684 if (isLayoutUpdated 2685 || mLastNumberOfChildren != recyclerView.getChildCount()) { 2686 mCurrAvailableWidth = availableWidth; 2687 if (isLayoutUpdated) { 2688 // It is very important we call setAdapter from here. Otherwise in some cases 2689 // the resolver list doesn't get populated, such as b/150922090, b/150918223 2690 // and b/150936654 2691 recyclerView.setAdapter(gridAdapter); 2692 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount( 2693 getMaxTargetsPerRow()); 2694 } 2695 2696 UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle(); 2697 int currentProfile = getProfileForUser(currentUserHandle); 2698 int initialProfile = findSelectedProfile(); 2699 if (currentProfile != initialProfile) { 2700 return; 2701 } 2702 2703 if (mLastNumberOfChildren == recyclerView.getChildCount()) { 2704 return; 2705 } 2706 2707 getMainThreadHandler().post(() -> { 2708 if (mResolverDrawerLayout == null || gridAdapter == null) { 2709 return; 2710 } 2711 2712 final int bottomInset = mSystemWindowInsets != null 2713 ? mSystemWindowInsets.bottom : 0; 2714 int offset = bottomInset; 2715 int rowsToShow = gridAdapter.getSystemRowCount() 2716 + gridAdapter.getProfileRowCount() 2717 + gridAdapter.getServiceTargetRowCount() 2718 + gridAdapter.getCallerAndRankedTargetRowCount(); 2719 2720 // then this is most likely not a SEND_* action, so check 2721 // the app target count 2722 if (rowsToShow == 0) { 2723 rowsToShow = gridAdapter.getRowCount(); 2724 } 2725 2726 // still zero? then use a default height and leave, which 2727 // can happen when there are no targets to show 2728 if (rowsToShow == 0 && !shouldShowStickyContentPreview()) { 2729 offset += getResources().getDimensionPixelSize( 2730 R.dimen.chooser_max_collapsed_height); 2731 mResolverDrawerLayout.setCollapsibleHeightReserved(offset); 2732 return; 2733 } 2734 2735 View stickyContentPreview = findViewById(R.id.content_preview_container); 2736 if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) { 2737 offset += stickyContentPreview.getHeight(); 2738 } 2739 2740 if (shouldShowTabs()) { 2741 offset += findViewById(R.id.tabs).getHeight(); 2742 } 2743 2744 View tabDivider = findViewById(R.id.resolver_tab_divider); 2745 if (tabDivider.getVisibility() == View.VISIBLE) { 2746 offset += tabDivider.getHeight(); 2747 } 2748 2749 if (recyclerView.getVisibility() == View.VISIBLE) { 2750 int directShareHeight = 0; 2751 rowsToShow = Math.min(4, rowsToShow); 2752 boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow); 2753 mLastNumberOfChildren = recyclerView.getChildCount(); 2754 for (int i = 0, childCount = recyclerView.getChildCount(); 2755 i < childCount && rowsToShow > 0; i++) { 2756 View child = recyclerView.getChildAt(i); 2757 if (((GridLayoutManager.LayoutParams) 2758 child.getLayoutParams()).getSpanIndex() != 0) { 2759 continue; 2760 } 2761 int height = child.getHeight(); 2762 offset += height; 2763 if (shouldShowExtraRow) { 2764 offset += height; 2765 } 2766 2767 if (gridAdapter.getTargetType( 2768 recyclerView.getChildAdapterPosition(child)) 2769 == ChooserListAdapter.TARGET_SERVICE) { 2770 directShareHeight = height; 2771 } 2772 rowsToShow--; 2773 } 2774 2775 boolean isExpandable = getResources().getConfiguration().orientation 2776 == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode(); 2777 if (directShareHeight != 0 && isSendAction(getTargetIntent()) 2778 && isExpandable) { 2779 // make sure to leave room for direct share 4->8 expansion 2780 int requiredExpansionHeight = 2781 (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE); 2782 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0; 2783 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight() 2784 - requiredExpansionHeight - topInset - bottomInset; 2785 2786 offset = Math.min(offset, minHeight); 2787 } 2788 } else { 2789 ViewGroup currentEmptyStateView = getActiveEmptyStateView(); 2790 if (currentEmptyStateView.getVisibility() == View.VISIBLE) { 2791 offset += currentEmptyStateView.getHeight(); 2792 } 2793 } 2794 2795 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top)); 2796 }); 2797 } 2798 } 2799 2800 /** 2801 * If we have a tabbed view and are showing 1 row in the current profile and an empty 2802 * state screen in the other profile, to prevent cropping of the empty state screen we show 2803 * a second row in the current profile. 2804 */ 2805 private boolean shouldShowExtraRow(int rowsToShow) { 2806 return shouldShowTabs() 2807 && rowsToShow == 1 2808 && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen( 2809 mChooserMultiProfilePagerAdapter.getInactiveListAdapter()); 2810 } 2811 2812 /** 2813 * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle 2814 * does not match either the personal or work user handle. 2815 **/ 2816 private int getProfileForUser(UserHandle currentUserHandle) { 2817 if (currentUserHandle.equals(getPersonalProfileUserHandle())) { 2818 return PROFILE_PERSONAL; 2819 } else if (currentUserHandle.equals(getWorkProfileUserHandle())) { 2820 return PROFILE_WORK; 2821 } 2822 Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile."); 2823 return -1; 2824 } 2825 2826 private ViewGroup getActiveEmptyStateView() { 2827 int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage(); 2828 return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView(); 2829 } 2830 2831 static class BaseChooserTargetComparator implements Comparator<ChooserTarget> { 2832 @Override 2833 public int compare(ChooserTarget lhs, ChooserTarget rhs) { 2834 // Descending order 2835 return (int) Math.signum(rhs.getScore() - lhs.getScore()); 2836 } 2837 } 2838 2839 @Override // ResolverListCommunicator 2840 public void onHandlePackagesChanged(ResolverListAdapter listAdapter) { 2841 mServicesRequested.clear(); 2842 mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged(); 2843 super.onHandlePackagesChanged(listAdapter); 2844 } 2845 2846 @Override // SelectableTargetInfoCommunicator 2847 public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) { 2848 return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info); 2849 } 2850 2851 @Override // SelectableTargetInfoCommunicator 2852 public Intent getReferrerFillInIntent() { 2853 return mReferrerFillInIntent; 2854 } 2855 2856 @Override // ChooserListCommunicator 2857 public int getMaxRankedTargets() { 2858 return getMaxTargetsPerRow(); 2859 } 2860 2861 @Override // ChooserListCommunicator 2862 public void sendListViewUpdateMessage(UserHandle userHandle) { 2863 Message msg = Message.obtain(); 2864 msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE; 2865 msg.obj = userHandle; 2866 mChooserHandler.sendMessageDelayed(msg, LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); 2867 } 2868 2869 @Override 2870 public void onListRebuilt(ResolverListAdapter listAdapter) { 2871 setupScrollListener(); 2872 maybeSetupGlobalLayoutListener(); 2873 2874 ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter; 2875 if (chooserListAdapter.getUserHandle() 2876 .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) { 2877 mChooserMultiProfilePagerAdapter.getActiveAdapterView() 2878 .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter()); 2879 mChooserMultiProfilePagerAdapter 2880 .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage()); 2881 } 2882 2883 if (chooserListAdapter.mDisplayList == null 2884 || chooserListAdapter.mDisplayList.isEmpty()) { 2885 chooserListAdapter.notifyDataSetChanged(); 2886 } else { 2887 chooserListAdapter.updateAlphabeticalList(); 2888 } 2889 2890 // don't support direct share on low ram devices 2891 if (ActivityManager.isLowRamDeviceStatic()) { 2892 getChooserActivityLogger().logSharesheetAppLoadComplete(); 2893 return; 2894 } 2895 2896 // no need to query direct share for work profile when its locked or disabled 2897 if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) { 2898 getChooserActivityLogger().logSharesheetAppLoadComplete(); 2899 return; 2900 } 2901 2902 if (ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { 2903 if (DEBUG) { 2904 Log.d(TAG, "querying direct share targets from ShortcutManager"); 2905 } 2906 2907 queryDirectShareTargets(chooserListAdapter, false); 2908 } 2909 if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) { 2910 if (DEBUG) { 2911 Log.d(TAG, "List built querying services"); 2912 } 2913 2914 queryTargetServices(chooserListAdapter); 2915 } 2916 2917 getChooserActivityLogger().logSharesheetAppLoadComplete(); 2918 } 2919 2920 @VisibleForTesting 2921 protected boolean isUserRunning(UserHandle userHandle) { 2922 UserManager userManager = getSystemService(UserManager.class); 2923 return userManager.isUserRunning(userHandle); 2924 } 2925 2926 @VisibleForTesting 2927 protected boolean isUserUnlocked(UserHandle userHandle) { 2928 UserManager userManager = getSystemService(UserManager.class); 2929 return userManager.isUserUnlocked(userHandle); 2930 } 2931 2932 @VisibleForTesting 2933 protected boolean isQuietModeEnabled(UserHandle userHandle) { 2934 UserManager userManager = getSystemService(UserManager.class); 2935 return userManager.isQuietModeEnabled(userHandle); 2936 } 2937 2938 private void setupScrollListener() { 2939 if (mResolverDrawerLayout == null) { 2940 return; 2941 } 2942 int elevatedViewResId = shouldShowTabs() ? R.id.resolver_tab_divider : R.id.chooser_header; 2943 final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId); 2944 final float defaultElevation = elevatedView.getElevation(); 2945 final float chooserHeaderScrollElevation = 2946 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation); 2947 mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener( 2948 new RecyclerView.OnScrollListener() { 2949 public void onScrollStateChanged(RecyclerView view, int scrollState) { 2950 if (scrollState == RecyclerView.SCROLL_STATE_IDLE) { 2951 if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) { 2952 mScrollStatus = SCROLL_STATUS_IDLE; 2953 setHorizontalScrollingEnabled(true); 2954 } 2955 } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { 2956 if (mScrollStatus == SCROLL_STATUS_IDLE) { 2957 mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL; 2958 setHorizontalScrollingEnabled(false); 2959 } 2960 } 2961 } 2962 2963 public void onScrolled(RecyclerView view, int dx, int dy) { 2964 if (view.getChildCount() > 0) { 2965 View child = view.getLayoutManager().findViewByPosition(0); 2966 if (child == null || child.getTop() < 0) { 2967 elevatedView.setElevation(chooserHeaderScrollElevation); 2968 return; 2969 } 2970 } 2971 2972 elevatedView.setElevation(defaultElevation); 2973 } 2974 }); 2975 } 2976 2977 private void maybeSetupGlobalLayoutListener() { 2978 if (shouldShowTabs()) { 2979 return; 2980 } 2981 final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView(); 2982 recyclerView.getViewTreeObserver() 2983 .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 2984 @Override 2985 public void onGlobalLayout() { 2986 // Fixes an issue were the accessibility border disappears on list creation. 2987 recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 2988 final TextView titleView = findViewById(R.id.title); 2989 if (titleView != null) { 2990 titleView.setFocusable(true); 2991 titleView.setFocusableInTouchMode(true); 2992 titleView.requestFocus(); 2993 titleView.requestAccessibilityFocus(); 2994 } 2995 } 2996 }); 2997 } 2998 2999 @Override // ChooserListCommunicator 3000 public boolean isSendAction(Intent targetIntent) { 3001 if (targetIntent == null) { 3002 return false; 3003 } 3004 3005 String action = targetIntent.getAction(); 3006 if (action == null) { 3007 return false; 3008 } 3009 3010 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) { 3011 return true; 3012 } 3013 3014 return false; 3015 } 3016 3017 /** 3018 * The sticky content preview is shown only when we have a tabbed view. It's shown above 3019 * the tabs so it is not part of the scrollable list. If we are not in tabbed view, 3020 * we instead show the content preview as a regular list item. 3021 */ 3022 private boolean shouldShowStickyContentPreview() { 3023 return shouldShowStickyContentPreviewNoOrientationCheck() 3024 && !getResources().getBoolean(R.bool.resolver_landscape_phone); 3025 } 3026 3027 private boolean shouldShowStickyContentPreviewNoOrientationCheck() { 3028 return shouldShowTabs() 3029 && mMultiProfilePagerAdapter.getListAdapterForUserHandle( 3030 UserHandle.of(UserHandle.myUserId())).getCount() > 0 3031 && isSendAction(getTargetIntent()); 3032 } 3033 3034 private void updateStickyContentPreview() { 3035 if (shouldShowStickyContentPreviewNoOrientationCheck()) { 3036 // The sticky content preview is only shown when we show the work and personal tabs. 3037 // We don't show it in landscape as otherwise there is no room for scrolling. 3038 // If the sticky content preview will be shown at some point with orientation change, 3039 // then always preload it to avoid subsequent resizing of the share sheet. 3040 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container); 3041 if (contentPreviewContainer.getChildCount() == 0) { 3042 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer); 3043 contentPreviewContainer.addView(contentPreviewView); 3044 } 3045 } 3046 if (shouldShowStickyContentPreview()) { 3047 showStickyContentPreview(); 3048 } else { 3049 hideStickyContentPreview(); 3050 } 3051 } 3052 3053 private void showStickyContentPreview() { 3054 if (isStickyContentPreviewShowing()) { 3055 return; 3056 } 3057 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container); 3058 contentPreviewContainer.setVisibility(View.VISIBLE); 3059 } 3060 3061 private boolean isStickyContentPreviewShowing() { 3062 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container); 3063 return contentPreviewContainer.getVisibility() == View.VISIBLE; 3064 } 3065 3066 private void hideStickyContentPreview() { 3067 if (!isStickyContentPreviewShowing()) { 3068 return; 3069 } 3070 ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container); 3071 contentPreviewContainer.setVisibility(View.GONE); 3072 } 3073 3074 private void logActionShareWithPreview() { 3075 Intent targetIntent = getTargetIntent(); 3076 int previewType = findPreferredContentPreview(targetIntent, getContentResolver()); 3077 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW) 3078 .setSubtype(previewType)); 3079 } 3080 3081 class ViewHolderBase extends RecyclerView.ViewHolder { 3082 private int mViewType; 3083 3084 ViewHolderBase(View itemView, int viewType) { 3085 super(itemView); 3086 this.mViewType = viewType; 3087 } 3088 3089 int getViewType() { 3090 return mViewType; 3091 } 3092 } 3093 3094 /** 3095 * Used to bind types of individual item including 3096 * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL}, 3097 * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW}, 3098 * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE}, 3099 * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}. 3100 */ 3101 final class ItemViewHolder extends ViewHolderBase { 3102 ResolverListAdapter.ViewHolder mWrappedViewHolder; 3103 int mListPosition = ChooserListAdapter.NO_POSITION; 3104 3105 ItemViewHolder(View itemView, boolean isClickable, int viewType) { 3106 super(itemView, viewType); 3107 mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView); 3108 if (isClickable) { 3109 itemView.setOnClickListener(v -> startSelected(mListPosition, 3110 false/* always */, true/* filterd */)); 3111 3112 itemView.setOnLongClickListener(v -> { 3113 final TargetInfo ti = mChooserMultiProfilePagerAdapter.getActiveListAdapter() 3114 .targetInfoForPosition(mListPosition, /* filtered */ true); 3115 3116 // This should always be the case for ItemViewHolder, check for validity 3117 if (ti instanceof DisplayResolveInfo) { 3118 showTargetDetails((DisplayResolveInfo) ti); 3119 } 3120 return true; 3121 }); 3122 } 3123 } 3124 } 3125 3126 /** 3127 * Add a footer to the list, to support scrolling behavior below the navbar. 3128 */ 3129 final class FooterViewHolder extends ViewHolderBase { 3130 FooterViewHolder(View itemView, int viewType) { 3131 super(itemView, viewType); 3132 } 3133 } 3134 3135 /** 3136 * Intentionally override the {@link ResolverActivity} implementation as we only need that 3137 * implementation for the intent resolver case. 3138 */ 3139 @Override 3140 public void onButtonClick(View v) {} 3141 3142 /** 3143 * Intentionally override the {@link ResolverActivity} implementation as we only need that 3144 * implementation for the intent resolver case. 3145 */ 3146 @Override 3147 protected void resetButtonBar() {} 3148 3149 @Override 3150 protected String getMetricsCategory() { 3151 return METRICS_CATEGORY_CHOOSER; 3152 } 3153 3154 @Override 3155 protected void onProfileTabSelected() { 3156 ChooserGridAdapter currentRootAdapter = 3157 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter(); 3158 currentRootAdapter.updateDirectShareExpansion(); 3159 // This fixes an edge case where after performing a variety of gestures, vertical scrolling 3160 // ends up disabled. That's because at some point the old tab's vertical scrolling is 3161 // disabled and the new tab's is enabled. For context, see b/159997845 3162 setVerticalScrollEnabled(true); 3163 if (mResolverDrawerLayout != null) { 3164 mResolverDrawerLayout.scrollNestedScrollableChildBackToTop(); 3165 } 3166 } 3167 3168 @Override 3169 protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 3170 if (shouldShowTabs()) { 3171 mChooserMultiProfilePagerAdapter 3172 .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom()); 3173 mChooserMultiProfilePagerAdapter.setupContainerPadding( 3174 getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container)); 3175 } 3176 return super.onApplyWindowInsets(v, insets); 3177 } 3178 3179 private void setHorizontalScrollingEnabled(boolean enabled) { 3180 ResolverViewPager viewPager = findViewById(R.id.profile_pager); 3181 viewPager.setSwipingEnabled(enabled); 3182 } 3183 3184 private void setVerticalScrollEnabled(boolean enabled) { 3185 ChooserGridLayoutManager layoutManager = 3186 (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView() 3187 .getLayoutManager(); 3188 layoutManager.setVerticalScrollEnabled(enabled); 3189 } 3190 3191 @Override 3192 void onHorizontalSwipeStateChanged(int state) { 3193 if (state == ViewPager.SCROLL_STATE_DRAGGING) { 3194 if (mScrollStatus == SCROLL_STATUS_IDLE) { 3195 mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL; 3196 setVerticalScrollEnabled(false); 3197 } 3198 } else if (state == ViewPager.SCROLL_STATE_IDLE) { 3199 if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) { 3200 mScrollStatus = SCROLL_STATUS_IDLE; 3201 setVerticalScrollEnabled(true); 3202 } 3203 } 3204 } 3205 3206 int getMaxTargetsPerRow() { 3207 int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT; 3208 if (mShouldDisplayLandscape) { 3209 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE; 3210 } 3211 return maxTargets; 3212 } 3213 /** 3214 * Adapter for all types of items and targets in ShareSheet. 3215 * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the 3216 * row level by this adapter but not on the item level. Individual targets within the row are 3217 * handled by {@link ChooserListAdapter} 3218 */ 3219 @VisibleForTesting 3220 public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { 3221 private ChooserListAdapter mChooserListAdapter; 3222 private final LayoutInflater mLayoutInflater; 3223 3224 private DirectShareViewHolder mDirectShareViewHolder; 3225 private int mChooserTargetWidth = 0; 3226 private boolean mShowAzLabelIfPoss; 3227 3228 private boolean mHideContentPreview = false; 3229 private boolean mLayoutRequested = false; 3230 3231 private int mFooterHeight = 0; 3232 3233 private static final int VIEW_TYPE_DIRECT_SHARE = 0; 3234 private static final int VIEW_TYPE_NORMAL = 1; 3235 private static final int VIEW_TYPE_CONTENT_PREVIEW = 2; 3236 private static final int VIEW_TYPE_PROFILE = 3; 3237 private static final int VIEW_TYPE_AZ_LABEL = 4; 3238 private static final int VIEW_TYPE_CALLER_AND_RANK = 5; 3239 private static final int VIEW_TYPE_FOOTER = 6; 3240 3241 private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20; 3242 3243 ChooserGridAdapter(ChooserListAdapter wrappedAdapter) { 3244 super(); 3245 mChooserListAdapter = wrappedAdapter; 3246 mLayoutInflater = LayoutInflater.from(ChooserActivity.this); 3247 3248 mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL; 3249 3250 wrappedAdapter.registerDataSetObserver(new DataSetObserver() { 3251 @Override 3252 public void onChanged() { 3253 super.onChanged(); 3254 notifyDataSetChanged(); 3255 } 3256 3257 @Override 3258 public void onInvalidated() { 3259 super.onInvalidated(); 3260 notifyDataSetChanged(); 3261 } 3262 }); 3263 } 3264 3265 public void setFooterHeight(int height) { 3266 mFooterHeight = height; 3267 } 3268 3269 /** 3270 * Calculate the chooser target width to maximize space per item 3271 * 3272 * @param width The new row width to use for recalculation 3273 * @return true if the view width has changed 3274 */ 3275 public boolean calculateChooserTargetWidth(int width) { 3276 if (width == 0) { 3277 return false; 3278 } 3279 3280 int newWidth = width / getMaxTargetsPerRow(); 3281 if (newWidth != mChooserTargetWidth) { 3282 mChooserTargetWidth = newWidth; 3283 return true; 3284 } 3285 3286 return false; 3287 } 3288 3289 /** 3290 * Hides the list item content preview. 3291 * <p>Not to be confused with the sticky content preview which is above the 3292 * personal and work tabs. 3293 */ 3294 public void hideContentPreview() { 3295 mHideContentPreview = true; 3296 mLayoutRequested = true; 3297 notifyDataSetChanged(); 3298 } 3299 3300 public boolean consumeLayoutRequest() { 3301 boolean oldValue = mLayoutRequested; 3302 mLayoutRequested = false; 3303 return oldValue; 3304 } 3305 3306 public int getRowCount() { 3307 return (int) ( 3308 getSystemRowCount() 3309 + getProfileRowCount() 3310 + getServiceTargetRowCount() 3311 + getCallerAndRankedTargetRowCount() 3312 + getAzLabelRowCount() 3313 + Math.ceil( 3314 (float) mChooserListAdapter.getAlphaTargetCount() 3315 / getMaxTargetsPerRow()) 3316 ); 3317 } 3318 3319 /** 3320 * Whether the "system" row of targets is displayed. 3321 * This area includes the content preview (if present) and action row. 3322 */ 3323 public int getSystemRowCount() { 3324 // For the tabbed case we show the sticky content preview above the tabs, 3325 // please refer to shouldShowStickyContentPreview 3326 if (shouldShowTabs()) { 3327 return 0; 3328 } 3329 3330 if (!isSendAction(getTargetIntent())) { 3331 return 0; 3332 } 3333 3334 if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) { 3335 return 0; 3336 } 3337 3338 return 1; 3339 } 3340 3341 public int getProfileRowCount() { 3342 if (shouldShowTabs()) { 3343 return 0; 3344 } 3345 return mChooserListAdapter.getOtherProfile() == null ? 0 : 1; 3346 } 3347 3348 public int getFooterRowCount() { 3349 return 1; 3350 } 3351 3352 public int getCallerAndRankedTargetRowCount() { 3353 return (int) Math.ceil( 3354 ((float) mChooserListAdapter.getCallerTargetCount() 3355 + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow()); 3356 } 3357 3358 // There can be at most one row in the listview, that is internally 3359 // a ViewGroup with 2 rows 3360 public int getServiceTargetRowCount() { 3361 if (isSendAction(getTargetIntent()) 3362 && !ActivityManager.isLowRamDeviceStatic()) { 3363 return 1; 3364 } 3365 return 0; 3366 } 3367 3368 public int getAzLabelRowCount() { 3369 // Only show a label if the a-z list is showing 3370 return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0; 3371 } 3372 3373 @Override 3374 public int getItemCount() { 3375 return (int) ( 3376 getSystemRowCount() 3377 + getProfileRowCount() 3378 + getServiceTargetRowCount() 3379 + getCallerAndRankedTargetRowCount() 3380 + getAzLabelRowCount() 3381 + mChooserListAdapter.getAlphaTargetCount() 3382 + getFooterRowCount() 3383 ); 3384 } 3385 3386 @Override 3387 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 3388 switch (viewType) { 3389 case VIEW_TYPE_CONTENT_PREVIEW: 3390 return new ItemViewHolder(createContentPreviewView(parent), false, viewType); 3391 case VIEW_TYPE_PROFILE: 3392 return new ItemViewHolder(createProfileView(parent), false, viewType); 3393 case VIEW_TYPE_AZ_LABEL: 3394 return new ItemViewHolder(createAzLabelView(parent), false, viewType); 3395 case VIEW_TYPE_NORMAL: 3396 return new ItemViewHolder( 3397 mChooserListAdapter.createView(parent), true, viewType); 3398 case VIEW_TYPE_DIRECT_SHARE: 3399 case VIEW_TYPE_CALLER_AND_RANK: 3400 return createItemGroupViewHolder(viewType, parent); 3401 case VIEW_TYPE_FOOTER: 3402 Space sp = new Space(parent.getContext()); 3403 sp.setLayoutParams(new RecyclerView.LayoutParams( 3404 LayoutParams.MATCH_PARENT, mFooterHeight)); 3405 return new FooterViewHolder(sp, viewType); 3406 default: 3407 // Since we catch all possible viewTypes above, no chance this is being called. 3408 return null; 3409 } 3410 } 3411 3412 @Override 3413 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 3414 int viewType = ((ViewHolderBase) holder).getViewType(); 3415 switch (viewType) { 3416 case VIEW_TYPE_DIRECT_SHARE: 3417 case VIEW_TYPE_CALLER_AND_RANK: 3418 bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder); 3419 break; 3420 case VIEW_TYPE_NORMAL: 3421 bindItemViewHolder(position, (ItemViewHolder) holder); 3422 break; 3423 default: 3424 } 3425 } 3426 3427 @Override 3428 public int getItemViewType(int position) { 3429 int count; 3430 3431 int countSum = (count = getSystemRowCount()); 3432 if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW; 3433 3434 countSum += (count = getProfileRowCount()); 3435 if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE; 3436 3437 countSum += (count = getServiceTargetRowCount()); 3438 if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE; 3439 3440 countSum += (count = getCallerAndRankedTargetRowCount()); 3441 if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK; 3442 3443 countSum += (count = getAzLabelRowCount()); 3444 if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL; 3445 3446 if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER; 3447 3448 return VIEW_TYPE_NORMAL; 3449 } 3450 3451 public int getTargetType(int position) { 3452 return mChooserListAdapter.getPositionTargetType(getListPosition(position)); 3453 } 3454 3455 private View createProfileView(ViewGroup parent) { 3456 View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false); 3457 mProfileView = profileRow.findViewById(R.id.profile_button); 3458 mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick); 3459 updateProfileViewButton(); 3460 return profileRow; 3461 } 3462 3463 private View createAzLabelView(ViewGroup parent) { 3464 return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false); 3465 } 3466 3467 private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) { 3468 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 3469 final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth, 3470 MeasureSpec.EXACTLY); 3471 int columnCount = holder.getColumnCount(); 3472 3473 final boolean isDirectShare = holder instanceof DirectShareViewHolder; 3474 3475 for (int i = 0; i < columnCount; i++) { 3476 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i)); 3477 final int column = i; 3478 v.setOnClickListener(new OnClickListener() { 3479 @Override 3480 public void onClick(View v) { 3481 startSelected(holder.getItemIndex(column), false, true); 3482 } 3483 }); 3484 3485 // Direct Share targets should not show any menu 3486 if (!isDirectShare) { 3487 v.setOnLongClickListener(v1 -> { 3488 final TargetInfo ti = mChooserListAdapter.targetInfoForPosition( 3489 holder.getItemIndex(column), true); 3490 // This should always be the case for non-DS targets, check for validity 3491 if (ti instanceof DisplayResolveInfo) { 3492 showTargetDetails((DisplayResolveInfo) ti); 3493 } 3494 return true; 3495 }); 3496 } 3497 3498 holder.addView(i, v); 3499 3500 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll = 3501 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be 3502 // done before measuring. 3503 if (isDirectShare) { 3504 final ViewHolder vh = (ViewHolder) v.getTag(); 3505 vh.text.setLines(2); 3506 vh.text.setHorizontallyScrolling(false); 3507 vh.text2.setVisibility(View.GONE); 3508 } 3509 3510 // Force height to be a given so we don't have visual disruption during scaling. 3511 v.measure(exactSpec, spec); 3512 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight()); 3513 } 3514 3515 final ViewGroup viewGroup = holder.getViewGroup(); 3516 3517 // Pre-measure and fix height so we can scale later. 3518 holder.measure(); 3519 setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight()); 3520 3521 if (isDirectShare) { 3522 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder; 3523 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight()); 3524 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight()); 3525 } 3526 3527 viewGroup.setTag(holder); 3528 return holder; 3529 } 3530 3531 private void setViewBounds(View view, int widthPx, int heightPx) { 3532 LayoutParams lp = view.getLayoutParams(); 3533 if (lp == null) { 3534 lp = new LayoutParams(widthPx, heightPx); 3535 view.setLayoutParams(lp); 3536 } else { 3537 lp.height = heightPx; 3538 lp.width = widthPx; 3539 } 3540 } 3541 3542 ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) { 3543 if (viewType == VIEW_TYPE_DIRECT_SHARE) { 3544 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate( 3545 R.layout.chooser_row_direct_share, parent, false); 3546 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, 3547 parentGroup, false); 3548 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, 3549 parentGroup, false); 3550 parentGroup.addView(row1); 3551 parentGroup.addView(row2); 3552 3553 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup, 3554 Lists.newArrayList(row1, row2), getMaxTargetsPerRow(), viewType); 3555 loadViewsIntoGroup(mDirectShareViewHolder); 3556 3557 return mDirectShareViewHolder; 3558 } else { 3559 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent, 3560 false); 3561 ItemGroupViewHolder holder = 3562 new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType); 3563 loadViewsIntoGroup(holder); 3564 3565 return holder; 3566 } 3567 } 3568 3569 /** 3570 * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from 3571 * showing on top of the AZ list if the AZ label is visible. All other types are placed into 3572 * their own row as determined by their target type, and dividers are added in the list to 3573 * separate each type. 3574 */ 3575 int getRowType(int rowPosition) { 3576 // Merge caller and ranked standard into a single row 3577 int positionType = mChooserListAdapter.getPositionTargetType(rowPosition); 3578 if (positionType == ChooserListAdapter.TARGET_CALLER) { 3579 return ChooserListAdapter.TARGET_STANDARD; 3580 } 3581 3582 // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z 3583 // row type the same as the suggestion row type 3584 if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) { 3585 return ChooserListAdapter.TARGET_STANDARD; 3586 } 3587 3588 return positionType; 3589 } 3590 3591 void bindItemViewHolder(int position, ItemViewHolder holder) { 3592 View v = holder.itemView; 3593 int listPosition = getListPosition(position); 3594 holder.mListPosition = listPosition; 3595 mChooserListAdapter.bindView(listPosition, v); 3596 } 3597 3598 void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) { 3599 final ViewGroup viewGroup = (ViewGroup) holder.itemView; 3600 int start = getListPosition(position); 3601 int startType = getRowType(start); 3602 3603 int columnCount = holder.getColumnCount(); 3604 int end = start + columnCount - 1; 3605 while (getRowType(end) != startType && end >= start) { 3606 end--; 3607 } 3608 3609 if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) { 3610 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option); 3611 3612 if (textView.getVisibility() != View.VISIBLE) { 3613 textView.setAlpha(0.0f); 3614 textView.setVisibility(View.VISIBLE); 3615 textView.setText(R.string.chooser_no_direct_share_targets); 3616 3617 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f); 3618 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f)); 3619 3620 float translationInPx = getResources().getDimensionPixelSize( 3621 R.dimen.chooser_row_text_option_translate); 3622 textView.setTranslationY(translationInPx); 3623 ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY", 3624 0.0f); 3625 translateAnim.setInterpolator(new DecelerateInterpolator(1.0f)); 3626 3627 AnimatorSet animSet = new AnimatorSet(); 3628 animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS); 3629 animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS); 3630 animSet.playTogether(fadeAnim, translateAnim); 3631 animSet.start(); 3632 } 3633 } 3634 3635 for (int i = 0; i < columnCount; i++) { 3636 final View v = holder.getView(i); 3637 3638 if (start + i <= end) { 3639 holder.setViewVisibility(i, View.VISIBLE); 3640 holder.setItemIndex(i, start + i); 3641 mChooserListAdapter.bindView(holder.getItemIndex(i), v); 3642 } else { 3643 holder.setViewVisibility(i, View.INVISIBLE); 3644 } 3645 } 3646 } 3647 3648 int getListPosition(int position) { 3649 position -= getSystemRowCount() + getProfileRowCount(); 3650 3651 final int serviceCount = mChooserListAdapter.getServiceTargetCount(); 3652 final int serviceRows = (int) Math.ceil((float) serviceCount / getMaxRankedTargets()); 3653 if (position < serviceRows) { 3654 return position * getMaxTargetsPerRow(); 3655 } 3656 3657 position -= serviceRows; 3658 3659 final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount() 3660 + mChooserListAdapter.getRankedTargetCount(); 3661 final int callerAndRankedRows = getCallerAndRankedTargetRowCount(); 3662 if (position < callerAndRankedRows) { 3663 return serviceCount + position * getMaxTargetsPerRow(); 3664 } 3665 3666 position -= getAzLabelRowCount() + callerAndRankedRows; 3667 3668 return callerAndRankedCount + serviceCount + position; 3669 } 3670 3671 public void handleScroll(View v, int y, int oldy) { 3672 boolean canExpandDirectShare = canExpandDirectShare(); 3673 if (mDirectShareViewHolder != null && canExpandDirectShare) { 3674 mDirectShareViewHolder.handleScroll( 3675 mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy, 3676 getMaxTargetsPerRow()); 3677 } 3678 } 3679 3680 /** 3681 * Only expand direct share area if there is a minimum number of targets. 3682 */ 3683 private boolean canExpandDirectShare() { 3684 // Do not enable until we have confirmed more apps are using sharing shortcuts 3685 // Check git history for enablement logic 3686 return false; 3687 } 3688 3689 public ChooserListAdapter getListAdapter() { 3690 return mChooserListAdapter; 3691 } 3692 3693 boolean shouldCellSpan(int position) { 3694 return getItemViewType(position) == VIEW_TYPE_NORMAL; 3695 } 3696 3697 void updateDirectShareExpansion() { 3698 if (mDirectShareViewHolder == null || !canExpandDirectShare()) { 3699 return; 3700 } 3701 RecyclerView activeAdapterView = 3702 mChooserMultiProfilePagerAdapter.getActiveAdapterView(); 3703 if (mResolverDrawerLayout.isCollapsed()) { 3704 mDirectShareViewHolder.collapse(activeAdapterView); 3705 } else { 3706 mDirectShareViewHolder.expand(activeAdapterView); 3707 } 3708 } 3709 } 3710 3711 /** 3712 * Used to bind types for group of items including: 3713 * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE}, 3714 * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}. 3715 */ 3716 abstract class ItemGroupViewHolder extends ViewHolderBase { 3717 protected int mMeasuredRowHeight; 3718 private int[] mItemIndices; 3719 protected final View[] mCells; 3720 private final int mColumnCount; 3721 3722 ItemGroupViewHolder(int cellCount, View itemView, int viewType) { 3723 super(itemView, viewType); 3724 this.mCells = new View[cellCount]; 3725 this.mItemIndices = new int[cellCount]; 3726 this.mColumnCount = cellCount; 3727 } 3728 3729 abstract ViewGroup addView(int index, View v); 3730 3731 abstract ViewGroup getViewGroup(); 3732 3733 abstract ViewGroup getRowByIndex(int index); 3734 3735 abstract ViewGroup getRow(int rowNumber); 3736 3737 abstract void setViewVisibility(int i, int visibility); 3738 3739 public int getColumnCount() { 3740 return mColumnCount; 3741 } 3742 3743 public void measure() { 3744 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 3745 getViewGroup().measure(spec, spec); 3746 mMeasuredRowHeight = getViewGroup().getMeasuredHeight(); 3747 } 3748 3749 public int getMeasuredRowHeight() { 3750 return mMeasuredRowHeight; 3751 } 3752 3753 public void setItemIndex(int itemIndex, int listIndex) { 3754 mItemIndices[itemIndex] = listIndex; 3755 } 3756 3757 public int getItemIndex(int itemIndex) { 3758 return mItemIndices[itemIndex]; 3759 } 3760 3761 public View getView(int index) { 3762 return mCells[index]; 3763 } 3764 } 3765 3766 class SingleRowViewHolder extends ItemGroupViewHolder { 3767 private final ViewGroup mRow; 3768 3769 SingleRowViewHolder(ViewGroup row, int cellCount, int viewType) { 3770 super(cellCount, row, viewType); 3771 3772 this.mRow = row; 3773 } 3774 3775 public ViewGroup getViewGroup() { 3776 return mRow; 3777 } 3778 3779 public ViewGroup getRowByIndex(int index) { 3780 return mRow; 3781 } 3782 3783 public ViewGroup getRow(int rowNumber) { 3784 if (rowNumber == 0) return mRow; 3785 return null; 3786 } 3787 3788 public ViewGroup addView(int index, View v) { 3789 mRow.addView(v); 3790 mCells[index] = v; 3791 3792 return mRow; 3793 } 3794 3795 public void setViewVisibility(int i, int visibility) { 3796 getView(i).setVisibility(visibility); 3797 } 3798 } 3799 3800 class DirectShareViewHolder extends ItemGroupViewHolder { 3801 private final ViewGroup mParent; 3802 private final List<ViewGroup> mRows; 3803 private int mCellCountPerRow; 3804 3805 private boolean mHideDirectShareExpansion = false; 3806 private int mDirectShareMinHeight = 0; 3807 private int mDirectShareCurrHeight = 0; 3808 private int mDirectShareMaxHeight = 0; 3809 3810 private final boolean[] mCellVisibility; 3811 3812 DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow, 3813 int viewType) { 3814 super(rows.size() * cellCountPerRow, parent, viewType); 3815 3816 this.mParent = parent; 3817 this.mRows = rows; 3818 this.mCellCountPerRow = cellCountPerRow; 3819 this.mCellVisibility = new boolean[rows.size() * cellCountPerRow]; 3820 } 3821 3822 public ViewGroup addView(int index, View v) { 3823 ViewGroup row = getRowByIndex(index); 3824 row.addView(v); 3825 mCells[index] = v; 3826 3827 return row; 3828 } 3829 3830 public ViewGroup getViewGroup() { 3831 return mParent; 3832 } 3833 3834 public ViewGroup getRowByIndex(int index) { 3835 return mRows.get(index / mCellCountPerRow); 3836 } 3837 3838 public ViewGroup getRow(int rowNumber) { 3839 return mRows.get(rowNumber); 3840 } 3841 3842 public void measure() { 3843 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 3844 getRow(0).measure(spec, spec); 3845 getRow(1).measure(spec, spec); 3846 3847 mDirectShareMinHeight = getRow(0).getMeasuredHeight(); 3848 mDirectShareCurrHeight = mDirectShareCurrHeight > 0 3849 ? mDirectShareCurrHeight : mDirectShareMinHeight; 3850 mDirectShareMaxHeight = 2 * mDirectShareMinHeight; 3851 } 3852 3853 public int getMeasuredRowHeight() { 3854 return mDirectShareCurrHeight; 3855 } 3856 3857 public int getMinRowHeight() { 3858 return mDirectShareMinHeight; 3859 } 3860 3861 public void setViewVisibility(int i, int visibility) { 3862 final View v = getView(i); 3863 if (visibility == View.VISIBLE) { 3864 mCellVisibility[i] = true; 3865 v.setVisibility(visibility); 3866 v.setAlpha(1.0f); 3867 } else if (visibility == View.INVISIBLE && mCellVisibility[i]) { 3868 mCellVisibility[i] = false; 3869 3870 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f); 3871 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS); 3872 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f)); 3873 fadeAnim.addListener(new AnimatorListenerAdapter() { 3874 public void onAnimationEnd(Animator animation) { 3875 v.setVisibility(View.INVISIBLE); 3876 } 3877 }); 3878 fadeAnim.start(); 3879 } 3880 } 3881 3882 public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) { 3883 // only exit early if fully collapsed, otherwise onListRebuilt() with shifting 3884 // targets can lock us into an expanded mode 3885 boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight; 3886 if (notExpanded) { 3887 if (mHideDirectShareExpansion) { 3888 return; 3889 } 3890 3891 // only expand if we have more than maxTargetsPerRow, and delay that decision 3892 // until they start to scroll 3893 ChooserListAdapter adapter = 3894 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 3895 int validTargets = adapter.getSelectableServiceTargetCount(); 3896 if (validTargets <= maxTargetsPerRow) { 3897 mHideDirectShareExpansion = true; 3898 return; 3899 } 3900 } 3901 3902 int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE); 3903 3904 int prevHeight = mDirectShareCurrHeight; 3905 int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight); 3906 newHeight = Math.max(newHeight, mDirectShareMinHeight); 3907 yDiff = newHeight - prevHeight; 3908 3909 updateDirectShareRowHeight(view, yDiff, newHeight); 3910 } 3911 3912 void expand(RecyclerView view) { 3913 updateDirectShareRowHeight(view, mDirectShareMaxHeight - mDirectShareCurrHeight, 3914 mDirectShareMaxHeight); 3915 } 3916 3917 void collapse(RecyclerView view) { 3918 updateDirectShareRowHeight(view, mDirectShareMinHeight - mDirectShareCurrHeight, 3919 mDirectShareMinHeight); 3920 } 3921 3922 private void updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight) { 3923 if (view == null || view.getChildCount() == 0 || yDiff == 0) { 3924 return; 3925 } 3926 3927 // locate the item to expand, and offset the rows below that one 3928 boolean foundExpansion = false; 3929 for (int i = 0; i < view.getChildCount(); i++) { 3930 View child = view.getChildAt(i); 3931 3932 if (foundExpansion) { 3933 child.offsetTopAndBottom(yDiff); 3934 } else { 3935 if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) { 3936 int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(), 3937 MeasureSpec.EXACTLY); 3938 int heightSpec = MeasureSpec.makeMeasureSpec(newHeight, 3939 MeasureSpec.EXACTLY); 3940 child.measure(widthSpec, heightSpec); 3941 child.getLayoutParams().height = child.getMeasuredHeight(); 3942 child.layout(child.getLeft(), child.getTop(), child.getRight(), 3943 child.getTop() + child.getMeasuredHeight()); 3944 3945 foundExpansion = true; 3946 } 3947 } 3948 } 3949 3950 if (foundExpansion) { 3951 mDirectShareCurrHeight = newHeight; 3952 } 3953 } 3954 } 3955 3956 static class ChooserTargetServiceConnection implements ServiceConnection { 3957 private DisplayResolveInfo mOriginalTarget; 3958 private ComponentName mConnectedComponent; 3959 private ChooserActivity mChooserActivity; 3960 private final UserHandle mUserHandle; 3961 private final Object mLock = new Object(); 3962 3963 private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() { 3964 @Override 3965 public void sendResult(List<ChooserTarget> targets) throws RemoteException { 3966 synchronized (mLock) { 3967 if (mChooserActivity == null) { 3968 Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from " 3969 + mConnectedComponent + "; ignoring..."); 3970 return; 3971 } 3972 Context contextAsUser = 3973 mChooserActivity.createContextAsUser(mUserHandle, 0 /* flags */); 3974 mChooserActivity.filterServiceTargets(contextAsUser, 3975 mOriginalTarget.getResolveInfo().activityInfo.packageName, targets); 3976 final Message msg = Message.obtain(); 3977 msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT; 3978 msg.obj = new ServiceResultInfo(mOriginalTarget, targets, 3979 ChooserTargetServiceConnection.this, mUserHandle); 3980 mChooserActivity.mChooserHandler.sendMessage(msg); 3981 } 3982 } 3983 }; 3984 3985 public ChooserTargetServiceConnection(ChooserActivity chooserActivity, 3986 DisplayResolveInfo dri, UserHandle userHandle) { 3987 mChooserActivity = chooserActivity; 3988 mOriginalTarget = dri; 3989 mUserHandle = userHandle; 3990 } 3991 3992 @Override 3993 public void onServiceConnected(ComponentName name, IBinder service) { 3994 if (DEBUG) Log.d(TAG, "onServiceConnected: " + name); 3995 synchronized (mLock) { 3996 if (mChooserActivity == null) { 3997 Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected"); 3998 return; 3999 } 4000 4001 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service); 4002 try { 4003 icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(), 4004 mOriginalTarget.getResolveInfo().filter, mChooserTargetResult); 4005 } catch (RemoteException e) { 4006 Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e); 4007 mChooserActivity.unbindService(this); 4008 mChooserActivity.mServiceConnections.remove(this); 4009 destroy(); 4010 } 4011 } 4012 } 4013 4014 @Override 4015 public void onServiceDisconnected(ComponentName name) { 4016 if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name); 4017 synchronized (mLock) { 4018 if (mChooserActivity == null) { 4019 Log.e(TAG, 4020 "destroyed ChooserTargetServiceConnection got onServiceDisconnected"); 4021 return; 4022 } 4023 4024 mChooserActivity.unbindService(this); 4025 mChooserActivity.mServiceConnections.remove(this); 4026 if (mChooserActivity.mServiceConnections.isEmpty()) { 4027 mChooserActivity.sendVoiceChoicesIfNeeded(); 4028 } 4029 mConnectedComponent = null; 4030 destroy(); 4031 } 4032 } 4033 4034 public void destroy() { 4035 synchronized (mLock) { 4036 mChooserActivity = null; 4037 mOriginalTarget = null; 4038 } 4039 } 4040 4041 @Override 4042 public String toString() { 4043 return "ChooserTargetServiceConnection{service=" 4044 + mConnectedComponent + ", activity=" 4045 + (mOriginalTarget != null 4046 ? mOriginalTarget.getResolveInfo().activityInfo.toString() 4047 : "<connection destroyed>") + "}"; 4048 } 4049 4050 public ComponentName getComponentName() { 4051 return mOriginalTarget.getResolveInfo().activityInfo.getComponentName(); 4052 } 4053 } 4054 4055 static class ServiceResultInfo { 4056 public final DisplayResolveInfo originalTarget; 4057 public final List<ChooserTarget> resultTargets; 4058 public final ChooserTargetServiceConnection connection; 4059 public final UserHandle userHandle; 4060 4061 public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt, 4062 ChooserTargetServiceConnection c, UserHandle userHandle) { 4063 originalTarget = ot; 4064 resultTargets = rt; 4065 connection = c; 4066 this.userHandle = userHandle; 4067 } 4068 } 4069 4070 static class ChooserTargetRankingInfo { 4071 public final List<AppTarget> scores; 4072 public final UserHandle userHandle; 4073 4074 ChooserTargetRankingInfo(List<AppTarget> chooserTargetScores, 4075 UserHandle userHandle) { 4076 this.scores = chooserTargetScores; 4077 this.userHandle = userHandle; 4078 } 4079 } 4080 4081 static class RefinementResultReceiver extends ResultReceiver { 4082 private ChooserActivity mChooserActivity; 4083 private TargetInfo mSelectedTarget; 4084 4085 public RefinementResultReceiver(ChooserActivity host, TargetInfo target, 4086 Handler handler) { 4087 super(handler); 4088 mChooserActivity = host; 4089 mSelectedTarget = target; 4090 } 4091 4092 @Override 4093 protected void onReceiveResult(int resultCode, Bundle resultData) { 4094 if (mChooserActivity == null) { 4095 Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); 4096 return; 4097 } 4098 if (resultData == null) { 4099 Log.e(TAG, "RefinementResultReceiver received null resultData"); 4100 return; 4101 } 4102 4103 switch (resultCode) { 4104 case RESULT_CANCELED: 4105 mChooserActivity.onRefinementCanceled(); 4106 break; 4107 case RESULT_OK: 4108 Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); 4109 if (intentParcelable instanceof Intent) { 4110 mChooserActivity.onRefinementResult(mSelectedTarget, 4111 (Intent) intentParcelable); 4112 } else { 4113 Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" 4114 + " in resultData with key Intent.EXTRA_INTENT"); 4115 } 4116 break; 4117 default: 4118 Log.w(TAG, "Unknown result code " + resultCode 4119 + " sent to RefinementResultReceiver"); 4120 break; 4121 } 4122 } 4123 4124 public void destroy() { 4125 mChooserActivity = null; 4126 mSelectedTarget = null; 4127 } 4128 } 4129 4130 /** 4131 * Used internally to round image corners while obeying view padding. 4132 */ 4133 public static class RoundedRectImageView extends ImageView { 4134 private int mRadius = 0; 4135 private Path mPath = new Path(); 4136 private Paint mOverlayPaint = new Paint(0); 4137 private Paint mRoundRectPaint = new Paint(0); 4138 private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 4139 private String mExtraImageCount = null; 4140 4141 public RoundedRectImageView(Context context) { 4142 super(context); 4143 } 4144 4145 public RoundedRectImageView(Context context, AttributeSet attrs) { 4146 this(context, attrs, 0); 4147 } 4148 4149 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) { 4150 this(context, attrs, defStyleAttr, 0); 4151 } 4152 4153 public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr, 4154 int defStyleRes) { 4155 super(context, attrs, defStyleAttr, defStyleRes); 4156 mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius); 4157 4158 mOverlayPaint.setColor(0x99000000); 4159 mOverlayPaint.setStyle(Paint.Style.FILL); 4160 4161 mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider)); 4162 mRoundRectPaint.setStyle(Paint.Style.STROKE); 4163 mRoundRectPaint.setStrokeWidth(context.getResources() 4164 .getDimensionPixelSize(R.dimen.chooser_preview_image_border)); 4165 4166 mTextPaint.setColor(Color.WHITE); 4167 mTextPaint.setTextSize(context.getResources() 4168 .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size)); 4169 mTextPaint.setTextAlign(Paint.Align.CENTER); 4170 } 4171 4172 private void updatePath(int width, int height) { 4173 mPath.reset(); 4174 4175 int imageWidth = width - getPaddingRight() - getPaddingLeft(); 4176 int imageHeight = height - getPaddingBottom() - getPaddingTop(); 4177 mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius, 4178 mRadius, Path.Direction.CW); 4179 } 4180 4181 /** 4182 * Sets the corner radius on all corners 4183 * 4184 * param radius 0 for no radius, > 0 for a visible corner radius 4185 */ 4186 public void setRadius(int radius) { 4187 mRadius = radius; 4188 updatePath(getWidth(), getHeight()); 4189 } 4190 4191 /** 4192 * Display an overlay with extra image count on 3rd image 4193 */ 4194 public void setExtraImageCount(int count) { 4195 if (count > 0) { 4196 this.mExtraImageCount = "+" + count; 4197 } else { 4198 this.mExtraImageCount = null; 4199 } 4200 } 4201 4202 @Override 4203 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { 4204 super.onSizeChanged(width, height, oldWidth, oldHeight); 4205 updatePath(width, height); 4206 } 4207 4208 @Override 4209 protected void onDraw(Canvas canvas) { 4210 if (mRadius != 0) { 4211 canvas.clipPath(mPath); 4212 } 4213 4214 super.onDraw(canvas); 4215 4216 int x = getPaddingLeft(); 4217 int y = getPaddingRight(); 4218 int width = getWidth() - getPaddingRight() - getPaddingLeft(); 4219 int height = getHeight() - getPaddingBottom() - getPaddingTop(); 4220 if (mExtraImageCount != null) { 4221 canvas.drawRect(x, y, width, height, mOverlayPaint); 4222 4223 int xPos = canvas.getWidth() / 2; 4224 int yPos = (int) ((canvas.getHeight() / 2.0f) 4225 - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f)); 4226 4227 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint); 4228 } 4229 4230 canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint); 4231 } 4232 } 4233 4234 @Override 4235 protected void maybeLogProfileChange() { 4236 getChooserActivityLogger().logShareheetProfileChanged(); 4237 } 4238 } 4239