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