/*
 * 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 android.content.ContentProvider.getUserIdFromUri;

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.app.prediction.AppTargetId;
import android.compat.annotation.UnsupportedAppUsage;
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.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.Drawable;
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.PatternMatcher;
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.TextUtils;
import android.util.AttributeSet;
import android.util.HashedStringCache;
import android.util.Log;
import android.util.Pair;
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.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Space;
import android.widget.TextView;
import android.widget.Toast;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
import com.android.internal.app.ResolverListAdapter.ViewHolder;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.MultiDisplayResolveInfo;
import com.android.internal.app.chooser.NotSelectableTargetInfo;
import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
import com.android.internal.app.chooser.TargetInfo;
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.FrameworkStatsLog;
import com.android.internal.widget.GridLayoutManager;
import com.android.internal.widget.RecyclerView;
import com.android.internal.widget.ResolverDrawerLayout;
import com.android.internal.widget.ViewPager;

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.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;
import java.util.stream.Collectors;

/**
 * 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 implements
        ChooserListAdapter.ChooserListCommunicator,
        SelectableTargetInfoCommunicator {
    private static final String TAG = "ChooserActivity";
    private AppPredictor mPersonalAppPredictor;
    private AppPredictor mWorkAppPredictor;
    private boolean mShouldDisplayLandscape;

    @UnsupportedAppUsage
    public 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 = 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_LOCATION_DIRECT_SHARE = "direct_share";
    public static final String CHOOSER_TARGET = "chooser_target";
    private static final String SHORTCUT_TARGET = "shortcut_target";
    private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
    public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";

    @VisibleForTesting
    public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;

    private boolean mIsAppPredictorComponentAvailable;
    private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
    private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
    private Map<ComponentName, ComponentName> mChooserTargetComponentNameCache;

    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;

    public static final int SELECTION_TYPE_SERVICE = 1;
    public static final int SELECTION_TYPE_APP = 2;
    public static final int SELECTION_TYPE_STANDARD = 3;
    public static final int SELECTION_TYPE_COPY = 4;

    private static final int SCROLL_STATUS_IDLE = 0;
    private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
    private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;

    // statsd logger wrapper
    protected ChooserActivityLogger mChooserActivityLogger;

    private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;

    @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 boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
            DeviceConfig.NAMESPACE_SYSTEMUI,
            SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
            true);
    private boolean mChooserTargetRankingEnabled = DeviceConfig.getBoolean(
            DeviceConfig.NAMESPACE_SYSTEMUI,
            SystemUiDeviceConfigFlags.CHOOSER_TARGET_RANKING_ENABLED,
            true);

    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 int mChooserRowServiceSpacing;

    private int mCurrAvailableWidth = 0;
    private int mLastNumberOfChildren = -1;

    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<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>();

    private static final int MAX_LOG_RANK_POSITION = 12;

    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";

    @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.
    protected static final int CONTENT_PREVIEW_IMAGE = 1;
    protected static final int CONTENT_PREVIEW_FILE = 2;
    protected static final int CONTENT_PREVIEW_TEXT = 3;
    protected MetricsLogger mMetricsLogger;

    private ContentPreviewCoordinator mPreviewCoord;
    private int mScrollStatus = SCROLL_STATUS_IDLE;

    @VisibleForTesting
    protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;

    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(() -> {
                int size = getResources().getDimensionPixelSize(
                        R.dimen.chooser_preview_image_max_dimen);
                final Bitmap bmp = loadThumbnail(uri, new Size(size, size));
                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 (shouldShowTabs()) {
                    hideStickyContentPreview();
                } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
                    mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().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 CHOOSER_TARGET_RANKING_SCORE = 7;

        private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
        private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;

        private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500;
        private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT,
                DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS);

        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);
            removeMessages(CHOOSER_TARGET_RANKING_SCORE);
        }

        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 "
                        + mDirectShareTimeout + "-"
                        + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
            }

            sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
                    WATCHDOG_TIMEOUT_MIN_MILLIS);
            sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
                    mAppendDirectShareEnabled ? mDirectShareTimeout : 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();
                mChooserMultiProfilePagerAdapter.getActiveListAdapter()
                        .completeServiceTargetLoading();
            }
        }

        @Override
        public void handleMessage(Message msg) {
            if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == 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
                                + sri.originalTarget.getResolveInfo().activityInfo.packageName
                                + " returned after being removed from active connections."
                                + " Have you considered returning results faster?");
                        break;
                    }
                    if (sri.resultTargets != null) {
                        ChooserListAdapter adapterForUserHandle =
                                mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
                                        sri.userHandle);
                        if (adapterForUserHandle != null) {
                            adapterForUserHandle.addServiceResults(sri.originalTarget,
                                    sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET,
                                    /* directShareShortcutInfoCache */ null, mServiceConnections);
                            if (!sri.resultTargets.isEmpty() && sri.originalTarget != null) {
                                mChooserTargetComponentNameCache.put(
                                        sri.resultTargets.get(0).getComponentName(),
                                        sri.originalTarget.getResolvedComponentName());
                            }
                        }
                    }
                    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:
                    mMinTimeoutPassed = true;
                    if (!mServiceConnections.isEmpty()) {
                        getChooserActivityLogger().logSharesheetDirectLoadTimeout();
                    }
                    unbindRemainingServices();
                    maybeStopServiceRequestTimer();
                    break;

                case LIST_VIEW_UPDATE_MESSAGE:
                    if (DEBUG) {
                        Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
                    }

                    UserHandle userHandle = (UserHandle) msg.obj;
                    mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle)
                            .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) {
                        ChooserListAdapter adapterForUserHandle =
                                mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
                                        resultInfo.userHandle);
                        if (adapterForUserHandle != null) {
                            adapterForUserHandle.addServiceResults(
                                    resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1,
                                    mDirectShareShortcutInfoCache, mServiceConnections);
                        }
                    }
                    break;

                case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
                    logDirectShareTargetReceived(
                            MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
                    sendVoiceChoicesIfNeeded();
                    getChooserActivityLogger().logSharesheetDirectLoadComplete();
                    break;

                case CHOOSER_TARGET_RANKING_SCORE:
                    if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_RANKING_SCORE");
                    final ChooserTargetRankingInfo scoreInfo = (ChooserTargetRankingInfo) msg.obj;
                    ChooserListAdapter adapterForUserHandle =
                            mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
                                    scoreInfo.userHandle);
                    if (adapterForUserHandle != null) {
                        adapterForUserHandle.addChooserTargetRankingScore(scoreInfo.scores);
                    }
                    break;

                default:
                    super.handleMessage(msg);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        final long intentReceivedTime = System.currentTimeMillis();
        getChooserActivityLogger().logSharesheetTriggered();
        // 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);


        // Exclude out Nearby from main list if chip is present, to avoid duplication
        ComponentName nearbySharingComponent = getNearbySharingComponent();
        boolean hasNearby = nearbySharingComponent != null;

        if (pa != null) {
            ComponentName[] names = new ComponentName[pa.length + (hasNearby ? 1 : 0)];
            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];
            }
            if (hasNearby) {
                names[names.length - 1] = nearbySharingComponent;
            }

            mFilteredComponentNames = names;
        } else if (hasNearby) {
            mFilteredComponentNames = new ComponentName[1];
            mFilteredComponentNames[0] = nearbySharingComponent;
        }

        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;
        }

        mShouldDisplayLandscape = shouldDisplayLandscape(
                getResources().getConfiguration().orientation);
        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));

        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);
            }

            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;
                            }
                            getChooserActivityLogger()
                                    .logSharesheetExpansionChanged(isCollapsed);
                        }
                    });
        }

        if (DEBUG) {
            Log.d(TAG, "System Time Cost is " + systemCost);
        }

        getChooserActivityLogger().logShareStarted(
                FrameworkStatsLog.SHARESHEET_STARTED,
                getReferrerPackageName(),
                target.getType(),
                initialIntents == null ? 0 : initialIntents.length,
                mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
                isWorkProfile(),
                findPreferredContentPreview(getTargetIntent(), getContentResolver()),
                target.getAction()
        );
        mDirectShareShortcutInfoCache = new HashMap<>();
        mChooserTargetComponentNameCache = new HashMap<>();
    }

    @Override
    protected int appliedThemeResId() {
        return R.style.Theme_DeviceDefault_Chooser;
    }

    private AppPredictor setupAppPredictorForUser(UserHandle userHandle,
            AppPredictor.Callback appPredictorCallback) {
        AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
        if (appPredictor == null) {
            return null;
        }
        mDirectShareAppTargetCache = new HashMap<>();
        appPredictor.registerPredictionUpdates(this.getMainExecutor(), appPredictorCallback);
        return appPredictor;
    }

    private AppPredictor.Callback createAppPredictorCallback(
            ChooserListAdapter chooserListAdapter) {
        return resultList -> {
            if (isFinishing() || isDestroyed()) {
                return;
            }
            if (chooserListAdapter.getCount() == 0) {
                return;
            }
            if (resultList.isEmpty()
                    && shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
                // APS may be disabled, so try querying targets ourselves.
                queryDirectShareTargets(chooserListAdapter, true);
                return;
            }
            final List<DisplayResolveInfo> driList =
                    getDisplayResolveInfos(chooserListAdapter);
            final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
                    new ArrayList<>();

            // Separate ChooserTargets ranking scores and ranked Shortcuts.
            List<AppTarget> shortcutResults = new ArrayList<>();
            List<AppTarget> chooserTargetScores = new ArrayList<>();
            for (AppTarget appTarget : resultList) {
                if (appTarget.getShortcutInfo() == null) {
                    continue;
                }
                if (appTarget.getShortcutInfo().getId().equals(CHOOSER_TARGET)) {
                    chooserTargetScores.add(appTarget);
                } else {
                    shortcutResults.add(appTarget);
                }
            }
            resultList = shortcutResults;
            if (mChooserTargetRankingEnabled) {
                sendChooserTargetRankingScore(chooserTargetScores,
                        chooserListAdapter.getUserHandle());
            }
            for (AppTarget appTarget : resultList) {
                shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
                        appTarget.getShortcutInfo(),
                        new ComponentName(
                                appTarget.getPackageName(), appTarget.getClassName())));
            }
            sendShareShortcutInfoList(shareShortcutInfos, driList, resultList,
                    chooserListAdapter.getUserHandle());
        };
    }

    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);
    }

    @Override
    protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
            Intent[] initialIntents,
            List<ResolveInfo> rList,
            boolean filterLastUsed) {
        if (shouldShowTabs()) {
            mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
                    initialIntents, rList, filterLastUsed);
        } else {
            mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
                    initialIntents, rList, filterLastUsed);
        }
        return mChooserMultiProfilePagerAdapter;
    }

    private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
            Intent[] initialIntents,
            List<ResolveInfo> rList,
            boolean filterLastUsed) {
        ChooserGridAdapter adapter = createChooserGridAdapter(
                /* context */ this,
                /* payloadIntents */ mIntents,
                initialIntents,
                rList,
                filterLastUsed,
                /* userHandle */ UserHandle.of(UserHandle.myUserId()));
        return new ChooserMultiProfilePagerAdapter(
                /* context */ this,
                adapter,
                getPersonalProfileUserHandle(),
                /* workProfileUserHandle= */ null,
                isSendAction(getTargetIntent()));
    }

    private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
            Intent[] initialIntents,
            List<ResolveInfo> rList,
            boolean filterLastUsed) {
        int selectedProfile = findSelectedProfile();
        ChooserGridAdapter personalAdapter = createChooserGridAdapter(
                /* context */ this,
                /* payloadIntents */ mIntents,
                selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
                rList,
                filterLastUsed,
                /* userHandle */ getPersonalProfileUserHandle());
        ChooserGridAdapter workAdapter = createChooserGridAdapter(
                /* context */ this,
                /* payloadIntents */ mIntents,
                selectedProfile == PROFILE_WORK ? initialIntents : null,
                rList,
                filterLastUsed,
                /* userHandle */ getWorkProfileUserHandle());
        return new ChooserMultiProfilePagerAdapter(
                /* context */ this,
                personalAdapter,
                workAdapter,
                selectedProfile,
                getPersonalProfileUserHandle(),
                getWorkProfileUserHandle(),
                isSendAction(getTargetIntent()));
    }

    private int findSelectedProfile() {
        int selectedProfile = getSelectedProfileExtra();
        if (selectedProfile == -1) {
            selectedProfile = getProfileForUser(getUser());
        }
        return selectedProfile;
    }

    @Override
    protected boolean postRebuildList(boolean rebuildCompleted) {
        updateStickyContentPreview();
        if (shouldShowStickyContentPreview()
                || mChooserMultiProfilePagerAdapter
                        .getCurrentRootAdapter().getContentPreviewRowCount() != 0) {
            logActionShareWithPreview();
        }
        return postRebuildListInternal(rebuildCompleted);
    }

    /**
     * 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 getSystemService(UserManager.class)
                .getUserInfo(UserHandle.myUserId()).isManagedProfile();
    }

    @Override
    protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
        return new PackageMonitor() {
            @Override
            public void onSomePackagesChanged() {
                handlePackagesChanged(listAdapter);
            }
        };
    }

    /**
     * Update UI to reflect changes in data.
     */
    public void handlePackagesChanged() {
        handlePackagesChanged(/* listAdapter */ null);
    }

    /**
     * Update UI to reflect changes in data.
     * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if
     * available.
     */
    private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
        // Refresh pinned items
        mPinnedSharedPrefs = getPinnedSharedPrefs(this);
        if (listAdapter == null) {
            mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
            if (mChooserMultiProfilePagerAdapter.getCount() > 1) {
                mChooserMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged();
            }
        } else {
            listAdapter.handlePackagesChanged();
        }
        updateProfileViewButton();
    }

    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);
            getChooserActivityLogger().logShareTargetSelected(
                    SELECTION_TYPE_COPY,
                    "",
                    -1);

            finish();
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ViewPager viewPager = findViewById(R.id.profile_pager);
        if (shouldShowTabs() && viewPager.isLayoutRtl()) {
            mMultiProfilePagerAdapter.setupViewPager(viewPager);
        }

        mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
        adjustPreviewWidth(newConfig.orientation, null);
        updateStickyContentPreview();
    }

    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 (mShouldDisplayLandscape) {
            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 ViewGroup createContentPreviewView(ViewGroup parent) {
        Intent targetIntent = getTargetIntent();
        int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
        return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
    }

    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(originalIntent);
        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, "", resolveIntent, 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(this),
                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 parent) {
        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 (!validForContentPreview(previewThumbnail)) {
                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);

        final ViewGroup actionRow =
                (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
        //TODO: addActionButton(actionRow, createCopyButton());
        addActionButton(actionRow, createNearbyButton(targetIntent));

        mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);

        String action = targetIntent.getAction();
        if (Intent.ACTION_SEND.equals(action)) {
            Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
            if (!validForContentPreview(uri)) {
                contentPreviewLayout.setVisibility(View.GONE);
                return contentPreviewLayout;
            }
            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 (validForContentPreview(uri) && 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);

        final ViewGroup actionRow =
                (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
        //TODO(b/120417119): addActionButton(actionRow, createCopyButton());
        addActionButton(actionRow, createNearbyButton(targetIntent));


        String action = targetIntent.getAction();
        if (Intent.ACTION_SEND.equals(action)) {
            Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
            if (!validForContentPreview(uri)) {
                contentPreviewLayout.setVisibility(View.GONE);
                return contentPreviewLayout;
            }
            loadFileUriIntoView(uri, contentPreviewLayout);
        } else {
            List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
            uris = uris.stream()
                    .filter(ChooserActivity::validForContentPreview)
                    .collect(Collectors.toList());
            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);
        }
    }

    /**
     * Indicate if the incoming content URI should be allowed.
     *
     * @param uri the uri to test
     * @return true if the URI is allowed for content preview
     */
    private static boolean validForContentPreview(Uri uri) throws SecurityException {
        if (uri == null) {
            return false;
        }
        int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT);
        if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) {
            Log.e(TAG, "dropped invalid content URI belonging to user " + userId);
            return false;
        }
        return true;
    }

    @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();

        mChooserMultiProfilePagerAdapter.getActiveListAdapter().destroyAppPredictor();
        if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
            mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
        }
    }

    @Override // ResolverListCommunicator
    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 addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
        if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
            mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
                    /* origTarget */ null,
                    Lists.newArrayList(mCallerChooserTargets),
                    TARGET_TYPE_DEFAULT,
                    /* directShareShortcutInfoCache */ null, mServiceConnections);
        }
    }

    @Override
    public int getLayoutResource() {
        return R.layout.chooser_grid;
    }

    @Override // ResolverListCommunicator
    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);
    }

    private void showTargetDetails(DisplayResolveInfo ti) {
        if (ti == null) return;

        List<DisplayResolveInfo> targetList;

        // For multiple targets, include info on all targets
        if (ti instanceof MultiDisplayResolveInfo) {
            MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
            targetList = mti.getTargets();
        } else {
            targetList = Collections.singletonList(ti);
        }

        ChooserTargetActionsDialogFragment f = new ChooserTargetActionsDialogFragment(
                targetList, mChooserMultiProfilePagerAdapter.getCurrentUserHandle());

        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) {
        ChooserListAdapter currentListAdapter =
                mChooserMultiProfilePagerAdapter.getActiveListAdapter();
        TargetInfo targetInfo = currentListAdapter
                .targetInfoForPosition(which, filtered);
        if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
            return;
        }

        final long selectionCost = System.currentTimeMillis() - mChooserShownTime;

        if (targetInfo instanceof MultiDisplayResolveInfo) {
            MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
            if (!mti.hasSelected()) {
                ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
                        mti, which,
                        mChooserMultiProfilePagerAdapter.getCurrentUserHandle());

                f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
                return;
            }
        }

        super.startSelected(which, always, filtered);


        if (currentListAdapter.getCount() > 0) {
            // 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 (currentListAdapter.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 = currentListAdapter.getChooserTargetForValue(value);
                    directTargetHashed = HashedStringCache.getInstance().hashString(
                            this,
                            TAG,
                            target.getComponentName().getPackageName()
                                    + target.getTitle().toString(),
                            mMaxHashSaltDays);
                    directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);

                    if (mCallerChooserTargets != null) {
                        numCallerProvided = mCallerChooserTargets.length;
                    }
                    getChooserActivityLogger().logShareTargetSelected(
                            SELECTION_TYPE_SERVICE,
                            targetInfo.getResolveInfo().activityInfo.processName,
                            value
                    );
                    break;
                case ChooserListAdapter.TARGET_CALLER:
                case ChooserListAdapter.TARGET_STANDARD:
                    cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
                    value -= currentListAdapter.getSelectableServiceTargetCount();
                    numCallerProvided = currentListAdapter.getCallerTargetCount();
                    getChooserActivityLogger().logShareTargetSelected(
                            SELECTION_TYPE_APP,
                            targetInfo.getResolveInfo().activityInfo.processName,
                            value
                    );
                    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;
                    getChooserActivityLogger().logShareTargetSelected(
                            SELECTION_TYPE_STANDARD,
                            targetInfo.getResolveInfo().activityInfo.processName,
                            value
                    );
                    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();
        ChooserListAdapter currentListAdapter =
                mChooserMultiProfilePagerAdapter.getActiveListAdapter();
        int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
                MAX_LOG_RANK_POSITION);

        for (int i = 0; i < maxRankedResults; i++) {
            if (currentListAdapter.mDisplayList.get(i)
                    .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    protected boolean shouldAddFooterView() {
        // To accommodate for window insets
        return true;
    }

    @Override
    protected void applyFooterView(int height) {
        int count = mChooserMultiProfilePagerAdapter.getItemCount();

        for (int i = 0; i < count; i++) {
            mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
        }
    }

    @VisibleForTesting
    protected void queryTargetServices(ChooserListAdapter adapter) {
        mQueriedTargetServicesTimeMs = System.currentTimeMillis();

        Context selectedProfileContext = createContextAsUser(
                adapter.getUserHandle(), 0 /* flags */);
        final PackageManager pm = selectedProfileContext.getPackageManager();
        ShortcutManager sm = selectedProfileContext.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 (ChooserFlags.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);

                UserHandle userHandle = adapter.getUserHandle();
                Pair<ComponentName, UserHandle> requestedItem =
                        new Pair<>(serviceComponent, userHandle);
                if (mServicesRequested.contains(requestedItem)) {
                    continue;
                }
                mServicesRequested.add(requestedItem);

                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,
                                adapter.getUserHandle());

                // Explicitly specify the user handle 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,
                        adapter.getUserHandle())) {
                    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)) {
                return new IntentFilter(intent.getAction(), dataString);
            }
            if (intent.getType() == null) {
                Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
                return null;
            }
            IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
            List<Uri> contentUris = new ArrayList<>();
            if (Intent.ACTION_SEND.equals(intent.getAction())) {
                Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
                if (uri != null) {
                    contentUris.add(uri);
                }
            } else {
                List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
                if (uris != null) {
                    contentUris.addAll(uris);
                }
            }
            for (Uri uri : contentUris) {
                intentFilter.addDataScheme(uri.getScheme());
                intentFilter.addDataAuthority(uri.getAuthority(), null);
                intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
            }
            return intentFilter;
        } 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;
    }

    @VisibleForTesting
    protected void queryDirectShareTargets(
                ChooserListAdapter adapter, boolean skipAppPredictionService) {
        mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
        UserHandle userHandle = adapter.getUserHandle();
        if (!skipAppPredictionService) {
            AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
            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(() -> {
            Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
            ShortcutManager sm = (ShortcutManager) selectedProfileContext
                    .getSystemService(Context.SHORTCUT_SERVICE);
            List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
            sendShareShortcutInfoList(resultList, driList, null, userHandle);
        });
    }

    /**
     * Returns {@code false} if {@code userHandle} is the work profile and it's either
     * in quiet mode or not running.
     */
    private boolean shouldQueryShortcutManager(UserHandle userHandle) {
        if (!shouldShowTabs()) {
            return true;
        }
        if (!getWorkProfileUserHandle().equals(userHandle)) {
            return true;
        }
        if (!isUserRunning(userHandle)) {
            return false;
        }
        if (!isUserUnlocked(userHandle)) {
            return false;
        }
        if (isQuietModeEnabled(userHandle)) {
            return false;
        }
        return true;
    }

    private void sendChooserTargetRankingScore(List<AppTarget> chooserTargetScores,
            UserHandle userHandle) {
        final Message msg = Message.obtain();
        msg.what = ChooserHandler.CHOOSER_TARGET_RANKING_SCORE;
        msg.obj = new ChooserTargetRankingInfo(chooserTargetScores, userHandle);
        mChooserHandler.sendMessage(msg);
    }

    private void sendShareShortcutInfoList(
                List<ShortcutManager.ShareShortcutInfo> resultList,
                List<DisplayResolveInfo> driList,
                @Nullable List<AppTarget> appTargets, UserHandle userHandle) {
        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, userHandle);
            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.getLabel(),
                    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));
            }
            if (mDirectShareShortcutInfoCache != null) {
                mDirectShareShortcutInfoCache.put(chooserTarget, shortcutInfo);
            }
        }
        // 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 && info instanceof MultiDisplayResolveInfo) {
            info = ((MultiDisplayResolveInfo) info).getSelectedTarget();
        }
        if (info != null) {
            sendClickToAppPredictor(info);
            final ResolveInfo ri = info.getResolveInfo();
            Intent targetIntent = getTargetIntent();
            if (ri != null && ri.activityInfo != null && targetIntent != null) {
                ChooserListAdapter currentListAdapter =
                        mChooserMultiProfilePagerAdapter.getActiveListAdapter();
                if (currentListAdapter != null) {
                    sendImpressionToAppPredictor(info, currentListAdapter);
                    currentListAdapter.updateModel(info.getResolvedComponentName());
                    currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
                            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 sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
        if (!mChooserTargetRankingEnabled) {
            return;
        }
        AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
        if (directShareAppPredictor == null) {
            return;
        }
        // Send DS target impression info to AppPredictor, only when user chooses app share.
        if (targetInfo instanceof ChooserTargetInfo) {
            return;
        }
        List<ChooserTargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
        List<AppTargetId> targetIds = new ArrayList<>();
        for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) {
            ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget();
            ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault(
                    chooserTarget.getComponentName(), chooserTarget.getComponentName());
            if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) {
                String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId();
                targetIds.add(new AppTargetId(
                        String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
                                SHORTCUT_TARGET)));
            } else {
                String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString());
                targetIds.add(new AppTargetId(
                        String.format("%s/%s/%s", titleHash, componentName.flattenToString(),
                                CHOOSER_TARGET)));
            }
        }
        directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
    }

    private void sendClickToAppPredictor(TargetInfo targetInfo) {
        AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
        if (directShareAppPredictor == null) {
            return;
        }
        if (!(targetInfo instanceof ChooserTargetInfo)) {
            return;
        }
        ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
        AppTarget appTarget = null;
        if (mDirectShareAppTargetCache != null) {
            appTarget = mDirectShareAppTargetCache.get(chooserTarget);
        }
        if (mChooserTargetRankingEnabled && appTarget == null) {
            // Send ChooserTarget sharing info to AppPredictor.
            ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault(
                    chooserTarget.getComponentName(), chooserTarget.getComponentName());
            try {
                appTarget = new AppTarget.Builder(
                        new AppTargetId(componentName.flattenToString()),
                        new ShortcutInfo.Builder(
                                createPackageContextAsUser(
                                        componentName.getPackageName(),
                                        0 /* flags */,
                                        getUser()),
                                CHOOSER_TARGET)
                            .setActivity(componentName)
                            .setShortLabel(ChooserUtil.md5(chooserTarget.getTitle().toString()))
                            .build())
                        .setClassName(componentName.getClassName())
                        .build();
            } catch (NameNotFoundException e) {
                Log.e(TAG, "Could not look up service " + componentName
                        + "; component name not found");
            }
        }
        // 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_LOCATION_DIRECT_SHARE)
                        .build());
        }
    }

    @Nullable
    private AppPredictor createAppPredictor(UserHandle userHandle) {
        if (!mIsAppPredictorComponentAvailable) {
            return null;
        }

        if (getPersonalProfileUserHandle().equals(userHandle)) {
            if (mPersonalAppPredictor != null) {
                return mPersonalAppPredictor;
            }
        } else {
            if (mWorkAppPredictor != null) {
                return mWorkAppPredictor;
            }
        }

        // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets.
        // Make AppPredictor work cross-profile.
        Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */);
        final IntentFilter filter = getTargetIntentFilter();
        Bundle extras = new Bundle();
        extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
        AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser)
            .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
            .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
            .setExtras(extras)
            .build();
        AppPredictionManager appPredictionManager =
                contextAsUser
                        .getSystemService(AppPredictionManager.class);
        AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
                appPredictionContext);
        if (getPersonalProfileUserHandle().equals(userHandle)) {
            mPersonalAppPredictor = appPredictionSession;
        } else {
            mWorkAppPredictor = appPredictionSession;
        }
        return appPredictionSession;
    }

    /**
     * This will return an app predictor if it is enabled for direct share sorting
     * and if one exists. Otherwise, it returns null.
     * @param userHandle
     */
    @Nullable
    private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) {
        return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
                && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : 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 getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
        return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : 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(Context contextAsUser, String packageName,
            List<ChooserTarget> targets) {
        if (targets == null) {
            return;
        }

        final PackageManager pm = contextAsUser.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);
            }
        }
    }

    /**
     * Sort intents alphabetically based on display label.
     */
    static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
        Collator mCollator;
        AzInfoComparator(Context context) {
            mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
        }

        @Override
        public int compare(
                DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
            return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
        }
    }

    protected MetricsLogger getMetricsLogger() {
        if (mMetricsLogger == null) {
            mMetricsLogger = new MetricsLogger();
        }
        return mMetricsLogger;
    }

    protected ChooserActivityLogger getChooserActivityLogger() {
        if (mChooserActivityLogger == null) {
            mChooserActivityLogger = new ChooserActivityLoggerImpl();
        }
        return mChooserActivityLogger;
    }

    public class ChooserListController extends ResolverListController {
        public ChooserListController(Context context,
                PackageManager pm,
                Intent targetIntent,
                String referrerPackageName,
                int launchedFromUid,
                UserHandle userId,
                AbstractResolverComparator resolverComparator) {
            super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
                    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);
        }
    }

    @VisibleForTesting
    public ChooserGridAdapter createChooserGridAdapter(Context context,
            List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
            boolean filterLastUsed, UserHandle userHandle) {
        ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents,
                initialIntents, rList, filterLastUsed,
                createListController(userHandle));
        AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter);
        AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
        chooserListAdapter.setAppPredictor(appPredictor);
        chooserListAdapter.setAppPredictorCallback(appPredictorCallback);
        return new ChooserGridAdapter(chooserListAdapter);
    }

    @VisibleForTesting
    public ChooserListAdapter createChooserListAdapter(Context context,
            List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
            boolean filterLastUsed, ResolverListController resolverListController) {
        return new ChooserListAdapter(context, payloadIntents, initialIntents, rList,
                filterLastUsed, resolverListController, this,
                this, context.getPackageManager());
    }

    @VisibleForTesting
    protected ResolverListController createListController(UserHandle userHandle) {
        AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle);
        AbstractResolverComparator resolverComparator;
        if (appPredictor != null) {
            resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
                    getReferrerPackageName(), appPredictor, userHandle);
        } else {
            resolverComparator =
                    new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
                        getReferrerPackageName(), null);
        }

        return new ChooserListController(
                this,
                mPm,
                getTargetIntent(),
                getReferrerPackageName(),
                mLaunchedFromUid,
                userHandle,
                resolverComparator);
    }

    @VisibleForTesting
    protected Bitmap loadThumbnail(Uri uri, Size size) {
        if (uri == null || size == null) {
            return null;
        }

        try {
            return getContentResolver().loadThumbnail(uri, size, null);
        } catch (IOException | NullPointerException | SecurityException ex) {
            logContentPreviewWarning(uri);
        }
        return null;
    }

    static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
        public Drawable getDisplayIcon(Context context) {
            AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
                    context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
            avd.start(); // Start animation after generation
            return avd;
        }
    }

    static final class EmptyTargetInfo extends NotSelectableTargetInfo {
        public Drawable getDisplayIcon(Context context) {
            return null;
        }
    }

    private void handleScroll(View view, int x, int y, int oldx, int oldy) {
        if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
            mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().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 (mChooserMultiProfilePagerAdapter == null) {
            return;
        }
        RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
        ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
        if (gridAdapter == null || recyclerView == null) {
            return;
        }

        final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
        boolean isLayoutUpdated = gridAdapter.consumeLayoutRequest()
                || gridAdapter.calculateChooserTargetWidth(availableWidth)
                || recyclerView.getAdapter() == null
                || availableWidth != mCurrAvailableWidth;
        if (isLayoutUpdated
                || mLastNumberOfChildren != recyclerView.getChildCount()) {
            mCurrAvailableWidth = availableWidth;
            if (isLayoutUpdated) {
                // It is very important we call setAdapter from here. Otherwise in some cases
                // the resolver list doesn't get populated, such as b/150922090, b/150918223
                // and b/150936654
                recyclerView.setAdapter(gridAdapter);
                ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
                        gridAdapter.getMaxTargetsPerRow());
            }

            UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
            int currentProfile = getProfileForUser(currentUserHandle);
            int initialProfile = findSelectedProfile();
            if (currentProfile != initialProfile) {
                return;
            }

            if (mLastNumberOfChildren == recyclerView.getChildCount()) {
                return;
            }

            getMainThreadHandler().post(() -> {
                if (mResolverDrawerLayout == null || gridAdapter == null) {
                    return;
                }

                final int bottomInset = mSystemWindowInsets != null
                                            ? mSystemWindowInsets.bottom : 0;
                int offset = bottomInset;
                int rowsToShow = gridAdapter.getContentPreviewRowCount()
                        + gridAdapter.getProfileRowCount()
                        + gridAdapter.getServiceTargetRowCount()
                        + gridAdapter.getCallerAndRankedTargetRowCount();

                // then this is most likely not a SEND_* action, so check
                // the app target count
                if (rowsToShow == 0) {
                    rowsToShow = gridAdapter.getRowCount();
                }

                // still zero? then use a default height and leave, which
                // can happen when there are no targets to show
                if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
                    offset += getResources().getDimensionPixelSize(
                            R.dimen.chooser_max_collapsed_height);
                    mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
                    return;
                }

                View stickyContentPreview = findViewById(R.id.content_preview_container);
                if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
                    offset += stickyContentPreview.getHeight();
                }

                if (shouldShowTabs()) {
                    offset += findViewById(R.id.tabs).getHeight();
                }

                View tabDivider = findViewById(R.id.resolver_tab_divider);
                if (tabDivider.getVisibility() == View.VISIBLE) {
                    offset += tabDivider.getHeight();
                }

                if (recyclerView.getVisibility() == View.VISIBLE) {
                    int directShareHeight = 0;
                    rowsToShow = Math.min(4, rowsToShow);
                    boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
                    mLastNumberOfChildren = recyclerView.getChildCount();
                    for (int i = 0, childCount = recyclerView.getChildCount();
                            i < childCount && rowsToShow > 0; i++) {
                        View child = recyclerView.getChildAt(i);
                        if (((GridLayoutManager.LayoutParams)
                                child.getLayoutParams()).getSpanIndex() != 0) {
                            continue;
                        }
                        int height = child.getHeight();
                        offset += height;
                        if (shouldShowExtraRow) {
                            offset += height;
                        }

                        if (gridAdapter.getTargetType(
                                recyclerView.getChildAdapterPosition(child))
                                == ChooserListAdapter.TARGET_SERVICE) {
                            directShareHeight = height;
                        }
                        rowsToShow--;
                    }

                    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);
                    }
                } else {
                    ViewGroup currentEmptyStateView = getActiveEmptyStateView();
                    if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
                        offset += currentEmptyStateView.getHeight();
                    }
                }

                mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
            });
        }
    }

    /**
     * If we have a tabbed view and are showing 1 row in the current profile and an empty
     * state screen in the other profile, to prevent cropping of the empty state screen we show
     * a second row in the current profile.
     */
    private boolean shouldShowExtraRow(int rowsToShow) {
        return shouldShowTabs()
                && rowsToShow == 1
                && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(
                        mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
    }

    /**
     * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
     * does not match either the personal or work user handle.
     **/
    private int getProfileForUser(UserHandle currentUserHandle) {
        if (currentUserHandle.equals(getPersonalProfileUserHandle())) {
            return PROFILE_PERSONAL;
        } else if (currentUserHandle.equals(getWorkProfileUserHandle())) {
            return PROFILE_WORK;
        }
        Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile.");
        return -1;
    }

    private ViewGroup getActiveEmptyStateView() {
        int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
        return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView();
    }

    static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
        @Override
        public int compare(ChooserTarget lhs, ChooserTarget rhs) {
            // Descending order
            return (int) Math.signum(rhs.getScore() - lhs.getScore());
        }
    }

    @Override // ResolverListCommunicator
    public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
        mServicesRequested.clear();
        mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
        super.onHandlePackagesChanged(listAdapter);
    }

    @Override // SelectableTargetInfoCommunicator
    public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
        return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
    }

    @Override // SelectableTargetInfoCommunicator
    public Intent getReferrerFillInIntent() {
        return mReferrerFillInIntent;
    }

    @Override // ChooserListCommunicator
    public int getMaxRankedTargets() {
        return mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() == null
                ? ChooserGridAdapter.MAX_TARGETS_PER_ROW_PORTRAIT
                : mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().getMaxTargetsPerRow();
    }

    @Override // ChooserListCommunicator
    public void sendListViewUpdateMessage(UserHandle userHandle) {
        Message msg = Message.obtain();
        msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
        msg.obj = userHandle;
        mChooserHandler.sendMessageDelayed(msg, LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
    }

    @Override
    public void onListRebuilt(ResolverListAdapter listAdapter) {
        setupScrollListener();
        maybeSetupGlobalLayoutListener();

        ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
        if (chooserListAdapter.getUserHandle()
                .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
            mChooserMultiProfilePagerAdapter.getActiveAdapterView()
                    .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
            mChooserMultiProfilePagerAdapter
                    .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
        }

        if (chooserListAdapter.mDisplayList == null
                || chooserListAdapter.mDisplayList.isEmpty()) {
            chooserListAdapter.notifyDataSetChanged();
        } else {
            chooserListAdapter.updateAlphabeticalList();
        }

        // don't support direct share on low ram devices
        if (ActivityManager.isLowRamDeviceStatic()) {
            getChooserActivityLogger().logSharesheetAppLoadComplete();
            return;
        }

        // no need to query direct share for work profile when its locked or disabled
        if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
            getChooserActivityLogger().logSharesheetAppLoadComplete();
            return;
        }

        if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
                || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
            if (DEBUG) {
                Log.d(TAG, "querying direct share targets from ShortcutManager");
            }

            queryDirectShareTargets(chooserListAdapter, false);
        }
        if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
            if (DEBUG) {
                Log.d(TAG, "List built querying services");
            }

            queryTargetServices(chooserListAdapter);
        }

        getChooserActivityLogger().logSharesheetAppLoadComplete();
    }

    @VisibleForTesting
    protected boolean isUserRunning(UserHandle userHandle) {
        UserManager userManager = getSystemService(UserManager.class);
        return userManager.isUserRunning(userHandle);
    }

    @VisibleForTesting
    protected boolean isUserUnlocked(UserHandle userHandle) {
        UserManager userManager = getSystemService(UserManager.class);
        return userManager.isUserUnlocked(userHandle);
    }

    @VisibleForTesting
    protected boolean isQuietModeEnabled(UserHandle userHandle) {
        UserManager userManager = getSystemService(UserManager.class);
        return userManager.isQuietModeEnabled(userHandle);
    }

    private void setupScrollListener() {
        if (mResolverDrawerLayout == null) {
            return;
        }
        int elevatedViewResId = shouldShowTabs() ? R.id.resolver_tab_divider : R.id.chooser_header;
        final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
        final float defaultElevation = elevatedView.getElevation();
        final float chooserHeaderScrollElevation =
                getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
        mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
                new RecyclerView.OnScrollListener() {
                    public void onScrollStateChanged(RecyclerView view, int scrollState) {
                        if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
                            if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
                                mScrollStatus = SCROLL_STATUS_IDLE;
                                setHorizontalScrollingEnabled(true);
                            }
                        } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
                            if (mScrollStatus == SCROLL_STATUS_IDLE) {
                                mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL;
                                setHorizontalScrollingEnabled(false);
                            }
                        }
                    }

                    public void onScrolled(RecyclerView view, int dx, int dy) {
                        if (view.getChildCount() > 0) {
                            View child = view.getLayoutManager().findViewByPosition(0);
                            if (child == null || child.getTop() < 0) {
                                elevatedView.setElevation(chooserHeaderScrollElevation);
                                return;
                            }
                        }

                        elevatedView.setElevation(defaultElevation);
                    }
                });
    }

    private void maybeSetupGlobalLayoutListener() {
        if (shouldShowTabs()) {
            return;
        }
        final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
        recyclerView.getViewTreeObserver()
                .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        // Fixes an issue were the accessibility border disappears on list creation.
                        recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        final TextView titleView = findViewById(R.id.title);
                        if (titleView != null) {
                            titleView.setFocusable(true);
                            titleView.setFocusableInTouchMode(true);
                            titleView.requestFocus();
                            titleView.requestAccessibilityFocus();
                        }
                    }
                });
    }

    @Override // ChooserListCommunicator
    public 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;
    }

    /**
     * The sticky content preview is shown only when we have a tabbed view. It's shown above
     * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
     * we instead show the content preview as a regular list item.
     */
    private boolean shouldShowStickyContentPreview() {
        return shouldShowStickyContentPreviewNoOrientationCheck()
                && !getResources().getBoolean(R.bool.resolver_landscape_phone);
    }

    private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
        return shouldShowTabs()
                && mMultiProfilePagerAdapter.getListAdapterForUserHandle(
                UserHandle.of(UserHandle.myUserId())).getCount() > 0
                && isSendAction(getTargetIntent());
    }

    private void updateStickyContentPreview() {
        if (shouldShowStickyContentPreviewNoOrientationCheck()) {
            // The sticky content preview is only shown when we show the work and personal tabs.
            // We don't show it in landscape as otherwise there is no room for scrolling.
            // If the sticky content preview will be shown at some point with orientation change,
            // then always preload it to avoid subsequent resizing of the share sheet.
            ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
            if (contentPreviewContainer.getChildCount() == 0) {
                ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
                contentPreviewContainer.addView(contentPreviewView);
            }
        }
        if (shouldShowStickyContentPreview()) {
            showStickyContentPreview();
        } else {
            hideStickyContentPreview();
        }
    }

    private void showStickyContentPreview() {
        if (isStickyContentPreviewShowing()) {
            return;
        }
        ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
        contentPreviewContainer.setVisibility(View.VISIBLE);
    }

    private boolean isStickyContentPreviewShowing() {
        ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
        return contentPreviewContainer.getVisibility() == View.VISIBLE;
    }

    private void hideStickyContentPreview() {
        if (!isStickyContentPreviewShowing()) {
            return;
        }
        ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
        contentPreviewContainer.setVisibility(View.GONE);
    }

    private void logActionShareWithPreview() {
        Intent targetIntent = getTargetIntent();
        int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
        getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
                .setSubtype(previewType));
    }

    class ViewHolderBase extends RecyclerView.ViewHolder {
        private int mViewType;

        ViewHolderBase(View itemView, int viewType) {
            super(itemView);
            this.mViewType = viewType;
        }

        int getViewType() {
            return mViewType;
        }
    }

    /**
     * Used to bind types of individual item including
     * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
     * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
     * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
     * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
     */
    final class ItemViewHolder extends ViewHolderBase {
        ResolverListAdapter.ViewHolder mWrappedViewHolder;
        int mListPosition = ChooserListAdapter.NO_POSITION;

        ItemViewHolder(View itemView, boolean isClickable, int viewType) {
            super(itemView, viewType);
            mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
            if (isClickable) {
                itemView.setOnClickListener(v -> startSelected(mListPosition,
                        false/* always */, true/* filterd */));

                itemView.setOnLongClickListener(v -> {
                    final TargetInfo ti = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
                            .targetInfoForPosition(mListPosition, /* filtered */ true);

                    // This should always be the case for ItemViewHolder, check for sanity
                    if (ti instanceof DisplayResolveInfo) {
                        showTargetDetails((DisplayResolveInfo) ti);
                    }
                    return true;
                });
            }
        }
    }

    /**
     * Add a footer to the list, to support scrolling behavior below the navbar.
     */
    final class FooterViewHolder extends ViewHolderBase {
        FooterViewHolder(View itemView, int viewType) {
            super(itemView, viewType);
        }
    }

    /**
     * Intentionally override the {@link ResolverActivity} implementation as we only need that
     * implementation for the intent resolver case.
     */
    @Override
    public void onButtonClick(View v) {}

    /**
     * Intentionally override the {@link ResolverActivity} implementation as we only need that
     * implementation for the intent resolver case.
     */
    @Override
    protected void resetButtonBar() {}

    @Override
    protected String getMetricsCategory() {
        return METRICS_CATEGORY_CHOOSER;
    }

    @Override
    protected void onProfileTabSelected() {
        ChooserGridAdapter currentRootAdapter =
                mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
        currentRootAdapter.updateDirectShareExpansion();
    }

    @Override
    protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
        if (shouldShowTabs()) {
            mChooserMultiProfilePagerAdapter
                    .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
            mChooserMultiProfilePagerAdapter.setupContainerPadding(
                    getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container));
        }
        return super.onApplyWindowInsets(v, insets);
    }

    private void setHorizontalScrollingEnabled(boolean enabled) {
        ResolverViewPager viewPager = findViewById(R.id.profile_pager);
        viewPager.setSwipingEnabled(enabled);
    }

    private void setVerticalScrollEnabled(boolean enabled) {
        ChooserGridLayoutManager layoutManager =
                (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView()
                        .getLayoutManager();
        layoutManager.setVerticalScrollEnabled(enabled);
    }

    @Override
    void onHorizontalSwipeStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_DRAGGING) {
            if (mScrollStatus == SCROLL_STATUS_IDLE) {
                mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL;
                setVerticalScrollEnabled(false);
            }
        } else if (state == ViewPager.SCROLL_STATE_IDLE) {
            if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) {
                mScrollStatus = SCROLL_STATUS_IDLE;
                setVerticalScrollEnabled(true);
            }
        }
    }

    /**
     * Adapter for all types of items and targets in ShareSheet.
     * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
     * row level by this adapter but not on the item level. Individual targets within the row are
     * handled by {@link ChooserListAdapter}
     */
    @VisibleForTesting
    public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        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 int mFooterHeight = 0;

        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 VIEW_TYPE_CALLER_AND_RANK = 5;
        private static final int VIEW_TYPE_FOOTER = 6;

        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;

        ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
            super();
            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();
                    notifyDataSetChanged();
                }
            });
        }

        public void setFooterHeight(int height) {
            mFooterHeight = height;
        }

        /**
         * 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;
        }

        int getMaxTargetsPerRow() {
            int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
            if (mShouldDisplayLandscape) {
                maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
            }
            return maxTargets;
        }

        /**
         * Hides the list item content preview.
         * <p>Not to be confused with the sticky content preview which is above the
         * personal and work tabs.
         */
        public void hideContentPreview() {
            mHideContentPreview = true;
            mLayoutRequested = true;
            notifyDataSetChanged();
        }

        public boolean consumeLayoutRequest() {
            boolean oldValue = mLayoutRequested;
            mLayoutRequested = false;
            return oldValue;
        }

        public int getRowCount() {
            return (int) (
                    getContentPreviewRowCount()
                            + getProfileRowCount()
                            + getServiceTargetRowCount()
                            + getCallerAndRankedTargetRowCount()
                            + getAzLabelRowCount()
                            + Math.ceil(
                            (float) mChooserListAdapter.getAlphaTargetCount()
                                    / getMaxTargetsPerRow())
            );
        }

        /**
         * Returns either {@code 0} or {@code 1} depending on whether we want to show the list item
         * content preview. Not to be confused with the sticky content preview which is above the
         * personal and work tabs.
         */
        public int getContentPreviewRowCount() {
            // For the tabbed case we show the sticky content preview above the tabs,
            // please refer to shouldShowStickyContentPreview
            if (shouldShowTabs()) {
                return 0;
            }
            if (!isSendAction(getTargetIntent())) {
                return 0;
            }

            if (mHideContentPreview || mChooserListAdapter == null
                    || mChooserListAdapter.getCount() == 0) {
                return 0;
            }

            return 1;
        }

        public int getProfileRowCount() {
            if (shouldShowTabs()) {
                return 0;
            }
            return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
        }

        public int getFooterRowCount() {
            return 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 int getItemCount() {
            return (int) (
                    getContentPreviewRowCount()
                            + getProfileRowCount()
                            + getServiceTargetRowCount()
                            + getCallerAndRankedTargetRowCount()
                            + getAzLabelRowCount()
                            + mChooserListAdapter.getAlphaTargetCount()
                            + getFooterRowCount()
            );
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            switch (viewType) {
                case VIEW_TYPE_CONTENT_PREVIEW:
                    return new ItemViewHolder(createContentPreviewView(parent), false, viewType);
                case VIEW_TYPE_PROFILE:
                    return new ItemViewHolder(createProfileView(parent), false, viewType);
                case VIEW_TYPE_AZ_LABEL:
                    return new ItemViewHolder(createAzLabelView(parent), false, viewType);
                case VIEW_TYPE_NORMAL:
                    return new ItemViewHolder(
                            mChooserListAdapter.createView(parent), true, viewType);
                case VIEW_TYPE_DIRECT_SHARE:
                case VIEW_TYPE_CALLER_AND_RANK:
                    return createItemGroupViewHolder(viewType, parent);
                case VIEW_TYPE_FOOTER:
                    Space sp = new Space(parent.getContext());
                    sp.setLayoutParams(new RecyclerView.LayoutParams(
                            LayoutParams.MATCH_PARENT, mFooterHeight));
                    return new FooterViewHolder(sp, viewType);
                default:
                    // Since we catch all possible viewTypes above, no chance this is being called.
                    return null;
            }
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            int viewType = ((ViewHolderBase) holder).getViewType();
            switch (viewType) {
                case VIEW_TYPE_DIRECT_SHARE:
                case VIEW_TYPE_CALLER_AND_RANK:
                    bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
                    break;
                case VIEW_TYPE_NORMAL:
                    bindItemViewHolder(position, (ItemViewHolder) holder);
                    break;
                default:
            }
        }

        @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_CALLER_AND_RANK;

            countSum += (count = getAzLabelRowCount());
            if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;

            if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER;

            return VIEW_TYPE_NORMAL;
        }

        public int getTargetType(int position) {
            return mChooserListAdapter.getPositionTargetType(getListPosition(position));
        }

        private View createProfileView(ViewGroup parent) {
            View profileRow = 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);
            updateProfileViewButton();
            return profileRow;
        }

        private View createAzLabelView(ViewGroup parent) {
            return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
        }

        private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder 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);
                    }
                });

                // Direct Share targets should not show any menu
                if (!isDirectShare) {
                    v.setOnLongClickListener(v1 -> {
                        final TargetInfo ti = mChooserListAdapter.targetInfoForPosition(
                                holder.getItemIndex(column), true);
                        // This should always be the case for non-DS targets, check for sanity
                        if (ti instanceof DisplayResolveInfo) {
                            showTargetDetails((DisplayResolveInfo) ti);
                        }
                        return true;
                    });
                }

                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;
            }
        }

        ItemGroupViewHolder createItemGroupViewHolder(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(), viewType);
                loadViewsIntoGroup(mDirectShareViewHolder);

                return mDirectShareViewHolder;
            } else {
                ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
                        false);
                ItemGroupViewHolder holder =
                        new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType);
                loadViewsIntoGroup(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 bindItemViewHolder(int position, ItemViewHolder holder) {
            View v = holder.itemView;
            int listPosition = getListPosition(position);
            holder.mListPosition = listPosition;
            mChooserListAdapter.bindView(listPosition, v);
        }

        void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
            final ViewGroup viewGroup = (ViewGroup) holder.itemView;
            int start = getListPosition(position);
            int startType = getRowType(start);
            if (viewGroup.getForeground() == null && position > 0) {
                viewGroup.setForeground(
                        getResources().getDrawable(R.drawable.chooser_row_layer_list, 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 = viewGroup.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 getListPosition(int position) {
            position -= getContentPreviewRowCount() + getProfileRowCount();

            final int serviceCount = mChooserListAdapter.getServiceTargetCount();
            final int serviceRows = (int) Math.ceil((float) serviceCount
                    / ChooserListAdapter.MAX_SERVICE_TARGETS);
            if (position < serviceRows) {
                return position * getMaxTargetsPerRow();
            }

            position -= serviceRows;

            final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
                                                 + mChooserListAdapter.getRankedTargetCount();
            final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
            if (position < callerAndRankedRows) {
                return serviceCount + position * getMaxTargetsPerRow();
            }

            position -= getAzLabelRowCount() + callerAndRankedRows;

            return callerAndRankedCount + serviceCount + position;
        }

        public void handleScroll(View v, int y, int oldy) {
            boolean canExpandDirectShare = canExpandDirectShare();
            if (mDirectShareViewHolder != null && canExpandDirectShare) {
                mDirectShareViewHolder.handleScroll(
                        mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
                        getMaxTargetsPerRow());
            }
        }

        /**
         * Only expand direct share area if there is a minimum number of targets.
         */
        private boolean canExpandDirectShare() {
            // Do not enable until we have confirmed more apps are using sharing shortcuts
            // Check git history for enablement logic
            return false;
        }

        public ChooserListAdapter getListAdapter() {
            return mChooserListAdapter;
        }

        boolean shouldCellSpan(int position) {
            return getItemViewType(position) == VIEW_TYPE_NORMAL;
        }

        void updateDirectShareExpansion() {
            if (mDirectShareViewHolder == null || !canExpandDirectShare()) {
                return;
            }
            RecyclerView activeAdapterView =
                    mChooserMultiProfilePagerAdapter.getActiveAdapterView();
            if (mResolverDrawerLayout.isCollapsed()) {
                mDirectShareViewHolder.collapse(activeAdapterView);
            } else {
                mDirectShareViewHolder.expand(activeAdapterView);
            }
        }
    }

    /**
     * Used to bind types for group of items including:
     * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
     * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
     */
    abstract class ItemGroupViewHolder extends ViewHolderBase {
        protected int mMeasuredRowHeight;
        private int[] mItemIndices;
        protected final View[] mCells;
        private final int mColumnCount;

        ItemGroupViewHolder(int cellCount, View itemView, int viewType) {
            super(itemView, viewType);
            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 ItemGroupViewHolder {
        private final ViewGroup mRow;

        SingleRowViewHolder(ViewGroup row, int cellCount, int viewType) {
            super(cellCount, row, viewType);

            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 ItemGroupViewHolder {
        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,
                int viewType) {
            super(rows.size() * cellCountPerRow, parent, viewType);

            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(RecyclerView 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
                ChooserListAdapter adapter =
                        mChooserMultiProfilePagerAdapter.getActiveListAdapter();
                int validTargets =
                        mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
                                : adapter.getSelectableServiceTargetCount();
                if (validTargets <= 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;

            updateDirectShareRowHeight(view, yDiff, newHeight);
        }

        void expand(RecyclerView view) {
            updateDirectShareRowHeight(view, mDirectShareMaxHeight - mDirectShareCurrHeight,
                    mDirectShareMaxHeight);
        }

        void collapse(RecyclerView view) {
            updateDirectShareRowHeight(view, mDirectShareMinHeight - mDirectShareCurrHeight,
                    mDirectShareMinHeight);
        }

        private void updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight) {
            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 UserHandle mUserHandle;
        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;
                    }
                    Context contextAsUser =
                            mChooserActivity.createContextAsUser(mUserHandle, 0 /* flags */);
                    mChooserActivity.filterServiceTargets(contextAsUser,
                            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, mUserHandle);
                    mChooserActivity.mChooserHandler.sendMessage(msg);
                }
            }
        };

        public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
                DisplayResolveInfo dri, UserHandle userHandle) {
            mChooserActivity = chooserActivity;
            mOriginalTarget = dri;
            mUserHandle = userHandle;
        }

        @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>") + "}";
        }

        public ComponentName getComponentName() {
            return mOriginalTarget.getResolveInfo().activityInfo.getComponentName();
        }
    }

    static class ServiceResultInfo {
        public final DisplayResolveInfo originalTarget;
        public final List<ChooserTarget> resultTargets;
        public final ChooserTargetServiceConnection connection;
        public final UserHandle userHandle;

        public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
                ChooserTargetServiceConnection c, UserHandle userHandle) {
            originalTarget = ot;
            resultTargets = rt;
            connection = c;
            this.userHandle = userHandle;
        }
    }

    static class ChooserTargetRankingInfo {
        public final List<AppTarget> scores;
        public final UserHandle userHandle;

        ChooserTargetRankingInfo(List<AppTarget> chooserTargetScores,
                UserHandle userHandle) {
            this.scores = chooserTargetScores;
            this.userHandle = userHandle;
        }
    }

    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);
        }
    }

    @Override
    protected void maybeLogProfileChange() {
        getChooserActivityLogger().logShareheetProfileChanged();
    }
}
