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