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