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