Hard fork of the ChoserActivity and ResolverActivity

The forked versions are flag guarded by the ChooserSelector.

Test: atest com.android.intentresolver
Test: adb shell pm resolve-activity -a android.intent.action.CHOOSER
Test: Observe that the action resolves to .ChooserActivity
Test: adb shell device_config put intentresolver \
com.android.intentresolver.flags.modular_framework true
Test: Reboot device
Test: adb shell pm resolve-activity -a android.intent.action.CHOOSER
Test: Observe that the action resolves to .v2.ChooserActivity
BUG: 302113519

Change-Id: I59584ed4649fca754826b17055a41be45a32f326
diff --git a/AndroidManifest-app.xml b/AndroidManifest-app.xml
index 9efc7ab..ec4fec8 100644
--- a/AndroidManifest-app.xml
+++ b/AndroidManifest-app.xml
@@ -60,6 +60,36 @@
                 android:visibleToInstantApps="true"
                 android:exported="false"/>
 
+        <receiver android:name="com.android.intentresolver.v2.ChooserSelector"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+
+        <activity android:name="com.android.intentresolver.v2.ChooserActivity"
+            android:enabled="false"
+            android:theme="@style/Theme.DeviceDefault.Chooser"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true"
+            android:documentLaunchMode="never"
+            android:relinquishTaskIdentity="true"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
+            android:visibleToInstantApps="true"
+            android:exported="true">
+
+            <!-- This intent filter is assigned a priority greater than 500 so
+                 that it will take precedence over the ChooserActivity
+                 in the process of resolving implicit action.CHOOSER intents
+                 whenever this activity is enabled by the experiment flag. -->
+            <intent-filter android:priority="501">
+                <action android:name="android.intent.action.CHOOSER" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.VOICE" />
+            </intent-filter>
+
+        </activity>
+
         <provider android:name="androidx.startup.InitializationProvider"
                 android:authorities="${applicationId}.androidx-startup"
                 tools:replace="android:authorities"
diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig
index 7b0ab05..ae83ca7 100644
--- a/aconfig/FeatureFlags.aconfig
+++ b/aconfig/FeatureFlags.aconfig
@@ -24,3 +24,10 @@
   description: "Enables caching target icons and labels in a local DB"
   bug: "285314844"
 }
+
+flag {
+  name: "modular_framework"
+  namespace: "intentresolver"
+  description: "Enables the new modular framework"
+  bug: "302113519"
+}
diff --git a/java/src/com/android/intentresolver/AnnotatedUserHandles.java b/java/src/com/android/intentresolver/AnnotatedUserHandles.java
index 168f36d..5d559f5 100644
--- a/java/src/com/android/intentresolver/AnnotatedUserHandles.java
+++ b/java/src/com/android/intentresolver/AnnotatedUserHandles.java
@@ -105,7 +105,7 @@
                 .build();
     }
 
-    @VisibleForTesting static Builder newBuilder() {
+    @VisibleForTesting public static Builder newBuilder() {
         return new Builder();
     }
 
@@ -173,7 +173,7 @@
     }
 
     @VisibleForTesting
-    static class Builder {
+    public static class Builder {
         private int mUserIdOfCallingApp;
         private UserHandle mUserHandleSharesheetLaunchedAs;
         private UserHandle mPersonalProfileUserHandle;
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 3f9e215..3a11bee 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -1143,7 +1143,7 @@
         }
 
         @Override
-        boolean isComponentFiltered(ComponentName name) {
+        public boolean isComponentFiltered(ComponentName name) {
             return mChooserRequest.getFilteredComponentNames().contains(name);
         }
 
diff --git a/java/src/com/android/intentresolver/ChooserGridLayoutManager.java b/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
index 5f37352..aaa7554 100644
--- a/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
+++ b/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
@@ -70,7 +70,7 @@
         return super.getRowCountForAccessibility(recycler, state) - 1;
     }
 
-    void setVerticalScrollEnabled(boolean verticalScrollEnabled) {
+    public void setVerticalScrollEnabled(boolean verticalScrollEnabled) {
         mVerticalScrollEnabled = verticalScrollEnabled;
     }
 
diff --git a/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java b/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
index 5fbf03a..df5a8dc 100644
--- a/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
+++ b/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
@@ -49,7 +49,7 @@
     }
 
     @VisibleForTesting
-    ChooserIntegratedDeviceComponents(
+    public ChooserIntegratedDeviceComponents(
             ComponentName editSharingComponent, ComponentName nearbySharingComponent) {
         mEditSharingComponent = editSharingComponent;
         mNearbySharingComponent = nearbySharingComponent;
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 230c18b..ec8800b 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -413,7 +413,7 @@
         }
     }
 
-    void updateAlphabeticalList() {
+    public void updateAlphabeticalList() {
         final ChooserActivity.AzInfoComparator comparator =
                 new ChooserActivity.AzInfoComparator(mContext);
         final List<DisplayResolveInfo> allTargets = new ArrayList<>();
diff --git a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
index 23a081d..080f9d2 100644
--- a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
@@ -46,7 +46,7 @@
     private final ChooserProfileAdapterBinder mAdapterBinder;
     private final BottomPaddingOverrideSupplier mBottomPaddingOverrideSupplier;
 
-    ChooserMultiProfilePagerAdapter(
+    public ChooserMultiProfilePagerAdapter(
             Context context,
             ChooserGridAdapter adapter,
             EmptyStateProvider emptyStateProvider,
@@ -68,7 +68,7 @@
                 featureFlags);
     }
 
-    ChooserMultiProfilePagerAdapter(
+    public ChooserMultiProfilePagerAdapter(
             Context context,
             ChooserGridAdapter personalAdapter,
             ChooserGridAdapter workAdapter,
diff --git a/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java
index 8c640dd..8ce42b2 100644
--- a/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java
@@ -79,11 +79,11 @@
         void bind(PageViewT view, SinglePageAdapterT adapter);
     }
 
-    static final int PROFILE_PERSONAL = 0;
-    static final int PROFILE_WORK = 1;
+    public static final int PROFILE_PERSONAL = 0;
+    public static final int PROFILE_WORK = 1;
 
     @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
-    @interface Profile {}
+    public @interface Profile {}
 
     private final Function<SinglePageAdapterT, ListAdapterT> mListAdapterExtractor;
     private final AdapterBinder<PageViewT, SinglePageAdapterT> mAdapterBinder;
@@ -197,7 +197,7 @@
         return getItemCount();
     }
 
-    protected int getCurrentPage() {
+    public int getCurrentPage() {
         return mCurrentPage;
     }
 
@@ -234,7 +234,7 @@
         return mItems.get(pageIndex);
     }
 
-    protected ViewGroup getEmptyStateView(int pageIndex) {
+    public ViewGroup getEmptyStateView(int pageIndex) {
         return getItem(pageIndex).getEmptyStateView();
     }
 
@@ -266,7 +266,7 @@
      * Performs view-related initialization procedures for the adapter specified
      * by <code>pageIndex</code>.
      */
-    protected final void setupListAdapter(int pageIndex) {
+    public final void setupListAdapter(int pageIndex) {
         mAdapterBinder.bind(getListViewForIndex(pageIndex), getAdapterForIndex(pageIndex));
     }
 
@@ -278,7 +278,7 @@
      * with <code>UserHandle.of(10)</code> returns the work profile {@link ListAdapterT}.
      */
     @Nullable
-    protected final ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) {
+    public final ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) {
         if (getPersonalListAdapter().getUserHandle().equals(userHandle)
                 || userHandle.equals(getCloneUserHandle())) {
             return getPersonalListAdapter();
@@ -297,7 +297,7 @@
      * @see #getInactiveListAdapter()
      */
     @VisibleForTesting
-    protected final ListAdapterT getActiveListAdapter() {
+    public final ListAdapterT getActiveListAdapter() {
         return mListAdapterExtractor.apply(getAdapterForIndex(getCurrentPage()));
     }
 
@@ -311,7 +311,7 @@
      */
     @VisibleForTesting
     @Nullable
-    protected final ListAdapterT getInactiveListAdapter() {
+    public final ListAdapterT getInactiveListAdapter() {
         if (getCount() < 2) {
             return null;
         }
@@ -330,16 +330,16 @@
         return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK));
     }
 
-    protected final SinglePageAdapterT getCurrentRootAdapter() {
+    public final SinglePageAdapterT getCurrentRootAdapter() {
         return getAdapterForIndex(getCurrentPage());
     }
 
-    protected final PageViewT getActiveAdapterView() {
+    public final PageViewT getActiveAdapterView() {
         return getListViewForIndex(getCurrentPage());
     }
 
     @Nullable
-    protected final PageViewT getInactiveAdapterView() {
+    public final PageViewT getInactiveAdapterView() {
         if (getCount() < 2) {
             return null;
         }
@@ -505,7 +505,7 @@
                     paddingBottom));
     }
 
-    protected void showListView(ListAdapterT activeListAdapter) {
+    public void showListView(ListAdapterT activeListAdapter) {
         ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(
                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
         descriptor.mRootView.findViewById(
diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java
index 95ed0d5..8c0d414 100644
--- a/java/src/com/android/intentresolver/ResolverListAdapter.java
+++ b/java/src/com/android/intentresolver/ResolverListAdapter.java
@@ -68,7 +68,7 @@
     protected final Context mContext;
     protected final LayoutInflater mInflater;
     protected final ResolverListCommunicator mResolverListCommunicator;
-    protected final ResolverListController mResolverListController;
+    public final ResolverListController mResolverListController;
 
     private final List<Intent> mIntents;
     private final Intent[] mInitialIntents;
@@ -229,7 +229,7 @@
                 packageName, userHandle, action);
     }
 
-    List<ResolvedComponentInfo> getUnfilteredResolveList() {
+    public List<ResolvedComponentInfo> getUnfilteredResolveList() {
         return mUnfilteredResolveList;
     }
 
@@ -808,7 +808,7 @@
         return mContext.getDrawable(R.drawable.resolver_icon_placeholder);
     }
 
-    void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
+    public void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
         final DisplayResolveInfo iconInfo = getFilteredItem();
         if (iconInfo != null) {
             mTargetDataLoader.loadAppTargetIcon(
@@ -834,7 +834,7 @@
         return mIntents;
     }
 
-    protected boolean isTabLoaded() {
+    public boolean isTabLoaded() {
         return mIsTabLoaded;
     }
 
@@ -893,7 +893,7 @@
      * Necessary methods to communicate between {@link ResolverListAdapter}
      * and {@link ResolverActivity}.
      */
-    interface ResolverListCommunicator {
+    public interface ResolverListCommunicator {
 
         Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent);
 
diff --git a/java/src/com/android/intentresolver/ResolverListController.java b/java/src/com/android/intentresolver/ResolverListController.java
index cb56ab3..0512157 100644
--- a/java/src/com/android/intentresolver/ResolverListController.java
+++ b/java/src/com/android/intentresolver/ResolverListController.java
@@ -333,7 +333,7 @@
                 && ai.name.equals(b.name.getClassName());
     }
 
-    boolean isComponentFiltered(ComponentName componentName) {
+    public boolean isComponentFiltered(ComponentName componentName) {
         return false;
     }
 
diff --git a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
index e0c5380..591c23b 100644
--- a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
@@ -40,7 +40,7 @@
         MultiProfilePagerAdapter<ListView, ResolverListAdapter, ResolverListAdapter> {
     private final BottomPaddingOverrideSupplier mBottomPaddingOverrideSupplier;
 
-    ResolverMultiProfilePagerAdapter(
+    public ResolverMultiProfilePagerAdapter(
             Context context,
             ResolverListAdapter adapter,
             EmptyStateProvider emptyStateProvider,
@@ -58,14 +58,14 @@
                 new BottomPaddingOverrideSupplier());
     }
 
-    ResolverMultiProfilePagerAdapter(Context context,
-            ResolverListAdapter personalAdapter,
-            ResolverListAdapter workAdapter,
-            EmptyStateProvider emptyStateProvider,
-            Supplier<Boolean> workProfileQuietModeChecker,
-            @Profile int defaultProfile,
-            UserHandle workProfileUserHandle,
-            UserHandle cloneProfileUserHandle) {
+    public ResolverMultiProfilePagerAdapter(Context context,
+                                            ResolverListAdapter personalAdapter,
+                                            ResolverListAdapter workAdapter,
+                                            EmptyStateProvider emptyStateProvider,
+                                            Supplier<Boolean> workProfileQuietModeChecker,
+                                            @Profile int defaultProfile,
+                                            UserHandle workProfileUserHandle,
+                                            UserHandle cloneProfileUserHandle) {
         this(
                 context,
                 ImmutableList.of(personalAdapter, workAdapter),
diff --git a/java/src/com/android/intentresolver/ResolverViewPager.java b/java/src/com/android/intentresolver/ResolverViewPager.java
index 0804a2b..0496579 100644
--- a/java/src/com/android/intentresolver/ResolverViewPager.java
+++ b/java/src/com/android/intentresolver/ResolverViewPager.java
@@ -69,7 +69,7 @@
      * Sets whether swiping sideways should happen.
      * <p>Note that swiping is always disabled for RTL layouts (b/159110029 for context).
      */
-    void setSwipingEnabled(boolean swipingEnabled) {
+    public void setSwipingEnabled(boolean swipingEnabled) {
         mSwipingEnabled = swipingEnabled;
     }
 
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java
new file mode 100644
index 0000000..9e43701
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java
@@ -0,0 +1,1851 @@
+/*
+ * 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.intentresolver.v2;
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
+import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
+import static com.android.intentresolver.v2.ResolverActivity.PROFILE_PERSONAL;
+import static com.android.intentresolver.v2.ResolverActivity.PROFILE_WORK;
+import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+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.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.graphics.Insets;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.chooser.ChooserTarget;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.widget.TextView;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager.widget.ViewPager;
+
+import com.android.intentresolver.ChooserActionFactory;
+import com.android.intentresolver.ChooserGridLayoutManager;
+import com.android.intentresolver.ChooserIntegratedDeviceComponents;
+import com.android.intentresolver.ChooserListAdapter;
+import com.android.intentresolver.ChooserMultiProfilePagerAdapter;
+import com.android.intentresolver.ChooserRefinementManager;
+import com.android.intentresolver.ChooserRequestParameters;
+import com.android.intentresolver.ChooserStackedAppDialogFragment;
+import com.android.intentresolver.ChooserTargetActionsDialogFragment;
+import com.android.intentresolver.EnterTransitionAnimationDelegate;
+import com.android.intentresolver.FeatureFlags;
+import com.android.intentresolver.IntentForwarderActivity;
+import com.android.intentresolver.R;
+import com.android.intentresolver.ResolverListAdapter;
+import com.android.intentresolver.ResolverListController;
+import com.android.intentresolver.ResolverViewPager;
+import com.android.intentresolver.SecureSettings;
+import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
+import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.contentpreview.BasePreviewViewModel;
+import com.android.intentresolver.contentpreview.ChooserContentPreviewUi;
+import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl;
+import com.android.intentresolver.contentpreview.PreviewViewModel;
+import com.android.intentresolver.emptystate.EmptyState;
+import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider;
+import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
+import com.android.intentresolver.grid.ChooserGridAdapter;
+import com.android.intentresolver.icons.DefaultTargetDataLoader;
+import com.android.intentresolver.icons.TargetDataLoader;
+import com.android.intentresolver.logging.EventLog;
+import com.android.intentresolver.measurements.Tracer;
+import com.android.intentresolver.model.AbstractResolverComparator;
+import com.android.intentresolver.model.AppPredictionServiceResolverComparator;
+import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
+import com.android.intentresolver.shortcuts.AppPredictorFactory;
+import com.android.intentresolver.shortcuts.ShortcutLoader;
+import com.android.intentresolver.widget.ImagePreviewView;
+import com.android.intentresolver.v2.Hilt_ChooserActivity;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+/**
+ * The Chooser Activity handles intent resolution specifically for sharing intents -
+ * for example, as generated by {@see android.content.Intent#createChooser(Intent, CharSequence)}.
+ *
+ */
+@AndroidEntryPoint(ResolverActivity.class)
+public class ChooserActivity extends Hilt_ChooserActivity implements
+        ResolverListAdapter.ResolverListCommunicator {
+    private static final String TAG = "ChooserActivity";
+
+    /**
+     * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
+     * in onStop when launched in a new task. If this extra is set to true, we do not finish
+     * ourselves when onStop gets called.
+     */
+    public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
+            = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
+
+    /**
+     * Transition name for the first image preview.
+     * To be used for shared element transition into this activity.
+     * @hide
+     */
+    public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image";
+
+    private static final boolean DEBUG = true;
+
+    public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
+    private static final String SHORTCUT_TARGET = "shortcut_target";
+
+    // TODO: these data structures are for one-time use in shuttling data from where they're
+    // populated in `ShortcutToChooserTargetConverter` to where they're consumed in
+    // `ShortcutSelectionLogic` which packs the appropriate elements into the final `TargetInfo`.
+    // That flow should be refactored so that `ChooserActivity` isn't responsible for holding their
+    // intermediate data, and then these members can be removed.
+    private final Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache = new HashMap<>();
+    private final Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache = new HashMap<>();
+
+    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;
+
+    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;
+
+    @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 {}
+
+    @Inject public FeatureFlags mFeatureFlags;
+    @Inject public EventLog mEventLog;
+
+    private ChooserIntegratedDeviceComponents mIntegratedDeviceComponents;
+
+    /* TODO: this is `nullable` because we have to defer the assignment til onCreate(). We make the
+     * only assignment there, and expect it to be ready by the time we ever use it --
+     * someday if we move all the usage to a component with a narrower lifecycle (something that
+     * matches our Activity's create/destroy lifecycle, not its Java object lifecycle) then we
+     * should be able to make this assignment as "final."
+     */
+    @Nullable
+    private ChooserRequestParameters mChooserRequest;
+
+    private ChooserRefinementManager mRefinementManager;
+
+    private ChooserContentPreviewUi mChooserContentPreviewUi;
+
+    private boolean mShouldDisplayLandscape;
+    private long mChooserShownTime;
+    protected boolean mIsSuccessfullySelected;
+
+    private int mCurrAvailableWidth = 0;
+    private Insets mLastAppliedInsets = null;
+    private int mLastNumberOfChildren = -1;
+    private int mMaxTargetsPerRow = 1;
+
+    private static final int MAX_LOG_RANK_POSITION = 12;
+
+    // TODO: are these used anywhere? They should probably be migrated to ChooserRequestParameters.
+    private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
+    private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
+
+    private SharedPreferences mPinnedSharedPrefs;
+    private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
+
+    private final ExecutorService mBackgroundThreadPoolExecutor = Executors.newFixedThreadPool(5);
+
+    private int mScrollStatus = SCROLL_STATUS_IDLE;
+
+    @VisibleForTesting
+    protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
+    private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate =
+            new EnterTransitionAnimationDelegate(this, () -> mResolverDrawerLayout);
+
+    private View mContentView = null;
+
+    private final SparseArray<ProfileRecord> mProfileRecords = new SparseArray<>();
+
+    private boolean mExcludeSharedText = false;
+    /**
+     * When we intend to finish the activity with a shared element transition, we can't immediately
+     * finish() when the transition is invoked, as the receiving end may not be able to start the
+     * animation and the UI breaks if this takes too long. Instead we defer finishing until onStop
+     * in order to wait for the transition to begin.
+     */
+    private boolean mFinishWhenStopped = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Tracer.INSTANCE.markLaunched();
+        final long intentReceivedTime = System.currentTimeMillis();
+        mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
+
+        try {
+            mChooserRequest = new ChooserRequestParameters(
+                    getIntent(),
+                    getReferrerPackageName(),
+                    getReferrer());
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Caller provided invalid Chooser request parameters", e);
+            finish();
+            super_onCreate(null);
+            return;
+        }
+        mPinnedSharedPrefs = getPinnedSharedPrefs(this);
+        mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
+        mShouldDisplayLandscape =
+                shouldDisplayLandscape(getResources().getConfiguration().orientation);
+        setRetainInOnStop(mChooserRequest.shouldRetainInOnStop());
+
+        createProfileRecords(
+                new AppPredictorFactory(
+                        this,
+                        mChooserRequest.getSharedText(),
+                        mChooserRequest.getTargetIntentFilter()),
+                mChooserRequest.getTargetIntentFilter());
+
+
+        super.onCreate(
+                savedInstanceState,
+                mChooserRequest.getTargetIntent(),
+                mChooserRequest.getAdditionalTargets(),
+                mChooserRequest.getTitle(),
+                mChooserRequest.getDefaultTitleResource(),
+                mChooserRequest.getInitialIntents(),
+                /* resolutionList= */ null,
+                /* supportsAlwaysUseOption= */ false,
+                new DefaultTargetDataLoader(this, getLifecycle(), false),
+                /* safeForwardingMode= */ true);
+
+        getEventLog().logSharesheetTriggered();
+
+        mIntegratedDeviceComponents = getIntegratedDeviceComponents();
+
+        mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class);
+
+        mRefinementManager.getRefinementCompletion().observe(this, completion -> {
+            if (completion.consume()) {
+                TargetInfo targetInfo = completion.getTargetInfo();
+                // targetInfo is non-null if the refinement process was successful.
+                if (targetInfo != null) {
+                    maybeRemoveSharedText(targetInfo);
+
+                    // We already block suspended targets from going to refinement, and we probably
+                    // can't recover a Chooser session if that's the reason the refined target fails
+                    // to launch now. Fire-and-forget the refined launch; ignore the return value
+                    // and just make sure the Sharesheet session gets cleaned up regardless.
+                    ChooserActivity.super.onTargetSelected(targetInfo, false);
+                }
+
+                finish();
+            }
+        });
+
+        BasePreviewViewModel previewViewModel =
+                new ViewModelProvider(this, createPreviewViewModelFactory())
+                        .get(BasePreviewViewModel.class);
+        mChooserContentPreviewUi = new ChooserContentPreviewUi(
+                getLifecycle(),
+                previewViewModel.createOrReuseProvider(mChooserRequest),
+                mChooserRequest.getTargetIntent(),
+                previewViewModel.createOrReuseImageLoader(),
+                createChooserActionFactory(),
+                mEnterTransitionAnimationDelegate,
+                new HeadlineGeneratorImpl(this));
+
+        updateStickyContentPreview();
+        if (shouldShowStickyContentPreview()
+                || mChooserMultiProfilePagerAdapter
+                .getCurrentRootAdapter().getSystemRowCount() != 0) {
+            getEventLog().logActionShareWithPreview(
+                    mChooserContentPreviewUi.getPreferredContentPreview());
+        }
+
+        mChooserShownTime = System.currentTimeMillis();
+        final long systemCost = mChooserShownTime - intentReceivedTime;
+        getEventLog().logChooserActivityShown(
+                isWorkProfile(), mChooserRequest.getTargetType(), systemCost);
+
+        if (mResolverDrawerLayout != null) {
+            mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
+
+            mResolverDrawerLayout.setOnCollapsedChangedListener(
+                    isCollapsed -> {
+                        mChooserMultiProfilePagerAdapter.setIsCollapsed(isCollapsed);
+                        getEventLog().logSharesheetExpansionChanged(isCollapsed);
+                    });
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "System Time Cost is " + systemCost);
+        }
+
+        getEventLog().logShareStarted(
+                getReferrerPackageName(),
+                mChooserRequest.getTargetType(),
+                mChooserRequest.getCallerChooserTargets().size(),
+                (mChooserRequest.getInitialIntents() == null)
+                        ? 0 : mChooserRequest.getInitialIntents().length,
+                isWorkProfile(),
+                mChooserContentPreviewUi.getPreferredContentPreview(),
+                mChooserRequest.getTargetAction(),
+                mChooserRequest.getChooserActions().size(),
+                mChooserRequest.getModifyShareAction() != null
+        );
+
+        mEnterTransitionAnimationDelegate.postponeTransition();
+    }
+
+    @VisibleForTesting
+    protected ChooserIntegratedDeviceComponents getIntegratedDeviceComponents() {
+        return ChooserIntegratedDeviceComponents.get(this, new SecureSettings());
+    }
+
+    @Override
+    protected int appliedThemeResId() {
+        return R.style.Theme_DeviceDefault_Chooser;
+    }
+
+    private void createProfileRecords(
+            AppPredictorFactory factory, IntentFilter targetIntentFilter) {
+        UserHandle mainUserHandle = getAnnotatedUserHandles().personalProfileUserHandle;
+        ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory);
+        if (record.shortcutLoader == null) {
+            Tracer.INSTANCE.endLaunchToShortcutTrace();
+        }
+
+        UserHandle workUserHandle = getAnnotatedUserHandles().workProfileUserHandle;
+        if (workUserHandle != null) {
+            createProfileRecord(workUserHandle, targetIntentFilter, factory);
+        }
+    }
+
+    private ProfileRecord createProfileRecord(
+            UserHandle userHandle, IntentFilter targetIntentFilter, AppPredictorFactory factory) {
+        AppPredictor appPredictor = factory.create(userHandle);
+        ShortcutLoader shortcutLoader = ActivityManager.isLowRamDeviceStatic()
+                    ? null
+                    : createShortcutLoader(
+                            this,
+                            appPredictor,
+                            userHandle,
+                            targetIntentFilter,
+                            shortcutsResult -> onShortcutsLoaded(userHandle, shortcutsResult));
+        ProfileRecord record = new ProfileRecord(appPredictor, shortcutLoader);
+        mProfileRecords.put(userHandle.getIdentifier(), record);
+        return record;
+    }
+
+    @Nullable
+    private ProfileRecord getProfileRecord(UserHandle userHandle) {
+        return mProfileRecords.get(userHandle.getIdentifier(), null);
+    }
+
+    @VisibleForTesting
+    protected ShortcutLoader createShortcutLoader(
+            Context context,
+            AppPredictor appPredictor,
+            UserHandle userHandle,
+            IntentFilter targetIntentFilter,
+            Consumer<ShortcutLoader.Result> callback) {
+        return new ShortcutLoader(
+                context,
+                getCoroutineScope(getLifecycle()),
+                appPredictor,
+                userHandle,
+                targetIntentFilter,
+                callback);
+    }
+
+    static SharedPreferences getPinnedSharedPrefs(Context context) {
+        return context.getSharedPreferences(PINNED_SHARED_PREFS_NAME, MODE_PRIVATE);
+    }
+
+    @Override
+    protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+            Intent[] initialIntents,
+            List<ResolveInfo> rList,
+            boolean filterLastUsed,
+            TargetDataLoader targetDataLoader) {
+        if (shouldShowTabs()) {
+            mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
+                    initialIntents, rList, filterLastUsed, targetDataLoader);
+        } else {
+            mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
+                    initialIntents, rList, filterLastUsed, targetDataLoader);
+        }
+        return mChooserMultiProfilePagerAdapter;
+    }
+
+    @Override
+    protected EmptyStateProvider createBlockerEmptyStateProvider() {
+        final boolean isSendAction = mChooserRequest.isSendActionTarget();
+
+        final EmptyState noWorkToPersonalEmptyState =
+                new DevicePolicyBlockerEmptyState(
+                        /* context= */ this,
+                        /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                        /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                        /* devicePolicyStringSubtitleId= */
+                        isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
+                        /* defaultSubtitleResource= */
+                        isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
+                                : R.string.resolver_cant_access_personal_apps_explanation,
+                        /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+                        /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+        final EmptyState noPersonalToWorkEmptyState =
+                new DevicePolicyBlockerEmptyState(
+                        /* context= */ this,
+                        /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                        /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                        /* devicePolicyStringSubtitleId= */
+                        isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
+                        /* defaultSubtitleResource= */
+                        isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
+                                : R.string.resolver_cant_access_work_apps_explanation,
+                        /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+                        /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+        return new NoCrossProfileEmptyStateProvider(
+                getAnnotatedUserHandles().personalProfileUserHandle,
+                noWorkToPersonalEmptyState,
+                noPersonalToWorkEmptyState,
+                createCrossProfileIntentsChecker(),
+                getAnnotatedUserHandles().tabOwnerUserHandleForLaunch);
+    }
+
+    private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
+            Intent[] initialIntents,
+            List<ResolveInfo> rList,
+            boolean filterLastUsed,
+            TargetDataLoader targetDataLoader) {
+        ChooserGridAdapter adapter = createChooserGridAdapter(
+                /* context */ this,
+                /* payloadIntents */ mIntents,
+                initialIntents,
+                rList,
+                filterLastUsed,
+                /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
+                targetDataLoader);
+        return new ChooserMultiProfilePagerAdapter(
+                /* context */ this,
+                adapter,
+                createEmptyStateProvider(/* workProfileUserHandle= */ null),
+                /* workProfileQuietModeChecker= */ () -> false,
+                /* workProfileUserHandle= */ null,
+                getAnnotatedUserHandles().cloneProfileUserHandle,
+                mMaxTargetsPerRow,
+                mFeatureFlags);
+    }
+
+    private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
+            Intent[] initialIntents,
+            List<ResolveInfo> rList,
+            boolean filterLastUsed,
+            TargetDataLoader targetDataLoader) {
+        int selectedProfile = findSelectedProfile();
+        ChooserGridAdapter personalAdapter = createChooserGridAdapter(
+                /* context */ this,
+                /* payloadIntents */ mIntents,
+                selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
+                rList,
+                filterLastUsed,
+                /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
+                targetDataLoader);
+        ChooserGridAdapter workAdapter = createChooserGridAdapter(
+                /* context */ this,
+                /* payloadIntents */ mIntents,
+                selectedProfile == PROFILE_WORK ? initialIntents : null,
+                rList,
+                filterLastUsed,
+                /* userHandle */ getAnnotatedUserHandles().workProfileUserHandle,
+                targetDataLoader);
+        return new ChooserMultiProfilePagerAdapter(
+                /* context */ this,
+                personalAdapter,
+                workAdapter,
+                createEmptyStateProvider(getAnnotatedUserHandles().workProfileUserHandle),
+                () -> mWorkProfileAvailability.isQuietModeEnabled(),
+                selectedProfile,
+                getAnnotatedUserHandles().workProfileUserHandle,
+                getAnnotatedUserHandles().cloneProfileUserHandle,
+                mMaxTargetsPerRow,
+                mFeatureFlags);
+    }
+
+    private int findSelectedProfile() {
+        int selectedProfile = getSelectedProfileExtra();
+        if (selectedProfile == -1) {
+            selectedProfile = getProfileForUser(
+                    getAnnotatedUserHandles().tabOwnerUserHandleForLaunch);
+        }
+        return selectedProfile;
+    }
+
+    /**
+     * 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) {
+            handlePackageChangePerProfile(mChooserMultiProfilePagerAdapter.getActiveListAdapter());
+            if (mChooserMultiProfilePagerAdapter.getCount() > 1) {
+                handlePackageChangePerProfile(
+                        mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
+            }
+        } else {
+            handlePackageChangePerProfile(listAdapter);
+        }
+        updateProfileViewButton();
+    }
+
+    private void handlePackageChangePerProfile(ResolverListAdapter adapter) {
+        ProfileRecord record = getProfileRecord(adapter.getUserHandle());
+        if (record != null && record.shortcutLoader != null) {
+            record.shortcutLoader.reset();
+        }
+        adapter.handlePackagesChanged();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
+        mFinishWhenStopped = false;
+        mRefinementManager.onActivityResume();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+        if (viewPager.isLayoutRtl()) {
+            mMultiProfilePagerAdapter.setupViewPager(viewPager);
+        }
+
+        mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
+        mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
+        mChooserMultiProfilePagerAdapter.setMaxTargetsPerRow(mMaxTargetsPerRow);
+        adjustPreviewWidth(newConfig.orientation, null);
+        updateStickyContentPreview();
+        updateTabPadding();
+    }
+
+    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(com.android.internal.R.id.content_preview_file_layout, width, parent);
+    }
+
+    private void updateTabPadding() {
+        if (shouldShowTabs()) {
+            View tabs = findViewById(com.android.internal.R.id.tabs);
+            float iconSize = getResources().getDimension(R.dimen.chooser_icon_size);
+            // The entire width consists of icons or padding. Divide the item padding in half to get
+            // paddingHorizontal.
+            float padding = (tabs.getWidth() - mMaxTargetsPerRow * iconSize)
+                    / mMaxTargetsPerRow / 2;
+            // Subtract the margin the buttons already have.
+            padding -= getResources().getDimension(R.dimen.resolver_profile_tab_margin);
+            tabs.setPadding((int) padding, 0, (int) padding, 0);
+        }
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * Create a view that will be shown in the content preview area
+     * @param parent reference to the parent container where the view should be attached to
+     * @return content preview view
+     */
+    protected ViewGroup createContentPreviewView(ViewGroup parent) {
+        ViewGroup layout = mChooserContentPreviewUi.displayContentPreview(
+                getResources(),
+                getLayoutInflater(),
+                parent,
+                mFeatureFlags.scrollablePreview()
+                        ? findViewById(R.id.chooser_headline_row_container)
+                        : null);
+
+        if (layout != null) {
+            adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
+        }
+
+        return layout;
+    }
+
+    @Nullable
+    private View getFirstVisibleImgPreviewView() {
+        View imagePreview = findViewById(R.id.scrollable_image_preview);
+        return imagePreview instanceof ImagePreviewView
+                ? ((ImagePreviewView) imagePreview).getTransitionView()
+                : null;
+    }
+
+    /**
+     * 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);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mRefinementManager.onActivityStop(isChangingConfigurations());
+
+        if (mFinishWhenStopped) {
+            mFinishWhenStopped = false;
+            finish();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (isFinishing()) {
+            mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET);
+        }
+
+        mBackgroundThreadPoolExecutor.shutdownNow();
+
+        destroyProfileRecords();
+    }
+
+    private void destroyProfileRecords() {
+        for (int i = 0; i < mProfileRecords.size(); ++i) {
+            mProfileRecords.valueAt(i).destroy();
+        }
+        mProfileRecords.clear();
+    }
+
+    @Override // ResolverListCommunicator
+    public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
+        if (mChooserRequest == null) {
+            return defIntent;
+        }
+
+        Intent result = defIntent;
+        if (mChooserRequest.getReplacementExtras() != null) {
+            final Bundle replExtras =
+                    mChooserRequest.getReplacementExtras().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 (mChooserRequest.getChosenComponentSender() != null) {
+            final ComponentName target = cti.getResolvedComponentName();
+            if (target != null) {
+                final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
+                try {
+                    mChooserRequest.getChosenComponentSender().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);
+                }
+            }
+        }
+    }
+
+    private void addCallerChooserTargets() {
+        if (!mChooserRequest.getCallerChooserTargets().isEmpty()) {
+            // Send the caller's chooser targets only to the default profile.
+            UserHandle defaultUser = (findSelectedProfile() == PROFILE_WORK)
+                    ? getAnnotatedUserHandles().workProfileUserHandle
+                    : getAnnotatedUserHandles().personalProfileUserHandle;
+            if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle() == defaultUser) {
+                mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
+                        /* origTarget */ null,
+                        new ArrayList<>(mChooserRequest.getCallerChooserTargets()),
+                        TARGET_TYPE_DEFAULT,
+                        /* directShareShortcutInfoCache */ Collections.emptyMap(),
+                        /* directShareAppTargetCache */ Collections.emptyMap());
+            }
+        }
+    }
+
+    @Override
+    public int getLayoutResource() {
+        return mFeatureFlags.scrollablePreview()
+                ? R.layout.chooser_grid_scrollable_preview
+                : 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(TargetInfo targetInfo) {
+        if (targetInfo == null) return;
+
+        List<DisplayResolveInfo> targetList = targetInfo.getAllDisplayTargets();
+        if (targetList.isEmpty()) {
+            Log.e(TAG, "No displayable data to show target details");
+            return;
+        }
+
+        // TODO: implement these type-conditioned behaviors polymorphically, and consider moving
+        // the logic into `ChooserTargetActionsDialogFragment.show()`.
+        boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned();
+        IntentFilter intentFilter = targetInfo.isSelectableTargetInfo()
+                ? mChooserRequest.getTargetIntentFilter() : null;
+        String shortcutTitle = targetInfo.isSelectableTargetInfo()
+                ? targetInfo.getDisplayLabel().toString() : null;
+        String shortcutIdKey = targetInfo.getDirectShareShortcutId();
+
+        ChooserTargetActionsDialogFragment.show(
+                getSupportFragmentManager(),
+                targetList,
+                // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be
+                // resolved correctly within the same tab.
+                targetInfo.getResolveInfo().userHandle,
+                shortcutIdKey,
+                shortcutTitle,
+                isShortcutPinned,
+                intentFilter);
+    }
+
+    @Override
+    protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
+        if (mRefinementManager.maybeHandleSelection(
+                target,
+                mChooserRequest.getRefinementIntentSender(),
+                getApplication(),
+                getMainThreadHandler())) {
+            return false;
+        }
+        updateModelAndChooserCounts(target);
+        maybeRemoveSharedText(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.isNotSelectableTargetInfo()) {
+            return;
+        }
+
+        final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
+
+        if ((targetInfo != null) && targetInfo.isMultiDisplayResolveInfo()) {
+            MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
+            if (!mti.hasSelected()) {
+                // Add userHandle based badge to the stackedAppDialogBox.
+                ChooserStackedAppDialogFragment.show(
+                        getSupportFragmentManager(),
+                        mti,
+                        which,
+                        targetInfo.getResolveInfo().userHandle);
+                return;
+            }
+        }
+
+        super.startSelected(which, always, filtered);
+
+        // TODO: both of the conditions around this switch logic *should* be redundant, and
+        // can be removed if certain invariants can be guaranteed. In particular, it seems
+        // like targetInfo (from `ChooserListAdapter.targetInfoForPosition()`) is *probably*
+        // expected to be null only at out-of-bounds indexes where `getPositionTargetType()`
+        // returns TARGET_BAD; then the switch falls through to a default no-op, and we don't
+        // need to null-check targetInfo. We only need the null check if it's possible that
+        // the ChooserListAdapter contains null elements "in the middle" of its list data,
+        // such that they're classified as belonging to one of the real target types. That
+        // should probably never happen. But why would this method ever be invoked with a
+        // null target at all? Even an out-of-bounds index should never be "selected"...
+        if ((currentListAdapter.getCount() > 0) && (targetInfo != null)) {
+            switch (currentListAdapter.getPositionTargetType(which)) {
+                case ChooserListAdapter.TARGET_SERVICE:
+                    getEventLog().logShareTargetSelected(
+                            EventLog.SELECTION_TYPE_SERVICE,
+                            targetInfo.getResolveInfo().activityInfo.processName,
+                            which,
+                            /* directTargetAlsoRanked= */ getRankedPosition(targetInfo),
+                            mChooserRequest.getCallerChooserTargets().size(),
+                            targetInfo.getHashedTargetIdForMetrics(this),
+                            targetInfo.isPinned(),
+                            mIsSuccessfullySelected,
+                            selectionCost
+                    );
+                    return;
+                case ChooserListAdapter.TARGET_CALLER:
+                case ChooserListAdapter.TARGET_STANDARD:
+                    getEventLog().logShareTargetSelected(
+                            EventLog.SELECTION_TYPE_APP,
+                            targetInfo.getResolveInfo().activityInfo.processName,
+                            (which - currentListAdapter.getSurfacedTargetInfo().size()),
+                            /* directTargetAlsoRanked= */ -1,
+                            currentListAdapter.getCallerTargetCount(),
+                            /* directTargetHashed= */ null,
+                            targetInfo.isPinned(),
+                            mIsSuccessfullySelected,
+                            selectionCost
+                    );
+                    return;
+                case ChooserListAdapter.TARGET_STANDARD_AZ:
+                    // A-Z targets are unranked standard targets; we use a value of -1 to mark that
+                    // they are from the alphabetical pool.
+                    // TODO: why do we log a different selection type if the -1 value already
+                    // designates the same condition?
+                    getEventLog().logShareTargetSelected(
+                            EventLog.SELECTION_TYPE_STANDARD,
+                            targetInfo.getResolveInfo().activityInfo.processName,
+                            /* value= */ -1,
+                            /* directTargetAlsoRanked= */ -1,
+                            /* numCallerProvided= */ 0,
+                            /* directTargetHashed= */ null,
+                            /* isPinned= */ false,
+                            mIsSuccessfullySelected,
+                            selectionCost
+                    );
+                    return;
+            }
+        }
+    }
+
+    private int getRankedPosition(TargetInfo targetInfo) {
+        String targetPackageName =
+                targetInfo.getChooserTargetComponentName().getPackageName();
+        ChooserListAdapter currentListAdapter =
+                mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+        int maxRankedResults = Math.min(
+                currentListAdapter.getDisplayResolveInfoCount(), MAX_LOG_RANK_POSITION);
+
+        for (int i = 0; i < maxRankedResults; i++) {
+            if (currentListAdapter.getDisplayResolveInfo(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);
+        }
+    }
+
+    private void logDirectShareTargetReceived(UserHandle forUser) {
+        ProfileRecord profileRecord = getProfileRecord(forUser);
+        if (profileRecord == null) {
+            return;
+        }
+        getEventLog().logDirectShareTargetReceived(
+                MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER,
+                (int) (SystemClock.elapsedRealtime() - profileRecord.loadingStartTime));
+    }
+
+    void updateModelAndChooserCounts(TargetInfo info) {
+        if (info != null && info.isMultiDisplayResolveInfo()) {
+            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);
+                    currentListAdapter.updateChooserCounts(
+                            ri.activityInfo.packageName,
+                            targetIntent.getAction(),
+                            ri.userHandle);
+                }
+                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 ResolveInfo");
+            }
+        }
+        mIsSuccessfullySelected = true;
+    }
+
+    private void maybeRemoveSharedText(@NonNull TargetInfo targetInfo) {
+        Intent targetIntent = targetInfo.getTargetIntent();
+        if (targetIntent == null) {
+            return;
+        }
+        Intent originalTargetIntent = new Intent(mChooserRequest.getTargetIntent());
+        // Our TargetInfo implementations add associated component to the intent, let's do the same
+        // for the sake of the comparison below.
+        if (targetIntent.getComponent() != null) {
+            originalTargetIntent.setComponent(targetIntent.getComponent());
+        }
+        // Use filterEquals as a way to check that the primary intent is in use (and not an
+        // alternative one). For example, an app is sharing an image and a link with mime type
+        // "image/png" and provides an alternative intent to share only the link with mime type
+        // "text/uri". Should there be a target that accepts only the latter, the alternative intent
+        // will be used and we don't want to exclude the link from it.
+        if (mExcludeSharedText && originalTargetIntent.filterEquals(targetIntent)) {
+            targetIntent.removeExtra(Intent.EXTRA_TEXT);
+        }
+    }
+
+    private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
+        // Send DS target impression info to AppPredictor, only when user chooses app share.
+        if (targetInfo.isChooserTargetInfo()) {
+            return;
+        }
+
+        AppPredictor directShareAppPredictor = getAppPredictor(
+                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+        if (directShareAppPredictor == null) {
+            return;
+        }
+        List<TargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
+        List<AppTargetId> targetIds = new ArrayList<>();
+        for (TargetInfo chooserTargetInfo : surfacedTargetInfo) {
+            ShortcutInfo shortcutInfo = chooserTargetInfo.getDirectShareShortcutInfo();
+            if (shortcutInfo != null) {
+                ComponentName componentName =
+                        chooserTargetInfo.getChooserTargetComponentName();
+                targetIds.add(new AppTargetId(
+                        String.format(
+                                "%s/%s/%s",
+                                shortcutInfo.getId(),
+                                componentName.flattenToString(),
+                                SHORTCUT_TARGET)));
+            }
+        }
+        directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
+    }
+
+    private void sendClickToAppPredictor(TargetInfo targetInfo) {
+        if (!targetInfo.isChooserTargetInfo()) {
+            return;
+        }
+
+        AppPredictor directShareAppPredictor = getAppPredictor(
+                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+        if (directShareAppPredictor == null) {
+            return;
+        }
+        AppTarget appTarget = targetInfo.getDirectShareAppTarget();
+        if (appTarget != null) {
+            // This is a direct share click that was provided by the APS
+            directShareAppPredictor.notifyAppTargetEvent(
+                    new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
+                        .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
+                        .build());
+        }
+    }
+
+    @Nullable
+    private AppPredictor getAppPredictor(UserHandle userHandle) {
+        ProfileRecord record = getProfileRecord(userHandle);
+        // We cannot use APS service when clone profile is present as APS service cannot sort
+        // cross profile targets as of now.
+        return ((record == null) || (getAnnotatedUserHandles().cloneProfileUserHandle != null))
+                ? null : record.appPredictor;
+    }
+
+    /**
+     * Sort intents alphabetically based on display label.
+     */
+    static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
+        Comparator<DisplayResolveInfo> mComparator;
+        AzInfoComparator(Context context) {
+            Collator collator = Collator
+                        .getInstance(context.getResources().getConfiguration().locale);
+            // Adding two stage comparator, first stage compares using displayLabel, next stage
+            //  compares using resolveInfo.userHandle
+            mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
+                    .thenComparingInt(target -> target.getResolveInfo().userHandle.getIdentifier());
+        }
+
+        @Override
+        public int compare(
+                DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
+            return mComparator.compare(lhsp, rhsp);
+        }
+    }
+
+    protected EventLog getEventLog() {
+        return mEventLog;
+    }
+
+    public class ChooserListController extends ResolverListController {
+        public ChooserListController(
+                Context context,
+                PackageManager pm,
+                Intent targetIntent,
+                String referrerPackageName,
+                int launchedFromUid,
+                AbstractResolverComparator resolverComparator,
+                UserHandle queryIntentsAsUser) {
+            super(
+                    context,
+                    pm,
+                    targetIntent,
+                    referrerPackageName,
+                    launchedFromUid,
+                    resolverComparator,
+                    queryIntentsAsUser);
+        }
+
+        @Override
+        public boolean isComponentFiltered(ComponentName name) {
+            return mChooserRequest.getFilteredComponentNames().contains(name);
+        }
+
+        @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,
+            TargetDataLoader targetDataLoader) {
+        ChooserListAdapter chooserListAdapter = createChooserListAdapter(
+                context,
+                payloadIntents,
+                initialIntents,
+                rList,
+                filterLastUsed,
+                createListController(userHandle),
+                userHandle,
+                getTargetIntent(),
+                mChooserRequest,
+                mMaxTargetsPerRow,
+                targetDataLoader);
+
+        return new ChooserGridAdapter(
+                context,
+                new ChooserGridAdapter.ChooserActivityDelegate() {
+                    @Override
+                    public boolean shouldShowTabs() {
+                        return ChooserActivity.this.shouldShowTabs();
+                    }
+
+                    @Override
+                    public View buildContentPreview(ViewGroup parent) {
+                        return createContentPreviewView(parent);
+                    }
+
+                    @Override
+                    public void onTargetSelected(int itemIndex) {
+                        startSelected(itemIndex, false, true);
+                    }
+
+                    @Override
+                    public void onTargetLongPressed(int selectedPosition) {
+                        final TargetInfo longPressedTargetInfo =
+                                mChooserMultiProfilePagerAdapter
+                                .getActiveListAdapter()
+                                .targetInfoForPosition(
+                                        selectedPosition, /* filtered= */ true);
+                        // Only a direct share target or an app target is expected
+                        if (longPressedTargetInfo.isDisplayResolveInfo()
+                                || longPressedTargetInfo.isSelectableTargetInfo()) {
+                            showTargetDetails(longPressedTargetInfo);
+                        }
+                    }
+
+                    @Override
+                    public void updateProfileViewButton(View newButtonFromProfileRow) {
+                        mProfileView = newButtonFromProfileRow;
+                        mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
+                        ChooserActivity.this.updateProfileViewButton();
+                    }
+                },
+                chooserListAdapter,
+                shouldShowContentPreview(),
+                mMaxTargetsPerRow,
+                mFeatureFlags);
+    }
+
+    @VisibleForTesting
+    public ChooserListAdapter createChooserListAdapter(
+            Context context,
+            List<Intent> payloadIntents,
+            Intent[] initialIntents,
+            List<ResolveInfo> rList,
+            boolean filterLastUsed,
+            ResolverListController resolverListController,
+            UserHandle userHandle,
+            Intent targetIntent,
+            ChooserRequestParameters chooserRequest,
+            int maxTargetsPerRow,
+            TargetDataLoader targetDataLoader) {
+        UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
+                && userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle)
+                ? getAnnotatedUserHandles().cloneProfileUserHandle : userHandle;
+        return new ChooserListAdapter(
+                context,
+                payloadIntents,
+                initialIntents,
+                rList,
+                filterLastUsed,
+                createListController(userHandle),
+                userHandle,
+                targetIntent,
+                this,
+                context.getPackageManager(),
+                getEventLog(),
+                chooserRequest,
+                maxTargetsPerRow,
+                initialIntentsUserSpace,
+                targetDataLoader);
+    }
+
+    @Override
+    protected void onWorkProfileStatusUpdated() {
+        UserHandle workUser = getAnnotatedUserHandles().workProfileUserHandle;
+        ProfileRecord record = workUser == null ? null : getProfileRecord(workUser);
+        if (record != null && record.shortcutLoader != null) {
+            record.shortcutLoader.reset();
+        }
+        super.onWorkProfileStatusUpdated();
+    }
+
+    @Override
+    @VisibleForTesting
+    protected ChooserListController createListController(UserHandle userHandle) {
+        AppPredictor appPredictor = getAppPredictor(userHandle);
+        AbstractResolverComparator resolverComparator;
+        if (appPredictor != null) {
+            resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
+                    getReferrerPackageName(), appPredictor, userHandle, getEventLog(),
+                    getIntegratedDeviceComponents().getNearbySharingComponent());
+        } else {
+            resolverComparator =
+                    new ResolverRankerServiceResolverComparator(
+                            this,
+                            getTargetIntent(),
+                            getReferrerPackageName(),
+                            null,
+                            getEventLog(),
+                            getResolverRankerServiceUserHandleList(userHandle),
+                            getIntegratedDeviceComponents().getNearbySharingComponent());
+        }
+
+        return new ChooserListController(
+                this,
+                mPm,
+                getTargetIntent(),
+                getReferrerPackageName(),
+                getAnnotatedUserHandles().userIdOfCallingApp,
+                resolverComparator,
+                getQueryIntentsUser(userHandle));
+    }
+
+    @VisibleForTesting
+    protected ViewModelProvider.Factory createPreviewViewModelFactory() {
+        return PreviewViewModel.Companion.getFactory();
+    }
+
+    private ChooserActionFactory createChooserActionFactory() {
+        return new ChooserActionFactory(
+                this,
+                mChooserRequest,
+                mIntegratedDeviceComponents,
+                getEventLog(),
+                (isExcluded) -> mExcludeSharedText = isExcluded,
+                this::getFirstVisibleImgPreviewView,
+                new ChooserActionFactory.ActionActivityStarter() {
+                    @Override
+                    public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) {
+                        safelyStartActivityAsUser(
+                                targetInfo, getAnnotatedUserHandles().personalProfileUserHandle);
+                        finish();
+                    }
+
+                    @Override
+                    public void safelyStartActivityAsPersonalProfileUserWithSharedElementTransition(
+                            TargetInfo targetInfo, View sharedElement, String sharedElementName) {
+                        ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
+                                ChooserActivity.this, sharedElement, sharedElementName);
+                        safelyStartActivityAsUser(
+                                targetInfo,
+                                getAnnotatedUserHandles().personalProfileUserHandle,
+                                options.toBundle());
+                        // Can't finish right away because the shared element transition may not
+                        // be ready to start.
+                        mFinishWhenStopped = true;
+                    }
+                },
+                (status) -> {
+                    if (status != null) {
+                        setResult(status);
+                    }
+                    finish();
+                });
+    }
+
+    /*
+     * 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();
+        // Skip height calculation if recycler view was scrolled to prevent it inaccurately
+        // calculating the height, as the logic below does not account for the scrolled offset.
+        if (gridAdapter == null || recyclerView == null
+                || recyclerView.computeVerticalScrollOffset() != 0) {
+            return;
+        }
+
+        final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
+        boolean isLayoutUpdated =
+                gridAdapter.calculateChooserTargetWidth(availableWidth)
+                || recyclerView.getAdapter() == null
+                || availableWidth != mCurrAvailableWidth;
+
+        boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
+
+        if (isLayoutUpdated
+                || insetsChanged
+                || 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(
+                        mMaxTargetsPerRow);
+
+                updateTabPadding();
+            }
+
+            UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
+            int currentProfile = getProfileForUser(currentUserHandle);
+            int initialProfile = findSelectedProfile();
+            if (currentProfile != initialProfile) {
+                return;
+            }
+
+            if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
+                return;
+            }
+
+            getMainThreadHandler().post(() -> {
+                if (mResolverDrawerLayout == null || gridAdapter == null) {
+                    return;
+                }
+                int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
+                mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
+                mEnterTransitionAnimationDelegate.markOffsetCalculated();
+                mLastAppliedInsets = mSystemWindowInsets;
+            });
+        }
+    }
+
+    private int calculateDrawerOffset(
+            int top, int bottom, RecyclerView recyclerView, ChooserGridAdapter gridAdapter) {
+
+        int offset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+        int rowsToShow = gridAdapter.getSystemRowCount()
+                + 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);
+            return offset;
+        }
+
+        View stickyContentPreview = findViewById(com.android.internal.R.id.content_preview_container);
+        if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
+            offset += stickyContentPreview.getHeight();
+        }
+
+        if (shouldShowTabs()) {
+            offset += findViewById(com.android.internal.R.id.tabs).getHeight();
+        }
+
+        if (recyclerView.getVisibility() == View.VISIBLE) {
+            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;
+                }
+                rowsToShow--;
+            }
+        } else {
+            ViewGroup currentEmptyStateView = getActiveEmptyStateView();
+            if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
+                offset += currentEmptyStateView.getHeight();
+            }
+        }
+
+        return 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_WORK}, if the given user handle matches work user handle.
+     * Returns {@link #PROFILE_PERSONAL}, otherwise.
+     **/
+    private int getProfileForUser(UserHandle currentUserHandle) {
+        if (currentUserHandle.equals(getAnnotatedUserHandles().workProfileUserHandle)) {
+            return PROFILE_WORK;
+        }
+        // We return personal profile, as it is the default when there is no work profile, personal
+        // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
+        return PROFILE_PERSONAL;
+    }
+
+    private ViewGroup getActiveEmptyStateView() {
+        int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
+        return mChooserMultiProfilePagerAdapter.getEmptyStateView(currentPage);
+    }
+
+    @Override // ResolverListCommunicator
+    public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
+        mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
+        super.onHandlePackagesChanged(listAdapter);
+    }
+
+    @Override
+    protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
+        setupScrollListener();
+        maybeSetupGlobalLayoutListener();
+
+        ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
+        UserHandle listProfileUserHandle = chooserListAdapter.getUserHandle();
+        if (listProfileUserHandle.equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
+            mChooserMultiProfilePagerAdapter.getActiveAdapterView()
+                    .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
+            mChooserMultiProfilePagerAdapter
+                    .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
+        }
+
+        //TODO: move this block inside ChooserListAdapter (should be called when
+        // ResolverListAdapter#mPostListReadyRunnable is executed.
+        if (chooserListAdapter.getDisplayResolveInfoCount() == 0) {
+            chooserListAdapter.notifyDataSetChanged();
+        } else {
+            chooserListAdapter.updateAlphabeticalList();
+        }
+
+        if (rebuildComplete) {
+            long duration = Tracer.INSTANCE.endAppTargetLoadingSection(listProfileUserHandle);
+            if (duration >= 0) {
+                Log.d(TAG, "app target loading time " + duration + " ms");
+            }
+            addCallerChooserTargets();
+            getEventLog().logSharesheetAppLoadComplete();
+            maybeQueryAdditionalPostProcessingTargets(
+                    listProfileUserHandle,
+                    chooserListAdapter.getDisplayResolveInfos());
+            mLatencyTracker.onActionEnd(ACTION_LOAD_SHARE_SHEET);
+        }
+    }
+
+    private void maybeQueryAdditionalPostProcessingTargets(
+            UserHandle userHandle,
+            DisplayResolveInfo[] displayResolveInfos) {
+        ProfileRecord record = getProfileRecord(userHandle);
+        if (record == null || record.shortcutLoader == null) {
+            return;
+        }
+        record.loadingStartTime = SystemClock.elapsedRealtime();
+        record.shortcutLoader.updateAppTargets(displayResolveInfos);
+    }
+
+    @MainThread
+    private void onShortcutsLoaded(UserHandle userHandle, ShortcutLoader.Result result) {
+        if (DEBUG) {
+            Log.d(TAG, "onShortcutsLoaded for user: " + userHandle);
+        }
+        mDirectShareShortcutInfoCache.putAll(result.getDirectShareShortcutInfoCache());
+        mDirectShareAppTargetCache.putAll(result.getDirectShareAppTargetCache());
+        ChooserListAdapter adapter =
+                mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle);
+        if (adapter != null) {
+            for (ShortcutLoader.ShortcutResultInfo resultInfo : result.getShortcutsByApp()) {
+                adapter.addServiceResults(
+                        resultInfo.getAppTarget(),
+                        resultInfo.getShortcuts(),
+                        result.isFromAppPredictor()
+                                ? TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
+                                : TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
+                        mDirectShareShortcutInfoCache,
+                        mDirectShareAppTargetCache);
+            }
+            adapter.completeServiceTargetLoading();
+        }
+
+        if (mMultiProfilePagerAdapter.getActiveListAdapter() == adapter) {
+            long duration = Tracer.INSTANCE.endLaunchToShortcutTrace();
+            if (duration >= 0) {
+                Log.d(TAG, "stat to first shortcut time: " + duration + " ms");
+            }
+        }
+        logDirectShareTargetReceived(userHandle);
+        sendVoiceChoicesIfNeeded();
+        getEventLog().logSharesheetDirectLoadComplete();
+    }
+
+    private void setupScrollListener() {
+        if (mResolverDrawerLayout == null) {
+            return;
+        }
+        int elevatedViewResId = shouldShowTabs() ? com.android.internal.R.id.tabs : com.android.internal.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() {
+                    @Override
+                    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);
+                            }
+                        }
+                    }
+
+                    @Override
+                    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(com.android.internal.R.id.title);
+                        if (titleView != null) {
+                            titleView.setFocusable(true);
+                            titleView.setFocusableInTouchMode(true);
+                            titleView.requestFocus();
+                            titleView.requestAccessibilityFocus();
+                        }
+                    }
+                });
+    }
+
+    /**
+     * 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();
+    }
+
+    private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
+        if (!shouldShowContentPreview()) {
+            return false;
+        }
+        boolean isEmpty = mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+                UserHandle.of(UserHandle.myUserId())).getCount() == 0;
+        return (mFeatureFlags.scrollablePreview() || shouldShowTabs())
+                && (!isEmpty || shouldShowContentPreviewWhenEmpty());
+    }
+
+    /**
+     * This method could be used to override the default behavior when we hide the preview area
+     * when the current tab doesn't have any items.
+     *
+     * @return true if we want to show the content preview area even if the tab for the current
+     *         user is empty
+     */
+    protected boolean shouldShowContentPreviewWhenEmpty() {
+        return false;
+    }
+
+    /**
+     * @return true if we want to show the content preview area
+     */
+    protected boolean shouldShowContentPreview() {
+        return (mChooserRequest != null) && mChooserRequest.isSendActionTarget();
+    }
+
+    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(com.android.internal.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(com.android.internal.R.id.content_preview_container);
+        contentPreviewContainer.setVisibility(View.VISIBLE);
+    }
+
+    private boolean isStickyContentPreviewShowing() {
+        ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
+        return contentPreviewContainer.getVisibility() == View.VISIBLE;
+    }
+
+    private void hideStickyContentPreview() {
+        if (!isStickyContentPreviewShowing()) {
+            return;
+        }
+        ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
+        contentPreviewContainer.setVisibility(View.GONE);
+    }
+
+    private View findRootView() {
+        if (mContentView == null) {
+            mContentView = findViewById(android.R.id.content);
+        }
+        return mContentView;
+    }
+
+    /**
+     * 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() {
+        // This fixes an edge case where after performing a variety of gestures, vertical scrolling
+        // ends up disabled. That's because at some point the old tab's vertical scrolling is
+        // disabled and the new tab's is enabled. For context, see b/159997845
+        setVerticalScrollEnabled(true);
+        if (mResolverDrawerLayout != null) {
+            mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
+        }
+    }
+
+    @Override
+    protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+        if (shouldShowTabs()) {
+            mChooserMultiProfilePagerAdapter
+                    .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
+            mChooserMultiProfilePagerAdapter.setupContainerPadding(
+                    getActiveEmptyStateView().findViewById(com.android.internal.R.id.resolver_empty_state_container));
+        }
+
+        WindowInsets result = super.onApplyWindowInsets(v, insets);
+        if (mResolverDrawerLayout != null) {
+            mResolverDrawerLayout.requestLayout();
+        }
+        return result;
+    }
+
+    private void setHorizontalScrollingEnabled(boolean enabled) {
+        ResolverViewPager viewPager = findViewById(com.android.internal.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);
+            }
+        }
+    }
+
+    @Override
+    protected void maybeLogProfileChange() {
+        getEventLog().logSharesheetProfileChanged();
+    }
+
+    private static class ProfileRecord {
+        /** The {@link AppPredictor} for this profile, if any. */
+        @Nullable
+        public final AppPredictor appPredictor;
+        /**
+         * null if we should not load shortcuts.
+         */
+        @Nullable
+        public final ShortcutLoader shortcutLoader;
+        public long loadingStartTime;
+
+        private ProfileRecord(
+                @Nullable AppPredictor appPredictor,
+                @Nullable ShortcutLoader shortcutLoader) {
+            this.appPredictor = appPredictor;
+            this.shortcutLoader = shortcutLoader;
+        }
+
+        public void destroy() {
+            if (appPredictor != null) {
+                appPredictor.destroy();
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/intentresolver/v2/ChooserSelector.kt b/java/src/com/android/intentresolver/v2/ChooserSelector.kt
new file mode 100644
index 0000000..378bc06
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/ChooserSelector.kt
@@ -0,0 +1,36 @@
+package com.android.intentresolver.v2
+
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import com.android.intentresolver.FeatureFlags
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint(BroadcastReceiver::class)
+class ChooserSelector : Hilt_ChooserSelector() {
+
+    @Inject lateinit var featureFlags: FeatureFlags
+
+    override fun onReceive(context: Context, intent: Intent) {
+        super.onReceive(context, intent)
+        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
+            context.packageManager.setComponentEnabledSetting(
+                ComponentName(CHOOSER_PACKAGE, CHOOSER_PACKAGE + CHOOSER_CLASS),
+                if (featureFlags.modularFramework()) {
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                } else {
+                    PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                },
+                /* flags = */ 0,
+            )
+        }
+    }
+
+    companion object {
+        private const val CHOOSER_PACKAGE = "com.android.intentresolver"
+        private const val CHOOSER_CLASS = ".v2.ChooserActivity"
+    }
+}
diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java
new file mode 100644
index 0000000..dd6842a
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java
@@ -0,0 +1,2426 @@
+/*
+ * 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.intentresolver.v2;
+
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
+
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.UiThread;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.VoiceInteractor.PickOptionRequest;
+import android.app.VoiceInteractor.PickOptionRequest.Option;
+import android.app.VoiceInteractor.Prompt;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.PermissionChecker;
+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.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Insets;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PatternMatcher;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.stats.devicepolicy.DevicePolicyEnums;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.Space;
+import android.widget.TabHost;
+import android.widget.TabWidget;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager.widget.ViewPager;
+
+import com.android.intentresolver.AnnotatedUserHandles;
+import com.android.intentresolver.MultiProfilePagerAdapter;
+import com.android.intentresolver.MultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
+import com.android.intentresolver.MultiProfilePagerAdapter.Profile;
+import com.android.intentresolver.R;
+import com.android.intentresolver.ResolverListAdapter;
+import com.android.intentresolver.ResolverListController;
+import com.android.intentresolver.ResolverMultiProfilePagerAdapter;
+import com.android.intentresolver.WorkProfileAvailabilityManager;
+import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.emptystate.CompositeEmptyStateProvider;
+import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
+import com.android.intentresolver.emptystate.EmptyState;
+import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.intentresolver.emptystate.NoAppsAvailableEmptyStateProvider;
+import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider;
+import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
+import com.android.intentresolver.emptystate.WorkProfilePausedEmptyStateProvider;
+import com.android.intentresolver.icons.DefaultTargetDataLoader;
+import com.android.intentresolver.icons.TargetDataLoader;
+import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
+import com.android.intentresolver.widget.ResolverDrawerLayout;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.util.LatencyTracker;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+
+/**
+ * This is a copy of ResolverActivity to support IntentResolver's ChooserActivity. This code is
+ * *not* the resolver that is actually triggered by the system right now (you want
+ * frameworks/base/core/java/com/android/internal/app/ResolverActivity.java for that), the full
+ * migration is not complete.
+ */
+@UiThread
+public class ResolverActivity extends FragmentActivity implements
+        ResolverListAdapter.ResolverListCommunicator {
+
+    public ResolverActivity() {
+        mIsIntentPicker = getClass().equals(ResolverActivity.class);
+    }
+
+    protected ResolverActivity(boolean isIntentPicker) {
+        mIsIntentPicker = isIntentPicker;
+    }
+
+    /**
+     * Whether to enable a launch mode that is safe to use when forwarding intents received from
+     * applications and running in system processes.  This mode uses Activity.startActivityAsCaller
+     * instead of the normal Activity.startActivity for launching the activity selected
+     * by the user.
+     */
+    private boolean mSafeForwardingMode;
+
+    private Button mAlwaysButton;
+    private Button mOnceButton;
+    protected View mProfileView;
+    private int mLastSelected = AbsListView.INVALID_POSITION;
+    private boolean mResolvingHome = false;
+    private String mProfileSwitchMessage;
+    private int mLayoutId;
+    @VisibleForTesting
+    protected final ArrayList<Intent> mIntents = new ArrayList<>();
+    private PickTargetOptionRequest mPickOptionRequest;
+    private String mReferrerPackage;
+    private CharSequence mTitle;
+    private int mDefaultTitleResId;
+    // Expected to be true if this object is ResolverActivity or is ResolverWrapperActivity.
+    private final boolean mIsIntentPicker;
+
+    // Whether or not this activity supports choosing a default handler for the intent.
+    @VisibleForTesting
+    protected boolean mSupportsAlwaysUseOption;
+    protected ResolverDrawerLayout mResolverDrawerLayout;
+    protected PackageManager mPm;
+
+    private static final String TAG = "ResolverActivity";
+    private static final boolean DEBUG = false;
+    private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key";
+
+    private boolean mRegistered;
+
+    protected Insets mSystemWindowInsets = null;
+    private Space mFooterSpacer = null;
+
+    /** See {@link #setRetainInOnStop}. */
+    private boolean mRetainInOnStop;
+
+    protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver";
+    protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser";
+
+    /** Tracks if we should ignore future broadcasts telling us the work profile is enabled */
+    private boolean mWorkProfileHasBeenEnabled = false;
+
+    private static final String TAB_TAG_PERSONAL = "personal";
+    private static final String TAB_TAG_WORK = "work";
+
+    private PackageMonitor mPersonalPackageMonitor;
+    private PackageMonitor mWorkPackageMonitor;
+
+    private TargetDataLoader mTargetDataLoader;
+
+    @VisibleForTesting
+    protected MultiProfilePagerAdapter mMultiProfilePagerAdapter;
+
+    protected WorkProfileAvailabilityManager mWorkProfileAvailability;
+
+    // Intent extra for connected audio devices
+    public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
+
+    /**
+     * Integer extra to indicate which profile should be automatically selected.
+     * <p>Can only be used if there is a work profile.
+     * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
+     */
+    protected static final String EXTRA_SELECTED_PROFILE =
+            "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
+
+    /**
+     * {@link UserHandle} extra to indicate the user of the user that the starting intent
+     * originated from.
+     * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()},
+     * as there are edge cases when the intent resolver is launched in the other profile.
+     * For example, when we have 0 resolved apps in current profile and multiple resolved
+     * apps in the other profile, opening a link from the current profile launches the intent
+     * resolver in the other one. b/148536209 for more info.
+     */
+    static final String EXTRA_CALLING_USER =
+            "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER";
+
+    protected static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL;
+    protected static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK;
+
+    private UserHandle mHeaderCreatorUser;
+
+    // User handle annotations are lazy-initialized to ensure that they're computed exactly once
+    // (even though they can't be computed prior to activity creation).
+    // TODO: use a less ad-hoc pattern for lazy initialization (by switching to Dagger or
+    // introducing a common `LazySingletonSupplier` API, etc), and/or migrate all dependents to a
+    // new component whose lifecycle is limited to the "created" Activity (so that we can just hold
+    // the annotations as a `final` ivar, which is a better way to show immutability).
+    private Supplier<AnnotatedUserHandles> mLazyAnnotatedUserHandles = () -> {
+        final AnnotatedUserHandles result = computeAnnotatedUserHandles();
+        mLazyAnnotatedUserHandles = () -> result;
+        return result;
+    };
+
+    // This method is called exactly once during creation to compute the immutable annotations
+    // accessible through the lazy supplier {@link mLazyAnnotatedUserHandles}.
+    // TODO: this is only defined so that tests can provide an override that injects fake
+    // annotations. Dagger could provide a cleaner model for our testing/injection requirements.
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected AnnotatedUserHandles computeAnnotatedUserHandles() {
+        return AnnotatedUserHandles.forShareActivity(this);
+    }
+
+    @Nullable
+    private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+
+    protected final LatencyTracker mLatencyTracker = getLatencyTracker();
+
+    private enum ActionTitle {
+        VIEW(Intent.ACTION_VIEW,
+                R.string.whichViewApplication,
+                R.string.whichViewApplicationNamed,
+                R.string.whichViewApplicationLabel),
+        EDIT(Intent.ACTION_EDIT,
+                R.string.whichEditApplication,
+                R.string.whichEditApplicationNamed,
+                R.string.whichEditApplicationLabel),
+        SEND(Intent.ACTION_SEND,
+                R.string.whichSendApplication,
+                R.string.whichSendApplicationNamed,
+                R.string.whichSendApplicationLabel),
+        SENDTO(Intent.ACTION_SENDTO,
+                R.string.whichSendToApplication,
+                R.string.whichSendToApplicationNamed,
+                R.string.whichSendToApplicationLabel),
+        SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
+                R.string.whichSendApplication,
+                R.string.whichSendApplicationNamed,
+                R.string.whichSendApplicationLabel),
+        CAPTURE_IMAGE(MediaStore.ACTION_IMAGE_CAPTURE,
+                R.string.whichImageCaptureApplication,
+                R.string.whichImageCaptureApplicationNamed,
+                R.string.whichImageCaptureApplicationLabel),
+        DEFAULT(null,
+                R.string.whichApplication,
+                R.string.whichApplicationNamed,
+                R.string.whichApplicationLabel),
+        HOME(Intent.ACTION_MAIN,
+                R.string.whichHomeApplication,
+                R.string.whichHomeApplicationNamed,
+                R.string.whichHomeApplicationLabel);
+
+        // titles for layout that deals with http(s) intents
+        public static final int BROWSABLE_TITLE_RES = R.string.whichOpenLinksWith;
+        public static final int BROWSABLE_HOST_TITLE_RES = R.string.whichOpenHostLinksWith;
+        public static final int BROWSABLE_HOST_APP_TITLE_RES = R.string.whichOpenHostLinksWithApp;
+        public static final int BROWSABLE_APP_TITLE_RES = R.string.whichOpenLinksWithApp;
+
+        public final String action;
+        public final int titleRes;
+        public final int namedTitleRes;
+        public final @StringRes int labelRes;
+
+        ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes) {
+            this.action = action;
+            this.titleRes = titleRes;
+            this.namedTitleRes = namedTitleRes;
+            this.labelRes = labelRes;
+        }
+
+        public static ActionTitle forAction(String action) {
+            for (ActionTitle title : values()) {
+                if (title != HOME && action != null && action.equals(title.action)) {
+                    return title;
+                }
+            }
+            return DEFAULT;
+        }
+    }
+
+    protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
+        return new PackageMonitor() {
+            @Override
+            public void onSomePackagesChanged() {
+                listAdapter.handlePackagesChanged();
+                updateProfileViewButton();
+            }
+
+            @Override
+            public boolean onPackageChanged(String packageName, int uid, String[] components) {
+                // We care about all package changes, not just the whole package itself which is
+                // default behavior.
+                return true;
+            }
+        };
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Use a specialized prompt when we're handling the 'Home' app startActivity()
+        final Intent intent = makeMyIntent();
+        final Set<String> categories = intent.getCategories();
+        if (Intent.ACTION_MAIN.equals(intent.getAction())
+                && categories != null
+                && categories.size() == 1
+                && categories.contains(Intent.CATEGORY_HOME)) {
+            // Note: this field is not set to true in the compatibility version.
+            mResolvingHome = true;
+        }
+
+        onCreate(
+                savedInstanceState,
+                intent,
+                /* additionalTargets= */ null,
+                /* title= */ null,
+                /* defaultTitleRes= */ 0,
+                /* initialIntents= */ null,
+                /* resolutionList= */ null,
+                /* supportsAlwaysUseOption= */ true,
+                createIconLoader(),
+                /* safeForwardingMode= */ true);
+    }
+
+    /**
+     * Compatibility version for other bundled services that use this overload without
+     * a default title resource
+     */
+    protected void onCreate(
+            Bundle savedInstanceState,
+            Intent intent,
+            CharSequence title,
+            Intent[] initialIntents,
+            List<ResolveInfo> resolutionList,
+            boolean supportsAlwaysUseOption,
+            boolean safeForwardingMode) {
+        onCreate(
+                savedInstanceState,
+                intent,
+                null,
+                title,
+                0,
+                initialIntents,
+                resolutionList,
+                supportsAlwaysUseOption,
+                createIconLoader(),
+                safeForwardingMode);
+    }
+
+    protected void onCreate(
+            Bundle savedInstanceState,
+            Intent intent,
+            Intent[] additionalTargets,
+            CharSequence title,
+            int defaultTitleRes,
+            Intent[] initialIntents,
+            List<ResolveInfo> resolutionList,
+            boolean supportsAlwaysUseOption,
+            TargetDataLoader targetDataLoader,
+            boolean safeForwardingMode) {
+        setTheme(appliedThemeResId());
+        super.onCreate(savedInstanceState);
+
+        // Determine whether we should show that intent is forwarded
+        // from managed profile to owner or other way around.
+        setProfileSwitchMessage(intent.getContentUserHint());
+
+        // Force computation of user handle annotations in order to validate the caller ID. (See the
+        // associated TODO comment to explain why this is structured as a lazy computation.)
+        AnnotatedUserHandles unusedReferenceToHandles = mLazyAnnotatedUserHandles.get();
+
+        mWorkProfileAvailability = createWorkProfileAvailabilityManager();
+
+        mPm = getPackageManager();
+
+        mReferrerPackage = getReferrerPackageName();
+
+        // The initial intent must come before any other targets that are to be added.
+        mIntents.add(0, new Intent(intent));
+        if (additionalTargets != null) {
+            Collections.addAll(mIntents, additionalTargets);
+        }
+
+        mTitle = title;
+        mDefaultTitleResId = defaultTitleRes;
+
+        mSupportsAlwaysUseOption = supportsAlwaysUseOption;
+        mSafeForwardingMode = safeForwardingMode;
+        mTargetDataLoader = targetDataLoader;
+
+        // The last argument of createResolverListAdapter is whether to do special handling
+        // of the last used choice to highlight it in the list.  We need to always
+        // turn this off when running under voice interaction, since it results in
+        // a more complicated UI that the current voice interaction flow is not able
+        // to handle. We also turn it off when the work tab is shown to simplify the UX.
+        // We also turn it off when clonedProfile is present on the device, because we might have
+        // different "last chosen" activities in the different profiles, and PackageManager doesn't
+        // provide any more information to help us select between them.
+        boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
+                && !shouldShowTabs() && !hasCloneProfile();
+        mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
+                initialIntents, resolutionList, filterLastUsed, targetDataLoader);
+        if (configureContentView(targetDataLoader)) {
+            return;
+        }
+
+        mPersonalPackageMonitor = createPackageMonitor(
+                mMultiProfilePagerAdapter.getPersonalListAdapter());
+        mPersonalPackageMonitor.register(
+                this, getMainLooper(), getAnnotatedUserHandles().personalProfileUserHandle, false);
+        if (shouldShowTabs()) {
+            mWorkPackageMonitor = createPackageMonitor(
+                    mMultiProfilePagerAdapter.getWorkListAdapter());
+            mWorkPackageMonitor.register(
+                    this, getMainLooper(), getAnnotatedUserHandles().workProfileUserHandle, false);
+        }
+
+        mRegistered = true;
+
+        final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel);
+        if (rdl != null) {
+            rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
+                @Override
+                public void onDismissed() {
+                    finish();
+                }
+            });
+
+            boolean hasTouchScreen = getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
+
+            if (isVoiceInteraction() || !hasTouchScreen) {
+                rdl.setCollapsed(false);
+            }
+
+            rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+            rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
+
+            mResolverDrawerLayout = rdl;
+        }
+
+        mProfileView = findViewById(com.android.internal.R.id.profile_button);
+        if (mProfileView != null) {
+            mProfileView.setOnClickListener(this::onProfileClick);
+            updateProfileViewButton();
+        }
+
+        final Set<String> categories = intent.getCategories();
+        MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
+                ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
+                : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
+                intent.getAction() + ":" + intent.getType() + ":"
+                        + (categories != null ? Arrays.toString(categories.toArray()) : ""));
+    }
+
+    protected MultiProfilePagerAdapter createMultiProfilePagerAdapter(
+            Intent[] initialIntents,
+            List<ResolveInfo> resolutionList,
+            boolean filterLastUsed,
+            TargetDataLoader targetDataLoader) {
+        MultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null;
+        if (shouldShowTabs()) {
+            resolverMultiProfilePagerAdapter =
+                    createResolverMultiProfilePagerAdapterForTwoProfiles(
+                            initialIntents, resolutionList, filterLastUsed, targetDataLoader);
+        } else {
+            resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForOneProfile(
+                    initialIntents, resolutionList, filterLastUsed, targetDataLoader);
+        }
+        return resolverMultiProfilePagerAdapter;
+    }
+
+    protected EmptyStateProvider createBlockerEmptyStateProvider() {
+        final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
+
+        if (!shouldShowNoCrossProfileIntentsEmptyState) {
+            // Implementation that doesn't show any blockers
+            return new EmptyStateProvider() {};
+        }
+
+        final EmptyState noWorkToPersonalEmptyState =
+                new DevicePolicyBlockerEmptyState(
+                        /* context= */ this,
+                        /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                        /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                        /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL,
+                        /* defaultSubtitleResource= */
+                        R.string.resolver_cant_access_personal_apps_explanation,
+                        /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+                        /* devicePolicyEventCategory= */
+                                ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+        final EmptyState noPersonalToWorkEmptyState =
+                new DevicePolicyBlockerEmptyState(
+                        /* context= */ this,
+                        /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                        /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+                        /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK,
+                        /* defaultSubtitleResource= */
+                        R.string.resolver_cant_access_work_apps_explanation,
+                        /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+                        /* devicePolicyEventCategory= */
+                                ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+        return new NoCrossProfileEmptyStateProvider(
+                getAnnotatedUserHandles().personalProfileUserHandle,
+                noWorkToPersonalEmptyState,
+                noPersonalToWorkEmptyState,
+                createCrossProfileIntentsChecker(),
+                getAnnotatedUserHandles().tabOwnerUserHandleForLaunch);
+    }
+
+    protected int appliedThemeResId() {
+        return R.style.Theme_DeviceDefault_Resolver;
+    }
+
+    /**
+     * Numerous layouts are supported, each with optional ViewGroups.
+     * Make sure the inset gets added to the correct View, using
+     * a footer for Lists so it can properly scroll under the navbar.
+     */
+    protected boolean shouldAddFooterView() {
+        if (useLayoutWithDefault()) return true;
+
+        View buttonBar = findViewById(com.android.internal.R.id.button_bar);
+        if (buttonBar == null || buttonBar.getVisibility() == View.GONE) return true;
+
+        return false;
+    }
+
+    protected void applyFooterView(int height) {
+        if (mFooterSpacer == null) {
+            mFooterSpacer = new Space(getApplicationContext());
+        } else {
+            ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
+                .getActiveAdapterView().removeFooterView(mFooterSpacer);
+        }
+        mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
+                                                                   mSystemWindowInsets.bottom));
+        ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
+            .getActiveAdapterView().addFooterView(mFooterSpacer);
+    }
+
+    protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+        mSystemWindowInsets = insets.getSystemWindowInsets();
+
+        mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
+                mSystemWindowInsets.right, 0);
+
+        resetButtonBar();
+
+        if (shouldUseMiniResolver()) {
+            View buttonContainer = findViewById(com.android.internal.R.id.button_bar_container);
+            buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+                    + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing));
+        }
+
+        // Need extra padding so the list can fully scroll up
+        if (shouldAddFooterView()) {
+            applyFooterView(mSystemWindowInsets.bottom);
+        }
+
+        return insets.consumeSystemWindowInsets();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
+        if (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault()
+                && !shouldUseMiniResolver()) {
+            updateIntentPickerPaddings();
+        }
+
+        if (mSystemWindowInsets != null) {
+            mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
+                    mSystemWindowInsets.right, 0);
+        }
+    }
+
+    public int getLayoutResource() {
+        return R.layout.resolver_list;
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        final Window window = this.getWindow();
+        final WindowManager.LayoutParams attrs = window.getAttributes();
+        attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+        window.setAttributes(attrs);
+
+        if (mRegistered) {
+            mPersonalPackageMonitor.unregister();
+            if (mWorkPackageMonitor != null) {
+                mWorkPackageMonitor.unregister();
+            }
+            mRegistered = false;
+        }
+        final Intent intent = getIntent();
+        if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
+                && !mResolvingHome && !mRetainInOnStop) {
+            // This resolver is in the unusual situation where it has been
+            // launched at the top of a new task.  We don't let it be added
+            // to the recent tasks shown to the user, and we need to make sure
+            // that each time we are launched we get the correct launching
+            // uid (not re-using the same resolver from an old launching uid),
+            // so we will now finish ourself since being no longer visible,
+            // the user probably can't get back to us.
+            if (!isChangingConfigurations()) {
+                finish();
+            }
+        }
+        // TODO: should we clean up the work-profile manager before we potentially finish() above?
+        mWorkProfileAvailability.unregisterWorkProfileStateReceiver(this);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (!isChangingConfigurations() && mPickOptionRequest != null) {
+            mPickOptionRequest.cancel();
+        }
+        if (mMultiProfilePagerAdapter != null
+                && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
+            mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
+        }
+    }
+
+    public void onButtonClick(View v) {
+        final int id = v.getId();
+        ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
+        ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
+        int which = currentListAdapter.hasFilteredItem()
+                ? currentListAdapter.getFilteredPosition()
+                : listView.getCheckedItemPosition();
+        boolean hasIndexBeenFiltered = !currentListAdapter.hasFilteredItem();
+        startSelected(which, id == com.android.internal.R.id.button_always, hasIndexBeenFiltered);
+    }
+
+    public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
+        if (isFinishing()) {
+            return;
+        }
+        ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
+                .resolveInfoForPosition(which, hasIndexBeenFiltered);
+        if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
+            Toast.makeText(this,
+                    getWorkProfileNotSupportedMsg(
+                            ri.activityInfo.loadLabel(getPackageManager()).toString()),
+                    Toast.LENGTH_LONG).show();
+            return;
+        }
+
+        TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+                .targetInfoForPosition(which, hasIndexBeenFiltered);
+        if (target == null) {
+            return;
+        }
+        if (onTargetSelected(target, always)) {
+            if (always && mSupportsAlwaysUseOption) {
+                MetricsLogger.action(
+                        this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
+            } else if (mSupportsAlwaysUseOption) {
+                MetricsLogger.action(
+                        this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
+            } else {
+                MetricsLogger.action(
+                        this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
+            }
+            MetricsLogger.action(this,
+                    mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
+                            ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
+                            : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
+            finish();
+        }
+    }
+
+    /**
+     * Replace me in subclasses!
+     */
+    @Override // ResolverListCommunicator
+    public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
+        return defIntent;
+    }
+
+    protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
+        final ItemClickListener listener = new ItemClickListener();
+        setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
+        if (shouldShowTabs() && mIsIntentPicker) {
+            final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel);
+            if (rdl != null) {
+                rdl.setMaxCollapsedHeight(getResources()
+                        .getDimensionPixelSize(useLayoutWithDefault()
+                                ? R.dimen.resolver_max_collapsed_height_with_default_with_tabs
+                                : R.dimen.resolver_max_collapsed_height_with_tabs));
+            }
+        }
+    }
+
+    protected boolean onTargetSelected(TargetInfo target, boolean always) {
+        final ResolveInfo ri = target.getResolveInfo();
+        final Intent intent = target != null ? target.getResolvedIntent() : null;
+
+        if (intent != null && (mSupportsAlwaysUseOption
+                || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem())
+                && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) {
+            // Build a reasonable intent filter, based on what matched.
+            IntentFilter filter = new IntentFilter();
+            Intent filterIntent;
+
+            if (intent.getSelector() != null) {
+                filterIntent = intent.getSelector();
+            } else {
+                filterIntent = intent;
+            }
+
+            String action = filterIntent.getAction();
+            if (action != null) {
+                filter.addAction(action);
+            }
+            Set<String> categories = filterIntent.getCategories();
+            if (categories != null) {
+                for (String cat : categories) {
+                    filter.addCategory(cat);
+                }
+            }
+            filter.addCategory(Intent.CATEGORY_DEFAULT);
+
+            int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
+            Uri data = filterIntent.getData();
+            if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
+                String mimeType = filterIntent.resolveType(this);
+                if (mimeType != null) {
+                    try {
+                        filter.addDataType(mimeType);
+                    } catch (IntentFilter.MalformedMimeTypeException e) {
+                        Log.w("ResolverActivity", e);
+                        filter = null;
+                    }
+                }
+            }
+            if (data != null && data.getScheme() != null) {
+                // We need the data specification if there was no type,
+                // OR if the scheme is not one of our magical "file:"
+                // or "content:" schemes (see IntentFilter for the reason).
+                if (cat != IntentFilter.MATCH_CATEGORY_TYPE
+                        || (!"file".equals(data.getScheme())
+                                && !"content".equals(data.getScheme()))) {
+                    filter.addDataScheme(data.getScheme());
+
+                    // Look through the resolved filter to determine which part
+                    // of it matched the original Intent.
+                    Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
+                    if (pIt != null) {
+                        String ssp = data.getSchemeSpecificPart();
+                        while (ssp != null && pIt.hasNext()) {
+                            PatternMatcher p = pIt.next();
+                            if (p.match(ssp)) {
+                                filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
+                                break;
+                            }
+                        }
+                    }
+                    Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
+                    if (aIt != null) {
+                        while (aIt.hasNext()) {
+                            IntentFilter.AuthorityEntry a = aIt.next();
+                            if (a.match(data) >= 0) {
+                                int port = a.getPort();
+                                filter.addDataAuthority(a.getHost(),
+                                        port >= 0 ? Integer.toString(port) : null);
+                                break;
+                            }
+                        }
+                    }
+                    pIt = ri.filter.pathsIterator();
+                    if (pIt != null) {
+                        String path = data.getPath();
+                        while (path != null && pIt.hasNext()) {
+                            PatternMatcher p = pIt.next();
+                            if (p.match(path)) {
+                                filter.addDataPath(p.getPath(), p.getType());
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (filter != null) {
+                final int N = mMultiProfilePagerAdapter.getActiveListAdapter()
+                        .getUnfilteredResolveList().size();
+                ComponentName[] set;
+                // If we don't add back in the component for forwarding the intent to a managed
+                // profile, the preferred activity may not be updated correctly (as the set of
+                // components we tell it we knew about will have changed).
+                final boolean needToAddBackProfileForwardingComponent =
+                        mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null;
+                if (!needToAddBackProfileForwardingComponent) {
+                    set = new ComponentName[N];
+                } else {
+                    set = new ComponentName[N + 1];
+                }
+
+                int bestMatch = 0;
+                for (int i=0; i<N; i++) {
+                    ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter()
+                            .getUnfilteredResolveList().get(i).getResolveInfoAt(0);
+                    set[i] = new ComponentName(r.activityInfo.packageName,
+                            r.activityInfo.name);
+                    if (r.match > bestMatch) bestMatch = r.match;
+                }
+
+                if (needToAddBackProfileForwardingComponent) {
+                    set[N] = mMultiProfilePagerAdapter.getActiveListAdapter()
+                            .getOtherProfile().getResolvedComponentName();
+                    final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter()
+                            .getOtherProfile().getResolveInfo().match;
+                    if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
+                }
+
+                if (always) {
+                    final int userId = getUserId();
+                    final PackageManager pm = getPackageManager();
+
+                    // Set the preferred Activity
+                    pm.addUniquePreferredActivity(filter, bestMatch, set, intent.getComponent());
+
+                    if (ri.handleAllWebDataURI) {
+                        // Set default Browser if needed
+                        final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId);
+                        if (TextUtils.isEmpty(packageName)) {
+                            pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId);
+                        }
+                    }
+                } else {
+                    try {
+                        mMultiProfilePagerAdapter.getActiveListAdapter()
+                                .mResolverListController.setLastChosen(intent, filter, bestMatch);
+                    } catch (RemoteException re) {
+                        Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
+                    }
+                }
+            }
+        }
+
+        if (target != null) {
+            safelyStartActivity(target);
+
+            // Rely on the ActivityManager to pop up a dialog regarding app suspension
+            // and return false
+            if (target.isSuspended()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public void onActivityStarted(TargetInfo cti) {
+        // Do nothing
+    }
+
+    @Override // ResolverListCommunicator
+    public boolean shouldGetActivityMetadata() {
+        return false;
+    }
+
+    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+        return !target.isSuspended();
+    }
+
+    // TODO: this method takes an unused `UserHandle` because the override in `ChooserActivity` uses
+    // that data to set up other components as dependencies of the controller. In reality, these
+    // methods don't require polymorphism, because they're only invoked from within their respective
+    // concrete class; `ResolverActivity` will never call this method expecting to get a
+    // `ChooserListController` (subclass) result, because `ResolverActivity` only invokes this
+    // method as part of handling `createMultiProfilePagerAdapter()`, which is itself overridden in
+    // `ChooserActivity`. A future refactoring could better express the coupling between the adapter
+    // and controller types; in the meantime, structuring as an override (with matching signatures)
+    // shows that these methods are *structurally* related, and helps to prevent any regressions in
+    // the future if resolver *were* to make any (non-overridden) calls to a version that used a
+    // different signature (and thus didn't return the subclass type).
+    @VisibleForTesting
+    protected ResolverListController createListController(UserHandle userHandle) {
+        ResolverRankerServiceResolverComparator resolverComparator =
+                new ResolverRankerServiceResolverComparator(
+                        this,
+                        getTargetIntent(),
+                        getReferrerPackageName(),
+                        null,
+                        null,
+                        getResolverRankerServiceUserHandleList(userHandle),
+                        null);
+        return new ResolverListController(
+                this,
+                mPm,
+                getTargetIntent(),
+                getReferrerPackageName(),
+                getAnnotatedUserHandles().userIdOfCallingApp,
+                resolverComparator,
+                getQueryIntentsUser(userHandle));
+    }
+
+    /**
+     * Finishing procedures to be performed after the list has been rebuilt.
+     * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
+     * @param rebuildCompleted
+     * @return <code>true</code> if the activity is finishing and creation should halt.
+     */
+    protected boolean postRebuildList(boolean rebuildCompleted) {
+        return postRebuildListInternal(rebuildCompleted);
+    }
+
+    void onHorizontalSwipeStateChanged(int state) {}
+
+    /**
+     * Callback called when user changes the profile tab.
+     * <p>This method is intended to be overridden by subclasses.
+     */
+    protected void onProfileTabSelected() { }
+
+    /**
+     * Add a label to signify that the user can pick a different app.
+     * @param adapter The adapter used to provide data to item views.
+     */
+    public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
+        final boolean useHeader = adapter.hasFilteredItem();
+        if (useHeader) {
+            FrameLayout stub = findViewById(com.android.internal.R.id.stub);
+            stub.setVisibility(View.VISIBLE);
+            TextView textView = (TextView) LayoutInflater.from(this).inflate(
+                    R.layout.resolver_different_item_header, null, false);
+            if (shouldShowTabs()) {
+                textView.setGravity(Gravity.CENTER);
+            }
+            stub.addView(textView);
+        }
+    }
+
+    protected void resetButtonBar() {
+        if (!mSupportsAlwaysUseOption) {
+            return;
+        }
+        final ViewGroup buttonLayout = findViewById(com.android.internal.R.id.button_bar);
+        if (buttonLayout == null) {
+            Log.e(TAG, "Layout unexpectedly does not have a button bar");
+            return;
+        }
+        ResolverListAdapter activeListAdapter =
+                mMultiProfilePagerAdapter.getActiveListAdapter();
+        View buttonBarDivider = findViewById(com.android.internal.R.id.resolver_button_bar_divider);
+        if (!useLayoutWithDefault()) {
+            int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+            buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+                    buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+                            R.dimen.resolver_button_bar_spacing) + inset);
+        }
+        if (activeListAdapter.isTabLoaded()
+                && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)
+                && !useLayoutWithDefault()) {
+            buttonLayout.setVisibility(View.INVISIBLE);
+            if (buttonBarDivider != null) {
+                buttonBarDivider.setVisibility(View.INVISIBLE);
+            }
+            setButtonBarIgnoreOffset(/* ignoreOffset */ false);
+            return;
+        }
+        if (buttonBarDivider != null) {
+            buttonBarDivider.setVisibility(View.VISIBLE);
+        }
+        buttonLayout.setVisibility(View.VISIBLE);
+        setButtonBarIgnoreOffset(/* ignoreOffset */ true);
+
+        mOnceButton = (Button) buttonLayout.findViewById(com.android.internal.R.id.button_once);
+        mAlwaysButton = (Button) buttonLayout.findViewById(com.android.internal.R.id.button_always);
+
+        resetAlwaysOrOnceButtonBar();
+    }
+
+    protected String getMetricsCategory() {
+        return METRICS_CATEGORY_RESOLVER;
+    }
+
+    @Override // ResolverListCommunicator
+    public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
+        if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
+            if (listAdapter.getUserHandle().equals(getAnnotatedUserHandles().workProfileUserHandle)
+                    && mWorkProfileAvailability.isWaitingToEnableWorkProfile()) {
+                // We have just turned on the work profile and entered the pass code to start it,
+                // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
+                // point in reloading the list now, since the work profile user is still
+                // turning on.
+                return;
+            }
+            boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true);
+            if (listRebuilt) {
+                ResolverListAdapter activeListAdapter =
+                        mMultiProfilePagerAdapter.getActiveListAdapter();
+                activeListAdapter.notifyDataSetChanged();
+                if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) {
+                    // We no longer have any items...  just finish the activity.
+                    finish();
+                }
+            }
+        } else {
+            mMultiProfilePagerAdapter.clearInactiveProfileCache();
+        }
+    }
+
+    protected void maybeLogProfileChange() {}
+
+    // @NonFinalForTesting
+    @VisibleForTesting
+    protected MyUserIdProvider createMyUserIdProvider() {
+        return new MyUserIdProvider();
+    }
+
+    // @NonFinalForTesting
+    @VisibleForTesting
+    protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+        return new CrossProfileIntentsChecker(getContentResolver());
+    }
+
+    protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() {
+        return new WorkProfileAvailabilityManager(
+                getSystemService(UserManager.class),
+                getAnnotatedUserHandles().workProfileUserHandle,
+                this::onWorkProfileStatusUpdated);
+    }
+
+    protected void onWorkProfileStatusUpdated() {
+        if (mMultiProfilePagerAdapter.getCurrentUserHandle().equals(
+                getAnnotatedUserHandles().workProfileUserHandle)) {
+            mMultiProfilePagerAdapter.rebuildActiveTab(true);
+        } else {
+            mMultiProfilePagerAdapter.clearInactiveProfileCache();
+        }
+    }
+
+    // @NonFinalForTesting
+    @VisibleForTesting
+    protected ResolverListAdapter createResolverListAdapter(
+            Context context,
+            List<Intent> payloadIntents,
+            Intent[] initialIntents,
+            List<ResolveInfo> resolutionList,
+            boolean filterLastUsed,
+            UserHandle userHandle,
+            TargetDataLoader targetDataLoader) {
+        UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
+                && userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle)
+                ? getAnnotatedUserHandles().cloneProfileUserHandle : userHandle;
+        return new ResolverListAdapter(
+                context,
+                payloadIntents,
+                initialIntents,
+                resolutionList,
+                filterLastUsed,
+                createListController(userHandle),
+                userHandle,
+                getTargetIntent(),
+                this,
+                initialIntentsUserSpace,
+                targetDataLoader);
+    }
+
+    private TargetDataLoader createIconLoader() {
+        Intent startIntent = getIntent();
+        boolean isAudioCaptureDevice =
+                startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
+        return new DefaultTargetDataLoader(this, getLifecycle(), isAudioCaptureDevice);
+    }
+
+    private LatencyTracker getLatencyTracker() {
+        return LatencyTracker.getInstance(this);
+    }
+
+    /**
+     * Get the string resource to be used as a label for the link to the resolver activity for an
+     * action.
+     *
+     * @param action The action to resolve
+     *
+     * @return The string resource to be used as a label
+     */
+    public static @StringRes int getLabelRes(String action) {
+        return ActionTitle.forAction(action).labelRes;
+    }
+
+    protected final EmptyStateProvider createEmptyStateProvider(
+            @Nullable UserHandle workProfileUserHandle) {
+        final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
+
+        final EmptyStateProvider workProfileOffEmptyStateProvider =
+                new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
+                        mWorkProfileAvailability,
+                        /* onSwitchOnWorkSelectedListener= */
+                        () -> {
+                            if (mOnSwitchOnWorkSelectedListener != null) {
+                                mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+                            }
+                        },
+                        getMetricsCategory());
+
+        final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
+                this,
+                workProfileUserHandle,
+                getAnnotatedUserHandles().personalProfileUserHandle,
+                getMetricsCategory(),
+                getAnnotatedUserHandles().tabOwnerUserHandleForLaunch
+        );
+
+        // Return composite provider, the order matters (the higher, the more priority)
+        return new CompositeEmptyStateProvider(
+                blockerEmptyStateProvider,
+                workProfileOffEmptyStateProvider,
+                noAppsEmptyStateProvider
+        );
+    }
+
+    private Intent makeMyIntent() {
+        Intent intent = new Intent(getIntent());
+        intent.setComponent(null);
+        // The resolver activity is set to be hidden from recent tasks.
+        // we don't want this attribute to be propagated to the next activity
+        // being launched.  Note that if the original Intent also had this
+        // flag set, we are now losing it.  That should be a very rare case
+        // and we can live with this.
+        intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+        // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate
+        // side, which means we want to open the target app on the same side as ResolverActivity.
+        if ((intent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
+            intent.setFlags(intent.getFlags() & ~FLAG_ACTIVITY_LAUNCH_ADJACENT);
+        }
+        return intent;
+    }
+
+    /**
+     * Call {@link Activity#onCreate} without initializing anything further. This should
+     * only be used when the activity is about to be immediately finished to avoid wasting
+     * initializing steps and leaking resources.
+     */
+    protected final void super_onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    private ResolverMultiProfilePagerAdapter
+            createResolverMultiProfilePagerAdapterForOneProfile(
+                    Intent[] initialIntents,
+                    List<ResolveInfo> resolutionList,
+                    boolean filterLastUsed,
+                    TargetDataLoader targetDataLoader) {
+        ResolverListAdapter adapter = createResolverListAdapter(
+                /* context */ this,
+                /* payloadIntents */ mIntents,
+                initialIntents,
+                resolutionList,
+                filterLastUsed,
+                /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
+                targetDataLoader);
+        return new ResolverMultiProfilePagerAdapter(
+                /* context */ this,
+                adapter,
+                createEmptyStateProvider(/* workProfileUserHandle= */ null),
+                /* workProfileQuietModeChecker= */ () -> false,
+                /* workProfileUserHandle= */ null,
+                getAnnotatedUserHandles().cloneProfileUserHandle);
+    }
+
+    private UserHandle getIntentUser() {
+        return getIntent().hasExtra(EXTRA_CALLING_USER)
+                ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
+                : getAnnotatedUserHandles().tabOwnerUserHandleForLaunch;
+    }
+
+    private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
+            Intent[] initialIntents,
+            List<ResolveInfo> resolutionList,
+            boolean filterLastUsed,
+            TargetDataLoader targetDataLoader) {
+        // In the edge case when we have 0 apps in the current profile and >1 apps in the other,
+        // the intent resolver is started in the other profile. Since this is the only case when
+        // this happens, we check for it here and set the current profile's tab.
+        int selectedProfile = getCurrentProfile();
+        UserHandle intentUser = getIntentUser();
+        if (!getAnnotatedUserHandles().tabOwnerUserHandleForLaunch.equals(intentUser)) {
+            if (getAnnotatedUserHandles().personalProfileUserHandle.equals(intentUser)) {
+                selectedProfile = PROFILE_PERSONAL;
+            } else if (getAnnotatedUserHandles().workProfileUserHandle.equals(intentUser)) {
+                selectedProfile = PROFILE_WORK;
+            }
+        } else {
+            int selectedProfileExtra = getSelectedProfileExtra();
+            if (selectedProfileExtra != -1) {
+                selectedProfile = selectedProfileExtra;
+            }
+        }
+        // We only show the default app for the profile of the current user. The filterLastUsed
+        // flag determines whether to show a default app and that app is not shown in the
+        // resolver list. So filterLastUsed should be false for the other profile.
+        ResolverListAdapter personalAdapter = createResolverListAdapter(
+                /* context */ this,
+                /* payloadIntents */ mIntents,
+                selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
+                resolutionList,
+                (filterLastUsed && UserHandle.myUserId()
+                        == getAnnotatedUserHandles().personalProfileUserHandle.getIdentifier()),
+                /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
+                targetDataLoader);
+        UserHandle workProfileUserHandle = getAnnotatedUserHandles().workProfileUserHandle;
+        ResolverListAdapter workAdapter = createResolverListAdapter(
+                /* context */ this,
+                /* payloadIntents */ mIntents,
+                selectedProfile == PROFILE_WORK ? initialIntents : null,
+                resolutionList,
+                (filterLastUsed && UserHandle.myUserId()
+                        == workProfileUserHandle.getIdentifier()),
+                /* userHandle */ workProfileUserHandle,
+                targetDataLoader);
+        return new ResolverMultiProfilePagerAdapter(
+                /* context */ this,
+                personalAdapter,
+                workAdapter,
+                createEmptyStateProvider(workProfileUserHandle),
+                () -> mWorkProfileAvailability.isQuietModeEnabled(),
+                selectedProfile,
+                workProfileUserHandle,
+                getAnnotatedUserHandles().cloneProfileUserHandle);
+    }
+
+    /**
+     * Returns {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} if the {@link
+     * #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied.
+     * @throws IllegalArgumentException if the value passed to the {@link #EXTRA_SELECTED_PROFILE}
+     * extra is not {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}
+     */
+    final int getSelectedProfileExtra() {
+        int selectedProfile = -1;
+        if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
+            selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
+            if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) {
+                throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value "
+                        + selectedProfile + ". Must be either ResolverActivity.PROFILE_PERSONAL or "
+                        + "ResolverActivity.PROFILE_WORK.");
+            }
+        }
+        return selectedProfile;
+    }
+
+    protected final @Profile int getCurrentProfile() {
+        UserHandle launchUser = getAnnotatedUserHandles().tabOwnerUserHandleForLaunch;
+        UserHandle personalUser = getAnnotatedUserHandles().personalProfileUserHandle;
+        return launchUser.equals(personalUser) ? PROFILE_PERSONAL : PROFILE_WORK;
+    }
+
+    protected final AnnotatedUserHandles getAnnotatedUserHandles() {
+        return mLazyAnnotatedUserHandles.get();
+    }
+
+    private boolean hasWorkProfile() {
+        return getAnnotatedUserHandles().workProfileUserHandle != null;
+    }
+
+    private boolean hasCloneProfile() {
+        return getAnnotatedUserHandles().cloneProfileUserHandle != null;
+    }
+
+    protected final boolean isLaunchedAsCloneProfile() {
+        UserHandle launchUser = getAnnotatedUserHandles().userHandleSharesheetLaunchedAs;
+        UserHandle cloneUser = getAnnotatedUserHandles().cloneProfileUserHandle;
+        return hasCloneProfile() && launchUser.equals(cloneUser);
+    }
+
+    protected final boolean shouldShowTabs() {
+        return hasWorkProfile();
+    }
+
+    protected final void onProfileClick(View v) {
+        final DisplayResolveInfo dri =
+                mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
+        if (dri == null) {
+            return;
+        }
+
+        // Do not show the profile switch message anymore.
+        mProfileSwitchMessage = null;
+
+        onTargetSelected(dri, false);
+        finish();
+    }
+
+    private void updateIntentPickerPaddings() {
+        View titleCont = findViewById(com.android.internal.R.id.title_container);
+        titleCont.setPadding(
+                titleCont.getPaddingLeft(),
+                titleCont.getPaddingTop(),
+                titleCont.getPaddingRight(),
+                getResources().getDimensionPixelSize(R.dimen.resolver_title_padding_bottom));
+        View buttonBar = findViewById(com.android.internal.R.id.button_bar);
+        buttonBar.setPadding(
+                buttonBar.getPaddingLeft(),
+                getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing),
+                buttonBar.getPaddingRight(),
+                getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing));
+    }
+
+    private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
+        if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
+            return;
+        }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
+                .setBoolean(
+                        currentUserHandle.equals(
+                                getAnnotatedUserHandles().personalProfileUserHandle))
+                .setStrings(getMetricsCategory(),
+                        cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
+                .write();
+    }
+
+    @Override // ResolverListCommunicator
+    public final void sendVoiceChoicesIfNeeded() {
+        if (!isVoiceInteraction()) {
+            // Clearly not needed.
+            return;
+        }
+
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount();
+        final Option[] options = new Option[count];
+        for (int i = 0; i < options.length; i++) {
+            TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
+            if (target == null) {
+                // If this occurs, a new set of targets is being loaded. Let that complete,
+                // and have the next call to send voice choices proceed instead.
+                return;
+            }
+            options[i] = optionForChooserTarget(target, i);
+        }
+
+        mPickOptionRequest = new PickTargetOptionRequest(
+                new Prompt(getTitle()), options, null);
+        getVoiceInteractor().submitRequest(mPickOptionRequest);
+    }
+
+    final Option optionForChooserTarget(TargetInfo target, int index) {
+        return new Option(getOrLoadDisplayLabel(target), index);
+    }
+
+    public final Intent getTargetIntent() {
+        return mIntents.isEmpty() ? null : mIntents.get(0);
+    }
+
+    protected final String getReferrerPackageName() {
+        final Uri referrer = getReferrer();
+        if (referrer != null && "android-app".equals(referrer.getScheme())) {
+            return referrer.getHost();
+        }
+        return null;
+    }
+
+    @Override // ResolverListCommunicator
+    public final void updateProfileViewButton() {
+        if (mProfileView == null) {
+            return;
+        }
+
+        final DisplayResolveInfo dri =
+                mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
+        if (dri != null && !shouldShowTabs()) {
+            mProfileView.setVisibility(View.VISIBLE);
+            View text = mProfileView.findViewById(com.android.internal.R.id.profile_button);
+            if (!(text instanceof TextView)) {
+                text = mProfileView.findViewById(com.android.internal.R.id.text1);
+            }
+            ((TextView) text).setText(dri.getDisplayLabel());
+        } else {
+            mProfileView.setVisibility(View.GONE);
+        }
+    }
+
+    private void setProfileSwitchMessage(int contentUserHint) {
+        if ((contentUserHint != UserHandle.USER_CURRENT)
+                && (contentUserHint != UserHandle.myUserId())) {
+            UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+            UserInfo originUserInfo = userManager.getUserInfo(contentUserHint);
+            boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile()
+                    : false;
+            boolean targetIsManaged = userManager.isManagedProfile();
+            if (originIsManaged && !targetIsManaged) {
+                mProfileSwitchMessage = getForwardToPersonalMsg();
+            } else if (!originIsManaged && targetIsManaged) {
+                mProfileSwitchMessage = getForwardToWorkMsg();
+            }
+        }
+    }
+
+    private String getForwardToPersonalMsg() {
+        return getSystemService(DevicePolicyManager.class).getResources().getString(
+                FORWARD_INTENT_TO_PERSONAL,
+                () -> getString(R.string.forward_intent_to_owner));
+    }
+
+    private String getForwardToWorkMsg() {
+        return getSystemService(DevicePolicyManager.class).getResources().getString(
+                FORWARD_INTENT_TO_WORK,
+                () -> getString(R.string.forward_intent_to_work));
+    }
+
+    protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
+        final ActionTitle title = mResolvingHome
+                ? ActionTitle.HOME
+                : ActionTitle.forAction(intent.getAction());
+
+        // While there may already be a filtered item, we can only use it in the title if the list
+        // is already sorted and all information relevant to it is already in the list.
+        final boolean named =
+                mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
+        if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
+            return getString(defaultTitleRes);
+        } else {
+            return named
+                    ? getString(
+                            title.namedTitleRes,
+                            getOrLoadDisplayLabel(
+                                    mMultiProfilePagerAdapter
+                                        .getActiveListAdapter().getFilteredItem()))
+                    : getString(title.titleRes);
+        }
+    }
+
+    final void dismiss() {
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    @Override
+    protected final void onRestart() {
+        super.onRestart();
+        if (!mRegistered) {
+            mPersonalPackageMonitor.register(
+                    this,
+                    getMainLooper(),
+                    getAnnotatedUserHandles().personalProfileUserHandle,
+                    false);
+            if (shouldShowTabs()) {
+                if (mWorkPackageMonitor == null) {
+                    mWorkPackageMonitor = createPackageMonitor(
+                            mMultiProfilePagerAdapter.getWorkListAdapter());
+                }
+                mWorkPackageMonitor.register(
+                        this,
+                        getMainLooper(),
+                        getAnnotatedUserHandles().workProfileUserHandle,
+                        false);
+            }
+            mRegistered = true;
+        }
+        if (shouldShowTabs() && mWorkProfileAvailability.isWaitingToEnableWorkProfile()) {
+            if (mWorkProfileAvailability.isQuietModeEnabled()) {
+                mWorkProfileAvailability.markWorkProfileEnabledBroadcastReceived();
+            }
+        }
+        mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
+        updateProfileViewButton();
+    }
+
+    @Override
+    protected final void onStart() {
+        super.onStart();
+
+        this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        if (shouldShowTabs()) {
+            mWorkProfileAvailability.registerWorkProfileStateReceiver(this);
+        }
+    }
+
+    @Override
+    protected final void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+        if (viewPager != null) {
+            outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+        }
+    }
+
+    @Override
+    protected final void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        resetButtonBar();
+        ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+        if (viewPager != null) {
+            viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+        }
+        mMultiProfilePagerAdapter.clearInactiveProfileCache();
+    }
+
+    private boolean hasManagedProfile() {
+        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        if (userManager == null) {
+            return false;
+        }
+
+        try {
+            List<UserInfo> profiles = userManager.getProfiles(getUserId());
+            for (UserInfo userInfo : profiles) {
+                if (userInfo != null && userInfo.isManagedProfile()) {
+                    return true;
+                }
+            }
+        } catch (SecurityException e) {
+            return false;
+        }
+        return false;
+    }
+
+    private boolean supportsManagedProfiles(ResolveInfo resolveInfo) {
+        try {
+            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
+                    resolveInfo.activityInfo.packageName, 0 /* default flags */);
+            return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
+            boolean filtered) {
+        if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) {
+            // Never allow the inactive profile to always open an app.
+            mAlwaysButton.setEnabled(false);
+            return;
+        }
+        // In case of clonedProfile being active, we do not allow the 'Always' option in the
+        // disambiguation dialog of Personal Profile as the package manager cannot distinguish
+        // between cross-profile preferred activities.
+        if (hasCloneProfile() && (mMultiProfilePagerAdapter.getCurrentPage() == PROFILE_PERSONAL)) {
+            mAlwaysButton.setEnabled(false);
+            return;
+        }
+        boolean enabled = false;
+        ResolveInfo ri = null;
+        if (hasValidSelection) {
+            ri = mMultiProfilePagerAdapter.getActiveListAdapter()
+                    .resolveInfoForPosition(checkedPos, filtered);
+            if (ri == null) {
+                Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled");
+                return;
+            } else if (ri.targetUserId != UserHandle.USER_CURRENT) {
+                Log.e(TAG, "Attempted to set selection to resolve info for another user");
+                return;
+            } else {
+                enabled = true;
+            }
+
+            mAlwaysButton.setText(getResources()
+                    .getString(R.string.activity_resolver_use_always));
+        }
+
+        if (ri != null) {
+            ActivityInfo activityInfo = ri.activityInfo;
+
+            boolean hasRecordPermission =
+                    mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO,
+                            activityInfo.packageName)
+                            == PackageManager.PERMISSION_GRANTED;
+
+            if (!hasRecordPermission) {
+                // OK, we know the record permission, is this a capture device
+                boolean hasAudioCapture =
+                        getIntent().getBooleanExtra(
+                                ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
+                enabled = !hasAudioCapture;
+            }
+        }
+        mAlwaysButton.setEnabled(enabled);
+    }
+
+    private String getWorkProfileNotSupportedMsg(String launcherName) {
+        return getSystemService(DevicePolicyManager.class).getResources().getString(
+                RESOLVER_WORK_PROFILE_NOT_SUPPORTED,
+                () -> getString(
+                        R.string.activity_resolver_work_profiles_support,
+                        launcherName),
+                launcherName);
+    }
+
+    @Override // ResolverListCommunicator
+    public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
+            boolean rebuildCompleted) {
+        if (isAutolaunching()) {
+            return;
+        }
+        if (mIsIntentPicker) {
+            ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
+                    .setUseLayoutWithDefault(useLayoutWithDefault());
+        }
+        if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) {
+            mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter);
+        } else {
+            mMultiProfilePagerAdapter.showListView(listAdapter);
+        }
+        // showEmptyResolverListEmptyState can mark the tab as loaded,
+        // which is a precondition for auto launching
+        if (rebuildCompleted && maybeAutolaunchActivity()) {
+            return;
+        }
+        if (doPostProcessing) {
+            maybeCreateHeader(listAdapter);
+            resetButtonBar();
+            onListRebuilt(listAdapter, rebuildCompleted);
+        }
+    }
+
+    /** Start the activity specified by the {@link TargetInfo}.*/
+    public final void safelyStartActivity(TargetInfo cti) {
+        // In case cloned apps are present, we would want to start those apps in cloned user
+        // space, which will not be same as the adapter's userHandle. resolveInfo.userHandle
+        // identifies the correct user space in such cases.
+        UserHandle activityUserHandle = cti.getResolveInfo().userHandle;
+        safelyStartActivityAsUser(cti, activityUserHandle, null);
+    }
+
+    /**
+     * Start activity as a fixed user handle.
+     * @param cti TargetInfo to be launched.
+     * @param user User to launch this activity as.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+        safelyStartActivityAsUser(cti, user, null);
+    }
+
+    protected final void safelyStartActivityAsUser(
+            TargetInfo cti, UserHandle user, @Nullable Bundle options) {
+        // We're dispatching intents that might be coming from legacy apps, so
+        // don't kill ourselves.
+        StrictMode.disableDeathOnFileUriExposure();
+        try {
+            safelyStartActivityInternal(cti, user, options);
+        } finally {
+            StrictMode.enableDeathOnFileUriExposure();
+        }
+    }
+
+    @VisibleForTesting
+    protected void safelyStartActivityInternal(
+            TargetInfo cti, UserHandle user, @Nullable Bundle options) {
+        // If the target is suspended, the activity will not be successfully launched.
+        // Do not unregister from package manager updates in this case
+        if (!cti.isSuspended() && mRegistered) {
+            if (mPersonalPackageMonitor != null) {
+                mPersonalPackageMonitor.unregister();
+            }
+            if (mWorkPackageMonitor != null) {
+                mWorkPackageMonitor.unregister();
+            }
+            mRegistered = false;
+        }
+        // If needed, show that intent is forwarded
+        // from managed profile to owner or other way around.
+        if (mProfileSwitchMessage != null) {
+            Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show();
+        }
+        if (!mSafeForwardingMode) {
+            if (cti.startAsUser(this, options, user)) {
+                onActivityStarted(cti);
+                maybeLogCrossProfileTargetLaunch(cti, user);
+            }
+            return;
+        }
+        try {
+            if (cti.startAsCaller(this, options, user.getIdentifier())) {
+                onActivityStarted(cti);
+                maybeLogCrossProfileTargetLaunch(cti, user);
+            }
+        } catch (RuntimeException e) {
+            Slog.wtf(TAG,
+                    "Unable to launch as uid " + getAnnotatedUserHandles().userIdOfCallingApp
+                    + " package " + getLaunchedFromPackage() + ", while running in "
+                    + ActivityThread.currentProcessName(), e);
+        }
+    }
+
+    final void showTargetDetails(ResolveInfo ri) {
+        Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+                .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        startActivityAsUser(in, mMultiProfilePagerAdapter.getCurrentUserHandle());
+    }
+
+    /**
+     * Sets up the content view.
+     * @return <code>true</code> if the activity is finishing and creation should halt.
+     */
+    private boolean configureContentView(TargetDataLoader targetDataLoader) {
+        if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) {
+            throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
+                    + "cannot be null.");
+        }
+        Trace.beginSection("configureContentView");
+        // We partially rebuild the inactive adapter to determine if we should auto launch
+        // isTabLoaded will be true here if the empty state screen is shown instead of the list.
+        boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true)
+                || mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded();
+        if (shouldShowTabs()) {
+            boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false)
+                    || mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded();
+            rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
+        }
+
+        if (shouldUseMiniResolver()) {
+            configureMiniResolverContent(targetDataLoader);
+            Trace.endSection();
+            return false;
+        }
+
+        if (useLayoutWithDefault()) {
+            mLayoutId = R.layout.resolver_list_with_default;
+        } else {
+            mLayoutId = getLayoutResource();
+        }
+        setContentView(mLayoutId);
+        mMultiProfilePagerAdapter.setupViewPager(findViewById(com.android.internal.R.id.profile_pager));
+        boolean result = postRebuildList(rebuildCompleted);
+        Trace.endSection();
+        return result;
+    }
+
+    /**
+     * Mini resolver is shown when the user is choosing between browser[s] in this profile and a
+     * single app in the other profile (see shouldUseMiniResolver()). It shows the single app icon
+     * and asks the user if they'd like to open that cross-profile app or use the in-profile
+     * browser.
+     */
+    private void configureMiniResolverContent(TargetDataLoader targetDataLoader) {
+        mLayoutId = R.layout.miniresolver;
+        setContentView(mLayoutId);
+
+        DisplayResolveInfo sameProfileResolveInfo =
+                mMultiProfilePagerAdapter.getActiveListAdapter().getFirstDisplayResolveInfo();
+        boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
+
+        final ResolverListAdapter inactiveAdapter =
+                mMultiProfilePagerAdapter.getInactiveListAdapter();
+        final DisplayResolveInfo otherProfileResolveInfo =
+                inactiveAdapter.getFirstDisplayResolveInfo();
+
+        // Load the icon asynchronously
+        ImageView icon = findViewById(com.android.internal.R.id.icon);
+        targetDataLoader.loadAppTargetIcon(
+                otherProfileResolveInfo,
+                inactiveAdapter.getUserHandle(),
+                (drawable) -> {
+                    if (!isDestroyed()) {
+                        otherProfileResolveInfo.getDisplayIconHolder().setDisplayIcon(drawable);
+                        new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo);
+                    }
+                });
+
+        ((TextView) findViewById(com.android.internal.R.id.open_cross_profile)).setText(
+                getResources().getString(
+                        inWorkProfile
+                                ? R.string.miniresolver_open_in_personal
+                                : R.string.miniresolver_open_in_work,
+                        getOrLoadDisplayLabel(otherProfileResolveInfo)));
+        ((Button) findViewById(com.android.internal.R.id.use_same_profile_browser)).setText(
+                inWorkProfile ? R.string.miniresolver_use_work_browser
+                        : R.string.miniresolver_use_personal_browser);
+
+        findViewById(com.android.internal.R.id.use_same_profile_browser).setOnClickListener(
+                v -> {
+                    safelyStartActivity(sameProfileResolveInfo);
+                    finish();
+                });
+
+        findViewById(com.android.internal.R.id.button_open).setOnClickListener(v -> {
+            Intent intent = otherProfileResolveInfo.getResolvedIntent();
+            safelyStartActivityAsUser(otherProfileResolveInfo, inactiveAdapter.getUserHandle());
+            finish();
+        });
+    }
+
+    /**
+     * Mini resolver should be used when all of the following are true:
+     * 1. This is the intent picker (ResolverActivity).
+     * 2. This profile only has web browser matches.
+     * 3. The other profile has a single non-browser match.
+     */
+    private boolean shouldUseMiniResolver() {
+        if (!mIsIntentPicker) {
+            return false;
+        }
+        if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
+                || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+            return false;
+        }
+        ResolverListAdapter sameProfileAdapter =
+                mMultiProfilePagerAdapter.getActiveListAdapter();
+        ResolverListAdapter otherProfileAdapter =
+                mMultiProfilePagerAdapter.getInactiveListAdapter();
+
+        if (sameProfileAdapter.getDisplayResolveInfoCount() == 0) {
+            Log.d(TAG, "No targets in the current profile");
+            return false;
+        }
+
+        if (otherProfileAdapter.getDisplayResolveInfoCount() != 1) {
+            Log.d(TAG, "Other-profile count: " + otherProfileAdapter.getDisplayResolveInfoCount());
+            return false;
+        }
+
+        if (otherProfileAdapter.allResolveInfosHandleAllWebDataUri()) {
+            Log.d(TAG, "Other profile is a web browser");
+            return false;
+        }
+
+        if (!sameProfileAdapter.allResolveInfosHandleAllWebDataUri()) {
+            Log.d(TAG, "Non-browser found in this profile");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Finishing procedures to be performed after the list has been rebuilt.
+     * @param rebuildCompleted
+     * @return <code>true</code> if the activity is finishing and creation should halt.
+     */
+    final boolean postRebuildListInternal(boolean rebuildCompleted) {
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+
+        // We only rebuild asynchronously when we have multiple elements to sort. In the case where
+        // we're already done, we can check if we should auto-launch immediately.
+        if (rebuildCompleted && maybeAutolaunchActivity()) {
+            return true;
+        }
+
+        setupViewVisibilities();
+
+        if (shouldShowTabs()) {
+            setupProfileTabs();
+        }
+
+        return false;
+    }
+
+    private int isPermissionGranted(String permission, int uid) {
+        return ActivityManager.checkComponentPermission(permission, uid,
+                /* owningUid= */-1, /* exported= */ true);
+    }
+
+    /**
+     * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
+     */
+    private boolean maybeAutolaunchActivity() {
+        int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
+        if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
+            return true;
+        } else if (numberOfProfiles == 2
+                && mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded()
+                && mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded()
+                && maybeAutolaunchIfCrossProfileSupported()) {
+            // TODO(b/280988288): If the ChooserActivity is shown we should consider showing the
+            //  correct intent-picker UIs (e.g., mini-resolver) if it was launched without
+            //  ACTION_SEND.
+            return true;
+        }
+        return false;
+    }
+
+    private boolean maybeAutolaunchIfSingleTarget() {
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+        if (count != 1) {
+            return false;
+        }
+
+        if (mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) {
+            return false;
+        }
+
+        // Only one target, so we're a candidate to auto-launch!
+        final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+                .targetInfoForPosition(0, false);
+        if (shouldAutoLaunchSingleChoice(target)) {
+            safelyStartActivity(target);
+            finish();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * When we have a personal and a work profile, we auto launch in the following scenario:
+     * - There is 1 resolved target on each profile
+     * - That target is the same app on both profiles
+     * - The target app has permission to communicate cross profiles
+     * - The target app has declared it supports cross-profile communication via manifest metadata
+     */
+    private boolean maybeAutolaunchIfCrossProfileSupported() {
+        ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
+        int count = activeListAdapter.getUnfilteredCount();
+        if (count != 1) {
+            return false;
+        }
+        ResolverListAdapter inactiveListAdapter =
+                mMultiProfilePagerAdapter.getInactiveListAdapter();
+        if (inactiveListAdapter.getUnfilteredCount() != 1) {
+            return false;
+        }
+        TargetInfo activeProfileTarget = activeListAdapter
+                .targetInfoForPosition(0, false);
+        TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
+        if (!Objects.equals(activeProfileTarget.getResolvedComponentName(),
+                inactiveProfileTarget.getResolvedComponentName())) {
+            return false;
+        }
+        if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
+            return false;
+        }
+        String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
+        if (!canAppInteractCrossProfiles(packageName)) {
+            return false;
+        }
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
+                .setBoolean(activeListAdapter.getUserHandle()
+                        .equals(getAnnotatedUserHandles().personalProfileUserHandle))
+                .setStrings(getMetricsCategory())
+                .write();
+        safelyStartActivity(activeProfileTarget);
+        finish();
+        return true;
+    }
+
+    /**
+     * Returns whether the package has the necessary permissions to interact across profiles on
+     * behalf of a given user.
+     *
+     * <p>This means meeting the following condition:
+     * <ul>
+     *     <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least
+     *     one of the following conditions must be fulfilled</li>
+     *     <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li>
+     *     <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li>
+     *     <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding
+     *     AppOps {@code android:interact_across_profiles} is set to "allow".</li>
+     * </ul>
+     *
+     */
+    private boolean canAppInteractCrossProfiles(String packageName) {
+        ApplicationInfo applicationInfo;
+        try {
+            applicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Package " + packageName + " does not exist on current user.");
+            return false;
+        }
+        if (!applicationInfo.crossProfile) {
+            return false;
+        }
+
+        int packageUid = applicationInfo.uid;
+
+        if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                packageUid) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        if (PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES,
+                PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isAutolaunching() {
+        return !mRegistered && isFinishing();
+    }
+
+    private void setupProfileTabs() {
+        maybeHideDivider();
+        TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost);
+        tabHost.setup();
+        ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+        viewPager.setSaveEnabled(false);
+
+        Button personalButton = (Button) getLayoutInflater().inflate(
+                R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
+        personalButton.setText(getPersonalTabLabel());
+        personalButton.setContentDescription(getPersonalTabAccessibilityLabel());
+
+        TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
+                .setContent(com.android.internal.R.id.profile_pager)
+                .setIndicator(personalButton);
+        tabHost.addTab(tabSpec);
+
+        Button workButton = (Button) getLayoutInflater().inflate(
+                R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
+        workButton.setText(getWorkTabLabel());
+        workButton.setContentDescription(getWorkTabAccessibilityLabel());
+
+        tabSpec = tabHost.newTabSpec(TAB_TAG_WORK)
+                .setContent(com.android.internal.R.id.profile_pager)
+                .setIndicator(workButton);
+        tabHost.addTab(tabSpec);
+
+        TabWidget tabWidget = tabHost.getTabWidget();
+        tabWidget.setVisibility(View.VISIBLE);
+        updateActiveTabStyle(tabHost);
+
+        tabHost.setOnTabChangedListener(tabId -> {
+            updateActiveTabStyle(tabHost);
+            if (TAB_TAG_PERSONAL.equals(tabId)) {
+                viewPager.setCurrentItem(0);
+            } else {
+                viewPager.setCurrentItem(1);
+            }
+            setupViewVisibilities();
+            maybeLogProfileChange();
+            onProfileTabSelected();
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
+                    .setInt(viewPager.getCurrentItem())
+                    .setStrings(getMetricsCategory())
+                    .write();
+        });
+
+        viewPager.setVisibility(View.VISIBLE);
+        tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
+        mMultiProfilePagerAdapter.setOnProfileSelectedListener(
+                new MultiProfilePagerAdapter.OnProfileSelectedListener() {
+                    @Override
+                    public void onProfileSelected(int index) {
+                        tabHost.setCurrentTab(index);
+                        resetButtonBar();
+                        resetCheckedItem();
+                    }
+
+                    @Override
+                    public void onProfilePageStateChanged(int state) {
+                        onHorizontalSwipeStateChanged(state);
+                    }
+                });
+        mOnSwitchOnWorkSelectedListener = () -> {
+            final View workTab = tabHost.getTabWidget().getChildAt(1);
+            workTab.setFocusable(true);
+            workTab.setFocusableInTouchMode(true);
+            workTab.requestFocus();
+        };
+    }
+
+    private String getPersonalTabLabel() {
+        return getSystemService(DevicePolicyManager.class).getResources().getString(
+                RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab));
+    }
+
+    private String getWorkTabLabel() {
+        return getSystemService(DevicePolicyManager.class).getResources().getString(
+                RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab));
+    }
+
+    private void maybeHideDivider() {
+        if (!mIsIntentPicker) {
+            return;
+        }
+        final View divider = findViewById(com.android.internal.R.id.divider);
+        if (divider == null) {
+            return;
+        }
+        divider.setVisibility(View.GONE);
+    }
+
+    private void resetCheckedItem() {
+        if (!mIsIntentPicker) {
+            return;
+        }
+        mLastSelected = ListView.INVALID_POSITION;
+        ListView inactiveListView = (ListView) mMultiProfilePagerAdapter.getInactiveAdapterView();
+        if (inactiveListView.getCheckedItemCount() > 0) {
+            inactiveListView.setItemChecked(inactiveListView.getCheckedItemPosition(), false);
+        }
+    }
+
+    private String getPersonalTabAccessibilityLabel() {
+        return getSystemService(DevicePolicyManager.class).getResources().getString(
+                RESOLVER_PERSONAL_TAB_ACCESSIBILITY,
+                () -> getString(R.string.resolver_personal_tab_accessibility));
+    }
+
+    private String getWorkTabAccessibilityLabel() {
+        return getSystemService(DevicePolicyManager.class).getResources().getString(
+                RESOLVER_WORK_TAB_ACCESSIBILITY,
+                () -> getString(R.string.resolver_work_tab_accessibility));
+    }
+
+    private static int getAttrColor(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    private void updateActiveTabStyle(TabHost tabHost) {
+        int currentTab = tabHost.getCurrentTab();
+        TextView selected = (TextView) tabHost.getTabWidget().getChildAt(currentTab);
+        TextView unselected = (TextView) tabHost.getTabWidget().getChildAt(1 - currentTab);
+        selected.setSelected(true);
+        unselected.setSelected(false);
+    }
+
+    private void setupViewVisibilities() {
+        ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
+        if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
+            addUseDifferentAppLabelIfNecessary(activeListAdapter);
+        }
+    }
+
+    /**
+     * Updates the button bar container {@code ignoreOffset} layout param.
+     * <p>Setting this to {@code true} means that the button bar will be glued to the bottom of
+     * the screen.
+     */
+    private void setButtonBarIgnoreOffset(boolean ignoreOffset) {
+        View buttonBarContainer = findViewById(com.android.internal.R.id.button_bar_container);
+        if (buttonBarContainer != null) {
+            ResolverDrawerLayout.LayoutParams layoutParams =
+                    (ResolverDrawerLayout.LayoutParams) buttonBarContainer.getLayoutParams();
+            layoutParams.ignoreOffset = ignoreOffset;
+            buttonBarContainer.setLayoutParams(layoutParams);
+        }
+    }
+
+    private void setupAdapterListView(ListView listView, ItemClickListener listener) {
+        listView.setOnItemClickListener(listener);
+        listView.setOnItemLongClickListener(listener);
+
+        if (mSupportsAlwaysUseOption) {
+            listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
+        }
+    }
+
+    /**
+     * Configure the area above the app selection list (title, content preview, etc).
+     */
+    private void maybeCreateHeader(ResolverListAdapter listAdapter) {
+        if (mHeaderCreatorUser != null
+                && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
+            return;
+        }
+        if (!shouldShowTabs()
+                && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
+            final TextView titleView = findViewById(com.android.internal.R.id.title);
+            if (titleView != null) {
+                titleView.setVisibility(View.GONE);
+            }
+        }
+
+        CharSequence title = mTitle != null
+                ? mTitle
+                : getTitleForAction(getTargetIntent(), mDefaultTitleResId);
+
+        if (!TextUtils.isEmpty(title)) {
+            final TextView titleView = findViewById(com.android.internal.R.id.title);
+            if (titleView != null) {
+                titleView.setText(title);
+            }
+            setTitle(title);
+        }
+
+        final ImageView iconView = findViewById(com.android.internal.R.id.icon);
+        if (iconView != null) {
+            listAdapter.loadFilteredItemIconTaskAsync(iconView);
+        }
+        mHeaderCreatorUser = listAdapter.getUserHandle();
+    }
+
+    private void resetAlwaysOrOnceButtonBar() {
+        // Disable both buttons initially
+        setAlwaysButtonEnabled(false, ListView.INVALID_POSITION, false);
+        mOnceButton.setEnabled(false);
+
+        int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter()
+                .getFilteredPosition();
+        if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) {
+            setAlwaysButtonEnabled(true, filteredPosition, false);
+            mOnceButton.setEnabled(true);
+            // Focus the button if we already have the default option
+            mOnceButton.requestFocus();
+            return;
+        }
+
+        // When the items load in, if an item was already selected, enable the buttons
+        ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
+        if (currentAdapterView != null
+                && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
+            setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true);
+            mOnceButton.setEnabled(true);
+        }
+    }
+
+    @Override // ResolverListCommunicator
+    public final boolean useLayoutWithDefault() {
+        // We only use the default app layout when the profile of the active user has a
+        // filtered item. We always show the same default app even in the inactive user profile.
+        boolean adapterForCurrentUserHasFilteredItem =
+                mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+                        getAnnotatedUserHandles().tabOwnerUserHandleForLaunch).hasFilteredItem();
+        return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem;
+    }
+
+    /**
+     * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets
+     * called and we are launched in a new task.
+     */
+    protected final void setRetainInOnStop(boolean retainInOnStop) {
+        mRetainInOnStop = retainInOnStop;
+    }
+
+    private boolean inactiveListAdapterHasItems() {
+        if (!shouldShowTabs()) {
+            return false;
+        }
+        return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0;
+    }
+
+    final class ItemClickListener implements AdapterView.OnItemClickListener,
+            AdapterView.OnItemLongClickListener {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            final ListView listView = parent instanceof ListView ? (ListView) parent : null;
+            if (listView != null) {
+                position -= listView.getHeaderViewsCount();
+            }
+            if (position < 0) {
+                // Header views don't count.
+                return;
+            }
+            // If we're still loading, we can't yet enable the buttons.
+            if (mMultiProfilePagerAdapter.getActiveListAdapter()
+                    .resolveInfoForPosition(position, true) == null) {
+                return;
+            }
+            ListView currentAdapterView =
+                    (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
+            final int checkedPos = currentAdapterView.getCheckedItemPosition();
+            final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
+            if (!useLayoutWithDefault()
+                    && (!hasValidSelection || mLastSelected != checkedPos)
+                    && mAlwaysButton != null) {
+                setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
+                mOnceButton.setEnabled(hasValidSelection);
+                if (hasValidSelection) {
+                    currentAdapterView.smoothScrollToPosition(checkedPos);
+                    mOnceButton.requestFocus();
+                }
+                mLastSelected = checkedPos;
+            } else {
+                startSelected(position, false, true);
+            }
+        }
+
+        @Override
+        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+            final ListView listView = parent instanceof ListView ? (ListView) parent : null;
+            if (listView != null) {
+                position -= listView.getHeaderViewsCount();
+            }
+            if (position < 0) {
+                // Header views don't count.
+                return false;
+            }
+            ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
+                    .resolveInfoForPosition(position, true);
+            showTargetDetails(ri);
+            return true;
+        }
+
+    }
+
+    /** Determine whether a given match result is considered "specific" in our application. */
+    public static final boolean isSpecificUriMatch(int match) {
+        match = (match & IntentFilter.MATCH_CATEGORY_MASK);
+        return match >= IntentFilter.MATCH_CATEGORY_HOST
+                && match <= IntentFilter.MATCH_CATEGORY_PATH;
+    }
+
+    static final class PickTargetOptionRequest extends PickOptionRequest {
+        public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
+                @Nullable Bundle extras) {
+            super(prompt, options, extras);
+        }
+
+        @Override
+        public void onCancel() {
+            super.onCancel();
+            final ResolverActivity ra = (ResolverActivity) getActivity();
+            if (ra != null) {
+                ra.mPickOptionRequest = null;
+                ra.finish();
+            }
+        }
+
+        @Override
+        public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
+            super.onPickOptionResult(finished, selections, result);
+            if (selections.length != 1) {
+                // TODO In a better world we would filter the UI presented here and let the
+                // user refine. Maybe later.
+                return;
+            }
+
+            final ResolverActivity ra = (ResolverActivity) getActivity();
+            if (ra != null) {
+                final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter()
+                        .getItem(selections[0].getIndex());
+                if (ra.onTargetSelected(ti, false)) {
+                    ra.mPickOptionRequest = null;
+                    ra.finish();
+                }
+            }
+        }
+    }
+    /**
+     * Returns the {@link UserHandle} to use when querying resolutions for intents in a
+     * {@link ResolverListController} configured for the provided {@code userHandle}.
+     */
+    protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
+        return getAnnotatedUserHandles().getQueryIntentsUser(userHandle);
+    }
+
+    /**
+     * Returns the {@link List} of {@link UserHandle} to pass on to the
+     * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
+     */
+    @VisibleForTesting(visibility = PROTECTED)
+    public final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
+        return getResolverRankerServiceUserHandleListInternal(userHandle);
+    }
+
+    @VisibleForTesting
+    protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(
+            UserHandle userHandle) {
+        List<UserHandle> userList = new ArrayList<>();
+        userList.add(userHandle);
+        // Add clonedProfileUserHandle to the list only if we are:
+        // a. Building the Personal Tab.
+        // b. CloneProfile exists on the device.
+        if (userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle)
+                && hasCloneProfile()) {
+            userList.add(getAnnotatedUserHandles().cloneProfileUserHandle);
+        }
+        return userList;
+    }
+
+    private CharSequence getOrLoadDisplayLabel(TargetInfo info) {
+        if (info.isDisplayResolveInfo()) {
+            mTargetDataLoader.getOrLoadLabel((DisplayResolveInfo) info);
+        }
+        CharSequence displayLabel = info.getDisplayLabel();
+        return displayLabel == null ? "" : displayLabel;
+    }
+}
diff --git a/java/tests/AndroidManifest.xml b/java/tests/AndroidManifest.xml
index 35dc2ee..03e32c6 100644
--- a/java/tests/AndroidManifest.xml
+++ b/java/tests/AndroidManifest.xml
@@ -27,6 +27,8 @@
         <uses-library android:name="android.test.runner" />
         <activity android:name="com.android.intentresolver.ChooserWrapperActivity" />
         <activity android:name="com.android.intentresolver.ResolverWrapperActivity" />
+        <activity android:name="com.android.intentresolver.v2.ChooserWrapperActivity" />
+        <activity android:name="com.android.intentresolver.v2.ResolverWrapperActivity" />
         <provider
             android:authorities="com.android.intentresolver.tests"
             android:name="com.android.intentresolver.TestContentProvider"
diff --git a/java/tests/src/com/android/intentresolver/MatcherUtils.java b/java/tests/src/com/android/intentresolver/MatcherUtils.java
index 6168968..97cc698 100644
--- a/java/tests/src/com/android/intentresolver/MatcherUtils.java
+++ b/java/tests/src/com/android/intentresolver/MatcherUtils.java
@@ -29,7 +29,7 @@
     /**
      * Returns a {@link Matcher} which only matches the first occurrence of a set criteria.
      */
-    static <T> Matcher<T> first(final Matcher<T> matcher) {
+    public static <T> Matcher<T> first(final Matcher<T> matcher) {
         return new BaseMatcher<T>() {
             boolean isFirstMatch = true;
 
diff --git a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
index 1f8d9be..4eb350f 100644
--- a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
+++ b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
@@ -43,7 +43,7 @@
                 createResolveInfo(i, UserHandle.USER_CURRENT));
     }
 
-    static ResolvedComponentInfo createResolvedComponentInfo(int i,
+    public static ResolvedComponentInfo createResolvedComponentInfo(int i,
             UserHandle resolvedForUser) {
         return new ResolvedComponentInfo(
                 createComponentName(i),
@@ -59,7 +59,7 @@
                 createResolveInfo(componentName, UserHandle.USER_CURRENT));
     }
 
-    static ResolvedComponentInfo createResolvedComponentInfo(
+    public static ResolvedComponentInfo createResolvedComponentInfo(
             ComponentName componentName, Intent intent, UserHandle resolvedForUser) {
         return new ResolvedComponentInfo(
                 componentName,
@@ -74,8 +74,8 @@
                 createResolveInfo(i, USER_SOMEONE_ELSE));
     }
 
-    static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
-            UserHandle resolvedForUser) {
+    public static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
+                                                                               UserHandle resolvedForUser) {
         return new ResolvedComponentInfo(
                 createComponentName(i),
                 createResolverIntent(i),
@@ -89,7 +89,7 @@
                 createResolveInfo(i, userId));
     }
 
-    static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
+    public static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
             int userId, UserHandle resolvedForUser) {
         return new ResolvedComponentInfo(
                 createComponentName(i),
diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java
new file mode 100644
index 0000000..32eabbe
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 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.intentresolver.v2;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.os.UserHandle;
+
+import com.android.intentresolver.AnnotatedUserHandles;
+import com.android.intentresolver.WorkProfileAvailabilityManager;
+import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.contentpreview.ImageLoader;
+import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
+import com.android.intentresolver.shortcuts.ShortcutLoader;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import kotlin.jvm.functions.Function2;
+
+/**
+ * Singleton providing overrides to be applied by any {@code IChooserWrapper} used in testing.
+ * We cannot directly mock the activity created since instrumentation creates it, so instead we use
+ * this singleton to modify behavior.
+ */
+public class ChooserActivityOverrideData {
+    private static ChooserActivityOverrideData sInstance = null;
+
+    public static ChooserActivityOverrideData getInstance() {
+        if (sInstance == null) {
+            sInstance = new ChooserActivityOverrideData();
+        }
+        return sInstance;
+    }
+
+    @SuppressWarnings("Since15")
+    public Function<PackageManager, PackageManager> createPackageManager;
+    public Function<TargetInfo, Boolean> onSafelyStartInternalCallback;
+    public Function<TargetInfo, Boolean> onSafelyStartCallback;
+    public Function2<UserHandle, Consumer<ShortcutLoader.Result>, ShortcutLoader>
+            shortcutLoaderFactory = (userHandle, callback) -> null;
+    public ChooserActivity.ChooserListController resolverListController;
+    public ChooserActivity.ChooserListController workResolverListController;
+    public Boolean isVoiceInteraction;
+    public Cursor resolverCursor;
+    public boolean resolverForceException;
+    public ImageLoader imageLoader;
+    public int alternateProfileSetting;
+    public Resources resources;
+    public AnnotatedUserHandles annotatedUserHandles;
+    public boolean hasCrossProfileIntents;
+    public boolean isQuietModeEnabled;
+    public Integer myUserId;
+    public WorkProfileAvailabilityManager mWorkProfileAvailability;
+    public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
+    public PackageManager packageManager;
+
+    public void reset() {
+        onSafelyStartInternalCallback = null;
+        isVoiceInteraction = null;
+        createPackageManager = null;
+        imageLoader = null;
+        resolverCursor = null;
+        resolverForceException = false;
+        resolverListController = mock(ChooserActivity.ChooserListController.class);
+        workResolverListController = mock(ChooserActivity.ChooserListController.class);
+        alternateProfileSetting = 0;
+        resources = null;
+        annotatedUserHandles = AnnotatedUserHandles.newBuilder()
+                    .setUserIdOfCallingApp(1234)  // Must be non-negative.
+                    .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM)
+                    .setPersonalProfileUserHandle(UserHandle.SYSTEM)
+                    .build();
+        hasCrossProfileIntents = true;
+        isQuietModeEnabled = false;
+        myUserId = null;
+        packageManager = null;
+        mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) {
+            @Override
+            public boolean isQuietModeEnabled() {
+                return isQuietModeEnabled;
+            }
+
+            @Override
+            public boolean isWorkProfileUserUnlocked() {
+                return true;
+            }
+
+            @Override
+            public void requestQuietModeEnabled(boolean enabled) {
+                isQuietModeEnabled = enabled;
+            }
+
+            @Override
+            public void markWorkProfileEnabledBroadcastReceived() {}
+
+            @Override
+            public boolean isWaitingToEnableWorkProfile() {
+                return false;
+            }
+        };
+        shortcutLoaderFactory = ((userHandle, resultConsumer) -> null);
+
+        mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+        when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+                .thenAnswer(invocation -> hasCrossProfileIntents);
+    }
+
+    private ChooserActivityOverrideData() {}
+}
+
diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
new file mode 100644
index 0000000..41b31d0
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
@@ -0,0 +1,280 @@
+/*
+ * 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.intentresolver.v2;
+
+import android.annotation.Nullable;
+import android.app.prediction.AppPredictor;
+import android.app.usage.UsageStatsManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.intentresolver.AnnotatedUserHandles;
+import com.android.intentresolver.ChooserIntegratedDeviceComponents;
+import com.android.intentresolver.ChooserListAdapter;
+import com.android.intentresolver.ChooserRequestParameters;
+import com.android.intentresolver.IChooserWrapper;
+import com.android.intentresolver.ResolverListController;
+import com.android.intentresolver.TestContentPreviewViewModel;
+import com.android.intentresolver.WorkProfileAvailabilityManager;
+import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
+import com.android.intentresolver.grid.ChooserGridAdapter;
+import com.android.intentresolver.icons.TargetDataLoader;
+import com.android.intentresolver.shortcuts.ShortcutLoader;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Simple wrapper around chooser activity to be able to initiate it under test. For more
+ * information, see {@code com.android.internal.app.ChooserWrapperActivity}.
+ */
+public class ChooserWrapperActivity extends ChooserActivity implements IChooserWrapper {
+    static final ChooserActivityOverrideData sOverrides = ChooserActivityOverrideData.getInstance();
+    private UsageStatsManager mUsm;
+
+    // ResolverActivity (the base class of ChooserActivity) inspects the launched-from UID at
+    // onCreate and needs to see some non-negative value in the test.
+    @Override
+    public int getLaunchedFromUid() {
+        return 1234;
+    }
+
+    @Override
+    public ChooserListAdapter createChooserListAdapter(
+            Context context,
+            List<Intent> payloadIntents,
+            Intent[] initialIntents,
+            List<ResolveInfo> rList,
+            boolean filterLastUsed,
+            ResolverListController resolverListController,
+            UserHandle userHandle,
+            Intent targetIntent,
+            ChooserRequestParameters chooserRequest,
+            int maxTargetsPerRow,
+            TargetDataLoader targetDataLoader) {
+        PackageManager packageManager =
+                sOverrides.packageManager == null ? context.getPackageManager()
+                        : sOverrides.packageManager;
+        return new ChooserListAdapter(
+                context,
+                payloadIntents,
+                initialIntents,
+                rList,
+                filterLastUsed,
+                createListController(userHandle),
+                userHandle,
+                targetIntent,
+                this,
+                packageManager,
+                getEventLog(),
+                chooserRequest,
+                maxTargetsPerRow,
+                userHandle,
+                targetDataLoader);
+    }
+
+    @Override
+    public ChooserListAdapter getAdapter() {
+        return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+    }
+
+    @Override
+    public ChooserListAdapter getPersonalListAdapter() {
+        return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0))
+                .getListAdapter();
+    }
+
+    @Override
+    public ChooserListAdapter getWorkListAdapter() {
+        if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+            return null;
+        }
+        return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1))
+                .getListAdapter();
+    }
+
+    @Override
+    public boolean getIsSelected() {
+        return mIsSuccessfullySelected;
+    }
+
+    @Override
+    protected ChooserIntegratedDeviceComponents getIntegratedDeviceComponents() {
+        return new ChooserIntegratedDeviceComponents(
+                /* editSharingComponent=*/ null,
+                // An arbitrary pre-installed activity that handles this type of intent:
+                /* nearbySharingComponent=*/ new ComponentName(
+                        "com.google.android.apps.messaging",
+                        ".ui.conversationlist.ShareIntentActivity"));
+    }
+
+    @Override
+    public UsageStatsManager getUsageStatsManager() {
+        if (mUsm == null) {
+            mUsm = getSystemService(UsageStatsManager.class);
+        }
+        return mUsm;
+    }
+
+    @Override
+    public boolean isVoiceInteraction() {
+        if (sOverrides.isVoiceInteraction != null) {
+            return sOverrides.isVoiceInteraction;
+        }
+        return super.isVoiceInteraction();
+    }
+
+    @Override
+    protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+        if (sOverrides.mCrossProfileIntentsChecker != null) {
+            return sOverrides.mCrossProfileIntentsChecker;
+        }
+        return super.createCrossProfileIntentsChecker();
+    }
+
+    @Override
+    protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() {
+        if (sOverrides.mWorkProfileAvailability != null) {
+            return sOverrides.mWorkProfileAvailability;
+        }
+        return super.createWorkProfileAvailabilityManager();
+    }
+
+    @Override
+    public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
+            @Nullable Bundle options) {
+        if (sOverrides.onSafelyStartInternalCallback != null
+                && sOverrides.onSafelyStartInternalCallback.apply(cti)) {
+            return;
+        }
+        super.safelyStartActivityInternal(cti, user, options);
+    }
+
+    @Override
+    protected ChooserListController createListController(UserHandle userHandle) {
+        if (userHandle == UserHandle.SYSTEM) {
+            return sOverrides.resolverListController;
+        }
+        return sOverrides.workResolverListController;
+    }
+
+    @Override
+    public PackageManager getPackageManager() {
+        if (sOverrides.createPackageManager != null) {
+            return sOverrides.createPackageManager.apply(super.getPackageManager());
+        }
+        return super.getPackageManager();
+    }
+
+    @Override
+    public Resources getResources() {
+        if (sOverrides.resources != null) {
+            return sOverrides.resources;
+        }
+        return super.getResources();
+    }
+
+    @Override
+    protected ViewModelProvider.Factory createPreviewViewModelFactory() {
+        return TestContentPreviewViewModel.Companion.wrap(
+                super.createPreviewViewModelFactory(),
+                sOverrides.imageLoader);
+    }
+
+    @Override
+    public Cursor queryResolver(ContentResolver resolver, Uri uri) {
+        if (sOverrides.resolverCursor != null) {
+            return sOverrides.resolverCursor;
+        }
+
+        if (sOverrides.resolverForceException) {
+            throw new SecurityException("Test exception handling");
+        }
+
+        return super.queryResolver(resolver, uri);
+    }
+
+    @Override
+    protected boolean isWorkProfile() {
+        if (sOverrides.alternateProfileSetting != 0) {
+            return sOverrides.alternateProfileSetting == MetricsEvent.MANAGED_PROFILE;
+        }
+        return super.isWorkProfile();
+    }
+
+    @Override
+    public DisplayResolveInfo createTestDisplayResolveInfo(
+            Intent originalIntent,
+            ResolveInfo pri,
+            CharSequence pLabel,
+            CharSequence pInfo,
+            Intent replacementIntent) {
+        return DisplayResolveInfo.newDisplayResolveInfo(
+                originalIntent,
+                pri,
+                pLabel,
+                pInfo,
+                replacementIntent);
+    }
+
+    @Override
+    protected AnnotatedUserHandles computeAnnotatedUserHandles() {
+        return sOverrides.annotatedUserHandles;
+    }
+
+    @Override
+    public UserHandle getCurrentUserHandle() {
+        return mMultiProfilePagerAdapter.getCurrentUserHandle();
+    }
+
+    @Override
+    public Context createContextAsUser(UserHandle user, int flags) {
+        // return the current context as a work profile doesn't really exist in these tests
+        return this;
+    }
+
+    @Override
+    protected ShortcutLoader createShortcutLoader(
+            Context context,
+            AppPredictor appPredictor,
+            UserHandle userHandle,
+            IntentFilter targetIntentFilter,
+            Consumer<ShortcutLoader.Result> callback) {
+        ShortcutLoader shortcutLoader =
+                sOverrides.shortcutLoaderFactory.invoke(userHandle, callback);
+        if (shortcutLoader != null) {
+            return shortcutLoader;
+        }
+        return super.createShortcutLoader(
+                context, appPredictor, userHandle, targetIntentFilter, callback);
+    }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/ResolverActivityTest.java b/java/tests/src/com/android/intentresolver/v2/ResolverActivityTest.java
new file mode 100644
index 0000000..f091183
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/ResolverActivityTest.java
@@ -0,0 +1,1105 @@
+/*
+ * Copyright (C) 2016 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.intentresolver.v2;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.intentresolver.MatcherUtils.first;
+import static com.android.intentresolver.v2.ResolverWrapperActivity.sOverrides;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.Espresso;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.intentresolver.AnnotatedUserHandles;
+import com.android.intentresolver.R;
+import com.android.intentresolver.ResolvedComponentInfo;
+import com.android.intentresolver.ResolverDataProvider;
+import com.android.intentresolver.widget.ResolverDrawerLayout;
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Resolver activity instrumentation tests
+ */
+@RunWith(AndroidJUnit4.class)
+public class ResolverActivityTest {
+
+    private static final UserHandle PERSONAL_USER_HANDLE = androidx.test.platform.app
+            .InstrumentationRegistry.getInstrumentation().getTargetContext().getUser();
+    private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10);
+    private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11);
+
+    protected Intent getConcreteIntentForLaunch(Intent clientIntent) {
+        clientIntent.setClass(
+                androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                ResolverWrapperActivity.class);
+        return clientIntent;
+    }
+
+    @Rule
+    public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
+            new ActivityTestRule<>(ResolverWrapperActivity.class, false, false);
+
+    @Before
+    public void setup() {
+        // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
+        // permissions we require (which we'll read from the manifest at runtime).
+        androidx.test.platform.app.InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity();
+
+        sOverrides.reset();
+    }
+
+    @Test
+    public void twoOptionsAndUserSelectsOne() throws InterruptedException {
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        waitForIdle();
+
+        assertThat(activity.getAdapter().getCount(), is(2));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+
+        ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+        onView(withText(toChoose.activityInfo.name))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Ignore // Failing - b/144929805
+    @Test
+    public void setMaxHeight() throws Exception {
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
+
+        setupResolverControllers(resolvedComponentInfos);
+        waitForIdle();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager);
+        final int initialResolverHeight = viewPager.getHeight();
+
+        activity.runOnUiThread(() -> {
+            ResolverDrawerLayout layout = (ResolverDrawerLayout)
+                    activity.findViewById(
+                            com.android.internal.R.id.contentPanel);
+            ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
+                    = initialResolverHeight - 1;
+            // Force a relayout
+            layout.invalidate();
+            layout.requestLayout();
+        });
+        waitForIdle();
+        assertThat("Drawer should be capped at maxHeight",
+                viewPager.getHeight() == (initialResolverHeight - 1));
+
+        activity.runOnUiThread(() -> {
+            ResolverDrawerLayout layout = (ResolverDrawerLayout)
+                    activity.findViewById(
+                            com.android.internal.R.id.contentPanel);
+            ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
+                    = initialResolverHeight + 1;
+            // Force a relayout
+            layout.invalidate();
+            layout.requestLayout();
+        });
+        waitForIdle();
+        assertThat("Drawer should not change height if its height is less than maxHeight",
+                viewPager.getHeight() == initialResolverHeight);
+    }
+
+    @Ignore // Failing - b/144929805
+    @Test
+    public void setShowAtTopToTrue() throws Exception {
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
+
+        setupResolverControllers(resolvedComponentInfos);
+        waitForIdle();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager);
+        final View divider = activity.findViewById(com.android.internal.R.id.divider);
+        final RelativeLayout profileView =
+                (RelativeLayout) activity.findViewById(com.android.internal.R.id.profile_button)
+                        .getParent();
+        assertThat("Drawer should show at bottom by default",
+                profileView.getBottom() + divider.getHeight() == viewPager.getTop()
+                        && profileView.getTop() > 0);
+
+        activity.runOnUiThread(() -> {
+            ResolverDrawerLayout layout = (ResolverDrawerLayout)
+                    activity.findViewById(
+                            com.android.internal.R.id.contentPanel);
+            layout.setShowAtTop(true);
+        });
+        waitForIdle();
+        assertThat("Drawer should show at top with new attribute",
+                profileView.getBottom() + divider.getHeight() == viewPager.getTop()
+                        && profileView.getTop() == 0);
+    }
+
+    @Test
+    public void hasLastChosenActivity() throws Exception {
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
+        ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+
+        setupResolverControllers(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        // The other entry is filtered to the last used slot
+        assertThat(activity.getAdapter().getCount(), is(1));
+        assertThat(activity.getAdapter().getPlaceholderCount(), is(1));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+
+        onView(withId(com.android.internal.R.id.button_once)).perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Test
+    public void hasOtherProfileOneOption() throws Exception {
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+
+        ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
+        Intent sendIntent = createSendImageIntent();
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        waitForIdle();
+
+        // The other entry is filtered to the last used slot
+        assertThat(activity.getAdapter().getCount(), is(1));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10,
+                        PERSONAL_USER_HANDLE);
+        // We pick the first one as there is another one in the work profile side
+        onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Test
+    public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        waitForIdle();
+
+        // The other entry is filtered to the other profile slot
+        assertThat(activity.getAdapter().getCount(), is(2));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+
+        // Confirm that the button bar is disabled by default
+        onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled())));
+
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
+
+        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once)).perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+
+    @Test
+    public void hasLastChosenActivityAndOtherProfile() throws Exception {
+        // In this case we prefer the other profile and don't display anything about the last
+        // chosen activity.
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+        setupResolverControllers(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        waitForIdle();
+
+        // The other entry is filtered to the other profile slot
+        assertThat(activity.getAdapter().getCount(), is(2));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+
+        // Confirm that the button bar is disabled by default
+        onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled())));
+
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
+
+        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once)).perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Test
+    public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
+        Intent sendIntent = createSendImageIntent();
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        onView(withId(com.android.internal.R.id.tabs)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
+        Intent sendIntent = createSendImageIntent();
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        onView(withId(com.android.internal.R.id.tabs)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException {
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10,
+                        PERSONAL_USER_HANDLE);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos,
+                new ArrayList<>(workResolvedComponentInfos));
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+        // The work list adapter must be populated in advance before tapping the other tab
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+    }
+
+    @Test
+    public void testWorkTab_workTabUsesExpectedAdapter() {
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+    }
+
+    @Test
+    public void testWorkTab_personalTabUsesExpectedAdapter() {
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(2));
+    }
+
+    @Test
+    public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+        waitForIdle();
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+    }
+
+    @Test
+    public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+        waitForIdle();
+        onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once))
+                .perform(click());
+
+        waitForIdle();
+        assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
+    @Test
+    public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets()
+            throws InterruptedException {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+
+        waitForIdle();
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+    }
+
+    @Test
+    public void testWorkTab_headerIsVisibleInPersonalTab() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createOpenWebsiteIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        TextView headerText = activity.findViewById(com.android.internal.R.id.title);
+        String initialText = headerText.getText().toString();
+        assertFalse("Header text is empty.", initialText.isEmpty());
+        assertThat(headerText.getVisibility(), is(View.VISIBLE));
+    }
+
+    @Test
+    public void testWorkTab_switchTabs_headerStaysSame() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createOpenWebsiteIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        TextView headerText = activity.findViewById(com.android.internal.R.id.title);
+        String initialText = headerText.getText().toString();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+
+        waitForIdle();
+        String currentText = headerText.getText().toString();
+        assertThat(headerText.getVisibility(), is(View.VISIBLE));
+        assertThat(String.format("Header text is not the same when switching tabs, personal profile"
+                        + " header was %s but work profile header is %s", initialText, currentText),
+                TextUtils.equals(initialText, currentText));
+    }
+
+    @Test
+    public void testWorkTab_noPersonalApps_canStartWorkApps()
+            throws InterruptedException {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+        waitForIdle();
+        onView(first(allOf(
+                withText(workResolvedComponentInfos.get(0)
+                        .getResolveInfoAt(0).activityInfo.applicationInfo.name),
+                isDisplayed())))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once))
+                .perform(click());
+        waitForIdle();
+
+        assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
+    @Test
+    public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE);
+        sOverrides.hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_workProfileDisabled_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE);
+        sOverrides.isQuietModeEnabled = true;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_turn_on_work_apps))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_noWorkAppsAvailable_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_no_work_apps_available))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        sOverrides.isQuietModeEnabled = true;
+        sOverrides.hasCrossProfileIntents = false;
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testMiniResolver() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE);
+        // Personal profile only has a browser
+        personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.open_cross_profile)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testMiniResolver_noCurrentProfileTarget() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(0, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        // Need to ensure mini resolver doesn't trigger here.
+        assertNotMiniResolver();
+    }
+
+    private void assertNotMiniResolver() {
+        try {
+            onView(withId(com.android.internal.R.id.open_cross_profile))
+                    .check(matches(isDisplayed()));
+        } catch (NoMatchingViewException e) {
+            return;
+        }
+        fail("Mini resolver present but shouldn't be");
+    }
+
+    @Test
+    public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        sOverrides.isQuietModeEnabled = true;
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_no_work_apps_available))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE);
+        sOverrides.hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            chosen[0] = result.first.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertNull(chosen[0]);
+    }
+
+    @Test
+    public void testLayoutWithDefault_withWorkTab_neverShown() throws RemoteException {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+
+        // In this case we prefer the other profile and don't display anything about the last
+        // chosen activity.
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsForTest(2, PERSONAL_USER_HANDLE);
+
+        setupResolverControllers(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        waitForIdle();
+
+        // The other entry is filtered to the last used slot
+        assertThat(activity.getAdapter().hasFilteredItem(), is(false));
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+        setupResolverControllers(resolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
+        assertThat(activity.getAdapter().getCount(), is(3));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
+        assertThat(activity.getAdapter().getCount(), is(3));
+    }
+
+    @Test
+    public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        2,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+
+        setupResolverControllers(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+        waitForIdle();
+
+        assertThat(activity.getAdapter().hasFilteredItem(), is(false));
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
+    }
+
+    @Test
+    public void testClonedProfilePresent_alwaysButtonDisabled() throws Exception {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+
+        setupResolverControllers(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        // Confirm that the button bar is disabled by default
+        onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled())));
+        onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled())));
+
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
+
+        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+
+        onView(withId(com.android.internal.R.id.button_once)).check(matches(isEnabled()));
+        onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled())));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalProfileActivityIsStartedInCorrectUser()
+            throws Exception {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
+
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE);
+        sOverrides.hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            selectedActivityUserHandle[0] = result.second;
+            return true;
+        };
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(first(allOf(withText(personalResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once))
+                .perform(click());
+        waitForIdle();
+
+        assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+    }
+
+    @Test
+    public void testClonedProfilePresent_workProfileActivityIsStartedInCorrectUser()
+            throws Exception {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
+
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+        sOverrides.onSafelyStartInternalCallback = result -> {
+            selectedActivityUserHandle[0] = result.second;
+            return true;
+        };
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+        waitForIdle();
+        onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+                .perform(click());
+        onView(withId(com.android.internal.R.id.button_once))
+                .perform(click());
+        waitForIdle();
+
+        assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalProfileResolverComparatorHasCorrectUsers()
+            throws Exception {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+        setupResolverControllers(resolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        List<UserHandle> result = activity
+                .getResolverRankerServiceUserHandleList(PERSONAL_USER_HANDLE);
+
+        assertThat(result.containsAll(
+                Lists.newArrayList(PERSONAL_USER_HANDLE, CLONE_PROFILE_USER_HANDLE)), is(true));
+    }
+
+    private Intent createSendImageIntent() {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+        sendIntent.setType("image/jpeg");
+        return sendIntent;
+    }
+
+    private Intent createOpenWebsiteIntent() {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_VIEW);
+        sendIntent.setData(Uri.parse("https://google.com"));
+        return sendIntent;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+            UserHandle resolvedForUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
+            int numberOfResults,
+            UserHandle resolvedForPersonalUser,
+            UserHandle resolvedForClonedUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < 1; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForPersonalUser));
+        }
+        for (int i = 1; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForClonedUser));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults,
+            UserHandle resolvedForUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            if (i == 0) {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+                        resolvedForUser));
+            } else {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
+            }
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults, int userId, UserHandle resolvedForUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            if (i == 0) {
+                infoList.add(
+                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+                                resolvedForUser));
+            } else {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
+            }
+        }
+        return infoList;
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) {
+        AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder();
+        handles
+                .setUserIdOfCallingApp(1234)  // Must be non-negative.
+                .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE)
+                .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE);
+        if (workAvailable) {
+            handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE);
+        }
+        if (cloneAvailable) {
+            handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE);
+        }
+        sOverrides.annotatedUserHandles = handles.build();
+    }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos) {
+        setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>());
+    }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos,
+            List<ResolvedComponentInfo> workResolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntentAsUser(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                        .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                        .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.of(10))))
+                        .thenReturn(new ArrayList<>(workResolvedComponentInfos));
+    }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java
new file mode 100644
index 0000000..610d031
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2017 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.intentresolver.v2;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.test.espresso.idling.CountingIdlingResource;
+
+import com.android.intentresolver.AnnotatedUserHandles;
+import com.android.intentresolver.ResolverListAdapter;
+import com.android.intentresolver.ResolverListController;
+import com.android.intentresolver.WorkProfileAvailabilityManager;
+import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.chooser.SelectableTargetInfo;
+import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
+import com.android.intentresolver.icons.TargetDataLoader;
+
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/*
+ * Simple wrapper around chooser activity to be able to initiate it under test
+ */
+public class ResolverWrapperActivity extends ResolverActivity {
+    static final OverrideData sOverrides = new OverrideData();
+
+    private final CountingIdlingResource mLabelIdlingResource =
+            new CountingIdlingResource("LoadLabelTask");
+
+    public ResolverWrapperActivity() {
+        super(/* isIntentPicker= */ true);
+    }
+
+    public CountingIdlingResource getLabelIdlingResource() {
+        return mLabelIdlingResource;
+    }
+
+    @Override
+    public ResolverListAdapter createResolverListAdapter(
+            Context context,
+            List<Intent> payloadIntents,
+            Intent[] initialIntents,
+            List<ResolveInfo> rList,
+            boolean filterLastUsed,
+            UserHandle userHandle,
+            TargetDataLoader targetDataLoader) {
+        return new ResolverListAdapter(
+                context,
+                payloadIntents,
+                initialIntents,
+                rList,
+                filterLastUsed,
+                createListController(userHandle),
+                userHandle,
+                payloadIntents.get(0),  // TODO: extract upstream
+                this,
+                userHandle,
+                new TargetDataLoaderWrapper(targetDataLoader, mLabelIdlingResource));
+    }
+
+    @Override
+    protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+        if (sOverrides.mCrossProfileIntentsChecker != null) {
+            return sOverrides.mCrossProfileIntentsChecker;
+        }
+        return super.createCrossProfileIntentsChecker();
+    }
+
+    @Override
+    protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() {
+        if (sOverrides.mWorkProfileAvailability != null) {
+            return sOverrides.mWorkProfileAvailability;
+        }
+        return super.createWorkProfileAvailabilityManager();
+    }
+
+    ResolverListAdapter getAdapter() {
+        return mMultiProfilePagerAdapter.getActiveListAdapter();
+    }
+
+    ResolverListAdapter getPersonalListAdapter() {
+        return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0));
+    }
+
+    ResolverListAdapter getWorkListAdapter() {
+        if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+            return null;
+        }
+        return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
+    }
+
+    @Override
+    public boolean isVoiceInteraction() {
+        if (sOverrides.isVoiceInteraction != null) {
+            return sOverrides.isVoiceInteraction;
+        }
+        return super.isVoiceInteraction();
+    }
+
+    @Override
+    public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
+            @Nullable Bundle options) {
+        if (sOverrides.onSafelyStartInternalCallback != null
+                && sOverrides.onSafelyStartInternalCallback.apply(new Pair<>(cti, user))) {
+            return;
+        }
+        super.safelyStartActivityInternal(cti, user, options);
+    }
+
+    @Override
+    protected ResolverListController createListController(UserHandle userHandle) {
+        if (userHandle == UserHandle.SYSTEM) {
+            return sOverrides.resolverListController;
+        }
+        return sOverrides.workResolverListController;
+    }
+
+    @Override
+    public PackageManager getPackageManager() {
+        if (sOverrides.createPackageManager != null) {
+            return sOverrides.createPackageManager.apply(super.getPackageManager());
+        }
+        return super.getPackageManager();
+    }
+
+    protected UserHandle getCurrentUserHandle() {
+        return mMultiProfilePagerAdapter.getCurrentUserHandle();
+    }
+
+    @Override
+    protected AnnotatedUserHandles computeAnnotatedUserHandles() {
+        return sOverrides.annotatedUserHandles;
+    }
+
+    @Override
+    public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+        super.startActivityAsUser(intent, options, user);
+    }
+
+    @Override
+    protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle
+            userHandle) {
+        return super.getResolverRankerServiceUserHandleListInternal(userHandle);
+    }
+
+    /**
+     * We cannot directly mock the activity created since instrumentation creates it.
+     * <p>
+     * Instead, we use static instances of this object to modify behavior.
+     */
+    static class OverrideData {
+        @SuppressWarnings("Since15")
+        public Function<PackageManager, PackageManager> createPackageManager;
+        public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback;
+        public ResolverListController resolverListController;
+        public ResolverListController workResolverListController;
+        public Boolean isVoiceInteraction;
+        public AnnotatedUserHandles annotatedUserHandles;
+        public Integer myUserId;
+        public boolean hasCrossProfileIntents;
+        public boolean isQuietModeEnabled;
+        public WorkProfileAvailabilityManager mWorkProfileAvailability;
+        public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
+
+        public void reset() {
+            onSafelyStartInternalCallback = null;
+            isVoiceInteraction = null;
+            createPackageManager = null;
+            resolverListController = mock(ResolverListController.class);
+            workResolverListController = mock(ResolverListController.class);
+            annotatedUserHandles = AnnotatedUserHandles.newBuilder()
+                    .setUserIdOfCallingApp(1234)  // Must be non-negative.
+                    .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM)
+                    .setPersonalProfileUserHandle(UserHandle.SYSTEM)
+                    .build();
+            myUserId = null;
+            hasCrossProfileIntents = true;
+            isQuietModeEnabled = false;
+
+            mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) {
+                @Override
+                public boolean isQuietModeEnabled() {
+                    return isQuietModeEnabled;
+                }
+
+                @Override
+                public boolean isWorkProfileUserUnlocked() {
+                    return true;
+                }
+
+                @Override
+                public void requestQuietModeEnabled(boolean enabled) {
+                    isQuietModeEnabled = enabled;
+                }
+
+                @Override
+                public void markWorkProfileEnabledBroadcastReceived() {}
+
+                @Override
+                public boolean isWaitingToEnableWorkProfile() {
+                    return false;
+                }
+            };
+
+            mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+            when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+                    .thenAnswer(invocation -> hasCrossProfileIntents);
+        }
+    }
+
+    private static class TargetDataLoaderWrapper extends TargetDataLoader {
+        private final TargetDataLoader mTargetDataLoader;
+        private final CountingIdlingResource mLabelIdlingResource;
+
+        private TargetDataLoaderWrapper(
+                TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource) {
+            mTargetDataLoader = targetDataLoader;
+            mLabelIdlingResource = labelIdlingResource;
+        }
+
+        @Override
+        public void loadAppTargetIcon(
+                @NonNull DisplayResolveInfo info,
+                @NonNull UserHandle userHandle,
+                @NonNull Consumer<Drawable> callback) {
+            mTargetDataLoader.loadAppTargetIcon(info, userHandle, callback);
+        }
+
+        @Override
+        public void loadDirectShareIcon(
+                @NonNull SelectableTargetInfo info,
+                @NonNull UserHandle userHandle,
+                @NonNull Consumer<Drawable> callback) {
+            mTargetDataLoader.loadDirectShareIcon(info, userHandle, callback);
+        }
+
+        @Override
+        public void loadLabel(
+                @NonNull DisplayResolveInfo info,
+                @NonNull Consumer<CharSequence[]> callback) {
+            mLabelIdlingResource.increment();
+            mTargetDataLoader.loadLabel(
+                    info,
+                    (result) -> {
+                        mLabelIdlingResource.decrement();
+                        callback.accept(result);
+                    });
+        }
+
+        @Override
+        public void getOrLoadLabel(@NonNull DisplayResolveInfo info) {
+            mTargetDataLoader.getOrLoadLabel(info);
+        }
+    }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java
new file mode 100644
index 0000000..1e74c7a
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java
@@ -0,0 +1,3160 @@
+/*
+ * Copyright (C) 2016 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.intentresolver.v2;
+
+import static android.app.Activity.RESULT_OK;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.longClick;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.hasSibling;
+import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.intentresolver.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST;
+import static com.android.intentresolver.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST;
+import static com.android.intentresolver.MatcherUtils.first;
+import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET;
+import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_DEFAULT;
+import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
+import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static junit.framework.Assert.assertNull;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.app.usage.UsageStatsManager;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.DeviceConfig;
+import android.service.chooser.ChooserAction;
+import android.service.chooser.ChooserTarget;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.contrib.RecyclerViewActions;
+import androidx.test.espresso.matcher.BoundedDiagnosingMatcher;
+import androidx.test.espresso.matcher.ViewMatchers;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.intentresolver.AnnotatedUserHandles;
+import com.android.intentresolver.ChooserListAdapter;
+import com.android.intentresolver.Flags;
+import com.android.intentresolver.IChooserWrapper;
+import com.android.intentresolver.R;
+import com.android.intentresolver.ResolvedComponentInfo;
+import com.android.intentresolver.ResolverDataProvider;
+import com.android.intentresolver.TestContentProvider;
+import com.android.intentresolver.TestPreviewImageLoader;
+import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.contentpreview.ImageLoader;
+import com.android.intentresolver.logging.EventLog;
+import com.android.intentresolver.logging.FakeEventLog;
+import com.android.intentresolver.shortcuts.ShortcutLoader;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+
+/**
+ * Instrumentation tests for ChooserActivity.
+ * <p>
+ * Legacy test suite migrated from framework CoreTests.
+ * <p>
+ */
+@RunWith(Parameterized.class)
+@HiltAndroidTest
+public class UnbundledChooserActivityTest {
+
+    private static FakeEventLog getEventLog(ChooserWrapperActivity activity) {
+        return (FakeEventLog) activity.mEventLog;
+    }
+
+    private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+            .getInstrumentation().getTargetContext().getUser();
+    private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10);
+    private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11);
+
+    private static final Function<PackageManager, PackageManager> DEFAULT_PM = pm -> pm;
+    private static final Function<PackageManager, PackageManager> NO_APP_PREDICTION_SERVICE_PM =
+            pm -> {
+                PackageManager mock = Mockito.spy(pm);
+                when(mock.getAppPredictionServicePackageName()).thenReturn(null);
+                return mock;
+            };
+
+    @Parameterized.Parameters
+    public static Collection packageManagers() {
+        return Arrays.asList(new Object[][] {
+                // Default PackageManager
+                { DEFAULT_PM },
+                // No App Prediction Service
+                { NO_APP_PREDICTION_SERVICE_PM}
+        });
+    }
+
+    private static final String TEST_MIME_TYPE = "application/TestType";
+
+    private static final int CONTENT_PREVIEW_IMAGE = 1;
+    private static final int CONTENT_PREVIEW_FILE = 2;
+    private static final int CONTENT_PREVIEW_TEXT = 3;
+
+    @Rule(order = 0)
+    public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Rule(order = 1)
+    public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this);
+
+    @Rule(order = 2)
+    public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
+            new ActivityTestRule<>(ChooserWrapperActivity.class, false, false);
+
+    @Before
+    public void setUp() {
+        // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
+        // permissions we require (which we'll read from the manifest at runtime).
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity();
+
+        cleanOverrideData();
+        mHiltAndroidRule.inject();
+    }
+
+    private final Function<PackageManager, PackageManager> mPackageManagerOverride;
+
+    public UnbundledChooserActivityTest(
+                Function<PackageManager, PackageManager> packageManagerOverride) {
+        mPackageManagerOverride = packageManagerOverride;
+    }
+
+    private void setDeviceConfigProperty(
+            @NonNull String propertyName,
+            @NonNull String value) {
+        // TODO: consider running with {@link #runWithShellPermissionIdentity()} to more narrowly
+        // request WRITE_DEVICE_CONFIG permissions if we get rid of the broad grant we currently
+        // configure in {@link #setup()}.
+        // TODO: is it really appropriate that this is always set with makeDefault=true?
+        boolean valueWasSet = DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                propertyName,
+                value,
+                true /* makeDefault */);
+        if (!valueWasSet) {
+            throw new IllegalStateException(
+                        "Could not set " + propertyName + " to " + value);
+        }
+    }
+
+    public void cleanOverrideData() {
+        ChooserActivityOverrideData.getInstance().reset();
+        ChooserActivityOverrideData.getInstance().createPackageManager = mPackageManagerOverride;
+
+        setDeviceConfigProperty(
+                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+                Boolean.toString(true));
+    }
+
+    @Test
+    public void customTitle() throws InterruptedException {
+        Intent viewIntent = createViewTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(
+                Intent.createChooser(viewIntent, "chooser test"));
+
+        waitForIdle();
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getAdapter().getServiceTargetCount(), is(0));
+        onView(withId(android.R.id.title)).check(matches(withText("chooser test")));
+    }
+
+    @Test
+    public void customTitleIgnoredForSendIntents() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test"));
+        waitForIdle();
+        onView(withId(android.R.id.title))
+                .check(matches(withText(R.string.whichSendApplication)));
+    }
+
+    @Test
+    public void emptyTitle() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(android.R.id.title))
+                .check(matches(withText(R.string.whichSendApplication)));
+    }
+
+    @Test
+    public void test_shareRichTextWithRichTitle_richTextAndRichTitleDisplayed() {
+        CharSequence title = new SpannableStringBuilder()
+                .append("Rich", new UnderlineSpan(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
+                .append(
+                        "Title",
+                        new ForegroundColorSpan(Color.RED),
+                        Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+        CharSequence sharedText = new SpannableStringBuilder()
+                .append(
+                        "Rich",
+                        new BackgroundColorSpan(Color.YELLOW),
+                        Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+                .append(
+                        "Text",
+                        new StyleSpan(Typeface.ITALIC),
+                        Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
+        sendIntent.putExtra(Intent.EXTRA_TITLE, title);
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(com.android.internal.R.id.content_preview_title))
+                .check((view, e) -> {
+                    assertThat(view).isInstanceOf(TextView.class);
+                    CharSequence text = ((TextView) view).getText();
+                    assertThat(text).isInstanceOf(Spanned.class);
+                    Spanned spanned = (Spanned) text;
+                    assertThat(spanned.getSpans(0, spanned.length(), Object.class))
+                            .hasLength(2);
+                    assertThat(spanned.getSpans(0, 4, UnderlineSpan.class)).hasLength(1);
+                    assertThat(spanned.getSpans(4, spanned.length(), ForegroundColorSpan.class))
+                            .hasLength(1);
+                });
+
+        onView(withId(com.android.internal.R.id.content_preview_text))
+                .check((view, e) -> {
+                    assertThat(view).isInstanceOf(TextView.class);
+                    CharSequence text = ((TextView) view).getText();
+                    assertThat(text).isInstanceOf(Spanned.class);
+                    Spanned spanned = (Spanned) text;
+                    assertThat(spanned.getSpans(0, spanned.length(), Object.class))
+                            .hasLength(2);
+                    assertThat(spanned.getSpans(0, 4, BackgroundColorSpan.class)).hasLength(1);
+                    assertThat(spanned.getSpans(4, spanned.length(), StyleSpan.class)).hasLength(1);
+                });
+    }
+
+    @Test
+    public void emptyPreviewTitleAndThumbnail() throws InterruptedException {
+        Intent sendIntent = createSendTextIntentWithPreview(null, null);
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.content_preview_title))
+                .check(matches(not(isDisplayed())));
+        onView(withId(com.android.internal.R.id.content_preview_thumbnail))
+                .check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void visiblePreviewTitleWithoutThumbnail() throws InterruptedException {
+        String previewTitle = "My Content Preview Title";
+        Intent sendIntent = createSendTextIntentWithPreview(previewTitle, null);
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.content_preview_title))
+                .check(matches(isDisplayed()));
+        onView(withId(com.android.internal.R.id.content_preview_title))
+                .check(matches(withText(previewTitle)));
+        onView(withId(com.android.internal.R.id.content_preview_thumbnail))
+                .check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void visiblePreviewTitleWithInvalidThumbnail() throws InterruptedException {
+        String previewTitle = "My Content Preview Title";
+        Intent sendIntent = createSendTextIntentWithPreview(previewTitle,
+                Uri.parse("tel:(+49)12345789"));
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.content_preview_title))
+                .check(matches(isDisplayed()));
+        onView(withId(com.android.internal.R.id.content_preview_thumbnail))
+                .check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void visiblePreviewTitleAndThumbnail() throws InterruptedException {
+        String previewTitle = "My Content Preview Title";
+        Uri uri = Uri.parse(
+                "android.resource://com.android.frameworks.coretests/"
+                + com.android.intentresolver.tests.R.drawable.test320x240);
+        Intent sendIntent = createSendTextIntentWithPreview(previewTitle, uri);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.content_preview_title))
+                .check(matches(isDisplayed()));
+        onView(withId(com.android.internal.R.id.content_preview_thumbnail))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test @Ignore
+    public void twoOptionsAndUserSelectsOne() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        assertThat(activity.getAdapter().getCount(), is(2));
+        onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+        onView(withText(toChoose.activityInfo.name))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Test @Ignore
+    public void fourOptionsStackedIntoOneTarget() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+
+        // create just enough targets to ensure the a-z list should be shown
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1);
+
+        // next create 4 targets in a single app that should be stacked into a single target
+        String packageName = "xxx.yyy";
+        String appName = "aaa";
+        ComponentName cn = new ComponentName(packageName, appName);
+        Intent intent = new Intent("fakeIntent");
+        List<ResolvedComponentInfo> infosToStack = new ArrayList<>();
+        for (int i = 0; i < 4; i++) {
+            ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i,
+                    UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
+            resolveInfo.activityInfo.applicationInfo.name = appName;
+            resolveInfo.activityInfo.applicationInfo.packageName = packageName;
+            resolveInfo.activityInfo.packageName = packageName;
+            resolveInfo.activityInfo.name = "ccc" + i;
+            infosToStack.add(new ResolvedComponentInfo(cn, intent, resolveInfo));
+        }
+        resolvedComponentInfos.addAll(infosToStack);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // expect 1 unique targets + 1 group + 4 ranked app targets
+        assertThat(activity.getAdapter().getCount(), is(6));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        onView(allOf(withText(appName), hasSibling(withText("")))).perform(click());
+        waitForIdle();
+
+        // clicking will launch a dialog to choose the activity within the app
+        onView(withText(appName)).check(matches(isDisplayed()));
+        int i = 0;
+        for (ResolvedComponentInfo rci: infosToStack) {
+            onView(withText("ccc" + i)).check(matches(isDisplayed()));
+            ++i;
+        }
+    }
+
+    @Test @Ignore
+    public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        UsageStatsManager usm = activity.getUsageStatsManager();
+        verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
+                .topK(any(List.class), anyInt());
+        assertThat(activity.getIsSelected(), is(false));
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            return true;
+        };
+        ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+        DisplayResolveInfo testDri =
+                activity.createTestDisplayResolveInfo(
+                        sendIntent, toChoose, "testLabel", "testInfo", sendIntent);
+        onView(withText(toChoose.activityInfo.name))
+                .perform(click());
+        waitForIdle();
+        verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
+                .updateChooserCounts(Mockito.anyString(), any(UserHandle.class),
+                        Mockito.anyString());
+        verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
+                .updateModel(testDri);
+        assertThat(activity.getIsSelected(), is(true));
+    }
+
+    @Ignore // b/148158199
+    @Test
+    public void noResultsFromPackageManager() {
+        setupResolverControllers(null);
+        Intent sendIntent = createSendTextIntent();
+        final ChooserActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        final IChooserWrapper wrapper = (IChooserWrapper) activity;
+
+        waitForIdle();
+        assertThat(activity.isFinishing(), is(false));
+
+        onView(withId(android.R.id.empty)).check(matches(isDisplayed()));
+        onView(withId(com.android.internal.R.id.profile_pager)).check(matches(not(isDisplayed())));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> wrapper.getAdapter().handlePackagesChanged()
+        );
+        // backward compatibility. looks like we finish when data is empty after package change
+        assertThat(activity.isFinishing(), is(true));
+    }
+
+    @Test
+    public void autoLaunchSingleResult() throws InterruptedException {
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1);
+        setupResolverControllers(resolvedComponentInfos);
+
+        Intent sendIntent = createSendTextIntent();
+        final ChooserActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        assertThat(chosen[0], is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
+        assertThat(activity.isFinishing(), is(true));
+    }
+
+    @Test @Ignore
+    public void hasOtherProfileOneOption() {
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+
+        ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
+        Intent sendIntent = createSendTextIntent();
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // The other entry is filtered to the other profile slot
+        assertThat(activity.getAdapter().getCount(), is(1));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
+        waitForIdle();
+
+        onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Test @Ignore
+    public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3);
+        ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+        setupResolverControllers(resolvedComponentInfos);
+        when(ChooserActivityOverrideData.getInstance().resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // The other entry is filtered to the other profile slot
+        assertThat(activity.getAdapter().getCount(), is(2));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(3);
+        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Test @Ignore
+    public void hasLastChosenActivityAndOtherProfile() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3);
+        ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // The other entry is filtered to the last used slot
+        assertThat(activity.getAdapter().getCount(), is(2));
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(3);
+        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(toChoose));
+    }
+
+    @Test
+    @Ignore("b/285309527")
+    public void testFilePlusTextSharing_ExcludeText() {
+        Uri uri = createTestContentProviderUri(null, "image/png");
+        Intent sendIntent = createSendImageIntent(uri);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.imageviewer", "ImageTarget"),
+                        sendIntent, PERSONAL_USER_HANDLE),
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.textviewer", "UriTarget"),
+                        new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
+        );
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.include_text_action))
+                .check(matches(isDisplayed()))
+                .perform(click());
+        waitForIdle();
+
+        onView(withId(R.id.content_preview_text)).check(matches(withText("File only")));
+
+        AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            launchedIntentRef.set(targetInfo.getTargetIntent());
+            return true;
+        };
+
+        onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+        waitForIdle();
+        assertThat(launchedIntentRef.get().hasExtra(Intent.EXTRA_TEXT)).isFalse();
+    }
+
+    @Test
+    @Ignore("b/285309527")
+    public void testFilePlusTextSharing_RemoveAndAddBackText() {
+        Uri uri = createTestContentProviderUri("application/pdf", "image/png");
+        Intent sendIntent = createSendImageIntent(uri);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+        final String text = "https://google.com/search?q=google";
+        sendIntent.putExtra(Intent.EXTRA_TEXT, text);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.imageviewer", "ImageTarget"),
+                        sendIntent, PERSONAL_USER_HANDLE),
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.textviewer", "UriTarget"),
+                        new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
+        );
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.include_text_action))
+                .check(matches(isDisplayed()))
+                .perform(click());
+        waitForIdle();
+        onView(withId(R.id.content_preview_text)).check(matches(withText("File only")));
+
+        onView(withId(R.id.include_text_action))
+                .perform(click());
+        waitForIdle();
+
+        onView(withId(R.id.content_preview_text)).check(matches(withText(text)));
+
+        AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            launchedIntentRef.set(targetInfo.getTargetIntent());
+            return true;
+        };
+
+        onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+        waitForIdle();
+        assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text);
+    }
+
+    @Test
+    @Ignore("b/285309527")
+    public void testFilePlusTextSharing_TextExclusionDoesNotAffectAlternativeIntent() {
+        Uri uri = createTestContentProviderUri("image/png", null);
+        Intent sendIntent = createSendImageIntent(uri);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
+
+        Intent alternativeIntent = createSendTextIntent();
+        final String text = "alternative intent";
+        alternativeIntent.putExtra(Intent.EXTRA_TEXT, text);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.imageviewer", "ImageTarget"),
+                        sendIntent, PERSONAL_USER_HANDLE),
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.textviewer", "UriTarget"),
+                        alternativeIntent, PERSONAL_USER_HANDLE)
+        );
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.include_text_action))
+                .check(matches(isDisplayed()))
+                .perform(click());
+        waitForIdle();
+
+        AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            launchedIntentRef.set(targetInfo.getTargetIntent());
+            return true;
+        };
+
+        onView(withText(resolvedComponentInfos.get(1).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+        waitForIdle();
+        assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text);
+    }
+
+    @Test
+    @Ignore("b/285309527")
+    public void testImagePlusTextSharing_failedThumbnailAndExcludedText_textChanges() {
+        Uri uri = createTestContentProviderUri("image/png", null);
+        Intent sendIntent = createSendImageIntent(uri);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                new TestPreviewImageLoader(Collections.emptyMap());
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.imageviewer", "ImageTarget"),
+                        sendIntent, PERSONAL_USER_HANDLE),
+                ResolverDataProvider.createResolvedComponentInfo(
+                        new ComponentName("org.textviewer", "UriTarget"),
+                        new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
+        );
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.include_text_action))
+                .check(matches(isDisplayed()))
+                .perform(click());
+        waitForIdle();
+
+        onView(withId(R.id.image_view))
+                .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
+        onView(withId(R.id.content_preview_text))
+                .check(matches(allOf(isDisplayed(), withText("Image only"))));
+    }
+
+    @Test
+    public void copyTextToClipboard() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final ChooserActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.copy)).check(matches(isDisplayed()));
+        onView(withId(R.id.copy)).perform(click());
+        ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(
+                Context.CLIPBOARD_SERVICE);
+        ClipData clipData = clipboard.getPrimaryClip();
+        assertThat(clipData).isNotNull();
+        assertThat(clipData.getItemAt(0).getText()).isEqualTo("testing intent sending");
+
+        ClipDescription clipDescription = clipData.getDescription();
+        assertThat("text/plain", is(clipDescription.getMimeType(0)));
+
+        assertEquals(mActivityRule.getActivityResult().getResultCode(), RESULT_OK);
+    }
+
+    @Test
+    public void copyTextToClipboardLogging() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.copy)).check(matches(isDisplayed()));
+        onView(withId(R.id.copy)).perform(click());
+        FakeEventLog eventLog = getEventLog(activity);
+        assertThat(eventLog.getActionSelected())
+                .isEqualTo(new FakeEventLog.ActionSelected(
+                        /* targetType = */ EventLog.SELECTION_TYPE_COPY));
+    }
+
+    @Test
+    @Ignore
+    public void testNearbyShareLogging() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(com.android.internal.R.id.chooser_nearby_button))
+                .check(matches(isDisplayed()));
+        onView(withId(com.android.internal.R.id.chooser_nearby_button)).perform(click());
+
+        // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
+    }
+
+
+
+    @Test @Ignore
+    public void testEditImageLogs() {
+        Uri uri = createTestContentProviderUri("image/png", null);
+        Intent sendIntent = createSendImageIntent(uri);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(com.android.internal.R.id.chooser_edit_button)).check(matches(isDisplayed()));
+        onView(withId(com.android.internal.R.id.chooser_edit_button)).perform(click());
+
+        // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
+    }
+
+
+    @Test
+    public void oneVisibleImagePreview() {
+        Uri uri = createTestContentProviderUri("image/png", null);
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createWideBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(R.id.scrollable_image_preview))
+                .check((view, exception) -> {
+                    if (exception != null) {
+                        throw exception;
+                    }
+                    RecyclerView recyclerView = (RecyclerView) view;
+                    assertThat(recyclerView.getAdapter().getItemCount(), is(1));
+                    assertThat(recyclerView.getChildCount(), is(1));
+                    View imageView = recyclerView.getChildAt(0);
+                    Rect rect = new Rect();
+                    boolean isPartiallyVisible = imageView.getGlobalVisibleRect(rect);
+                    assertThat(
+                            "image preview view is not fully visible",
+                            isPartiallyVisible
+                                    && rect.width() == imageView.getWidth()
+                                    && rect.height() == imageView.getHeight());
+                });
+    }
+
+    @Test
+    public void allThumbnailsFailedToLoad_hidePreview() {
+        Uri uri = createTestContentProviderUri("image/jpg", null);
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                new TestPreviewImageLoader(Collections.emptyMap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(R.id.scrollable_image_preview))
+                .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
+    }
+
+    @Test
+    public void testSlowUriMetadata_fallbackToFilePreview() throws InterruptedException {
+        Uri uri = createTestContentProviderUri(
+                "application/pdf", "image/png", /*streamTypeTimeout=*/4_000);
+        ArrayList<Uri> uris = new ArrayList<>(1);
+        uris.add(uri);
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        assertThat(launchActivityWithTimeout(Intent.createChooser(sendIntent, null), 2_000))
+                .isTrue();
+        waitForIdle();
+
+        onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png")));
+        onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testSendManyFilesWithSmallMetadataDelayAndOneImage_fallbackToFilePreviewUi()
+            throws InterruptedException {
+        Uri fileUri = createTestContentProviderUri(
+                "application/pdf", "application/pdf", /*streamTypeTimeout=*/150);
+        Uri imageUri = createTestContentProviderUri("application/pdf", "image/png");
+        ArrayList<Uri> uris = new ArrayList<>(50);
+        for (int i = 0; i < 49; i++) {
+            uris.add(fileUri);
+        }
+        uris.add(imageUri);
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(imageUri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+        assertThat(launchActivityWithTimeout(Intent.createChooser(sendIntent, null), 2_000))
+                .isTrue();
+
+        waitForIdle();
+
+        onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png")));
+        onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testManyVisibleImagePreview_ScrollableImagePreview() {
+        Uri uri = createTestContentProviderUri("image/png", null);
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(R.id.scrollable_image_preview))
+                .perform(RecyclerViewActions.scrollToLastPosition())
+                .check((view, exception) -> {
+                    if (exception != null) {
+                        throw exception;
+                    }
+                    RecyclerView recyclerView = (RecyclerView) view;
+                    assertThat(recyclerView.getAdapter().getItemCount(), is(uris.size()));
+                });
+    }
+
+    @Test
+    public void testPartiallyLoadedMetadata_previewIsShownForTheLoadedPart()
+            throws InterruptedException {
+        Uri imgOneUri = createTestContentProviderUri("image/png", null);
+        Uri imgTwoUri = createTestContentProviderUri("image/png", null)
+                .buildUpon()
+                .path("image-2.png")
+                .build();
+        Uri docUri = createTestContentProviderUri("application/pdf", "image/png", 3_000);
+        ArrayList<Uri> uris = new ArrayList<>(2);
+        // two large previews to fill the screen and be presented right away and one
+        // document that would be delayed by the URI metadata reading
+        uris.add(imgOneUri);
+        uris.add(imgTwoUri);
+        uris.add(docUri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        Map<Uri, Bitmap> bitmaps = new HashMap<>();
+        bitmaps.put(imgOneUri, createWideBitmap(Color.RED));
+        bitmaps.put(imgTwoUri, createWideBitmap(Color.GREEN));
+        bitmaps.put(docUri, createWideBitmap(Color.BLUE));
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                new TestPreviewImageLoader(bitmaps);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        assertThat(launchActivityWithTimeout(Intent.createChooser(sendIntent, null), 1_000))
+                .isTrue();
+        waitForIdle();
+
+        onView(withId(R.id.scrollable_image_preview))
+                .check((view, exception) -> {
+                    if (exception != null) {
+                        throw exception;
+                    }
+                    RecyclerView recyclerView = (RecyclerView) view;
+                    assertThat(recyclerView.getChildCount()).isAtLeast(1);
+                    // the first view is a preview
+                    View imageView = recyclerView.getChildAt(0).findViewById(R.id.image);
+                    assertThat(imageView).isNotNull();
+                })
+                .perform(RecyclerViewActions.scrollToLastPosition())
+                .check((view, exception) -> {
+                    if (exception != null) {
+                        throw exception;
+                    }
+                    RecyclerView recyclerView = (RecyclerView) view;
+                    assertThat(recyclerView.getChildCount()).isAtLeast(1);
+                    // check that the last view is a loading indicator
+                    View loadingIndicator =
+                            recyclerView.getChildAt(recyclerView.getChildCount() - 1);
+                    assertThat(loadingIndicator).isNotNull();
+                });
+        waitForIdle();
+    }
+
+    @Test
+    public void testImageAndTextPreview() {
+        final Uri uri = createTestContentProviderUri("image/png", null);
+        final String sharedText = "text-" + System.currentTimeMillis();
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withText(sharedText))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_shareImageWithRichText_RichTextIsDisplayed() {
+        final Uri uri = createTestContentProviderUri("image/png", null);
+        final CharSequence sharedText = new SpannableStringBuilder()
+                .append(
+                        "text-",
+                        new StyleSpan(Typeface.BOLD_ITALIC),
+                        Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
+                .append(
+                        Long.toString(System.currentTimeMillis()),
+                        new ForegroundColorSpan(Color.RED),
+                        Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withText(sharedText.toString()))
+                .check(matches(isDisplayed()))
+                .check((view, e) -> {
+                    if (e != null) {
+                        throw e;
+                    }
+                    assertThat(view).isInstanceOf(TextView.class);
+                    CharSequence text = ((TextView) view).getText();
+                    assertThat(text).isInstanceOf(Spanned.class);
+                    Spanned spanned = (Spanned) text;
+                    Object[] spans = spanned.getSpans(0, text.length(), Object.class);
+                    assertThat(spans).hasLength(2);
+                    assertThat(spanned.getSpans(0, 5, StyleSpan.class)).hasLength(1);
+                    assertThat(spanned.getSpans(5, text.length(), ForegroundColorSpan.class))
+                            .hasLength(1);
+                });
+    }
+
+    @Test
+    public void testTextPreviewWhenTextIsSharedWithMultipleImages() {
+        final Uri uri = createTestContentProviderUri("image/png", null);
+        final String sharedText = "text-" + System.currentTimeMillis();
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .resolverListController
+                        .getResolversForIntentAsUser(
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.isA(List.class),
+                                Mockito.any(UserHandle.class)))
+                .thenReturn(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withText(sharedText)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testOnCreateLogging() {
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown();
+        assertThat(event).isNotNull();
+        assertThat(event.isWorkProfile()).isFalse();
+        assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE);
+    }
+
+    @Test
+    public void testOnCreateLoggingFromWorkProfile() {
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+        ChooserActivityOverrideData.getInstance().alternateProfileSetting =
+                MetricsEvent.MANAGED_PROFILE;
+
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown();
+        assertThat(event).isNotNull();
+        assertThat(event.isWorkProfile()).isTrue();
+        assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE);
+    }
+
+    @Test
+    public void testEmptyPreviewLogging() {
+        Intent sendIntent = createSendTextIntentWithPreview(null, null);
+
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent,
+                        "empty preview logger test"));
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown();
+        assertThat(event).isNotNull();
+        assertThat(event.isWorkProfile()).isFalse();
+        assertThat(event.getTargetMimeType()).isNull();
+    }
+
+    @Test
+    public void testTitlePreviewLogging() {
+        Intent sendIntent = createSendTextIntentWithPreview("TestTitle", null);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        assertThat(eventLog.getActionShareWithPreview())
+                .isEqualTo(new FakeEventLog.ActionShareWithPreview(
+                        /* previewType = */ CONTENT_PREVIEW_TEXT));
+    }
+
+    @Test
+    public void testImagePreviewLogging() {
+        Uri uri = createTestContentProviderUri("image/png", null);
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createBitmap());
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        assertThat(eventLog.getActionShareWithPreview())
+                .isEqualTo(new FakeEventLog.ActionShareWithPreview(
+                        /* previewType = */ CONTENT_PREVIEW_IMAGE));
+    }
+
+    @Test
+    public void oneVisibleFilePreview() throws InterruptedException {
+        Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf")));
+        onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+    }
+
+
+    @Test
+    public void moreThanOneVisibleFilePreview() throws InterruptedException {
+        Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+        uris.add(uri);
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf")));
+        onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 2 more files")));
+        onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void contentProviderThrowSecurityException() throws InterruptedException {
+        Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        ChooserActivityOverrideData.getInstance().resolverForceException = true;
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf")));
+        onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void contentProviderReturnsNoColumns() throws InterruptedException {
+        Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.getCount()).thenReturn(1);
+        Mockito.doNothing().when(cursor).close();
+        when(cursor.moveToFirst()).thenReturn(true);
+        when(cursor.getColumnIndex(Mockito.anyString())).thenReturn(-1);
+
+        ChooserActivityOverrideData.getInstance().resolverCursor = cursor;
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+        onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf")));
+        onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed()));
+        onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 1 more file")));
+        onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testGetBaseScore() {
+        final float testBaseScore = 0.89f;
+
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .resolverListController
+                        .getScore(Mockito.isA(DisplayResolveInfo.class)))
+                .thenReturn(testBaseScore);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        final DisplayResolveInfo testDri =
+                activity.createTestDisplayResolveInfo(
+                        sendIntent,
+                        ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE),
+                        "testLabel",
+                        "testInfo",
+                        sendIntent);
+        final ChooserListAdapter adapter = activity.getAdapter();
+
+        assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
+                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
+                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
+    }
+
+    // This test is too long and too slow and should not be taken as an example for future tests.
+    @Test
+    public void testDirectTargetSelectionLogging() {
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // create test shortcut loader factory, remember loaders and their callbacks
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                createShortcutLoaderFactory();
+
+        // Start activity
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // verify that ShortcutLoader was queried
+        ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+                ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+        verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
+
+        // send shortcuts
+        assertThat(
+                "Wrong number of app targets",
+                appTargets.getValue().length,
+                is(resolvedComponentInfos.size()));
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(1, "");
+        ShortcutLoader.Result result = new ShortcutLoader.Result(
+                true,
+                appTargets.getValue(),
+                new ShortcutLoader.ShortcutResultInfo[] {
+                        new ShortcutLoader.ShortcutResultInfo(
+                                appTargets.getValue()[0],
+                                serviceTargets
+                        )
+                },
+                new HashMap<>(),
+                new HashMap<>()
+        );
+        activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+        waitForIdle();
+
+        final ChooserListAdapter activeAdapter = activity.getAdapter();
+        assertThat(
+                "Chooser should have 3 targets (2 apps, 1 direct)",
+                activeAdapter.getCount(),
+                is(3));
+        assertThat(
+                "Chooser should have exactly one selectable direct target",
+                activeAdapter.getSelectableServiceTargetCount(),
+                is(1));
+        assertThat(
+                "The resolver info must match the resolver info used to create the target",
+                activeAdapter.getItem(0).getResolveInfo(),
+                is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
+
+        // Click on the direct target
+        String name = serviceTargets.get(0).getTitle().toString();
+        onView(withText(name))
+                .perform(click());
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        assertThat(eventLog.getShareTargetSelected()).hasSize(1);
+        FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0);
+        assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
+        assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(-1);
+        var hashResult = call.getDirectTargetHashed();
+        var hash = hashResult == null ? "" : hashResult.hashedString;
+        assertWithMessage("Hash is not predictable but must be obfuscated")
+                .that(hash).isNotEqualTo(name);
+    }
+
+    // This test is too long and too slow and should not be taken as an example for future tests.
+    @Test
+    public void testDirectTargetLoggingWithRankedAppTarget() {
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // create test shortcut loader factory, remember loaders and their callbacks
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                createShortcutLoaderFactory();
+
+        // Start activity
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // verify that ShortcutLoader was queried
+        ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+                ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+        verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
+
+        // send shortcuts
+        assertThat(
+                "Wrong number of app targets",
+                appTargets.getValue().length,
+                is(resolvedComponentInfos.size()));
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(
+                1,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ShortcutLoader.Result result = new ShortcutLoader.Result(
+                true,
+                appTargets.getValue(),
+                new ShortcutLoader.ShortcutResultInfo[] {
+                        new ShortcutLoader.ShortcutResultInfo(
+                                appTargets.getValue()[0],
+                                serviceTargets
+                        )
+                },
+                new HashMap<>(),
+                new HashMap<>()
+        );
+        activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+        waitForIdle();
+
+        final ChooserListAdapter activeAdapter = activity.getAdapter();
+        assertThat(
+                "Chooser should have 3 targets (2 apps, 1 direct)",
+                activeAdapter.getCount(),
+                is(3));
+        assertThat(
+                "Chooser should have exactly one selectable direct target",
+                activeAdapter.getSelectableServiceTargetCount(),
+                is(1));
+        assertThat(
+                "The resolver info must match the resolver info used to create the target",
+                activeAdapter.getItem(0).getResolveInfo(),
+                is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
+
+        // Click on the direct target
+        String name = serviceTargets.get(0).getTitle().toString();
+        onView(withText(name))
+                .perform(click());
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        assertThat(eventLog.getShareTargetSelected()).hasSize(1);
+        FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0);
+
+        assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
+        assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(0);
+    }
+
+    @Test
+    public void testShortcutTargetWithApplyAppLimits() {
+        // Set up resources
+        Resources resources = Mockito.spy(
+                InstrumentationRegistry.getInstrumentation().getContext().getResources());
+        ChooserActivityOverrideData.getInstance().resources = resources;
+        doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp);
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // create test shortcut loader factory, remember loaders and their callbacks
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                createShortcutLoaderFactory();
+
+        // Start activity
+        final IChooserWrapper activity = (IChooserWrapper) mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // verify that ShortcutLoader was queried
+        ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+                ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+        verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
+
+        // send shortcuts
+        assertThat(
+                "Wrong number of app targets",
+                appTargets.getValue().length,
+                is(resolvedComponentInfos.size()));
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(
+                2,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ShortcutLoader.Result result = new ShortcutLoader.Result(
+                true,
+                appTargets.getValue(),
+                new ShortcutLoader.ShortcutResultInfo[] {
+                        new ShortcutLoader.ShortcutResultInfo(
+                                appTargets.getValue()[0],
+                                serviceTargets
+                        )
+                },
+                new HashMap<>(),
+                new HashMap<>()
+        );
+        activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+        waitForIdle();
+
+        final ChooserListAdapter activeAdapter = activity.getAdapter();
+        assertThat(
+                "Chooser should have 3 targets (2 apps, 1 direct)",
+                activeAdapter.getCount(),
+                is(3));
+        assertThat(
+                "Chooser should have exactly one selectable direct target",
+                activeAdapter.getSelectableServiceTargetCount(),
+                is(1));
+        assertThat(
+                "The resolver info must match the resolver info used to create the target",
+                activeAdapter.getItem(0).getResolveInfo(),
+                is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
+        assertThat(
+                "The display label must match",
+                activeAdapter.getItem(0).getDisplayLabel(),
+                is("testTitle0"));
+    }
+
+    @Test
+    public void testShortcutTargetWithoutApplyAppLimits() {
+        setDeviceConfigProperty(
+                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+                Boolean.toString(false));
+        // Set up resources
+        Resources resources = Mockito.spy(
+                InstrumentationRegistry.getInstrumentation().getContext().getResources());
+        ChooserActivityOverrideData.getInstance().resources = resources;
+        doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp);
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // create test shortcut loader factory, remember loaders and their callbacks
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                createShortcutLoaderFactory();
+
+        // Start activity
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // verify that ShortcutLoader was queried
+        ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+                ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+        verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
+
+        // send shortcuts
+        assertThat(
+                "Wrong number of app targets",
+                appTargets.getValue().length,
+                is(resolvedComponentInfos.size()));
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(
+                2,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ShortcutLoader.Result result = new ShortcutLoader.Result(
+                true,
+                appTargets.getValue(),
+                new ShortcutLoader.ShortcutResultInfo[] {
+                        new ShortcutLoader.ShortcutResultInfo(
+                                appTargets.getValue()[0],
+                                serviceTargets
+                        )
+                },
+                new HashMap<>(),
+                new HashMap<>()
+        );
+        activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+        waitForIdle();
+
+        final ChooserListAdapter activeAdapter = activity.getAdapter();
+        assertThat(
+                "Chooser should have 4 targets (2 apps, 2 direct)",
+                activeAdapter.getCount(),
+                is(4));
+        assertThat(
+                "Chooser should have exactly two selectable direct target",
+                activeAdapter.getSelectableServiceTargetCount(),
+                is(2));
+        assertThat(
+                "The resolver info must match the resolver info used to create the target",
+                activeAdapter.getItem(0).getResolveInfo(),
+                is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
+        assertThat(
+                "The display label must match",
+                activeAdapter.getItem(0).getDisplayLabel(),
+                is("testTitle0"));
+        assertThat(
+                "The display label must match",
+                activeAdapter.getItem(1).getDisplayLabel(),
+                is("testTitle1"));
+    }
+
+    @Test
+    public void testLaunchWithCallerProvidedTarget() {
+        setDeviceConfigProperty(
+                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+                Boolean.toString(false));
+        // Set up resources
+        Resources resources = Mockito.spy(
+                InstrumentationRegistry.getInstrumentation().getContext().getResources());
+        ChooserActivityOverrideData.getInstance().resources = resources;
+        doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp);
+
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos, resolvedComponentInfos);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+
+        // set caller-provided target
+        Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
+        String callerTargetLabel = "Caller Target";
+        ChooserTarget[] targets = new ChooserTarget[] {
+                new ChooserTarget(
+                        callerTargetLabel,
+                        Icon.createWithBitmap(createBitmap()),
+                        0.1f,
+                        resolvedComponentInfos.get(0).name,
+                        new Bundle())
+        };
+        chooserIntent.putExtra(Intent.EXTRA_CHOOSER_TARGETS, targets);
+
+        // create test shortcut loader factory, remember loaders and their callbacks
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                createShortcutLoaderFactory();
+
+        // Start activity
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+
+        // verify that ShortcutLoader was queried
+        ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+                ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+        verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
+
+        // send shortcuts
+        assertThat(
+                "Wrong number of app targets",
+                appTargets.getValue().length,
+                is(resolvedComponentInfos.size()));
+        ShortcutLoader.Result result = new ShortcutLoader.Result(
+                true,
+                appTargets.getValue(),
+                new ShortcutLoader.ShortcutResultInfo[0],
+                new HashMap<>(),
+                new HashMap<>());
+        activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+        waitForIdle();
+
+        final ChooserListAdapter activeAdapter = activity.getAdapter();
+        assertThat(
+                "Chooser should have 3 targets (2 apps, 1 direct)",
+                activeAdapter.getCount(),
+                is(3));
+        assertThat(
+                "Chooser should have exactly two selectable direct target",
+                activeAdapter.getSelectableServiceTargetCount(),
+                is(1));
+        assertThat(
+                "The display label must match",
+                activeAdapter.getItem(0).getDisplayLabel(),
+                is(callerTargetLabel));
+
+        // Switch to work profile and ensure that the target *doesn't* show up there.
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        for (int i = 0; i < activity.getWorkListAdapter().getCount(); i++) {
+            assertThat(
+                    "Chooser target should not show up in opposite profile",
+                    activity.getWorkListAdapter().getItem(i).getDisplayLabel(),
+                    not(callerTargetLabel));
+        }
+    }
+
+    @Test
+    public void testLaunchWithCustomAction() throws InterruptedException {
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        Context testContext = InstrumentationRegistry.getInstrumentation().getContext();
+        final String customActionLabel = "Custom Action";
+        final String testAction = "test-broadcast-receiver-action";
+        Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
+        chooserIntent.putExtra(
+                Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS,
+                new ChooserAction[] {
+                        new ChooserAction.Builder(
+                                Icon.createWithResource("", Resources.ID_NULL),
+                                customActionLabel,
+                                PendingIntent.getBroadcast(
+                                        testContext,
+                                        123,
+                                        new Intent(testAction),
+                                        PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT))
+                                .build()
+                });
+        // Start activity
+        mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+
+        final CountDownLatch broadcastInvoked = new CountDownLatch(1);
+        BroadcastReceiver testReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                broadcastInvoked.countDown();
+            }
+        };
+        testContext.registerReceiver(testReceiver, new IntentFilter(testAction),
+                Context.RECEIVER_EXPORTED);
+
+        try {
+            onView(withText(customActionLabel)).perform(click());
+            assertTrue("Timeout waiting for broadcast",
+                    broadcastInvoked.await(5000, TimeUnit.MILLISECONDS));
+        } finally {
+            testContext.unregisterReceiver(testReceiver);
+        }
+    }
+
+    @Test
+    public void testLaunchWithShareModification() throws InterruptedException {
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        Context testContext = InstrumentationRegistry.getInstrumentation().getContext();
+        final String modifyShareAction = "test-broadcast-receiver-action";
+        Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
+        String label = "modify share";
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                testContext,
+                123,
+                new Intent(modifyShareAction),
+                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+        ChooserAction action = new ChooserAction.Builder(Icon.createWithBitmap(
+                createBitmap()), label, pendingIntent).build();
+        chooserIntent.putExtra(
+                Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION,
+                action);
+        // Start activity
+        mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+
+        final CountDownLatch broadcastInvoked = new CountDownLatch(1);
+        BroadcastReceiver testReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                broadcastInvoked.countDown();
+            }
+        };
+        testContext.registerReceiver(testReceiver, new IntentFilter(modifyShareAction),
+                Context.RECEIVER_EXPORTED);
+
+        try {
+            onView(withText(label)).perform(click());
+            assertTrue("Timeout waiting for broadcast",
+                    broadcastInvoked.await(5000, TimeUnit.MILLISECONDS));
+
+        } finally {
+            testContext.unregisterReceiver(testReceiver);
+        }
+    }
+
+    @Test
+    public void testUpdateMaxTargetsPerRow_columnCountIsUpdated() throws InterruptedException {
+        updateMaxTargetsPerRowResource(/* targetsPerRow= */ 4);
+        givenAppTargets(/* appCount= */ 16);
+        Intent sendIntent = createSendTextIntent();
+        final ChooserActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+
+        updateMaxTargetsPerRowResource(/* targetsPerRow= */ 6);
+        InstrumentationRegistry.getInstrumentation()
+                .runOnMainSync(() -> activity.onConfigurationChanged(
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext().getResources().getConfiguration()));
+
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.resolver_list))
+                .check(matches(withGridColumnCount(6)));
+    }
+
+    // This test is too long and too slow and should not be taken as an example for future tests.
+    @Test @Ignore
+    public void testDirectTargetLoggingWithAppTargetNotRankedPortrait()
+            throws InterruptedException {
+        testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_PORTRAIT, 4);
+    }
+
+    @Test @Ignore
+    public void testDirectTargetLoggingWithAppTargetNotRankedLandscape()
+            throws InterruptedException {
+        testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_LANDSCAPE, 8);
+    }
+
+    private void testDirectTargetLoggingWithAppTargetNotRanked(
+            int orientation, int appTargetsExpected) {
+        Configuration configuration =
+                new Configuration(InstrumentationRegistry.getInstrumentation().getContext()
+                        .getResources().getConfiguration());
+        configuration.orientation = orientation;
+
+        Resources resources = Mockito.spy(
+                InstrumentationRegistry.getInstrumentation().getContext().getResources());
+        ChooserActivityOverrideData.getInstance().resources = resources;
+        doReturn(configuration).when(resources).getConfiguration();
+
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(15);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // Create direct share target
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
+                resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0, PERSONAL_USER_HANDLE);
+
+        // Start activity
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        // Insert the direct share target
+        Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+        directShareToShortcutInfos.put(serviceTargets.get(0), null);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> activity.getAdapter().addServiceResults(
+                        activity.createTestDisplayResolveInfo(sendIntent,
+                                ri,
+                                "testLabel",
+                                "testInfo",
+                                sendIntent),
+                        serviceTargets,
+                        TARGET_TYPE_CHOOSER_TARGET,
+                        directShareToShortcutInfos,
+                        /* directShareToAppTargets */ null)
+        );
+
+        assertThat(
+                String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)",
+                        appTargetsExpected + 16, appTargetsExpected),
+                activity.getAdapter().getCount(), is(appTargetsExpected + 16));
+        assertThat("Chooser should have exactly one selectable direct target",
+                activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+        assertThat("The resolver info must match the resolver info used to create the target",
+                activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+
+        // Click on the direct target
+        String name = serviceTargets.get(0).getTitle().toString();
+        onView(withText(name))
+                .perform(click());
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        var invocations = eventLog.getShareTargetSelected();
+        assertWithMessage("Only one ShareTargetSelected event logged")
+                .that(invocations).hasSize(1);
+        FakeEventLog.ShareTargetSelected call = invocations.get(0);
+        assertWithMessage("targetType should be SELECTION_TYPE_SERVICE")
+                .that(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
+        assertWithMessage(
+                "The packages shouldn't match for app target and direct target")
+                .that(call.getDirectTargetAlsoRanked()).isEqualTo(-1);
+    }
+
+    @Test
+    public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        onView(withId(android.R.id.tabs)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        onView(withId(android.R.id.tabs)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testWorkTab_eachTabUsesExpectedAdapter() {
+        int personalProfileTargets = 3;
+        int otherProfileTargets = 1;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(
+                        personalProfileTargets + otherProfileTargets, /* userID */ 10);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
+                workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets));
+        assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
+    }
+
+    @Test
+    public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
+    }
+
+    @Test @Ignore
+    public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(first(allOf(
+                withText(workResolvedComponentInfos.get(0)
+                        .getResolveInfoAt(0).activityInfo.applicationInfo.name),
+                isDisplayed())))
+                .perform(click());
+        waitForIdle();
+        assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+    }
+
+    @Test
+    public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_workProfileDisabled_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_turn_on_work_apps))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_noWorkAppsAvailable_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_no_work_apps_available))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SCROLLABLE_PREVIEW)
+    public void testWorkTab_previewIsScrollable() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(300);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+
+        Uri uri = createTestContentProviderUri("image/png", null);
+
+        ArrayList<Uri> uris = new ArrayList<>();
+        uris.add(uri);
+
+        Intent sendIntent = createSendUriIntentWithPreview(uris);
+        ChooserActivityOverrideData.getInstance().imageLoader =
+                createImageLoader(uri, createWideBitmap());
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Scrollable preview test"));
+        waitForIdle();
+
+        onView(withId(R.id.scrollable_image_preview))
+                .check(matches(isDisplayed()));
+
+        onView(withId(com.android.internal.R.id.contentPanel)).perform(swipeUp());
+        waitForIdle();
+
+        onView(withId(R.id.chooser_headline_row_container))
+                .check(matches(isCompletelyDisplayed()));
+        onView(withId(R.id.headline))
+                .check(matches(isDisplayed()));
+        onView(withId(R.id.scrollable_image_preview))
+                .check(matches(not(isDisplayed())));
+    }
+
+    @Ignore // b/220067877
+    @Test
+    public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true;
+        ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true;
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_no_work_apps_available))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test @Ignore("b/222124533")
+    public void testAppTargetLogging() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // TODO(b/222124533): other test cases use a timeout to make sure that the UI is fully
+        // populated; without one, this test flakes. Ideally we should address the need for a
+        // timeout everywhere instead of introducing one to fix this particular test.
+
+        assertThat(activity.getAdapter().getCount(), is(2));
+        onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
+
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+        onView(withText(toChoose.activityInfo.name))
+                .perform(click());
+        waitForIdle();
+
+        // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
+    }
+
+    @Test
+    public void testDirectTargetLogging() {
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // create test shortcut loader factory, remember loaders and their callbacks
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                new SparseArray<>();
+        ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
+                (userHandle, callback) -> {
+                    Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
+                            new Pair<>(mock(ShortcutLoader.class), callback);
+                    shortcutLoaders.put(userHandle.getIdentifier(), pair);
+                    return pair.first;
+                };
+
+        // Start activity
+        ChooserWrapperActivity activity =
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // verify that ShortcutLoader was queried
+        ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+                ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+        verify(shortcutLoaders.get(0).first, times(1))
+                .updateAppTargets(appTargets.capture());
+
+        // send shortcuts
+        assertThat(
+                "Wrong number of app targets",
+                appTargets.getValue().length,
+                is(resolvedComponentInfos.size()));
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ShortcutLoader.Result result = new ShortcutLoader.Result(
+                // TODO: test another value as well
+                false,
+                appTargets.getValue(),
+                new ShortcutLoader.ShortcutResultInfo[] {
+                        new ShortcutLoader.ShortcutResultInfo(
+                                appTargets.getValue()[0],
+                                serviceTargets
+                        )
+                },
+                new HashMap<>(),
+                new HashMap<>()
+        );
+        activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+        waitForIdle();
+
+        assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
+                activity.getAdapter().getCount(), is(3));
+        assertThat("Chooser should have exactly one selectable direct target",
+                activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+        assertThat(
+                "The resolver info must match the resolver info used to create the target",
+                activity.getAdapter().getItem(0).getResolveInfo(),
+                is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
+
+        // Click on the direct target
+        String name = serviceTargets.get(0).getTitle().toString();
+        onView(withText(name))
+                .perform(click());
+        waitForIdle();
+
+        FakeEventLog eventLog = getEventLog(activity);
+        assertThat(eventLog.getShareTargetSelected()).hasSize(1);
+        FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0);
+        assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
+    }
+
+    @Test
+    public void testDirectTargetPinningDialog() {
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // create test shortcut loader factory, remember loaders and their callbacks
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                new SparseArray<>();
+        ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
+                (userHandle, callback) -> {
+                    Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
+                            new Pair<>(mock(ShortcutLoader.class), callback);
+                    shortcutLoaders.put(userHandle.getIdentifier(), pair);
+                    return pair.first;
+                };
+
+        // Start activity
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        // verify that ShortcutLoader was queried
+        ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+                ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+        verify(shortcutLoaders.get(0).first, times(1))
+                .updateAppTargets(appTargets.capture());
+
+        // send shortcuts
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(
+                1,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ShortcutLoader.Result result = new ShortcutLoader.Result(
+                // TODO: test another value as well
+                false,
+                appTargets.getValue(),
+                new ShortcutLoader.ShortcutResultInfo[] {
+                        new ShortcutLoader.ShortcutResultInfo(
+                                appTargets.getValue()[0],
+                                serviceTargets
+                        )
+                },
+                new HashMap<>(),
+                new HashMap<>()
+        );
+        activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+        waitForIdle();
+
+        // Long-click on the direct target
+        String name = serviceTargets.get(0).getTitle().toString();
+        onView(withText(name)).perform(longClick());
+        waitForIdle();
+
+        onView(withId(R.id.chooser_dialog_content)).check(matches(isDisplayed()));
+    }
+
+    @Test @Ignore
+    public void testEmptyDirectRowLogging() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        setupResolverControllers(resolvedComponentInfos);
+
+        // Start activity
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+
+        // Thread.sleep shouldn't be a thing in an integration test but it's
+        // necessary here because of the way the code is structured
+        Thread.sleep(3000);
+
+        assertThat("Chooser should have 2 app targets",
+                activity.getAdapter().getCount(), is(2));
+        assertThat("Chooser should have no direct targets",
+                activity.getAdapter().getSelectableServiceTargetCount(), is(0));
+
+        // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
+    }
+
+    @Ignore // b/220067877
+    @Test
+    public void testCopyTextToClipboardLogging() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        setupResolverControllers(resolvedComponentInfos);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(com.android.internal.R.id.chooser_copy_button)).check(matches(isDisplayed()));
+        onView(withId(com.android.internal.R.id.chooser_copy_button)).perform(click());
+
+        // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
+    }
+
+    @Test @Ignore("b/222124533")
+    public void testSwitchProfileLogging() throws InterruptedException {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        onView(withText(R.string.resolver_personal_tab)).perform(click());
+        waitForIdle();
+
+        // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
+    }
+
+    @Test
+    public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
+        waitForIdle();
+
+        assertNull(chosen[0]);
+    }
+
+    @Test
+    public void testOneInitialIntent_noAutolaunch() {
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(1);
+        setupResolverControllers(personalResolvedComponentInfos);
+        Intent chooserIntent = createChooserIntent(createSendTextIntent(),
+                new Intent[] {new Intent("action.fake")});
+        ResolveInfo[] chosen = new ResolveInfo[1];
+        ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
+            chosen[0] = targetInfo.getResolveInfo();
+            return true;
+        };
+        ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
+        ResolveInfo ri = createFakeResolveInfo();
+        when(
+                ChooserActivityOverrideData
+                        .getInstance().packageManager
+                        .resolveActivity(any(Intent.class), any()))
+                .thenReturn(ri);
+        waitForIdle();
+
+        IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+
+        assertNull(chosen[0]);
+        assertThat(activity
+                .getPersonalListAdapter().getCallerTargetCount(), is(1));
+    }
+
+    @Test
+    public void testWorkTab_withInitialIntents_workTabDoesNotIncludePersonalInitialIntents() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 1;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent[] initialIntents = {
+                new Intent("action.fake1"),
+                new Intent("action.fake2")
+        };
+        Intent chooserIntent = createChooserIntent(createSendTextIntent(), initialIntents);
+        ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .packageManager
+                        .resolveActivity(any(Intent.class), any()))
+                .thenReturn(createFakeResolveInfo());
+        waitForIdle();
+
+        IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+
+        assertThat(activity.getPersonalListAdapter().getCallerTargetCount(), is(2));
+        assertThat(activity.getWorkListAdapter().getCallerTargetCount(), is(0));
+    }
+
+    @Test
+    public void testWorkTab_xProfileIntentsDisabled_personalToWork_nonSendIntent_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets);
+        ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent[] initialIntents = {
+                new Intent("action.fake1"),
+                new Intent("action.fake2")
+        };
+        Intent chooserIntent = createChooserIntent(new Intent(), initialIntents);
+        ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .packageManager
+                        .resolveActivity(any(Intent.class), any()))
+                .thenReturn(createFakeResolveInfo());
+
+        mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testWorkTab_noWorkAppsAvailable_nonSendIntent_emptyStateShown() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(0);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent[] initialIntents = {
+                new Intent("action.fake1"),
+                new Intent("action.fake2")
+        };
+        Intent chooserIntent = createChooserIntent(new Intent(), initialIntents);
+        ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .packageManager
+                        .resolveActivity(any(Intent.class), any()))
+                .thenReturn(createFakeResolveInfo());
+
+        mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        onView(withText(R.string.resolver_no_work_apps_available))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testDeduplicateCallerTargetRankedTarget() {
+        // Create 4 ranked app targets.
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(4);
+        setupResolverControllers(personalResolvedComponentInfos);
+        // Create caller target which is duplicate with one of app targets
+        Intent chooserIntent = createChooserIntent(createSendTextIntent(),
+                new Intent[] {new Intent("action.fake")});
+        ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(0,
+                UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .packageManager
+                        .resolveActivity(any(Intent.class), any()))
+                .thenReturn(ri);
+        waitForIdle();
+
+        IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent);
+        waitForIdle();
+
+        // Total 4 targets (1 caller target, 3 ranked targets)
+        assertThat(activity.getAdapter().getCount(), is(4));
+        assertThat(activity.getAdapter().getCallerTargetCount(), is(1));
+        assertThat(activity.getAdapter().getRankedTargetCount(), is(3));
+    }
+
+    @Test
+    public void test_query_shortcut_loader_for_the_selected_tab() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        ShortcutLoader personalProfileShortcutLoader = mock(ShortcutLoader.class);
+        ShortcutLoader workProfileShortcutLoader = mock(ShortcutLoader.class);
+        final SparseArray<ShortcutLoader> shortcutLoaders = new SparseArray<>();
+        shortcutLoaders.put(0, personalProfileShortcutLoader);
+        shortcutLoaders.put(10, workProfileShortcutLoader);
+        ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
+                (userHandle, callback) -> shortcutLoaders.get(userHandle.getIdentifier(), null);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+        waitForIdle();
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        waitForIdle();
+
+        verify(personalProfileShortcutLoader, times(1)).updateAppTargets(any());
+
+        onView(withText(R.string.resolver_work_tab)).perform(click());
+        waitForIdle();
+
+        verify(workProfileShortcutLoader, times(1)).updateAppTargets(any());
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+        // enable cloneProfile
+        markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        CLONE_PROFILE_USER_HANDLE);
+        setupResolverControllers(resolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+
+        final IChooserWrapper activity = (IChooserWrapper) mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, "personalProfileTest"));
+        waitForIdle();
+
+        assertThat(activity.getPersonalListAdapter().getUserHandle(), is(PERSONAL_USER_HANDLE));
+        assertThat(activity.getAdapter().getCount(), is(3));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+        markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
+                4);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "multi tab test"));
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
+    }
+
+    private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
+        Intent chooserIntent = new Intent();
+        chooserIntent.setAction(Intent.ACTION_CHOOSER);
+        chooserIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+        chooserIntent.putExtra(Intent.EXTRA_TITLE, "some title");
+        chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
+        chooserIntent.setType("text/plain");
+        if (initialIntents != null) {
+            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents);
+        }
+        return chooserIntent;
+    }
+
+    /* This is a "test of a test" to make sure that our inherited test class
+     * is successfully configured to operate on the unbundled-equivalent
+     * ChooserWrapperActivity.
+     *
+     * TODO: remove after unbundling is complete.
+     */
+    @Test
+    public void testWrapperActivityHasExpectedConcreteType() {
+        final ChooserActivity activity = mActivityRule.launchActivity(
+                Intent.createChooser(new Intent("ACTION_FOO"), "foo"));
+        waitForIdle();
+        assertThat(activity).isInstanceOf(ChooserWrapperActivity.class);
+    }
+
+    private ResolveInfo createFakeResolveInfo() {
+        ResolveInfo ri = new ResolveInfo();
+        ri.activityInfo = new ActivityInfo();
+        ri.activityInfo.name = "FakeActivityName";
+        ri.activityInfo.packageName = "fake.package.name";
+        ri.activityInfo.applicationInfo = new ApplicationInfo();
+        ri.activityInfo.applicationInfo.packageName = "fake.package.name";
+        ri.userHandle = UserHandle.CURRENT;
+        return ri;
+    }
+
+    private Intent createSendTextIntent() {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+        sendIntent.setType("text/plain");
+        return sendIntent;
+    }
+
+    private Intent createSendImageIntent(Uri imageThumbnail) {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.putExtra(Intent.EXTRA_STREAM, imageThumbnail);
+        sendIntent.setType("image/png");
+        if (imageThumbnail != null) {
+            ClipData.Item clipItem = new ClipData.Item(imageThumbnail);
+            sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem));
+        }
+
+        return sendIntent;
+    }
+
+    private Uri createTestContentProviderUri(
+            @Nullable String mimeType, @Nullable String streamType) {
+        return createTestContentProviderUri(mimeType, streamType, 0);
+    }
+
+    private Uri createTestContentProviderUri(
+            @Nullable String mimeType, @Nullable String streamType, long streamTypeTimeout) {
+        String packageName =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+        Uri.Builder builder = Uri.parse("content://" + packageName + "/image.png")
+                .buildUpon();
+        if (mimeType != null) {
+            builder.appendQueryParameter(TestContentProvider.PARAM_MIME_TYPE, mimeType);
+        }
+        if (streamType != null) {
+            builder.appendQueryParameter(TestContentProvider.PARAM_STREAM_TYPE, streamType);
+        }
+        if (streamTypeTimeout > 0) {
+            builder.appendQueryParameter(
+                    TestContentProvider.PARAM_STREAM_TYPE_TIMEOUT,
+                    Long.toString(streamTypeTimeout));
+        }
+        return builder.build();
+    }
+
+    private Intent createSendTextIntentWithPreview(String title, Uri imageThumbnail) {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+        sendIntent.putExtra(Intent.EXTRA_TITLE, title);
+        if (imageThumbnail != null) {
+            ClipData.Item clipItem = new ClipData.Item(imageThumbnail);
+            sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem));
+        }
+
+        return sendIntent;
+    }
+
+    private Intent createSendUriIntentWithPreview(ArrayList<Uri> uris) {
+        Intent sendIntent = new Intent();
+
+        if (uris.size() > 1) {
+            sendIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
+            sendIntent.putExtra(Intent.EXTRA_STREAM, uris);
+        } else {
+            sendIntent.setAction(Intent.ACTION_SEND);
+            sendIntent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
+        }
+
+        return sendIntent;
+    }
+
+    private Intent createViewTextIntent() {
+        Intent viewIntent = new Intent();
+        viewIntent.setAction(Intent.ACTION_VIEW);
+        viewIntent.putExtra(Intent.EXTRA_TEXT, "testing intent viewing");
+        return viewIntent;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
+            int numberOfResults,
+            UserHandle resolvedForPersonalUser,
+            UserHandle resolvedForClonedUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < 1; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForPersonalUser));
+        }
+        for (int i = 1; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForClonedUser));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            if (i == 0) {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+                        PERSONAL_USER_HANDLE));
+            } else {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                        PERSONAL_USER_HANDLE));
+            }
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults, int userId) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            if (i == 0) {
+                infoList.add(
+                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+                                PERSONAL_USER_HANDLE));
+            } else {
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                        PERSONAL_USER_HANDLE));
+            }
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId(
+            int numberOfResults, int userId) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+                    PERSONAL_USER_HANDLE));
+        }
+        return infoList;
+    }
+
+    private List<ChooserTarget> createDirectShareTargets(int numberOfResults, String packageName) {
+        Icon icon = Icon.createWithBitmap(createBitmap());
+        String testTitle = "testTitle";
+        List<ChooserTarget> targets = new ArrayList<>();
+        for (int i = 0; i < numberOfResults; i++) {
+            ComponentName componentName;
+            if (packageName.isEmpty()) {
+                componentName = ResolverDataProvider.createComponentName(i);
+            } else {
+                componentName = new ComponentName(packageName, packageName + ".class");
+            }
+            ChooserTarget tempTarget = new ChooserTarget(
+                    testTitle + i,
+                    icon,
+                    (float) (1 - ((i + 1) / 10.0)),
+                    componentName,
+                    null);
+            targets.add(tempTarget);
+        }
+        return targets;
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private boolean launchActivityWithTimeout(Intent intent, long timeout)
+            throws InterruptedException {
+        final int initialState = 0;
+        final int completedState = 1;
+        final int timeoutState = 2;
+        final AtomicInteger state = new AtomicInteger(initialState);
+        final CountDownLatch cdl = new CountDownLatch(1);
+
+        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
+        try {
+            executor.execute(() -> {
+                mActivityRule.launchActivity(intent);
+                state.compareAndSet(initialState, completedState);
+                cdl.countDown();
+            });
+            executor.schedule(
+                    () -> {
+                        state.compareAndSet(initialState, timeoutState);
+                        cdl.countDown();
+                    },
+                    timeout,
+                    TimeUnit.MILLISECONDS);
+            cdl.await();
+            return state.get() == completedState;
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    private Bitmap createBitmap() {
+        return createBitmap(200, 200);
+    }
+
+    private Bitmap createWideBitmap() {
+        return createWideBitmap(Color.RED);
+    }
+
+    private Bitmap createWideBitmap(int bgColor) {
+        WindowManager windowManager = InstrumentationRegistry.getInstrumentation()
+                .getTargetContext()
+                .getSystemService(WindowManager.class);
+        int width = 3000;
+        if (windowManager != null) {
+            Rect bounds = windowManager.getMaximumWindowMetrics().getBounds();
+            width = bounds.width() + 200;
+        }
+        return createBitmap(width, 100, bgColor);
+    }
+
+    private Bitmap createBitmap(int width, int height) {
+        return createBitmap(width, height, Color.RED);
+    }
+
+    private Bitmap createBitmap(int width, int height, int bgColor) {
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        Paint paint = new Paint();
+        paint.setColor(bgColor);
+        paint.setStyle(Paint.Style.FILL);
+        canvas.drawPaint(paint);
+
+        paint.setColor(Color.WHITE);
+        paint.setAntiAlias(true);
+        paint.setTextSize(14.f);
+        paint.setTextAlign(Paint.Align.CENTER);
+        canvas.drawText("Hi!", (width / 2.f), (height / 2.f), paint);
+
+        return bitmap;
+    }
+
+    private List<ShareShortcutInfo> createShortcuts(Context context) {
+        Intent testIntent = new Intent("TestIntent");
+
+        List<ShareShortcutInfo> shortcuts = new ArrayList<>();
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut1")
+                        .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0  2
+                new ComponentName("package1", "class1")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut2")
+                        .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1  3
+                new ComponentName("package2", "class2")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut3")
+                        .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2  0
+                new ComponentName("package3", "class3")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut4")
+                        .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3  2
+                new ComponentName("package4", "class4")));
+
+        return shortcuts;
+    }
+
+    private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) {
+        AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder();
+        handles
+                .setUserIdOfCallingApp(1234)  // Must be non-negative.
+                .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE)
+                .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE);
+        if (workAvailable) {
+            handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE);
+        }
+        if (cloneAvailable) {
+            handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE);
+        }
+        ChooserWrapperActivity.sOverrides.annotatedUserHandles = handles.build();
+    }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos) {
+        setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>());
+    }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos,
+            List<ResolvedComponentInfo> workResolvedComponentInfos) {
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .resolverListController
+                        .getResolversForIntentAsUser(
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.isA(List.class),
+                                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .workResolverListController
+                        .getResolversForIntentAsUser(
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.isA(List.class),
+                                eq(UserHandle.SYSTEM)))
+                .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(
+                ChooserActivityOverrideData
+                        .getInstance()
+                        .workResolverListController
+                        .getResolversForIntentAsUser(
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
+                                Mockito.isA(List.class),
+                                eq(UserHandle.of(10))))
+                .thenReturn(new ArrayList<>(workResolvedComponentInfos));
+    }
+
+    private static GridRecyclerSpanCountMatcher withGridColumnCount(int columnCount) {
+        return new GridRecyclerSpanCountMatcher(Matchers.is(columnCount));
+    }
+
+    private static class GridRecyclerSpanCountMatcher extends
+            BoundedDiagnosingMatcher<View, RecyclerView> {
+
+        private final Matcher<Integer> mIntegerMatcher;
+
+        private GridRecyclerSpanCountMatcher(Matcher<Integer> integerMatcher) {
+            super(RecyclerView.class);
+            this.mIntegerMatcher = integerMatcher;
+        }
+
+        @Override
+        protected void describeMoreTo(Description description) {
+            description.appendText("RecyclerView grid layout span count to match: ");
+            this.mIntegerMatcher.describeTo(description);
+        }
+
+        @Override
+        protected boolean matchesSafely(RecyclerView view, Description mismatchDescription) {
+            int spanCount = ((GridLayoutManager) view.getLayoutManager()).getSpanCount();
+            if (this.mIntegerMatcher.matches(spanCount)) {
+                return true;
+            } else {
+                mismatchDescription.appendText("RecyclerView grid layout span count was ")
+                        .appendValue(spanCount);
+                return false;
+            }
+        }
+    }
+
+    private void givenAppTargets(int appCount) {
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsForTest(appCount);
+        setupResolverControllers(resolvedComponentInfos);
+    }
+
+    private void updateMaxTargetsPerRowResource(int targetsPerRow) {
+        Resources resources = Mockito.spy(
+                InstrumentationRegistry.getInstrumentation().getContext().getResources());
+        ChooserActivityOverrideData.getInstance().resources = resources;
+        doReturn(targetsPerRow).when(resources).getInteger(
+                R.integer.config_chooser_max_targets_per_row);
+    }
+
+    private SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>>
+            createShortcutLoaderFactory() {
+        SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+                new SparseArray<>();
+        ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
+                (userHandle, callback) -> {
+                    Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
+                            new Pair<>(mock(ShortcutLoader.class), callback);
+                    shortcutLoaders.put(userHandle.getIdentifier(), pair);
+                    return pair.first;
+                };
+        return shortcutLoaders;
+    }
+
+    private static ImageLoader createImageLoader(Uri uri, Bitmap bitmap) {
+        return new TestPreviewImageLoader(Collections.singletonMap(uri, bitmap));
+    }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java
new file mode 100644
index 0000000..e4ec177
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2022 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.intentresolver.v2;
+
+import static android.testing.PollingCheck.waitFor;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isSelected;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.intentresolver.v2.ChooserWrapperActivity.sOverrides;
+import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.WORK;
+import static org.hamcrest.CoreMatchers.not;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.companion.DeviceFilter;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.intentresolver.AnnotatedUserHandles;
+import com.android.intentresolver.R;
+import com.android.intentresolver.ResolvedComponentInfo;
+import com.android.intentresolver.ResolverDataProvider;
+import com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+
+@DeviceFilter.MediumType
+@RunWith(Parameterized.class)
+@HiltAndroidTest
+public class UnbundledChooserActivityWorkProfileTest {
+
+    private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+            .getInstrumentation().getTargetContext().getUser();
+    private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+
+    @Rule(order = 0)
+    public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this);
+
+    @Rule(order = 1)
+    public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
+            new ActivityTestRule<>(ChooserWrapperActivity.class, false,
+                    false);
+    private final TestCase mTestCase;
+
+    public UnbundledChooserActivityWorkProfileTest(TestCase testCase) {
+        mTestCase = testCase;
+    }
+
+    @Before
+    public void cleanOverrideData() {
+        // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
+        // permissions we require (which we'll read from the manifest at runtime).
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity();
+
+        sOverrides.reset();
+    }
+
+    @Test
+    public void testBlocker() {
+        setUpPersonalAndWorkComponentInfos();
+        sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
+
+        launchActivity(mTestCase.getIsSendAction());
+        switchToTab(mTestCase.getTab());
+
+        switch (mTestCase.getExpectedBlocker()) {
+            case NO_BLOCKER:
+                assertNoBlockerDisplayed();
+                break;
+            case PERSONAL_PROFILE_SHARE_BLOCKER:
+                assertCantSharePersonalAppsBlockerDisplayed();
+                break;
+            case WORK_PROFILE_SHARE_BLOCKER:
+                assertCantShareWorkAppsBlockerDisplayed();
+                break;
+            case PERSONAL_PROFILE_ACCESS_BLOCKER:
+                assertCantAccessPersonalAppsBlockerDisplayed();
+                break;
+            case WORK_PROFILE_ACCESS_BLOCKER:
+                assertCantAccessWorkAppsBlockerDisplayed();
+                break;
+        }
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection tests() {
+        return Arrays.asList(
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ true,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ WORK,
+                        /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ WORK_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ true,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                ),
+                new TestCase(
+                        /* isSendAction= */ false,
+                        /* hasCrossProfileIntents= */ false,
+                        /* myUserHandle= */ PERSONAL_USER_HANDLE,
+                        /* tab= */ PERSONAL,
+                        /* expectedBlocker= */ NO_BLOCKER
+                )
+        );
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+            int numberOfResults, int userId, UserHandle resolvedForUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(
+                    ResolverDataProvider
+                            .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+            UserHandle resolvedForUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
+        }
+        return infoList;
+    }
+
+    private void setUpPersonalAndWorkComponentInfos() {
+        ChooserWrapperActivity.sOverrides.annotatedUserHandles = AnnotatedUserHandles.newBuilder()
+                .setUserIdOfCallingApp(1234)  // Must be non-negative.
+                .setUserHandleSharesheetLaunchedAs(mTestCase.getMyUserHandle())
+                .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE)
+                .setWorkProfileUserHandle(WORK_USER_HANDLE)
+                .build();
+        int workProfileTargets = 4;
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3,
+                        /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+    }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> personalResolvedComponentInfos,
+            List<ResolvedComponentInfo> workResolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntentAsUser(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                        .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(UserHandle.SYSTEM)))
+                        .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+        when(sOverrides.workResolverListController.getResolversForIntentAsUser(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class),
+                eq(WORK_USER_HANDLE)))
+                        .thenReturn(new ArrayList<>(workResolvedComponentInfos));
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private void assertCantAccessWorkAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_access_work_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertCantAccessPersonalAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertCantShareWorkAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_share_with_work_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertCantSharePersonalAppsBlockerDisplayed() {
+        onView(withText(R.string.resolver_cross_profile_blocked))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation))
+                .check(matches(isDisplayed()));
+    }
+
+    private void assertNoBlockerDisplayed() {
+        try {
+            onView(withText(R.string.resolver_cross_profile_blocked))
+                    .check(matches(not(isDisplayed())));
+        } catch (NoMatchingViewException ignored) {
+        }
+    }
+
+    private void switchToTab(Tab tab) {
+        final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
+                : R.string.resolver_personal_tab;
+
+        waitFor(() -> {
+            onView(withText(stringId)).perform(click());
+            waitForIdle();
+
+            try {
+                onView(withText(stringId)).check(matches(isSelected()));
+                return true;
+            } catch (AssertionFailedError e) {
+                return false;
+            }
+        });
+
+        onView(withId(com.android.internal.R.id.contentPanel))
+                .perform(swipeUp());
+        waitForIdle();
+    }
+
+    private Intent createTextIntent(boolean isSendAction) {
+        Intent sendIntent = new Intent();
+        if (isSendAction) {
+            sendIntent.setAction(Intent.ACTION_SEND);
+        }
+        sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+        sendIntent.setType("text/plain");
+        return sendIntent;
+    }
+
+    private void launchActivity(boolean isSendAction) {
+        Intent sendIntent = createTextIntent(isSendAction);
+        mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
+        waitForIdle();
+    }
+
+    public static class TestCase {
+        private final boolean mIsSendAction;
+        private final boolean mHasCrossProfileIntents;
+        private final UserHandle mMyUserHandle;
+        private final Tab mTab;
+        private final ExpectedBlocker mExpectedBlocker;
+
+        public enum ExpectedBlocker {
+            NO_BLOCKER,
+            PERSONAL_PROFILE_SHARE_BLOCKER,
+            WORK_PROFILE_SHARE_BLOCKER,
+            PERSONAL_PROFILE_ACCESS_BLOCKER,
+            WORK_PROFILE_ACCESS_BLOCKER
+        }
+
+        public enum Tab {
+            WORK,
+            PERSONAL
+        }
+
+        public TestCase(boolean isSendAction, boolean hasCrossProfileIntents,
+                UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
+            mIsSendAction = isSendAction;
+            mHasCrossProfileIntents = hasCrossProfileIntents;
+            mMyUserHandle = myUserHandle;
+            mTab = tab;
+            mExpectedBlocker = expectedBlocker;
+        }
+
+        public boolean getIsSendAction() {
+            return mIsSendAction;
+        }
+
+        public boolean hasCrossProfileIntents() {
+            return mHasCrossProfileIntents;
+        }
+
+        public UserHandle getMyUserHandle() {
+            return mMyUserHandle;
+        }
+
+        public Tab getTab() {
+            return mTab;
+        }
+
+        public ExpectedBlocker getExpectedBlocker() {
+            return mExpectedBlocker;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder("test");
+
+            if (mTab == WORK) {
+                result.append("WorkTab_");
+            } else {
+                result.append("PersonalTab_");
+            }
+
+            if (mIsSendAction) {
+                result.append("sendAction_");
+            } else {
+                result.append("notSendAction_");
+            }
+
+            if (mHasCrossProfileIntents) {
+                result.append("hasCrossProfileIntents_");
+            } else {
+                result.append("doesNotHaveCrossProfileIntents_");
+            }
+
+            if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
+                result.append("myUserIsPersonal_");
+            } else {
+                result.append("myUserIsWork_");
+            }
+
+            if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
+                result.append("thenNoBlocker");
+            } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) {
+                result.append("thenAccessBlockerOnPersonalProfile");
+            } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) {
+                result.append("thenShareBlockerOnPersonalProfile");
+            } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) {
+                result.append("thenAccessBlockerOnWorkProfile");
+            } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) {
+                result.append("thenShareBlockerOnWorkProfile");
+            }
+
+            return result.toString();
+        }
+    }
+}