content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.internal.app;
  
  import static java.lang.annotation.RetentionPolicy.SOURCE;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.AnimatorSet;
  import android.animation.ObjectAnimator;
  import android.animation.ValueAnimator;
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.Activity;
  import android.app.ActivityManager;
  import android.app.prediction.AppPredictionContext;
  import android.app.prediction.AppPredictionManager;
  import android.app.prediction.AppPredictor;
  import android.app.prediction.AppTarget;
  import android.app.prediction.AppTargetEvent;
  import android.content.ClipData;
  import android.content.ClipboardManager;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.IntentSender;
  import android.content.IntentSender.SendIntentException;
  import android.content.ServiceConnection;
  import android.content.SharedPreferences;
  import android.content.pm.ActivityInfo;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.LabeledIntent;
  import android.content.pm.LauncherApps;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.pm.ResolveInfo;
  import android.content.pm.ShortcutInfo;
  import android.content.pm.ShortcutManager;
  import android.content.res.Configuration;
  import android.content.res.Resources;
  import android.database.Cursor;
  import android.database.DataSetObserver;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.Path;
  import android.graphics.drawable.AnimatedVectorDrawable;
  import android.graphics.drawable.BitmapDrawable;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.Icon;
  import android.metrics.LogMaker;
  import android.net.Uri;
  import android.os.AsyncTask;
  import android.os.Bundle;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Message;
  import android.os.Parcelable;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.ResultReceiver;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.os.storage.StorageManager;
  import android.provider.DeviceConfig;
  import android.provider.DocumentsContract;
  import android.provider.Downloads;
  import android.provider.OpenableColumns;
  import android.provider.Settings;
  import android.service.chooser.ChooserTarget;
  import android.service.chooser.ChooserTargetService;
  import android.service.chooser.IChooserTargetResult;
  import android.service.chooser.IChooserTargetService;
  import android.text.SpannableStringBuilder;
  import android.text.TextUtils;
  import android.util.AttributeSet;
  import android.util.HashedStringCache;
  import android.util.Log;
  import android.util.Size;
  import android.util.Slog;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.View.MeasureSpec;
  import android.view.View.OnClickListener;
  import android.view.View.OnLongClickListener;
  import android.view.ViewGroup;
  import android.view.ViewGroup.LayoutParams;
  import android.view.animation.AccelerateInterpolator;
  import android.view.animation.DecelerateInterpolator;
  import android.widget.AbsListView;
  import android.widget.BaseAdapter;
  import android.widget.Button;
  import android.widget.ImageView;
  import android.widget.ListView;
  import android.widget.TextView;
  import android.widget.Toast;
  
  import com.android.internal.R;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
  import com.android.internal.content.PackageMonitor;
  import com.android.internal.logging.MetricsLogger;
  import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
  import com.android.internal.util.ImageUtils;
  import com.android.internal.widget.ResolverDrawerLayout;
  
  import com.google.android.collect.Lists;
  
  import java.io.File;
  import java.io.IOException;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.net.URISyntaxException;
  import java.text.Collator;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * The Chooser Activity handles intent resolution specifically for sharing intents -
   * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
   *
   */
  public class ChooserActivity extends ResolverActivity {
      private static final String TAG = "ChooserActivity";
  
  
      /**
       * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
       * in onStop when launched in a new task. If this extra is set to true, we do not finish
       * ourselves when onStop gets called.
       */
      public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
              = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
  
      private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
  
      private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
      private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
  
      private static final boolean DEBUG = false;
  
      /**
       * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
       * {@link AppPredictionManager} will be queried for direct share targets.
       */
      // TODO(b/123089490): Replace with system flag
      private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
      private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
      // TODO(b/123088566) Share these in a better way.
      private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
      public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
      private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
      public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
  
      private boolean mIsAppPredictorComponentAvailable;
      private AppPredictor mAppPredictor;
      private AppPredictor.Callback mAppPredictorCallback;
      private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
  
      /**
       * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
       * binding to every ChooserTargetService implementation.
       */
      // TODO(b/121287573): Replace with a system flag (setprop?)
      private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
      private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
  
      public static final int TARGET_TYPE_DEFAULT = 0;
      public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
      public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
      public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
  
      @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
              TARGET_TYPE_DEFAULT,
              TARGET_TYPE_CHOOSER_TARGET,
              TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
              TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface ShareTargetType {}
  
      /**
       * The transition time between placeholders for direct share to a message
       * indicating that non are available.
       */
      private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
  
      private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
  
      // TODO(b/121287224): Re-evaluate this limit
      private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
  
      private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
  
      private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
      private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
              SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
              DEFAULT_SALT_EXPIRATION_DAYS);
  
      private Bundle mReplacementExtras;
      private IntentSender mChosenComponentSender;
      private IntentSender mRefinementIntentSender;
      private RefinementResultReceiver mRefinementResultReceiver;
      private ChooserTarget[] mCallerChooserTargets;
      private ComponentName[] mFilteredComponentNames;
  
      private Intent mReferrerFillInIntent;
  
      private long mChooserShownTime;
      protected boolean mIsSuccessfullySelected;
  
      private long mQueriedTargetServicesTimeMs;
      private long mQueriedSharingShortcutsTimeMs;
  
      private ChooserListAdapter mChooserListAdapter;
      private ChooserRowAdapter mChooserRowAdapter;
      private int mChooserRowServiceSpacing;
  
      private int mCurrAvailableWidth = 0;
  
      /** {@link ChooserActivity#getBaseScore} */
      public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
      /** {@link ChooserActivity#getBaseScore} */
      public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
      private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
      // TODO: Update to handle landscape instead of using static value
      private static final int MAX_RANKED_TARGETS = 4;
  
      private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
      private final Set<ComponentName> mServicesRequested = new HashSet<>();
  
      private static final int MAX_LOG_RANK_POSITION = 12;
  
      @VisibleForTesting
      public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
  
      private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
      private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
  
      private SharedPreferences mPinnedSharedPrefs;
      private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
  
      private boolean mListViewDataChanged = false;
  
      @Retention(SOURCE)
      @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
      private @interface ContentPreviewType {
      }
  
      // Starting at 1 since 0 is considered "undefined" for some of the database transformations
      // of tron logs.
      private static final int CONTENT_PREVIEW_IMAGE = 1;
      private static final int CONTENT_PREVIEW_FILE = 2;
      private static final int CONTENT_PREVIEW_TEXT = 3;
      protected MetricsLogger mMetricsLogger;
  
      // Sorted list of DisplayResolveInfos for the alphabetical app section.
      private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
  
      private ContentPreviewCoordinator mPreviewCoord;
  
      private class ContentPreviewCoordinator {
          private static final int IMAGE_FADE_IN_MILLIS = 150;
          private static final int IMAGE_LOAD_TIMEOUT = 1;
          private static final int IMAGE_LOAD_INTO_VIEW = 2;
  
          private final int mImageLoadTimeoutMillis =
                  getResources().getInteger(R.integer.config_shortAnimTime);
  
          private final View mParentView;
          private boolean mHideParentOnFail;
          private boolean mAtLeastOneLoaded = false;
  
          class LoadUriTask {
              public final Uri mUri;
              public final int mImageResourceId;
              public final int mExtraCount;
              public final Bitmap mBmp;
  
              LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
                  this.mImageResourceId = imageResourceId;
                  this.mUri = uri;
                  this.mExtraCount = extraCount;
                  this.mBmp = bmp;
              }
          }
  
          // If at least one image loads within the timeout period, allow other
          // loads to continue. Otherwise terminate and optionally hide
          // the parent area
          private final Handler mHandler = new Handler() {
              @Override
              public void handleMessage(Message msg) {
                  switch (msg.what) {
                      case IMAGE_LOAD_TIMEOUT:
                          maybeHideContentPreview();
                          break;
  
                      case IMAGE_LOAD_INTO_VIEW:
                          if (isFinishing()) break;
  
                          LoadUriTask task = (LoadUriTask) msg.obj;
                          RoundedRectImageView imageView = mParentView.findViewById(
                                  task.mImageResourceId);
                          if (task.mBmp == null) {
                              imageView.setVisibility(View.GONE);
                              maybeHideContentPreview();
                              return;
                          }
  
                          mAtLeastOneLoaded = true;
                          imageView.setVisibility(View.VISIBLE);
                          imageView.setAlpha(0.0f);
                          imageView.setImageBitmap(task.mBmp);
  
                          ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
                                  1.0f);
                          fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
                          fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
                          fadeAnim.start();
  
                          if (task.mExtraCount > 0) {
                              imageView.setExtraImageCount(task.mExtraCount);
                          }
                  }
              }
          };
  
          ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
              super();
  
              this.mParentView = parentView;
              this.mHideParentOnFail = hideParentOnFail;
          }
  
          private void loadUriIntoView(final int imageResourceId, final Uri uri,
                  final int extraImages) {
              mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
  
              AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
                  final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
                  final Message msg = Message.obtain();
                  msg.what = IMAGE_LOAD_INTO_VIEW;
                  msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
                  mHandler.sendMessage(msg);
              });
          }
  
          private void cancelLoads() {
              mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
              mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
          }
  
          private void maybeHideContentPreview() {
              if (!mAtLeastOneLoaded && mHideParentOnFail) {
                  Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
                          + " within " + mImageLoadTimeoutMillis + "ms.");
                  collapseParentView();
                  if (mChooserRowAdapter != null) {
                      mChooserRowAdapter.hideContentPreview();
                  }
                  mHideParentOnFail = false;
              }
          }
  
          private void collapseParentView() {
              // This will effectively hide the content preview row by forcing the height
              // to zero. It is faster than forcing a relayout of the listview
              final View v = mParentView;
              int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
              int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
              v.measure(widthSpec, heightSpec);
              v.getLayoutParams().height = 0;
              v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
              v.invalidate();
          }
      }
  
      private final ChooserHandler mChooserHandler = new ChooserHandler();
  
      private class ChooserHandler extends Handler {
          private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
          private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
          private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
          private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
          private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
          private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
  
          private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
          private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
  
          private boolean mMinTimeoutPassed = false;
  
          private void removeAllMessages() {
              removeMessages(LIST_VIEW_UPDATE_MESSAGE);
              removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
              removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
              removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
              removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
              removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
          }
  
          private void restartServiceRequestTimer() {
              mMinTimeoutPassed = false;
              removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
              removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
  
              if (DEBUG) {
                  Log.d(TAG, "queryTargets setting watchdog timer for "
                          + WATCHDOG_TIMEOUT_MIN_MILLIS + "-"
                          + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
              }
  
              sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
                      WATCHDOG_TIMEOUT_MIN_MILLIS);
              sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
                      WATCHDOG_TIMEOUT_MAX_MILLIS);
          }
  
          private void maybeStopServiceRequestTimer() {
              // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
              // and older-style direct share services, have had time to load, otherwise
              // just checking mServiceConnections could force us to end prematurely
              if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
                  logDirectShareTargetReceived(
                          MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
                  sendVoiceChoicesIfNeeded();
                  mChooserListAdapter.completeServiceTargetLoading();
              }
          }
  
          @Override
          public void handleMessage(Message msg) {
              if (mChooserListAdapter == null || isDestroyed()) {
                  return;
              }
  
              switch (msg.what) {
                  case CHOOSER_TARGET_SERVICE_RESULT:
                      if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
                      final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
                      if (!mServiceConnections.contains(sri.connection)) {
                          Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
                                  + " returned after being removed from active connections."
                                  + " Have you considered returning results faster?");
                          break;
                      }
                      if (sri.resultTargets != null) {
                          mChooserListAdapter.addServiceResults(sri.originalTarget,
                                  sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
                      }
                      unbindService(sri.connection);
                      sri.connection.destroy();
                      mServiceConnections.remove(sri.connection);
                      maybeStopServiceRequestTimer();
                      break;
  
                  case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
                      mMinTimeoutPassed = true;
                      maybeStopServiceRequestTimer();
                      break;
  
                  case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
                      unbindRemainingServices();
                      maybeStopServiceRequestTimer();
                      break;
  
                  case LIST_VIEW_UPDATE_MESSAGE:
                      if (DEBUG) {
                          Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
                      }
  
                      mChooserListAdapter.refreshListView();
                      break;
  
                  case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
                      if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
                      final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
                      if (resultInfo.resultTargets != null) {
                          mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
                                  resultInfo.resultTargets, msg.arg1);
                      }
                      break;
  
                  case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
                      logDirectShareTargetReceived(
                              MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
                      sendVoiceChoicesIfNeeded();
                      break;
  
                  default:
                      super.handleMessage(msg);
              }
          }
      };
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          final long intentReceivedTime = System.currentTimeMillis();
          // This is the only place this value is being set. Effectively final.
          mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
  
          mIsSuccessfullySelected = false;
          Intent intent = getIntent();
          Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
          if (targetParcelable instanceof Uri) {
              try {
                  targetParcelable = Intent.parseUri(targetParcelable.toString(),
                          Intent.URI_INTENT_SCHEME);
              } catch (URISyntaxException ex) {
                  // doesn't parse as an intent; let the next test fail and error out
              }
          }
  
          if (!(targetParcelable instanceof Intent)) {
              Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
              finish();
              super.onCreate(null);
              return;
          }
          Intent target = (Intent) targetParcelable;
          if (target != null) {
              modifyTargetIntent(target);
          }
          Parcelable[] targetsParcelable
                  = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
          if (targetsParcelable != null) {
              final boolean offset = target == null;
              Intent[] additionalTargets =
                      new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
              for (int i = 0; i < targetsParcelable.length; i++) {
                  if (!(targetsParcelable[i] instanceof Intent)) {
                      Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
                              + targetsParcelable[i]);
                      finish();
                      super.onCreate(null);
                      return;
                  }
                  final Intent additionalTarget = (Intent) targetsParcelable[i];
                  if (i == 0 && target == null) {
                      target = additionalTarget;
                      modifyTargetIntent(target);
                  } else {
                      additionalTargets[offset ? i - 1 : i] = additionalTarget;
                      modifyTargetIntent(additionalTarget);
                  }
              }
              setAdditionalTargets(additionalTargets);
          }
  
          mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
  
          // Do not allow the title to be changed when sharing content
          CharSequence title = null;
          if (target != null) {
              if (!isSendAction(target)) {
                  title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
              } else {
                  Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
                          + " preview title by using EXTRA_TITLE property of the wrapped"
                          + " EXTRA_INTENT.");
              }
          }
  
          int defaultTitleRes = 0;
          if (title == null) {
              defaultTitleRes = com.android.internal.R.string.chooseActivity;
          }
  
          Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
          Intent[] initialIntents = null;
          if (pa != null) {
              int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
              initialIntents = new Intent[count];
              for (int i = 0; i < count; i++) {
                  if (!(pa[i] instanceof Intent)) {
                      Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
                      finish();
                      super.onCreate(null);
                      return;
                  }
                  final Intent in = (Intent) pa[i];
                  modifyTargetIntent(in);
                  initialIntents[i] = in;
              }
          }
  
          mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
  
          mChosenComponentSender = intent.getParcelableExtra(
                  Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
          mRefinementIntentSender = intent.getParcelableExtra(
                  Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
          setSafeForwardingMode(true);
  
          mPinnedSharedPrefs = getPinnedSharedPrefs(this);
  
          pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
          if (pa != null) {
              ComponentName[] names = new ComponentName[pa.length];
              for (int i = 0; i < pa.length; i++) {
                  if (!(pa[i] instanceof ComponentName)) {
                      Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
                      names = null;
                      break;
                  }
                  names[i] = (ComponentName) pa[i];
              }
              mFilteredComponentNames = names;
          }
  
          pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
          if (pa != null) {
              int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
              ChooserTarget[] targets = new ChooserTarget[count];
              for (int i = 0; i < count; i++) {
                  if (!(pa[i] instanceof ChooserTarget)) {
                      Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
                      targets = null;
                      break;
                  }
                  targets[i] = (ChooserTarget) pa[i];
              }
              mCallerChooserTargets = targets;
          }
  
          setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
          super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                  null, false);
  
          mChooserShownTime = System.currentTimeMillis();
          final long systemCost = mChooserShownTime - intentReceivedTime;
  
          getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
                  .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
                          MetricsEvent.PARENT_PROFILE)
                  .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
                  .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
  
          AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
          if (appPredictor != null) {
              mDirectShareAppTargetCache = new HashMap<>();
              mAppPredictorCallback = resultList -> {
                  if (isFinishing() || isDestroyed()) {
                      return;
                  }
                  // May be null if there are no apps to perform share/open action.
                  if (mChooserListAdapter == null) {
                      return;
                  }
                  if (resultList.isEmpty()) {
                      // APS may be disabled, so try querying targets ourselves.
                      queryDirectShareTargets(mChooserListAdapter, true);
                      return;
                  }
                  final List<DisplayResolveInfo> driList =
                          getDisplayResolveInfos(mChooserListAdapter);
                  final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
                          new ArrayList<>();
                  for (AppTarget appTarget : resultList) {
                      if (appTarget.getShortcutInfo() == null) {
                          continue;
                      }
                      shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
                              appTarget.getShortcutInfo(),
                              new ComponentName(
                                  appTarget.getPackageName(), appTarget.getClassName())));
                  }
                  sendShareShortcutInfoList(shareShortcutInfos, driList, resultList);
              };
              appPredictor
                  .registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
          }
  
          mChooserRowServiceSpacing = getResources()
                                          .getDimensionPixelSize(R.dimen.chooser_service_spacing);
  
          if (mResolverDrawerLayout != null) {
              mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
  
              // expand/shrink direct share 4 -> 8 viewgroup
              if (isSendAction(target)) {
                  mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
              }
  
              final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
              final float defaultElevation = chooserHeader.getElevation();
              final float chooserHeaderScrollElevation =
                      getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
  
              mAdapterView.setOnScrollListener(new AbsListView.OnScrollListener() {
                  public void onScrollStateChanged(AbsListView view, int scrollState) {
                  }
  
                  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                          int totalItemCount) {
                      if (view.getChildCount() > 0) {
                          if (firstVisibleItem > 0 || view.getChildAt(0).getTop() < 0) {
                              chooserHeader.setElevation(chooserHeaderScrollElevation);
                              return;
                          }
                      }
  
                      chooserHeader.setElevation(defaultElevation);
                  }
              });
  
              mResolverDrawerLayout.setOnCollapsedChangedListener(
                      new ResolverDrawerLayout.OnCollapsedChangedListener() {
  
                          // Only consider one expansion per activity creation
                          private boolean mWrittenOnce = false;
  
                          @Override
                          public void onCollapsedChanged(boolean isCollapsed) {
                              if (!isCollapsed && !mWrittenOnce) {
                                  incrementNumSheetExpansions();
                                  mWrittenOnce = true;
                              }
                          }
                      });
          }
  
          if (DEBUG) {
              Log.d(TAG, "System Time Cost is " + systemCost);
          }
      }
  
  
      static SharedPreferences getPinnedSharedPrefs(Context context) {
          // The code below is because in the android:ui process, no one can hear you scream.
          // The package info in the context isn't initialized in the way it is for normal apps,
          // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
          // build the path manually below using the same policy that appears in ContextImpl.
          // This fails silently under the hood if there's a problem, so if we find ourselves in
          // the case where we don't have access to credential encrypted storage we just won't
          // have our pinned target info.
          final File prefsFile = new File(new File(
                  Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
                          context.getUserId(), context.getPackageName()),
                  "shared_prefs"),
                  PINNED_SHARED_PREFS_NAME + ".xml");
          return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
      }
  
      /**
       * Returns true if app prediction service is defined and the component exists on device.
       */
      @VisibleForTesting
      public boolean isAppPredictionServiceAvailable() {
          if (getPackageManager().getAppPredictionServicePackageName() == null) {
              // Default AppPredictionService is not defined.
              return false;
          }
  
          final String appPredictionServiceName =
                  getString(R.string.config_defaultAppPredictionService);
          if (appPredictionServiceName == null) {
              return false;
          }
          final ComponentName appPredictionComponentName =
                  ComponentName.unflattenFromString(appPredictionServiceName);
          if (appPredictionComponentName == null) {
              return false;
          }
  
          // Check if the app prediction component actually exists on the device.
          Intent intent = new Intent();
          intent.setComponent(appPredictionComponentName);
          if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
              Log.e(TAG, "App prediction service is defined, but does not exist: "
                      + appPredictionServiceName);
              return false;
          }
          return true;
      }
  
      /**
       * Check if the profile currently used is a work profile.
       * @return true if it is work profile, false if it is parent profile (or no work profile is
       * set up)
       */
      protected boolean isWorkProfile() {
          return ((UserManager) getSystemService(Context.USER_SERVICE))
                  .getUserInfo(UserHandle.myUserId()).isManagedProfile();
      }
  
      @Override
      protected PackageMonitor createPackageMonitor() {
          return new PackageMonitor() {
              @Override
              public void onSomePackagesChanged() {
                  handlePackagesChanged();
              }
          };
      }
  
      /**
       * Update UI to reflect changes in data.
       */
      public void handlePackagesChanged() {
          mAdapter.handlePackagesChanged();
          bindProfileView();
      }
  
      private void onCopyButtonClicked(View v) {
          Intent targetIntent = getTargetIntent();
          if (targetIntent == null) {
              finish();
          } else {
              final String action = targetIntent.getAction();
  
              ClipData clipData = null;
              if (Intent.ACTION_SEND.equals(action)) {
                  String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
                  Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
  
                  if (extraText != null) {
                      clipData = ClipData.newPlainText(null, extraText);
                  } else if (extraStream != null) {
                      clipData = ClipData.newUri(getContentResolver(), null, extraStream);
                  } else {
                      Log.w(TAG, "No data available to copy to clipboard");
                      return;
                  }
              } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
                  final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
                          Intent.EXTRA_STREAM);
                  clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
                  for (int i = 1; i < streams.size(); i++) {
                      clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
                  }
              } else {
                  // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
                  // so warn about unexpected action
                  Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
                  return;
              }
  
              ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
                      Context.CLIPBOARD_SERVICE);
              clipboardManager.setPrimaryClip(clipData);
              Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
  
              // Log share completion via copy
              LogMaker targetLogMaker = new LogMaker(
                      MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
              getMetricsLogger().write(targetLogMaker);
  
              finish();
          }
      }
  
      @Override
      public void onConfigurationChanged(Configuration newConfig) {
          super.onConfigurationChanged(newConfig);
  
          adjustPreviewWidth(newConfig.orientation, null);
      }
  
      private boolean shouldDisplayLandscape(int orientation) {
          // Sharesheet fixes the # of items per row and therefore can not correctly lay out
          // when in the restricted size of multi-window mode. In the future, would be nice
          // to use minimum dp size requirements instead
          return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
      }
  
      private void adjustPreviewWidth(int orientation, View parent) {
          int width = -1;
          if (shouldDisplayLandscape(orientation)) {
              width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
          }
  
          parent = parent == null ? getWindow().getDecorView() : parent;
  
          updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
          updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
          updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
      }
  
      private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
          View view = parent.findViewById(layoutResourceId);
          if (view != null && view.getLayoutParams() != null) {
              LayoutParams params = view.getLayoutParams();
              params.width = width;
              view.setLayoutParams(params);
          }
      }
  
      private ComponentName getNearbySharingComponent() {
          String nearbyComponent = Settings.Secure.getString(
                  getContentResolver(),
                  Settings.Secure.NEARBY_SHARING_COMPONENT);
          if (TextUtils.isEmpty(nearbyComponent)) {
              nearbyComponent = getString(R.string.config_defaultNearbySharingComponent);
          }
          if (TextUtils.isEmpty(nearbyComponent)) {
              return null;
          }
          return ComponentName.unflattenFromString(nearbyComponent);
      }
  
      private TargetInfo getNearbySharingTarget(Intent originalIntent) {
          final ComponentName cn = getNearbySharingComponent();
          if (cn == null) return null;
  
          final Intent resolveIntent = new Intent();
          resolveIntent.setComponent(cn);
          final ResolveInfo ri = getPackageManager().resolveActivity(
                  resolveIntent, PackageManager.GET_META_DATA);
          if (ri == null || ri.activityInfo == null) {
              Log.e(TAG, "Device-specified nearby sharing component (" + cn
                      + ") not available");
              return null;
          }
  
          // Allow the nearby sharing component to provide a more appropriate icon and label
          // for the chip.
          CharSequence name = null;
          Drawable icon = null;
          final Bundle metaData = ri.activityInfo.metaData;
          if (metaData != null) {
              try {
                  final Resources pkgRes = getPackageManager().getResourcesForActivity(cn);
                  final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY);
                  name = pkgRes.getString(nameResId);
                  final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY);
                  icon = pkgRes.getDrawable(resId);
              } catch (Resources.NotFoundException ex) {
              } catch (NameNotFoundException ex) {
              }
          }
          if (TextUtils.isEmpty(name)) {
              name = ri.loadLabel(getPackageManager());
          }
          if (icon == null) {
              icon = ri.loadIcon(getPackageManager());
          }
  
          final DisplayResolveInfo dri = new DisplayResolveInfo(
                  originalIntent, ri, name, "", null);
          dri.setDisplayIcon(icon);
          return dri;
      }
  
      private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
          Button b = (Button) LayoutInflater.from(this).inflate(R.layout.chooser_action_button, null);
          if (icon != null) {
              final int size = getResources()
                      .getDimensionPixelSize(R.dimen.chooser_action_button_icon_size);
              icon.setBounds(0, 0, size, size);
              b.setCompoundDrawablesRelative(icon, null, null, null);
          }
          b.setText(title);
          b.setOnClickListener(r);
          return b;
      }
  
      private Button createCopyButton() {
          final Button b = createActionButton(
                  getDrawable(R.drawable.ic_menu_copy_material),
                  getString(R.string.copy), this::onCopyButtonClicked);
          b.setId(R.id.chooser_copy_button);
          return b;
      }
  
      private @Nullable Button createNearbyButton(Intent originalIntent) {
          final TargetInfo ti = getNearbySharingTarget(originalIntent);
          if (ti == null) return null;
  
          return createActionButton(
                  ti.getDisplayIcon(),
                  ti.getDisplayLabel(),
                  (View unused) -> {
                      safelyStartActivity(ti);
                      finish();
                  }
          );
      }
  
      private void addActionButton(ViewGroup parent, Button b) {
          if (b == null) return;
          final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
                          LayoutParams.WRAP_CONTENT,
                          LayoutParams.WRAP_CONTENT
                  );
          final int gap = getResources().getDimensionPixelSize(R.dimen.resolver_icon_margin) / 2;
          lp.setMarginsRelative(gap, 0, gap, 0);
          parent.addView(b, lp);
      }
  
      private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
              Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
              ViewGroup parent) {
          if (convertView != null) return convertView;
  
          ViewGroup layout = null;
  
          switch (previewType) {
              case CONTENT_PREVIEW_TEXT:
                  layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
                  break;
              case CONTENT_PREVIEW_IMAGE:
                  layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
                  break;
              case CONTENT_PREVIEW_FILE:
                  layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
                  break;
              default:
                  Log.e(TAG, "Unexpected content preview type: " + previewType);
          }
  
          if (layout != null) {
              adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
          }
  
          return layout;
      }
  
      private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
              ViewGroup parent) {
          ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
                  R.layout.chooser_grid_preview_text, parent, false);
  
          final ViewGroup actionRow =
                  (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
          addActionButton(actionRow, createCopyButton());
          addActionButton(actionRow, createNearbyButton(targetIntent));
  
          CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
          if (sharingText == null) {
              contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
                      View.GONE);
          } else {
              TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
              textView.setText(sharingText);
          }
  
          String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
          if (TextUtils.isEmpty(previewTitle)) {
              contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
                      View.GONE);
          } else {
              TextView previewTitleView = contentPreviewLayout.findViewById(
                      R.id.content_preview_title);
              previewTitleView.setText(previewTitle);
  
              ClipData previewData = targetIntent.getClipData();
              Uri previewThumbnail = null;
              if (previewData != null) {
                  if (previewData.getItemCount() > 0) {
                      ClipData.Item previewDataItem = previewData.getItemAt(0);
                      previewThumbnail = previewDataItem.getUri();
                  }
              }
  
              ImageView previewThumbnailView = contentPreviewLayout.findViewById(
                      R.id.content_preview_thumbnail);
              if (previewThumbnail == null) {
                  previewThumbnailView.setVisibility(View.GONE);
              } else {
                  mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
                  mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
              }
          }
  
          return contentPreviewLayout;
      }
  
      private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
              ViewGroup parent) {
          ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
                  R.layout.chooser_grid_preview_image, parent, false);
          mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
  
          String action = targetIntent.getAction();
          if (Intent.ACTION_SEND.equals(action)) {
              Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
              mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
          } else {
              ContentResolver resolver = getContentResolver();
  
              List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
              List<Uri> imageUris = new ArrayList<>();
              for (Uri uri : uris) {
                  if (isImageType(resolver.getType(uri))) {
                      imageUris.add(uri);
                  }
              }
  
              if (imageUris.size() == 0) {
                  Log.i(TAG, "Attempted to display image preview area with zero"
                          + " available images detected in EXTRA_STREAM list");
                  contentPreviewLayout.setVisibility(View.GONE);
                  return contentPreviewLayout;
              }
  
              mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
  
              if (imageUris.size() == 2) {
                  mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
                          imageUris.get(1), 0);
              } else if (imageUris.size() > 2) {
                  mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
                          imageUris.get(1), 0);
                  mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
                          imageUris.get(2), imageUris.size() - 3);
              }
          }
  
          return contentPreviewLayout;
      }
  
      private static class FileInfo {
          public final String name;
          public final boolean hasThumbnail;
  
          FileInfo(String name, boolean hasThumbnail) {
              this.name = name;
              this.hasThumbnail = hasThumbnail;
          }
      }
  
      /**
       * Wrapping the ContentResolver call to expose for easier mocking,
       * and to avoid mocking Android core classes.
       */
      @VisibleForTesting
      public Cursor queryResolver(ContentResolver resolver, Uri uri) {
          return resolver.query(uri, null, null, null, null);
      }
  
      private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
          String fileName = null;
          boolean hasThumbnail = false;
  
          try (Cursor cursor = queryResolver(resolver, uri)) {
              if (cursor != null && cursor.getCount() > 0) {
                  int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                  int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
                  int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
  
                  cursor.moveToFirst();
                  if (nameIndex != -1) {
                      fileName = cursor.getString(nameIndex);
                  } else if (titleIndex != -1) {
                      fileName = cursor.getString(titleIndex);
                  }
  
                  if (flagsIndex != -1) {
                      hasThumbnail = (cursor.getInt(flagsIndex)
                              & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
                  }
              }
          } catch (SecurityException | NullPointerException e) {
              logContentPreviewWarning(uri);
          }
  
          if (TextUtils.isEmpty(fileName)) {
              fileName = uri.getPath();
              int index = fileName.lastIndexOf('/');
              if (index != -1) {
                  fileName = fileName.substring(index + 1);
              }
          }
  
          return new FileInfo(fileName, hasThumbnail);
      }
  
      private void logContentPreviewWarning(Uri uri) {
          // The ContentResolver already logs the exception. Log something more informative.
          Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
                  + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
                  + "and set your Intent's clipData and flags in accordance with that method's "
                  + "documentation");
      }
  
      private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
              ViewGroup parent) {
  
          ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
                  R.layout.chooser_grid_preview_file, parent, false);
  
          // TODO(b/120417119): Disable file copy until after moving to sysui,
          // due to permissions issues
          //((ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row))
          //        .addView(createCopyButton());
  
          String action = targetIntent.getAction();
          if (Intent.ACTION_SEND.equals(action)) {
              Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
              loadFileUriIntoView(uri, contentPreviewLayout);
          } else {
              List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
              int uriCount = uris.size();
  
              if (uriCount == 0) {
                  contentPreviewLayout.setVisibility(View.GONE);
                  Log.i(TAG,
                          "Appears to be no uris available in EXTRA_STREAM, removing "
                                  + "preview area");
                  return contentPreviewLayout;
              } else if (uriCount == 1) {
                  loadFileUriIntoView(uris.get(0), contentPreviewLayout);
              } else {
                  FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
                  int remUriCount = uriCount - 1;
                  String fileName = getResources().getQuantityString(R.plurals.file_count,
                          remUriCount, fileInfo.name, remUriCount);
  
                  TextView fileNameView = contentPreviewLayout.findViewById(
                          R.id.content_preview_filename);
                  fileNameView.setText(fileName);
  
                  View thumbnailView = contentPreviewLayout.findViewById(
                          R.id.content_preview_file_thumbnail);
                  thumbnailView.setVisibility(View.GONE);
  
                  ImageView fileIconView = contentPreviewLayout.findViewById(
                          R.id.content_preview_file_icon);
                  fileIconView.setVisibility(View.VISIBLE);
                  fileIconView.setImageResource(R.drawable.ic_file_copy);
              }
          }
  
          return contentPreviewLayout;
      }
  
      private void loadFileUriIntoView(final Uri uri, final View parent) {
          FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
  
          TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
          fileNameView.setText(fileInfo.name);
  
          if (fileInfo.hasThumbnail) {
              mPreviewCoord = new ContentPreviewCoordinator(parent, false);
              mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
          } else {
              View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
              thumbnailView.setVisibility(View.GONE);
  
              ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
              fileIconView.setVisibility(View.VISIBLE);
              fileIconView.setImageResource(R.drawable.chooser_file_generic);
          }
      }
  
      @VisibleForTesting
      protected boolean isImageType(String mimeType) {
          return mimeType != null && mimeType.startsWith("image/");
      }
  
      @ContentPreviewType
      private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
          if (uri == null) {
              return CONTENT_PREVIEW_TEXT;
          }
  
          String mimeType = resolver.getType(uri);
          return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
      }
  
      /**
       * In {@link android.content.Intent#getType}, the app may specify a very general
       * mime-type that broadly covers all data being shared, such as {@literal *}/*
       * when sending an image and text. We therefore should inspect each item for the
       * the preferred type, in order of IMAGE, FILE, TEXT.
       */
      @ContentPreviewType
      private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
          String action = targetIntent.getAction();
          if (Intent.ACTION_SEND.equals(action)) {
              Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
              return findPreferredContentPreview(uri, resolver);
          } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
              List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
              if (uris == null || uris.isEmpty()) {
                  return CONTENT_PREVIEW_TEXT;
              }
  
              for (Uri uri : uris) {
                  // Defaulting to file preview when there are mixed image/file types is
                  // preferable, as it shows the user the correct number of items being shared
                  if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
                      return CONTENT_PREVIEW_FILE;
                  }
              }
  
              return CONTENT_PREVIEW_IMAGE;
          }
  
          return CONTENT_PREVIEW_TEXT;
      }
  
      private int getNumSheetExpansions() {
          return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
      }
  
      private void incrementNumSheetExpansions() {
          getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
                  getNumSheetExpansions() + 1).apply();
      }
  
      @Override
      protected void onDestroy() {
          super.onDestroy();
          if (mRefinementResultReceiver != null) {
              mRefinementResultReceiver.destroy();
              mRefinementResultReceiver = null;
          }
          unbindRemainingServices();
          mChooserHandler.removeAllMessages();
  
          if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
  
          if (mAppPredictor != null) {
              mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
              mAppPredictor.destroy();
          }
      }
  
      @Override
      public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
          Intent result = defIntent;
          if (mReplacementExtras != null) {
              final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
              if (replExtras != null) {
                  result = new Intent(defIntent);
                  result.putExtras(replExtras);
              }
          }
          if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
                  || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
              result = Intent.createChooser(result,
                      getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
  
              // Don't auto-launch single intents if the intent is being forwarded. This is done
              // because automatically launching a resolving application as a response to the user
              // action of switching accounts is pretty unexpected.
              result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
          }
          return result;
      }
  
      @Override
      public void onActivityStarted(TargetInfo cti) {
          if (mChosenComponentSender != null) {
              final ComponentName target = cti.getResolvedComponentName();
              if (target != null) {
                  final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
                  try {
                      mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
                  } catch (IntentSender.SendIntentException e) {
                      Slog.e(TAG, "Unable to launch supplied IntentSender to report "
                              + "the chosen component: " + e);
                  }
              }
          }
      }
  
      @Override
      public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
          final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
          mChooserListAdapter = (ChooserListAdapter) adapter;
          if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
              mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
                      TARGET_TYPE_DEFAULT);
          }
          mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
          if (listView != null) {
              listView.setItemsCanFocus(true);
          }
      }
  
      @Override
      public int getLayoutResource() {
          return R.layout.chooser_grid;
      }
  
      @Override
      public boolean shouldGetActivityMetadata() {
          return true;
      }
  
      @Override
      public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
          // Note that this is only safe because the Intent handled by the ChooserActivity is
          // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
          // method can not be replaced in the ResolverActivity whole hog.
          if (!super.shouldAutoLaunchSingleChoice(target)) {
              return false;
          }
  
          return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
      }
  
      @Override
      public void showTargetDetails(ResolveInfo ri) {
          if (ri == null) {
              return;
          }
  
          ComponentName name = ri.activityInfo.getComponentName();
          boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
          ResolverTargetActionsDialogFragment f =
                  new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
                      name, pinned);
          f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
      }
  
      private void modifyTargetIntent(Intent in) {
          if (isSendAction(in)) {
              in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
                      Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
          }
      }
  
      @Override
      protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
          if (mRefinementIntentSender != null) {
              final Intent fillIn = new Intent();
              final List<Intent> sourceIntents = target.getAllSourceIntents();
              if (!sourceIntents.isEmpty()) {
                  fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
                  if (sourceIntents.size() > 1) {
                      final Intent[] alts = new Intent[sourceIntents.size() - 1];
                      for (int i = 1, N = sourceIntents.size(); i < N; i++) {
                          alts[i - 1] = sourceIntents.get(i);
                      }
                      fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
                  }
                  if (mRefinementResultReceiver != null) {
                      mRefinementResultReceiver.destroy();
                  }
                  mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
                  fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
                          mRefinementResultReceiver);
                  try {
                      mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
                      return false;
                  } catch (SendIntentException e) {
                      Log.e(TAG, "Refinement IntentSender failed to send", e);
                  }
              }
          }
          updateModelAndChooserCounts(target);
          return super.onTargetSelected(target, alwaysCheck);
      }
  
      @Override
      public void startSelected(int which, boolean always, boolean filtered) {
          TargetInfo targetInfo = mChooserListAdapter.targetInfoForPosition(which, filtered);
          if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
              return;
          }
  
          final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
          super.startSelected(which, always, filtered);
  
          if (mChooserListAdapter != null) {
              // Log the index of which type of target the user picked.
              // Lower values mean the ranking was better.
              int cat = 0;
              int value = which;
              int directTargetAlsoRanked = -1;
              int numCallerProvided = 0;
              HashedStringCache.HashResult directTargetHashed = null;
              switch (mChooserListAdapter.getPositionTargetType(which)) {
                  case ChooserListAdapter.TARGET_SERVICE:
                      cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
                      // Log the package name + target name to answer the question if most users
                      // share to mostly the same person or to a bunch of different people.
                      ChooserTarget target =
                              mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
                      directTargetHashed = HashedStringCache.getInstance().hashString(
                              this,
                              TAG,
                              target.getComponentName().getPackageName()
                                      + target.getTitle().toString(),
                              mMaxHashSaltDays);
                      directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
  
                      if (mCallerChooserTargets != null) {
                          numCallerProvided = mCallerChooserTargets.length;
                      }
                      break;
                  case ChooserListAdapter.TARGET_CALLER:
                  case ChooserListAdapter.TARGET_STANDARD:
                      cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
                      value -= mChooserListAdapter.getSelectableServiceTargetCount();
                      numCallerProvided = mChooserListAdapter.getCallerTargetCount();
                      break;
                  case ChooserListAdapter.TARGET_STANDARD_AZ:
                      // A-Z targets are unranked standard targets; we use -1 to mark that they
                      // are from the alphabetical pool.
                      value = -1;
                      cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
                      break;
              }
  
              if (cat != 0) {
                  LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
                  if (directTargetHashed != null) {
                      targetLogMaker.addTaggedData(
                              MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
                      targetLogMaker.addTaggedData(
                                      MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
                                      directTargetHashed.saltGeneration);
                      targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
                                      directTargetAlsoRanked);
                  }
                  targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
                          numCallerProvided);
                  getMetricsLogger().write(targetLogMaker);
              }
  
              if (mIsSuccessfullySelected) {
                  if (DEBUG) {
                      Log.d(TAG, "User Selection Time Cost is " + selectionCost);
                      Log.d(TAG, "position of selected app/service/caller is " +
                              Integer.toString(value));
                  }
                  MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
                          (int) selectionCost);
                  MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
              }
          }
      }
  
      private int getRankedPosition(SelectableTargetInfo targetInfo) {
          String targetPackageName =
                  targetInfo.getChooserTarget().getComponentName().getPackageName();
          int maxRankedResults = Math.min(mChooserListAdapter.mDisplayList.size(),
                          MAX_LOG_RANK_POSITION);
  
          for (int i = 0; i < maxRankedResults; i++) {
              if (mChooserListAdapter.mDisplayList.get(i)
                      .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
                  return i;
              }
          }
          return -1;
      }
  
      void queryTargetServices(ChooserListAdapter adapter) {
          mQueriedTargetServicesTimeMs = System.currentTimeMillis();
  
          final PackageManager pm = getPackageManager();
          ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
          int targetsToQuery = 0;
  
          for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
              final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
              if (adapter.getScore(dri) == 0) {
                  // A score of 0 means the app hasn't been used in some time;
                  // don't query it as it's not likely to be relevant.
                  continue;
              }
              final ActivityInfo ai = dri.getResolveInfo().activityInfo;
              if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
                      && sm.hasShareTargets(ai.packageName)) {
                  // Share targets will be queried from ShortcutManager
                  continue;
              }
              final Bundle md = ai.metaData;
              final String serviceName = md != null ? convertServiceName(ai.packageName,
                      md.getString(ChooserTargetService.META_DATA_NAME)) : null;
              if (serviceName != null) {
                  final ComponentName serviceComponent = new ComponentName(
                          ai.packageName, serviceName);
  
                  if (mServicesRequested.contains(serviceComponent)) {
                      continue;
                  }
                  mServicesRequested.add(serviceComponent);
  
                  final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
                          .setComponent(serviceComponent);
  
                  if (DEBUG) {
                      Log.d(TAG, "queryTargets found target with service " + serviceComponent);
                  }
  
                  try {
                      final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
                      if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
                          Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
                                  + " permission " + ChooserTargetService.BIND_PERMISSION
                                  + " - this service will not be queried for ChooserTargets."
                                  + " add android:permission=\""
                                  + ChooserTargetService.BIND_PERMISSION + "\""
                                  + " to the <service> tag for " + serviceComponent
                                  + " in the manifest.");
                          continue;
                      }
                  } catch (NameNotFoundException e) {
                      Log.e(TAG, "Could not look up service " + serviceComponent
                              + "; component name not found");
                      continue;
                  }
  
                  final ChooserTargetServiceConnection conn =
                          new ChooserTargetServiceConnection(this, dri);
  
                  // Explicitly specify Process.myUserHandle instead of calling bindService
                  // to avoid the warning from calling from the system process without an explicit
                  // user handle
                  if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
                          Process.myUserHandle())) {
                      if (DEBUG) {
                          Log.d(TAG, "Binding service connection for target " + dri
                                  + " intent " + serviceIntent);
                      }
                      mServiceConnections.add(conn);
                      targetsToQuery++;
                  }
              }
              if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
                  if (DEBUG) {
                      Log.d(TAG, "queryTargets hit query target limit "
                              + QUERY_TARGET_SERVICE_LIMIT);
                  }
                  break;
              }
          }
  
          mChooserHandler.restartServiceRequestTimer();
      }
  
      private IntentFilter getTargetIntentFilter() {
          try {
              final Intent intent = getTargetIntent();
              String dataString = intent.getDataString();
              if (TextUtils.isEmpty(dataString)) {
                  dataString = intent.getType();
              }
              return new IntentFilter(intent.getAction(), dataString);
          } catch (Exception e) {
              Log.e(TAG, "failed to get target intent filter", e);
              return null;
          }
      }
  
      private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
          // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
          // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
          List<DisplayResolveInfo> driList = new ArrayList<>();
          int targetsToQuery = 0;
          for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
              final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
              if (adapter.getScore(dri) == 0) {
                  // A score of 0 means the app hasn't been used in some time;
                  // don't query it as it's not likely to be relevant.
                  continue;
              }
              driList.add(dri);
              targetsToQuery++;
              // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
              if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
                  if (DEBUG) {
                      Log.d(TAG, "queryTargets hit query target limit "
                              + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
                  }
                  break;
              }
          }
          return driList;
      }
  
      private void queryDirectShareTargets(
                  ChooserListAdapter adapter, boolean skipAppPredictionService) {
          mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
          if (!skipAppPredictionService) {
              AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
              if (appPredictor != null) {
                  appPredictor.requestPredictionUpdate();
                  return;
              }
          }
          // Default to just querying ShortcutManager if AppPredictor not present.
          final IntentFilter filter = getTargetIntentFilter();
          if (filter == null) {
              return;
          }
          final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
  
          AsyncTask.execute(() -> {
              ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
              List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
              sendShareShortcutInfoList(resultList, driList, null);
          });
      }
  
      private void sendShareShortcutInfoList(
                  List<ShortcutManager.ShareShortcutInfo> resultList,
                  List<DisplayResolveInfo> driList,
                  @Nullable List<AppTarget> appTargets) {
          if (appTargets != null && appTargets.size() != resultList.size()) {
              throw new RuntimeException("resultList and appTargets must have the same size."
                      + " resultList.size()=" + resultList.size()
                      + " appTargets.size()=" + appTargets.size());
          }
  
          for (int i = resultList.size() - 1; i >= 0; i--) {
              final String packageName = resultList.get(i).getTargetComponent().getPackageName();
              if (!isPackageEnabled(packageName)) {
                  resultList.remove(i);
                  if (appTargets != null) {
                      appTargets.remove(i);
                  }
              }
          }
  
          // If |appTargets| is not null, results are from AppPredictionService and already sorted.
          final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
                  TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
  
          // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
          // for direct share targets. After ShareSheet is refactored we should use the
          // ShareShortcutInfos directly.
          boolean resultMessageSent = false;
          for (int i = 0; i < driList.size(); i++) {
              List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
              for (int j = 0; j < resultList.size(); j++) {
                  if (driList.get(i).getResolvedComponentName().equals(
                              resultList.get(j).getTargetComponent())) {
                      matchingShortcuts.add(resultList.get(j));
                  }
              }
              if (matchingShortcuts.isEmpty()) {
                  continue;
              }
              List<ChooserTarget> chooserTargets = convertToChooserTarget(
                      matchingShortcuts, resultList, appTargets, shortcutType);
  
              final Message msg = Message.obtain();
              msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
              msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
              msg.arg1 = shortcutType;
              mChooserHandler.sendMessage(msg);
              resultMessageSent = true;
          }
  
          if (resultMessageSent) {
              sendShortcutManagerShareTargetResultCompleted();
          }
      }
  
      private void sendShortcutManagerShareTargetResultCompleted() {
          final Message msg = Message.obtain();
          msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
          mChooserHandler.sendMessage(msg);
      }
  
      private boolean isPackageEnabled(String packageName) {
          if (TextUtils.isEmpty(packageName)) {
              return false;
          }
          ApplicationInfo appInfo;
          try {
              appInfo = getPackageManager().getApplicationInfo(packageName, 0);
          } catch (NameNotFoundException e) {
              return false;
          }
  
          if (appInfo != null && appInfo.enabled
                  && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
              return true;
          }
          return false;
      }
  
      /**
       * Converts a list of ShareShortcutInfos to ChooserTargets.
       * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
       *                         share intent filter.
       * @param allShortcuts List of all the shortcuts from all the packages on the device that are
       *                    returned for the current sharing action.
       * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
       * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
       *                    TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
       * @return A list of ChooserTargets sorted by score in descending order.
       */
      @VisibleForTesting
      @NonNull
      public List<ChooserTarget> convertToChooserTarget(
              @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
              @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
              @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
          // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
          // list instead of the actual rank value when converting a rank to a score.
          List<Integer> scoreList = new ArrayList<>();
          if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
              for (int i = 0; i < matchingShortcuts.size(); i++) {
                  int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
                  if (!scoreList.contains(shortcutRank)) {
                      scoreList.add(shortcutRank);
                  }
              }
              Collections.sort(scoreList);
          }
  
          List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
          for (int i = 0; i < matchingShortcuts.size(); i++) {
              ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
              int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
  
              float score;
              if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
                  // Incoming results are ordered. Create a score based on index in the original list.
                  score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
              } else {
                  // Create a score based on the rank of the shortcut.
                  int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
                  score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
              }
  
              Bundle extras = new Bundle();
              extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
              ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
                      null, // Icon will be loaded later if this target is selected to be shown.
                      score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
  
              chooserTargetList.add(chooserTarget);
              if (mDirectShareAppTargetCache != null && allAppTargets != null) {
                  mDirectShareAppTargetCache.put(chooserTarget,
                          allAppTargets.get(indexInAllShortcuts));
              }
          }
  
          // Sort ChooserTargets by score in descending order
          Comparator<ChooserTarget> byScore =
                  (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
          Collections.sort(chooserTargetList, byScore);
          return chooserTargetList;
      }
  
      private String convertServiceName(String packageName, String serviceName) {
          if (TextUtils.isEmpty(serviceName)) {
              return null;
          }
  
          final String fullName;
          if (serviceName.startsWith(".")) {
              // Relative to the app package. Prepend the app package name.
              fullName = packageName + serviceName;
          } else if (serviceName.indexOf('.') >= 0) {
              // Fully qualified package name.
              fullName = serviceName;
          } else {
              fullName = null;
          }
          return fullName;
      }
  
      void unbindRemainingServices() {
          if (DEBUG) {
              Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
          }
          for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
              final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
              if (DEBUG) Log.d(TAG, "unbinding " + conn);
              unbindService(conn);
              conn.destroy();
          }
          mServicesRequested.clear();
          mServiceConnections.clear();
      }
  
      private void logDirectShareTargetReceived(int logCategory) {
          final long queryTime =
                  logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
                          ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
          final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
          getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
      }
  
      void updateModelAndChooserCounts(TargetInfo info) {
          if (info != null) {
              sendClickToAppPredictor(info);
              final ResolveInfo ri = info.getResolveInfo();
              Intent targetIntent = getTargetIntent();
              if (ri != null && ri.activityInfo != null && targetIntent != null) {
                  if (mAdapter != null) {
                      mAdapter.updateModel(info.getResolvedComponentName());
                      mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
                              targetIntent.getAction());
                  }
                  if (DEBUG) {
                      Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
                      Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
                  }
              } else if (DEBUG) {
                  Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
              }
          }
          mIsSuccessfullySelected = true;
      }
  
      private void sendClickToAppPredictor(TargetInfo targetInfo) {
          AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled();
          if (directShareAppPredictor == null) {
              return;
          }
          if (!(targetInfo instanceof ChooserTargetInfo)) {
              return;
          }
          ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
          AppTarget appTarget = null;
          if (mDirectShareAppTargetCache != null) {
              appTarget = mDirectShareAppTargetCache.get(chooserTarget);
          }
          // This is a direct share click that was provided by the APS
          if (appTarget != null) {
              directShareAppPredictor.notifyAppTargetEvent(
                      new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
                          .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
                          .build());
          }
      }
  
      @Nullable
      private AppPredictor getAppPredictor() {
          if (!mIsAppPredictorComponentAvailable) {
              return null;
          }
          if (mAppPredictor == null) {
              final IntentFilter filter = getTargetIntentFilter();
              Bundle extras = new Bundle();
              extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
              AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(this)
                  .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
                  .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
                  .setExtras(extras)
                  .build();
              AppPredictionManager appPredictionManager
                      = getSystemService(AppPredictionManager.class);
              mAppPredictor = appPredictionManager.createAppPredictionSession(appPredictionContext);
          }
          return mAppPredictor;
      }
  
      /**
       * This will return an app predictor if it is enabled for direct share sorting
       * and if one exists. Otherwise, it returns null.
       */
      @Nullable
      private AppPredictor getAppPredictorForDirectShareIfEnabled() {
          return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
                  ? getAppPredictor() : null;
      }
  
      /**
       * This will return an app predictor if it is enabled for share activity sorting
       * and if one exists. Otherwise, it returns null.
       */
      @Nullable
      private AppPredictor getAppPredictorForShareActivitesIfEnabled() {
          return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? getAppPredictor() : null;
      }
  
      void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
          if (mRefinementResultReceiver != null) {
              mRefinementResultReceiver.destroy();
              mRefinementResultReceiver = null;
          }
          if (selectedTarget == null) {
              Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
          } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
              Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
                      + " cannot match refined source intent " + matchingIntent);
          } else {
              TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
              if (super.onTargetSelected(clonedTarget, false)) {
                  updateModelAndChooserCounts(clonedTarget);
                  finish();
                  return;
              }
          }
          onRefinementCanceled();
      }
  
      void onRefinementCanceled() {
          if (mRefinementResultReceiver != null) {
              mRefinementResultReceiver.destroy();
              mRefinementResultReceiver = null;
          }
          finish();
      }
  
      boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
          final List<Intent> targetIntents = target.getAllSourceIntents();
          for (int i = 0, N = targetIntents.size(); i < N; i++) {
              final Intent targetIntent = targetIntents.get(i);
              if (targetIntent.filterEquals(matchingIntent)) {
                  return true;
              }
          }
          return false;
      }
  
      void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
          if (targets == null) {
              return;
          }
  
          final PackageManager pm = getPackageManager();
          for (int i = targets.size() - 1; i >= 0; i--) {
              final ChooserTarget target = targets.get(i);
              final ComponentName targetName = target.getComponentName();
              if (packageName != null && packageName.equals(targetName.getPackageName())) {
                  // Anything from the original target's package is fine.
                  continue;
              }
  
              boolean remove;
              try {
                  final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
                  remove = !ai.exported || ai.permission != null;
              } catch (NameNotFoundException e) {
                  Log.e(TAG, "Target " + target + " returned by " + packageName
                          + " component not found");
                  remove = true;
              }
  
              if (remove) {
                  targets.remove(i);
              }
          }
      }
  
      private void updateAlphabeticalList() {
          mSortedList.clear();
          mSortedList.addAll(getDisplayList());
          Collections.sort(mSortedList, new AzInfoComparator(ChooserActivity.this));
      }
  
      /**
       * Sort intents alphabetically based on display label.
       */
      class AzInfoComparator implements Comparator<ResolverActivity.DisplayResolveInfo> {
          Collator mCollator;
          AzInfoComparator(Context context) {
              mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
          }
  
          @Override
          public int compare(ResolverActivity.DisplayResolveInfo lhsp,
                  ResolverActivity.DisplayResolveInfo rhsp) {
              return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
          }
      }
  
      protected MetricsLogger getMetricsLogger() {
          if (mMetricsLogger == null) {
              mMetricsLogger = new MetricsLogger();
          }
          return mMetricsLogger;
      }
  
      public class ChooserListController extends ResolverListController {
          public ChooserListController(Context context,
                  PackageManager pm,
                  Intent targetIntent,
                  String referrerPackageName,
                  int launchedFromUid,
                  AbstractResolverComparator resolverComparator) {
              super(context, pm, targetIntent, referrerPackageName, launchedFromUid,
                      resolverComparator);
          }
  
          @Override
          boolean isComponentFiltered(ComponentName name) {
              if (mFilteredComponentNames == null) {
                  return false;
              }
              for (ComponentName filteredComponentName : mFilteredComponentNames) {
                  if (name.equals(filteredComponentName)) {
                      return true;
                  }
              }
              return false;
          }
  
          @Override
          public boolean isComponentPinned(ComponentName name) {
              return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
          }
  
      }
  
      @Override
      public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
              Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
              boolean filterLastUsed) {
          final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
                  initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
          return adapter;
      }
  
      @VisibleForTesting
      protected ResolverListController createListController() {
          AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
          AbstractResolverComparator resolverComparator;
          if (appPredictor != null) {
              resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
                      getReferrerPackageName(), appPredictor, getUser());
          } else {
              resolverComparator =
                      new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
                          getReferrerPackageName(), null);
          }
  
          return new ChooserListController(
                  this,
                  mPm,
                  getTargetIntent(),
                  getReferrerPackageName(),
                  mLaunchedFromUid,
                  resolverComparator);
      }
  
      @VisibleForTesting
      protected Bitmap loadThumbnail(Uri uri, Size size) {
          if (uri == null || size == null) {
              return null;
          }
  
          try {
              return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
          } catch (IOException | NullPointerException | SecurityException ex) {
              logContentPreviewWarning(uri);
          }
          return null;
      }
  
      interface ChooserTargetInfo extends TargetInfo {
          float getModifiedScore();
  
          ChooserTarget getChooserTarget();
  
          /**
            * Do not label as 'equals', since this doesn't quite work
            * as intended with java 8.
            */
          default boolean isSimilar(ChooserTargetInfo other) {
              if (other == null) return false;
  
              ChooserTarget ct1 = getChooserTarget();
              ChooserTarget ct2 = other.getChooserTarget();
  
              // If either is null, there is not enough info to make an informed decision
              // about equality, so just exit
              if (ct1 == null || ct2 == null) return false;
  
              if (ct1.getComponentName().equals(ct2.getComponentName())
                      && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
                      && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
                  return true;
              }
  
              return false;
          }
      }
  
      /**
        * Distinguish between targets that selectable by the user, vs those that are
        * placeholders for the system while information is loading in an async manner.
        */
      abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
  
          public Intent getResolvedIntent() {
              return null;
          }
  
          public ComponentName getResolvedComponentName() {
              return null;
          }
  
          public boolean start(Activity activity, Bundle options) {
              return false;
          }
  
          public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
              return false;
          }
  
          public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
              return false;
          }
  
          public ResolveInfo getResolveInfo() {
              return null;
          }
  
          public CharSequence getDisplayLabel() {
              return null;
          }
  
          public CharSequence getExtendedInfo() {
              return null;
          }
  
          public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
              return null;
          }
  
          public List<Intent> getAllSourceIntents() {
              return null;
          }
  
          public float getModifiedScore() {
              return -0.1f;
          }
  
          public ChooserTarget getChooserTarget() {
              return null;
          }
  
          public boolean isSuspended() {
              return false;
          }
  
          public boolean isPinned() {
              return false;
          }
      }
  
      final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
          public Drawable getDisplayIcon() {
              AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
                      getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
              avd.start(); // Start animation after generation
              return avd;
          }
      }
  
  
      final class EmptyTargetInfo extends NotSelectableTargetInfo {
          public Drawable getDisplayIcon() {
              return null;
          }
      }
  
      final class SelectableTargetInfo implements ChooserTargetInfo {
          private final DisplayResolveInfo mSourceInfo;
          private final ResolveInfo mBackupResolveInfo;
          private final ChooserTarget mChooserTarget;
          private final String mDisplayLabel;
          private Drawable mBadgeIcon = null;
          private CharSequence mBadgeContentDescription;
          private Drawable mDisplayIcon;
          private final Intent mFillInIntent;
          private final int mFillInFlags;
          private final float mModifiedScore;
          private boolean mIsSuspended = false;
  
          SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
                  float modifiedScore) {
              mSourceInfo = sourceInfo;
              mChooserTarget = chooserTarget;
              mModifiedScore = modifiedScore;
              if (sourceInfo != null) {
                  final ResolveInfo ri = sourceInfo.getResolveInfo();
                  if (ri != null) {
                      final ActivityInfo ai = ri.activityInfo;
                      if (ai != null && ai.applicationInfo != null) {
                          final PackageManager pm = getPackageManager();
                          mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
                          mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
                          mIsSuspended =
                                  (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
                      }
                  }
              }
              // TODO(b/121287224): do this in the background thread, and only for selected targets
              mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
  
              if (sourceInfo != null) {
                  mBackupResolveInfo = null;
              } else {
                  mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
              }
  
              mFillInIntent = null;
              mFillInFlags = 0;
  
              mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
          }
  
          private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
              mSourceInfo = other.mSourceInfo;
              mBackupResolveInfo = other.mBackupResolveInfo;
              mChooserTarget = other.mChooserTarget;
              mBadgeIcon = other.mBadgeIcon;
              mBadgeContentDescription = other.mBadgeContentDescription;
              mDisplayIcon = other.mDisplayIcon;
              mFillInIntent = fillInIntent;
              mFillInFlags = flags;
              mModifiedScore = other.mModifiedScore;
  
              mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
          }
  
          private String sanitizeDisplayLabel(CharSequence label) {
              SpannableStringBuilder sb = new SpannableStringBuilder(label);
              sb.clearSpans();
              return sb.toString();
          }
  
          public boolean isSuspended() {
              return mIsSuspended;
          }
  
          public boolean isPinned() {
              return mSourceInfo != null && mSourceInfo.isPinned();
          }
  
          /**
           * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
           * the call to LauncherApps#getShortcuts(ShortcutQuery).
           */
          // TODO(121287224): Refactor code to apply the suggestion above
          private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
              Drawable directShareIcon = null;
  
              // First get the target drawable and associated activity info
              final Icon icon = target.getIcon();
              if (icon != null) {
                  directShareIcon = icon.loadDrawable(ChooserActivity.this);
              } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
                  Bundle extras = target.getIntentExtras();
                  if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
                      CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
                      LauncherApps launcherApps = (LauncherApps) getSystemService(
                              Context.LAUNCHER_APPS_SERVICE);
                      final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
                      q.setPackage(target.getComponentName().getPackageName());
                      q.setShortcutIds(Arrays.asList(shortcutId.toString()));
                      q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
                      final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
                      if (shortcuts != null && shortcuts.size() > 0) {
                          directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
                      }
                  }
              }
  
              if (directShareIcon == null) return null;
  
              ActivityInfo info = null;
              try {
                  info = mPm.getActivityInfo(target.getComponentName(), 0);
              } catch (NameNotFoundException error) {
                  Log.e(TAG, "Could not find activity associated with ChooserTarget");
              }
  
              if (info == null) return null;
  
              // Now fetch app icon and raster with no badging even in work profile
              Bitmap appIcon = makePresentationGetter(info).getIconBitmap(
                      UserHandle.getUserHandleForUid(UserHandle.myUserId()));
  
              // Raster target drawable with appIcon as a badge
              SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
              Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
              sif.recycle();
  
              return new BitmapDrawable(getResources(), directShareBadgedIcon);
          }
  
          public float getModifiedScore() {
              return mModifiedScore;
          }
  
          @Override
          public Intent getResolvedIntent() {
              if (mSourceInfo != null) {
                  return mSourceInfo.getResolvedIntent();
              }
  
              final Intent targetIntent = new Intent(getTargetIntent());
              targetIntent.setComponent(mChooserTarget.getComponentName());
              targetIntent.putExtras(mChooserTarget.getIntentExtras());
              return targetIntent;
          }
  
          @Override
          public ComponentName getResolvedComponentName() {
              if (mSourceInfo != null) {
                  return mSourceInfo.getResolvedComponentName();
              } else if (mBackupResolveInfo != null) {
                  return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
                          mBackupResolveInfo.activityInfo.name);
              }
              return null;
          }
  
          private Intent getBaseIntentToSend() {
              Intent result = getResolvedIntent();
              if (result == null) {
                  Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
              } else {
                  result = new Intent(result);
                  if (mFillInIntent != null) {
                      result.fillIn(mFillInIntent, mFillInFlags);
                  }
                  result.fillIn(mReferrerFillInIntent, 0);
              }
              return result;
          }
  
          @Override
          public boolean start(Activity activity, Bundle options) {
              throw new RuntimeException("ChooserTargets should be started as caller.");
          }
  
          @Override
          public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
              final Intent intent = getBaseIntentToSend();
              if (intent == null) {
                  return false;
              }
              intent.setComponent(mChooserTarget.getComponentName());
              intent.putExtras(mChooserTarget.getIntentExtras());
  
              // Important: we will ignore the target security checks in ActivityManager
              // if and only if the ChooserTarget's target package is the same package
              // where we got the ChooserTargetService that provided it. This lets a
              // ChooserTargetService provide a non-exported or permission-guarded target
              // to the chooser for the user to pick.
              //
              // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
              // so we'll obey the caller's normal security checks.
              final boolean ignoreTargetSecurity = mSourceInfo != null
                      && mSourceInfo.getResolvedComponentName().getPackageName()
                      .equals(mChooserTarget.getComponentName().getPackageName());
              return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
          }
  
          @Override
          public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
              throw new RuntimeException("ChooserTargets should be started as caller.");
          }
  
          @Override
          public ResolveInfo getResolveInfo() {
              return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
          }
  
          @Override
          public CharSequence getDisplayLabel() {
              return mDisplayLabel;
          }
  
          @Override
          public CharSequence getExtendedInfo() {
              // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
              return null;
          }
  
          @Override
          public Drawable getDisplayIcon() {
              return mDisplayIcon;
          }
  
          public ChooserTarget getChooserTarget() {
              return mChooserTarget;
          }
  
          @Override
          public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
              return new SelectableTargetInfo(this, fillInIntent, flags);
          }
  
          @Override
          public List<Intent> getAllSourceIntents() {
              final List<Intent> results = new ArrayList<>();
              if (mSourceInfo != null) {
                  // We only queried the service for the first one in our sourceinfo.
                  results.add(mSourceInfo.getAllSourceIntents().get(0));
              }
              return results;
          }
      }
  
      private void handleScroll(View view, int x, int y, int oldx, int oldy) {
          if (mChooserRowAdapter != null) {
              mChooserRowAdapter.handleScroll(view, y, oldy);
          }
      }
  
      /*
       * Need to dynamically adjust how many icons can fit per row before we add them,
       * which also means setting the correct offset to initially show the content
       * preview area + 2 rows of targets
       */
      private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
              int oldTop, int oldRight, int oldBottom) {
          if (mChooserRowAdapter == null || mAdapterView == null) {
              return;
          }
  
          final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
          if (mChooserRowAdapter.consumeLayoutRequest()
                  || mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
                  || mAdapterView.getAdapter() == null
                  || availableWidth != mCurrAvailableWidth) {
              mCurrAvailableWidth = availableWidth;
              mAdapterView.setAdapter(mChooserRowAdapter);
  
              getMainThreadHandler().post(() -> {
                  if (mResolverDrawerLayout == null || mChooserRowAdapter == null) {
                      return;
                  }
  
                  final int bottomInset = mSystemWindowInsets != null
                                              ? mSystemWindowInsets.bottom : 0;
                  int offset = bottomInset;
                  int rowsToShow = mChooserRowAdapter.getContentPreviewRowCount()
                          + mChooserRowAdapter.getProfileRowCount()
                          + mChooserRowAdapter.getServiceTargetRowCount()
                          + mChooserRowAdapter.getCallerAndRankedTargetRowCount();
  
                  // then this is most likely not a SEND_* action, so check
                  // the app target count
                  if (rowsToShow == 0) {
                      rowsToShow = mChooserRowAdapter.getCount();
                  }
  
                  // still zero? then use a default height and leave, which
                  // can happen when there are no targets to show
                  if (rowsToShow == 0) {
                      offset += getResources().getDimensionPixelSize(
                              R.dimen.chooser_max_collapsed_height);
                      mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
                      return;
                  }
  
                  int directShareHeight = 0;
                  rowsToShow = Math.min(4, rowsToShow);
                  for (int i = 0; i < Math.min(rowsToShow, mAdapterView.getChildCount()); i++) {
                      View child = mAdapterView.getChildAt(i);
                      int height = child.getHeight();
                      offset += height;
  
                      if (child.getTag() != null
                              && (child.getTag() instanceof DirectShareViewHolder)) {
                          directShareHeight = height;
                      }
                  }
  
                  boolean isExpandable = getResources().getConfiguration().orientation
                          == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
                  if (directShareHeight != 0 && isSendAction(getTargetIntent()) && isExpandable) {
                      // make sure to leave room for direct share 4->8 expansion
                      int requiredExpansionHeight =
                              (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
                      int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
                      int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
                                          - requiredExpansionHeight - topInset - bottomInset;
  
                      offset = Math.min(offset, minHeight);
                  }
  
                  mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
              });
          }
      }
  
      @Override
      protected boolean shouldAddFooterView() {
          // To accommodate for window insets
          return true;
      }
  
      public class ChooserListAdapter extends ResolveListAdapter {
          public static final int TARGET_BAD = -1;
          public static final int TARGET_CALLER = 0;
          public static final int TARGET_SERVICE = 1;
          public static final int TARGET_STANDARD = 2;
          public static final int TARGET_STANDARD_AZ = 3;
  
          private static final int MAX_SUGGESTED_APP_TARGETS = 4;
          private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
  
          private static final int MAX_SERVICE_TARGETS = 8;
  
          private final int mMaxShortcutTargetsPerApp =
                  getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
  
          private int mNumShortcutResults = 0;
  
          // Reserve spots for incoming direct share targets by adding placeholders
          private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
          private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
          private final List<TargetInfo> mCallerTargets = new ArrayList<>();
  
          private final BaseChooserTargetComparator mBaseTargetComparator
                  = new BaseChooserTargetComparator();
  
          public ChooserListAdapter(Context context, List<Intent> payloadIntents,
                  Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
                  boolean filterLastUsed, ResolverListController resolverListController) {
              // Don't send the initial intents through the shared ResolverActivity path,
              // we want to separate them into a different section.
              super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
                      resolverListController);
  
              createPlaceHolders();
  
              if (initialIntents != null) {
                  final PackageManager pm = getPackageManager();
                  for (int i = 0; i < initialIntents.length; i++) {
                      final Intent ii = initialIntents[i];
                      if (ii == null) {
                          continue;
                      }
  
                      // We reimplement Intent#resolveActivityInfo here because if we have an
                      // implicit intent, we want the ResolveInfo returned by PackageManager
                      // instead of one we reconstruct ourselves. The ResolveInfo returned might
                      // have extra metadata and resolvePackageName set and we want to respect that.
                      ResolveInfo ri = null;
                      ActivityInfo ai = null;
                      final ComponentName cn = ii.getComponent();
                      if (cn != null) {
                          try {
                              ai = pm.getActivityInfo(ii.getComponent(), 0);
                              ri = new ResolveInfo();
                              ri.activityInfo = ai;
                          } catch (PackageManager.NameNotFoundException ignored) {
                              // ai will == null below
                          }
                      }
                      if (ai == null) {
                          ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
                          ai = ri != null ? ri.activityInfo : null;
                      }
                      if (ai == null) {
                          Log.w(TAG, "No activity found for " + ii);
                          continue;
                      }
                      UserManager userManager =
                              (UserManager) getSystemService(Context.USER_SERVICE);
                      if (ii instanceof LabeledIntent) {
                          LabeledIntent li = (LabeledIntent) ii;
                          ri.resolvePackageName = li.getSourcePackage();
                          ri.labelRes = li.getLabelResource();
                          ri.nonLocalizedLabel = li.getNonLocalizedLabel();
                          ri.icon = li.getIconResource();
                          ri.iconResourceId = ri.icon;
                      }
                      if (userManager.isManagedProfile()) {
                          ri.noResourceId = true;
                          ri.icon = 0;
                      }
                      ResolveInfoPresentationGetter getter = makePresentationGetter(ri);
                      mCallerTargets.add(new DisplayResolveInfo(ii, ri,
                              getter.getLabel(), getter.getSubLabel(), ii));
                  }
              }
          }
  
          @Override
          public void handlePackagesChanged() {
              if (DEBUG) {
                  Log.d(TAG, "clearing queryTargets on package change");
              }
              createPlaceHolders();
              mServicesRequested.clear();
              notifyDataSetChanged();
  
              super.handlePackagesChanged();
          }
  
          @Override
          public void notifyDataSetChanged() {
              if (!mListViewDataChanged) {
                  mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
                          LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
                  mListViewDataChanged = true;
              }
          }
  
          private void refreshListView() {
              if (mListViewDataChanged) {
                  super.notifyDataSetChanged();
              }
              mListViewDataChanged = false;
          }
  
  
          private void createPlaceHolders() {
              mNumShortcutResults = 0;
              mServiceTargets.clear();
              for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
                  mServiceTargets.add(mPlaceHolderTargetInfo);
              }
          }
  
          @Override
          public View onCreateView(ViewGroup parent) {
              return mInflater.inflate(
                      com.android.internal.R.layout.resolve_grid_item, parent, false);
          }
  
          @Override
          protected void onBindView(View view, TargetInfo info) {
              super.onBindView(view, info);
  
              // If target is loading, show a special placeholder shape in the label, make unclickable
              final ViewHolder holder = (ViewHolder) view.getTag();
              if (info instanceof PlaceHolderTargetInfo) {
                  final int maxWidth = getResources().getDimensionPixelSize(
                          R.dimen.chooser_direct_share_label_placeholder_max_width);
                  holder.text.setMaxWidth(maxWidth);
                  holder.text.setBackground(getResources().getDrawable(
                          R.drawable.chooser_direct_share_label_placeholder, getTheme()));
                  // Prevent rippling by removing background containing ripple
                  holder.itemView.setBackground(null);
              } else {
                  holder.text.setMaxWidth(Integer.MAX_VALUE);
                  holder.text.setBackground(null);
                  holder.itemView.setBackground(holder.defaultItemViewBackground);
              }
          }
  
          @Override
          public void onListRebuilt() {
              updateAlphabeticalList();
  
              // don't support direct share on low ram devices
              if (ActivityManager.isLowRamDeviceStatic()) {
                  return;
              }
  
              if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
                          || USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
                  if (DEBUG) {
                      Log.d(TAG, "querying direct share targets from ShortcutManager");
                  }
  
                  queryDirectShareTargets(this, false);
              }
              if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
                  if (DEBUG) {
                      Log.d(TAG, "List built querying services");
                  }
  
                  queryTargetServices(this);
              }
          }
  
          @Override
          public boolean shouldGetResolvedFilter() {
              return true;
          }
  
          @Override
          public int getCount() {
              return getRankedTargetCount() + getAlphaTargetCount()
                      + getSelectableServiceTargetCount() + getCallerTargetCount();
          }
  
          @Override
          public int getUnfilteredCount() {
              int appTargets = super.getUnfilteredCount();
              if (appTargets > getMaxRankedTargets()) {
                  appTargets = appTargets + getMaxRankedTargets();
              }
              return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
          }
  
  
          public int getCallerTargetCount() {
              return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
          }
  
          /**
            * Filter out placeholders and non-selectable service targets
            */
          public int getSelectableServiceTargetCount() {
              int count = 0;
              for (ChooserTargetInfo info : mServiceTargets) {
                  if (info instanceof SelectableTargetInfo) {
                      count++;
                  }
              }
              return count;
          }
  
          public int getServiceTargetCount() {
              if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
                  return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
              }
  
              return 0;
          }
  
          int getAlphaTargetCount() {
              int standardCount = super.getCount();
              return standardCount > getMaxRankedTargets() ? standardCount : 0;
          }
  
          int getRankedTargetCount() {
              int spacesAvailable = getMaxRankedTargets() - getCallerTargetCount();
              return Math.min(spacesAvailable, super.getCount());
          }
  
          private int getMaxRankedTargets() {
              return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
          }
  
          public int getPositionTargetType(int position) {
              int offset = 0;
  
              final int serviceTargetCount = getServiceTargetCount();
              if (position < serviceTargetCount) {
                  return TARGET_SERVICE;
              }
              offset += serviceTargetCount;
  
              final int callerTargetCount = getCallerTargetCount();
              if (position - offset < callerTargetCount) {
                  return TARGET_CALLER;
              }
              offset += callerTargetCount;
  
              final int rankedTargetCount = getRankedTargetCount();
              if (position - offset < rankedTargetCount) {
                  return TARGET_STANDARD;
              }
              offset += rankedTargetCount;
  
              final int standardTargetCount = getAlphaTargetCount();
              if (position - offset < standardTargetCount) {
                  return TARGET_STANDARD_AZ;
              }
  
              return TARGET_BAD;
          }
  
          @Override
          public TargetInfo getItem(int position) {
              return targetInfoForPosition(position, true);
          }
  
  
          /**
           * Find target info for a given position.
           * Since ChooserActivity displays several sections of content, determine which
           * section provides this item.
           */
          @Override
          public TargetInfo targetInfoForPosition(int position, boolean filtered) {
              int offset = 0;
  
              // Direct share targets
              final int serviceTargetCount = filtered ? getServiceTargetCount() :
                                                 getSelectableServiceTargetCount();
              if (position < serviceTargetCount) {
                  return mServiceTargets.get(position);
              }
              offset += serviceTargetCount;
  
              // Targets provided by calling app
              final int callerTargetCount = getCallerTargetCount();
              if (position - offset < callerTargetCount) {
                  return mCallerTargets.get(position - offset);
              }
              offset += callerTargetCount;
  
              // Ranked standard app targets
              final int rankedTargetCount = getRankedTargetCount();
              if (position - offset < rankedTargetCount) {
                  return filtered ? super.getItem(position - offset)
                          : getDisplayResolveInfo(position - offset);
              }
              offset += rankedTargetCount;
  
              // Alphabetical complete app target list.
              if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
                  return mSortedList.get(position - offset);
              }
  
              return null;
          }
  
  
          /**
           * Evaluate targets for inclusion in the direct share area. May not be included
           * if score is too low.
           */
          public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
                  @ShareTargetType int targetType) {
              if (DEBUG) {
                  Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
                          + " targets");
              }
  
              if (targets.size() == 0) {
                  return;
              }
  
              final float baseScore = getBaseScore(origTarget, targetType);
              Collections.sort(targets, mBaseTargetComparator);
  
              final boolean isShortcutResult =
                      (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
                              || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
              final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
                                         : MAX_CHOOSER_TARGETS_PER_APP;
              float lastScore = 0;
              boolean shouldNotify = false;
              for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
                  final ChooserTarget target = targets.get(i);
                  float targetScore = target.getScore();
                  targetScore *= baseScore;
                  if (i > 0 && targetScore >= lastScore) {
                      // Apply a decay so that the top app can't crowd out everything else.
                      // This incents ChooserTargetServices to define what's truly better.
                      targetScore = lastScore * 0.95f;
                  }
                  boolean isInserted = insertServiceTarget(
                          new SelectableTargetInfo(origTarget, target, targetScore));
  
                  if (isInserted && isShortcutResult) {
                      mNumShortcutResults++;
                  }
  
                  shouldNotify |= isInserted;
  
                  if (DEBUG) {
                      Log.d(TAG, " => " + target.toString() + " score=" + targetScore
                              + " base=" + target.getScore()
                              + " lastScore=" + lastScore
                              + " baseScore=" + baseScore);
                  }
  
                  lastScore = targetScore;
              }
  
              if (shouldNotify) {
                  notifyDataSetChanged();
              }
          }
  
          private int getNumShortcutResults() {
              return mNumShortcutResults;
          }
  
          /**
            * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
            * <ol>
            *   <li>App-supplied targets
            *   <li>Shortcuts ranked via App Prediction Manager
            *   <li>Shortcuts ranked via legacy heuristics
            *   <li>Legacy direct share targets
            * </ol>
            */
          public float getBaseScore(DisplayResolveInfo target, @ShareTargetType int targetType) {
              if (target == null) {
                  return CALLER_TARGET_SCORE_BOOST;
              }
  
              if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
                  return SHORTCUT_TARGET_SCORE_BOOST;
              }
  
              float score = super.getScore(target);
              if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
                  return score * SHORTCUT_TARGET_SCORE_BOOST;
              }
  
              return score;
          }
  
          /**
           * Calling this marks service target loading complete, and will attempt to no longer
           * update the direct share area.
           */
          public void completeServiceTargetLoading() {
              mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
  
              if (mServiceTargets.isEmpty()) {
                  mServiceTargets.add(new EmptyTargetInfo());
              }
              notifyDataSetChanged();
          }
  
          private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
              // Avoid inserting any potentially late results
              if (mServiceTargets.size() == 1
                      && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
                  return false;
              }
  
              // Check for duplicates and abort if found
              for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
                  if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
                      return false;
                  }
              }
  
              int currentSize = mServiceTargets.size();
              final float newScore = chooserTargetInfo.getModifiedScore();
              for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
                  final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
                  if (serviceTarget == null) {
                      mServiceTargets.set(i, chooserTargetInfo);
                      return true;
                  } else if (newScore > serviceTarget.getModifiedScore()) {
                      mServiceTargets.add(i, chooserTargetInfo);
                      return true;
                  }
              }
  
              if (currentSize < MAX_SERVICE_TARGETS) {
                  mServiceTargets.add(chooserTargetInfo);
                  return true;
              }
  
              return false;
          }
      }
  
      static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
          @Override
          public int compare(ChooserTarget lhs, ChooserTarget rhs) {
              // Descending order
              return (int) Math.signum(rhs.getScore() - lhs.getScore());
          }
      }
  
  
      private boolean isSendAction(Intent targetIntent) {
          if (targetIntent == null) {
              return false;
          }
  
          String action = targetIntent.getAction();
          if (action == null) {
              return false;
          }
  
          if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
              return true;
          }
  
          return false;
      }
  
      class ChooserRowAdapter extends BaseAdapter {
          private ChooserListAdapter mChooserListAdapter;
          private final LayoutInflater mLayoutInflater;
  
          private DirectShareViewHolder mDirectShareViewHolder;
          private int mChooserTargetWidth = 0;
          private boolean mShowAzLabelIfPoss;
  
          private boolean mHideContentPreview = false;
          private boolean mLayoutRequested = false;
  
          private static final int VIEW_TYPE_DIRECT_SHARE = 0;
          private static final int VIEW_TYPE_NORMAL = 1;
          private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
          private static final int VIEW_TYPE_PROFILE = 3;
          private static final int VIEW_TYPE_AZ_LABEL = 4;
  
          private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
          private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
  
          private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
  
          public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
              mChooserListAdapter = wrappedAdapter;
              mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
  
              mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
  
              wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
                  @Override
                  public void onChanged() {
                      super.onChanged();
                      notifyDataSetChanged();
                  }
  
                  @Override
                  public void onInvalidated() {
                      super.onInvalidated();
                      notifyDataSetInvalidated();
                  }
              });
          }
  
          /**
           * Calculate the chooser target width to maximize space per item
           *
           * @param width The new row width to use for recalculation
           * @return true if the view width has changed
           */
          public boolean calculateChooserTargetWidth(int width) {
              if (width == 0) {
                  return false;
              }
  
              int newWidth =  width / getMaxTargetsPerRow();
              if (newWidth != mChooserTargetWidth) {
                  mChooserTargetWidth = newWidth;
                  return true;
              }
  
              return false;
          }
  
          private int getMaxTargetsPerRow() {
              int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
              if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
                  maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
              }
  
              return maxTargets;
          }
  
          public void hideContentPreview() {
              mHideContentPreview = true;
              mLayoutRequested = true;
              notifyDataSetChanged();
          }
  
          public boolean consumeLayoutRequest() {
              boolean oldValue = mLayoutRequested;
              mLayoutRequested = false;
              return oldValue;
          }
  
          @Override
          public boolean areAllItemsEnabled() {
              return false;
          }
  
          @Override
          public boolean isEnabled(int position) {
              int viewType = getItemViewType(position);
              if (viewType == VIEW_TYPE_CONTENT_PREVIEW || viewType == VIEW_TYPE_AZ_LABEL) {
                  return false;
              }
              return true;
          }
  
          @Override
          public int getCount() {
              return (int) (
                      getContentPreviewRowCount()
                              + getProfileRowCount()
                              + getServiceTargetRowCount()
                              + getCallerAndRankedTargetRowCount()
                              + getAzLabelRowCount()
                              + Math.ceil(
                              (float) mChooserListAdapter.getAlphaTargetCount()
                                      / getMaxTargetsPerRow())
              );
          }
  
          public int getContentPreviewRowCount() {
              if (!isSendAction(getTargetIntent())) {
                  return 0;
              }
  
              if (mHideContentPreview || mChooserListAdapter == null
                      || mChooserListAdapter.getCount() == 0) {
                  return 0;
              }
  
              return 1;
          }
  
          public int getProfileRowCount() {
              return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
          }
  
          public int getCallerAndRankedTargetRowCount() {
              return (int) Math.ceil(
                      ((float) mChooserListAdapter.getCallerTargetCount()
                              + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
          }
  
          // There can be at most one row in the listview, that is internally
          // a ViewGroup with 2 rows
          public int getServiceTargetRowCount() {
              if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
                  return 1;
              }
              return 0;
          }
  
          public int getAzLabelRowCount() {
              // Only show a label if the a-z list is showing
              return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
          }
  
          @Override
          public Object getItem(int position) {
              // We have nothing useful to return here.
              return position;
          }
  
          @Override
          public long getItemId(int position) {
              return position;
          }
  
          @Override
          public View getView(int position, View convertView, ViewGroup parent) {
              final RowViewHolder holder;
              int viewType = getItemViewType(position);
  
              if (viewType == VIEW_TYPE_CONTENT_PREVIEW) {
                  return createContentPreviewView(convertView, parent);
              }
  
              if (viewType == VIEW_TYPE_PROFILE) {
                  return createProfileView(convertView, parent);
              }
  
              if (viewType == VIEW_TYPE_AZ_LABEL) {
                  return createAzLabelView(parent);
              }
  
              if (convertView == null) {
                  holder = createViewHolder(viewType, parent);
              } else {
                  holder = (RowViewHolder) convertView.getTag();
              }
  
              bindViewHolder(position, holder);
  
              return holder.getViewGroup();
          }
  
          @Override
          public int getItemViewType(int position) {
              int count;
  
              int countSum = (count = getContentPreviewRowCount());
              if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
  
              countSum += (count = getProfileRowCount());
              if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
  
              countSum += (count = getServiceTargetRowCount());
              if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
  
              countSum += (count = getCallerAndRankedTargetRowCount());
              if (count > 0 && position < countSum) return VIEW_TYPE_NORMAL;
  
              countSum += (count = getAzLabelRowCount());
              if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
  
              return VIEW_TYPE_NORMAL;
          }
  
          @Override
          public int getViewTypeCount() {
              return 5;
          }
  
          private ViewGroup createContentPreviewView(View convertView, ViewGroup parent) {
              Intent targetIntent = getTargetIntent();
              int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
  
              if (convertView == null) {
                  getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
                          .setSubtype(previewType));
              }
  
              return displayContentPreview(previewType, targetIntent, mLayoutInflater,
                      (ViewGroup) convertView, parent);
          }
  
          private View createProfileView(View convertView, ViewGroup parent) {
              View profileRow = convertView != null ? convertView : mLayoutInflater.inflate(
                      R.layout.chooser_profile_row, parent, false);
              profileRow.setBackground(
                      getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
              mProfileView = profileRow.findViewById(R.id.profile_button);
              mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
              bindProfileView();
              return profileRow;
          }
  
          private View createAzLabelView(ViewGroup parent) {
              return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
          }
  
          private RowViewHolder loadViewsIntoRow(RowViewHolder holder) {
              final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
              final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
                      MeasureSpec.EXACTLY);
              int columnCount = holder.getColumnCount();
  
              final boolean isDirectShare = holder instanceof DirectShareViewHolder;
  
              for (int i = 0; i < columnCount; i++) {
                  final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
                  final int column = i;
                  v.setOnClickListener(new OnClickListener() {
                      @Override
                      public void onClick(View v) {
                          startSelected(holder.getItemIndex(column), false, true);
                      }
                  });
                  v.setOnLongClickListener(new OnLongClickListener() {
                      @Override
                      public boolean onLongClick(View v) {
                          showTargetDetails(
                                  mChooserListAdapter.resolveInfoForPosition(
                                          holder.getItemIndex(column), true));
                          return true;
                      }
                  });
                  ViewGroup row = holder.addView(i, v);
  
                  // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
                  // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
                  // done before measuring.
                  if (isDirectShare) {
                      final ViewHolder vh = (ViewHolder) v.getTag();
                      vh.text.setLines(2);
                      vh.text.setHorizontallyScrolling(false);
                      vh.text2.setVisibility(View.GONE);
                  }
  
                  // Force height to be a given so we don't have visual disruption during scaling.
                  v.measure(exactSpec, spec);
                  setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
              }
  
              final ViewGroup viewGroup = holder.getViewGroup();
  
              // Pre-measure and fix height so we can scale later.
              holder.measure();
              setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
  
              if (isDirectShare) {
                  DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
                  setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
                  setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
              }
  
              viewGroup.setTag(holder);
  
              return holder;
          }
  
          private void setViewBounds(View view, int widthPx, int heightPx) {
              LayoutParams lp = view.getLayoutParams();
              if (lp == null) {
                  lp = new LayoutParams(widthPx, heightPx);
                  view.setLayoutParams(lp);
              } else {
                  lp.height = heightPx;
                  lp.width = widthPx;
              }
          }
  
          RowViewHolder createViewHolder(int viewType, ViewGroup parent) {
              if (viewType == VIEW_TYPE_DIRECT_SHARE) {
                  ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
                          R.layout.chooser_row_direct_share, parent, false);
                  ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
                          parentGroup, false);
                  ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
                          parentGroup, false);
                  parentGroup.addView(row1);
                  parentGroup.addView(row2);
  
                  mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
                          Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
                  loadViewsIntoRow(mDirectShareViewHolder);
  
                  return mDirectShareViewHolder;
              } else {
                  ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
                          false);
                  RowViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
                  loadViewsIntoRow(holder);
  
                  return holder;
              }
          }
  
          /**
           * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
           * showing on top of the AZ list if the AZ label is visible. All other types are placed into
           * their own row as determined by their target type, and dividers are added in the list to
           * separate each type.
           */
          int getRowType(int rowPosition) {
              // Merge caller and ranked standard into a single row
              int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
              if (positionType == ChooserListAdapter.TARGET_CALLER) {
                  return ChooserListAdapter.TARGET_STANDARD;
              }
  
              // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
              // row type the same as the suggestion row type
              if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
                  return ChooserListAdapter.TARGET_STANDARD;
              }
  
              return positionType;
          }
  
          void bindViewHolder(int rowPosition, RowViewHolder holder) {
              final int start = getFirstRowPosition(rowPosition);
              final int startType = getRowType(start);
              final int lastStartType = getRowType(getFirstRowPosition(rowPosition - 1));
  
              final ViewGroup row = holder.getViewGroup();
  
              if (startType != lastStartType
                      || rowPosition == getContentPreviewRowCount() + getProfileRowCount()) {
                  row.setForeground(
                          getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
              } else {
                  row.setForeground(null);
              }
  
              int columnCount = holder.getColumnCount();
              int end = start + columnCount - 1;
              while (getRowType(end) != startType && end >= start) {
                  end--;
              }
  
              if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
                  final TextView textView = row.findViewById(R.id.chooser_row_text_option);
  
                  if (textView.getVisibility() != View.VISIBLE) {
                      textView.setAlpha(0.0f);
                      textView.setVisibility(View.VISIBLE);
                      textView.setText(R.string.chooser_no_direct_share_targets);
  
                      ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
                      fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
  
                      float translationInPx = getResources().getDimensionPixelSize(
                              R.dimen.chooser_row_text_option_translate);
                      textView.setTranslationY(translationInPx);
                      ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
                              0.0f);
                      translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
  
                      AnimatorSet animSet = new AnimatorSet();
                      animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
                      animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
                      animSet.playTogether(fadeAnim, translateAnim);
                      animSet.start();
                  }
              }
  
              for (int i = 0; i < columnCount; i++) {
                  final View v = holder.getView(i);
                  if (start + i <= end) {
                      holder.setViewVisibility(i, View.VISIBLE);
                      holder.setItemIndex(i, start + i);
                      mChooserListAdapter.bindView(holder.getItemIndex(i), v);
                  } else {
                      holder.setViewVisibility(i, View.INVISIBLE);
                  }
              }
          }
  
          int getFirstRowPosition(int row) {
              row -= getContentPreviewRowCount() + getProfileRowCount();
  
              final int serviceCount = mChooserListAdapter.getServiceTargetCount();
              final int serviceRows = (int) Math.ceil((float) serviceCount
                      / ChooserListAdapter.MAX_SERVICE_TARGETS);
              if (row < serviceRows) {
                  return row * getMaxTargetsPerRow();
              }
  
              final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
                                                   + mChooserListAdapter.getRankedTargetCount();
              final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
              if (row < callerAndRankedRows + serviceRows) {
                  return serviceCount + (row - serviceRows) * getMaxTargetsPerRow();
              }
  
              row -= getAzLabelRowCount();
  
              return callerAndRankedCount + serviceCount
                      + (row - callerAndRankedRows - serviceRows) * getMaxTargetsPerRow();
          }
  
          public void handleScroll(View v, int y, int oldy) {
              // Only expand direct share area if there is a minimum number of shortcuts,
              // which will help reduce the amount of visible shuffling due to older-style
              // direct share targets.
              int orientation = getResources().getConfiguration().orientation;
              boolean canExpandDirectShare =
                      mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
                      && orientation == Configuration.ORIENTATION_PORTRAIT
                      && !isInMultiWindowMode();
  
              if (mDirectShareViewHolder != null && canExpandDirectShare) {
                  mDirectShareViewHolder.handleScroll(mAdapterView, y, oldy, getMaxTargetsPerRow());
              }
          }
      }
  
      abstract class RowViewHolder {
          protected int mMeasuredRowHeight;
          private int[] mItemIndices;
          protected final View[] mCells;
          private final int mColumnCount;
  
          RowViewHolder(int cellCount) {
              this.mCells = new View[cellCount];
              this.mItemIndices = new int[cellCount];
              this.mColumnCount = cellCount;
          }
  
          abstract ViewGroup addView(int index, View v);
  
          abstract ViewGroup getViewGroup();
  
          abstract ViewGroup getRowByIndex(int index);
  
          abstract ViewGroup getRow(int rowNumber);
  
          abstract void setViewVisibility(int i, int visibility);
  
          public int getColumnCount() {
              return mColumnCount;
          }
  
          public void measure() {
              final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
              getViewGroup().measure(spec, spec);
              mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
          }
  
          public int getMeasuredRowHeight() {
              return mMeasuredRowHeight;
          }
  
          public void setItemIndex(int itemIndex, int listIndex) {
              mItemIndices[itemIndex] = listIndex;
          }
  
          public int getItemIndex(int itemIndex) {
              return mItemIndices[itemIndex];
          }
  
          public View getView(int index) {
              return mCells[index];
          }
      }
  
      class SingleRowViewHolder extends RowViewHolder {
          private final ViewGroup mRow;
  
          SingleRowViewHolder(ViewGroup row, int cellCount) {
              super(cellCount);
  
              this.mRow = row;
          }
  
          public ViewGroup getViewGroup() {
              return mRow;
          }
  
          public ViewGroup getRowByIndex(int index) {
              return mRow;
          }
  
          public ViewGroup getRow(int rowNumber) {
              if (rowNumber == 0) return mRow;
              return null;
          }
  
          public ViewGroup addView(int index, View v) {
              mRow.addView(v);
              mCells[index] = v;
  
              return mRow;
          }
  
          public void setViewVisibility(int i, int visibility) {
              getView(i).setVisibility(visibility);
          }
      }
  
      class DirectShareViewHolder extends RowViewHolder {
          private final ViewGroup mParent;
          private final List<ViewGroup> mRows;
          private int mCellCountPerRow;
  
          private boolean mHideDirectShareExpansion = false;
          private int mDirectShareMinHeight = 0;
          private int mDirectShareCurrHeight = 0;
          private int mDirectShareMaxHeight = 0;
  
          private final boolean[] mCellVisibility;
  
          DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
              super(rows.size() * cellCountPerRow);
  
              this.mParent = parent;
              this.mRows = rows;
              this.mCellCountPerRow = cellCountPerRow;
              this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
          }
  
          public ViewGroup addView(int index, View v) {
              ViewGroup row = getRowByIndex(index);
              row.addView(v);
              mCells[index] = v;
  
              return row;
          }
  
          public ViewGroup getViewGroup() {
              return mParent;
          }
  
          public ViewGroup getRowByIndex(int index) {
              return mRows.get(index / mCellCountPerRow);
          }
  
          public ViewGroup getRow(int rowNumber) {
              return mRows.get(rowNumber);
          }
  
          public void measure() {
              final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
              getRow(0).measure(spec, spec);
              getRow(1).measure(spec, spec);
  
              mDirectShareMinHeight = getRow(0).getMeasuredHeight();
              mDirectShareCurrHeight = mDirectShareCurrHeight > 0
                                           ? mDirectShareCurrHeight : mDirectShareMinHeight;
              mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
          }
  
          public int getMeasuredRowHeight() {
              return mDirectShareCurrHeight;
          }
  
          public int getMinRowHeight() {
              return mDirectShareMinHeight;
          }
  
          public void setViewVisibility(int i, int visibility) {
              final View v = getView(i);
              if (visibility == View.VISIBLE) {
                  mCellVisibility[i] = true;
                  v.setVisibility(visibility);
                  v.setAlpha(1.0f);
              } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
                  mCellVisibility[i] = false;
  
                  ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
                  fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
                  fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
                  fadeAnim.addListener(new AnimatorListenerAdapter() {
                      public void onAnimationEnd(Animator animation) {
                          v.setVisibility(View.INVISIBLE);
                      }
                  });
                  fadeAnim.start();
              }
          }
  
          public void handleScroll(AbsListView view, int y, int oldy, int maxTargetsPerRow) {
              // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
              // targets can lock us into an expanded mode
              boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
              if (notExpanded) {
                  if (mHideDirectShareExpansion) {
                      return;
                  }
  
                  // only expand if we have more than maxTargetsPerRow, and delay that decision
                  // until they start to scroll
                  if (mChooserListAdapter.getSelectableServiceTargetCount() <= maxTargetsPerRow) {
                      mHideDirectShareExpansion = true;
                      return;
                  }
              }
  
              int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
  
              int prevHeight = mDirectShareCurrHeight;
              int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
              newHeight = Math.max(newHeight, mDirectShareMinHeight);
              yDiff = newHeight - prevHeight;
  
              if (view == null || view.getChildCount() == 0 || yDiff == 0) {
                  return;
              }
  
              // locate the item to expand, and offset the rows below that one
              boolean foundExpansion = false;
              for (int i = 0; i < view.getChildCount(); i++) {
                  View child = view.getChildAt(i);
  
                  if (foundExpansion) {
                      child.offsetTopAndBottom(yDiff);
                  } else {
                      if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
                          int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
                                  MeasureSpec.EXACTLY);
                          int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
                                  MeasureSpec.EXACTLY);
                          child.measure(widthSpec, heightSpec);
                          child.getLayoutParams().height = child.getMeasuredHeight();
                          child.layout(child.getLeft(), child.getTop(), child.getRight(),
                                  child.getTop() + child.getMeasuredHeight());
  
                          foundExpansion = true;
                      }
                  }
              }
  
              if (foundExpansion) {
                  mDirectShareCurrHeight = newHeight;
              }
          }
      }
  
      static class ChooserTargetServiceConnection implements ServiceConnection {
          private DisplayResolveInfo mOriginalTarget;
          private ComponentName mConnectedComponent;
          private ChooserActivity mChooserActivity;
          private final Object mLock = new Object();
  
          private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
              @Override
              public void sendResult(List<ChooserTarget> targets) throws RemoteException {
                  synchronized (mLock) {
                      if (mChooserActivity == null) {
                          Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
                                  + mConnectedComponent + "; ignoring...");
                          return;
                      }
                      mChooserActivity.filterServiceTargets(
                              mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
                      final Message msg = Message.obtain();
                      msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
                      msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
                              ChooserTargetServiceConnection.this);
                      mChooserActivity.mChooserHandler.sendMessage(msg);
                  }
              }
          };
  
          public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
                  DisplayResolveInfo dri) {
              mChooserActivity = chooserActivity;
              mOriginalTarget = dri;
          }
  
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
              synchronized (mLock) {
                  if (mChooserActivity == null) {
                      Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
                      return;
                  }
  
                  final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
                  try {
                      icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
                              mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
                  } catch (RemoteException e) {
                      Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
                      mChooserActivity.unbindService(this);
                      mChooserActivity.mServiceConnections.remove(this);
                      destroy();
                  }
              }
          }
  
          @Override
          public void onServiceDisconnected(ComponentName name) {
              if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
              synchronized (mLock) {
                  if (mChooserActivity == null) {
                      Log.e(TAG,
                              "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
                      return;
                  }
  
                  mChooserActivity.unbindService(this);
                  mChooserActivity.mServiceConnections.remove(this);
                  if (mChooserActivity.mServiceConnections.isEmpty()) {
                      mChooserActivity.sendVoiceChoicesIfNeeded();
                  }
                  mConnectedComponent = null;
                  destroy();
              }
          }
  
          public void destroy() {
              synchronized (mLock) {
                  mChooserActivity = null;
                  mOriginalTarget = null;
              }
          }
  
          @Override
          public String toString() {
              return "ChooserTargetServiceConnection{service="
                      + mConnectedComponent + ", activity="
                      + (mOriginalTarget != null
                      ? mOriginalTarget.getResolveInfo().activityInfo.toString()
                      : "<connection destroyed>") + "}";
          }
      }
  
      static class ServiceResultInfo {
          public final DisplayResolveInfo originalTarget;
          public final List<ChooserTarget> resultTargets;
          public final ChooserTargetServiceConnection connection;
  
          public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
                  ChooserTargetServiceConnection c) {
              originalTarget = ot;
              resultTargets = rt;
              connection = c;
          }
      }
  
      static class RefinementResultReceiver extends ResultReceiver {
          private ChooserActivity mChooserActivity;
          private TargetInfo mSelectedTarget;
  
          public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
                  Handler handler) {
              super(handler);
              mChooserActivity = host;
              mSelectedTarget = target;
          }
  
          @Override
          protected void onReceiveResult(int resultCode, Bundle resultData) {
              if (mChooserActivity == null) {
                  Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
                  return;
              }
              if (resultData == null) {
                  Log.e(TAG, "RefinementResultReceiver received null resultData");
                  return;
              }
  
              switch (resultCode) {
                  case RESULT_CANCELED:
                      mChooserActivity.onRefinementCanceled();
                      break;
                  case RESULT_OK:
                      Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
                      if (intentParcelable instanceof Intent) {
                          mChooserActivity.onRefinementResult(mSelectedTarget,
                                  (Intent) intentParcelable);
                      } else {
                          Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
                                  + " in resultData with key Intent.EXTRA_INTENT");
                      }
                      break;
                  default:
                      Log.w(TAG, "Unknown result code " + resultCode
                              + " sent to RefinementResultReceiver");
                      break;
              }
          }
  
          public void destroy() {
              mChooserActivity = null;
              mSelectedTarget = null;
          }
      }
  
      /**
       * Used internally to round image corners while obeying view padding.
       */
      public static class RoundedRectImageView extends ImageView {
          private int mRadius = 0;
          private Path mPath = new Path();
          private Paint mOverlayPaint = new Paint(0);
          private Paint mRoundRectPaint = new Paint(0);
          private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
          private String mExtraImageCount = null;
  
          public RoundedRectImageView(Context context) {
              super(context);
          }
  
          public RoundedRectImageView(Context context, AttributeSet attrs) {
              this(context, attrs, 0);
          }
  
          public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
              this(context, attrs, defStyleAttr, 0);
          }
  
          public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
                  int defStyleRes) {
              super(context, attrs, defStyleAttr, defStyleRes);
              mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
  
              mOverlayPaint.setColor(0x99000000);
              mOverlayPaint.setStyle(Paint.Style.FILL);
  
              mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
              mRoundRectPaint.setStyle(Paint.Style.STROKE);
              mRoundRectPaint.setStrokeWidth(context.getResources()
                      .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
  
              mTextPaint.setColor(Color.WHITE);
              mTextPaint.setTextSize(context.getResources()
                      .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
              mTextPaint.setTextAlign(Paint.Align.CENTER);
          }
  
          private void updatePath(int width, int height) {
              mPath.reset();
  
              int imageWidth = width - getPaddingRight() - getPaddingLeft();
              int imageHeight = height - getPaddingBottom() - getPaddingTop();
              mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
                      mRadius, Path.Direction.CW);
          }
  
          /**
            * Sets the corner radius on all corners
            *
            * param radius 0 for no radius, &gt; 0 for a visible corner radius
            */
          public void setRadius(int radius) {
              mRadius = radius;
              updatePath(getWidth(), getHeight());
          }
  
          /**
            * Display an overlay with extra image count on 3rd image
            */
          public void setExtraImageCount(int count) {
              if (count > 0) {
                  this.mExtraImageCount = "+" + count;
              } else {
                  this.mExtraImageCount = null;
              }
          }
  
          @Override
          protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
              super.onSizeChanged(width, height, oldWidth, oldHeight);
              updatePath(width, height);
          }
  
          @Override
          protected void onDraw(Canvas canvas) {
              if (mRadius != 0) {
                  canvas.clipPath(mPath);
              }
  
              super.onDraw(canvas);
  
              int x = getPaddingLeft();
              int y = getPaddingRight();
              int width = getWidth() - getPaddingRight() - getPaddingLeft();
              int height = getHeight() - getPaddingBottom() - getPaddingTop();
              if (mExtraImageCount != null) {
                  canvas.drawRect(x, y, width, height, mOverlayPaint);
  
                  int xPos = canvas.getWidth() / 2;
                  int yPos = (int) ((canvas.getHeight() / 2.0f)
                          - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
  
                  canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
              }
  
              canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
          }
      }
  }
  