[Partial screen sharing] App selector initial version

Adds an activity that inherits ChooserActivity
to select an app in the partial screen sharing
flow under a feature flag.
It doesn't have a proper visual design, analytics,
enterprise policies handling.

Bug: 218314434
Test: start partial screen sharing
Change-Id: Ia7d9ae07e2ef35d33f85530f26cab4e6cdc1f94a
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 2ed0f98..737d5e3 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -265,6 +265,7 @@
             return mResolverListController.getResolversForIntent(
                             /* shouldGetResolvedFilter= */ true,
                             mResolverListCommunicator.shouldGetActivityMetadata(),
+                            mResolverListCommunicator.shouldGetOnlyDefaultActivities(),
                             mIntents);
         }
     }
@@ -727,6 +728,7 @@
     protected List<ResolvedComponentInfo> getResolversForUser(UserHandle userHandle) {
         return mResolverListController.getResolversForIntentAsUser(true,
                 mResolverListCommunicator.shouldGetActivityMetadata(),
+                mResolverListCommunicator.shouldGetOnlyDefaultActivities(),
                 mIntents, userHandle);
     }
 
@@ -820,6 +822,12 @@
 
         boolean shouldGetActivityMetadata();
 
+        /**
+         * @return true to filter only apps that can handle
+         *     {@link android.content.Intent#CATEGORY_DEFAULT} intents
+         */
+        default boolean shouldGetOnlyDefaultActivities() { return true; };
+
         Intent getTargetIntent();
 
         void onHandlePackagesChanged(ResolverListAdapter listAdapter);
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 2757363..100fcd8 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -110,17 +110,19 @@
     public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
             boolean shouldGetResolvedFilter,
             boolean shouldGetActivityMetadata,
+            boolean shouldGetOnlyDefaultActivities,
             List<Intent> intents) {
         return getResolversForIntentAsUser(shouldGetResolvedFilter, shouldGetActivityMetadata,
-                intents, mUserHandle);
+                shouldGetOnlyDefaultActivities, intents, mUserHandle);
     }
 
     public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUser(
             boolean shouldGetResolvedFilter,
             boolean shouldGetActivityMetadata,
+            boolean shouldGetOnlyDefaultActivities,
             List<Intent> intents,
             UserHandle userHandle) {
-        int baseFlags = PackageManager.MATCH_DEFAULT_ONLY
+        int baseFlags = (shouldGetOnlyDefaultActivities ? PackageManager.MATCH_DEFAULT_ONLY : 0)
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 8214842..e8c7ce0 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -266,6 +266,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(
@@ -289,6 +290,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test"));
@@ -309,6 +311,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -329,6 +332,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -352,6 +356,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -378,6 +383,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -403,6 +409,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -424,6 +431,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -478,6 +486,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -518,6 +527,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -552,6 +562,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(null);
         Intent sendIntent = createSendTextIntent();
@@ -587,6 +598,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -649,6 +661,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
         when(ChooserActivityOverrideData.getInstance().resolverListController.getLastChosen())
                 .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
@@ -688,6 +701,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final IChooserWrapper activity = (IChooserWrapper)
@@ -720,6 +734,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
             Mockito.anyBoolean(),
             Mockito.anyBoolean(),
+            Mockito.anyBoolean(),
             Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final ChooserActivity activity =
@@ -747,6 +762,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
             Mockito.anyBoolean(),
             Mockito.anyBoolean(),
+            Mockito.anyBoolean(),
             Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
@@ -776,6 +792,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final IChooserWrapper activity = (IChooserWrapper)
@@ -846,6 +863,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final IChooserWrapper activity = (IChooserWrapper)
@@ -922,6 +940,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -958,6 +977,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -997,6 +1017,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1091,6 +1112,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
             Mockito.anyBoolean(),
             Mockito.anyBoolean(),
+            Mockito.anyBoolean(),
             Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1124,6 +1146,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -1157,6 +1180,7 @@
                     .getResolversForIntent(
                             Mockito.anyBoolean(),
                             Mockito.anyBoolean(),
+                            Mockito.anyBoolean(),
                             Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1189,6 +1213,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1218,6 +1243,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -1250,6 +1276,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -1284,6 +1311,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         when(
@@ -1323,6 +1351,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -1365,6 +1394,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -1409,6 +1439,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -1485,6 +1516,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -1566,6 +1598,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         // Create direct share target
@@ -1638,6 +1671,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
         // Create direct share target
@@ -1745,6 +1779,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -2060,6 +2095,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -2142,6 +2178,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -2229,6 +2266,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -2298,6 +2336,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
 
@@ -2446,6 +2485,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
@@ -2475,6 +2515,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
@@ -2528,6 +2569,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent chooserIntent = createChooserIntent(createSendTextIntent(),
@@ -2664,6 +2706,7 @@
         when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         // Create caller target which is duplicate with one of app targets
@@ -3057,6 +3100,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(
@@ -3066,6 +3110,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(workResolvedComponentInfos));
         when(
@@ -3075,6 +3120,7 @@
                         .getResolversForIntentAsUser(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class),
                                 eq(UserHandle.SYSTEM)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
@@ -3131,6 +3177,7 @@
                         .getResolversForIntent(
                                 Mockito.anyBoolean(),
                                 Mockito.anyBoolean(),
+                                Mockito.anyBoolean(),
                                 Mockito.isA(List.class)))
                 .thenReturn(resolvedComponentInfos);
     }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 43fba52..92c05b0 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -96,6 +96,7 @@
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -127,6 +128,7 @@
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
         waitForIdle();
 
@@ -171,6 +173,7 @@
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
         waitForIdle();
 
@@ -203,6 +206,7 @@
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
         when(sOverrides.resolverListController.getLastChosen())
                 .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
@@ -273,6 +277,7 @@
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -317,6 +322,7 @@
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
         when(sOverrides.resolverListController.getLastChosen())
                 .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
@@ -807,6 +813,7 @@
                 createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendImageIntent();
@@ -831,6 +838,7 @@
                 createResolvedComponentsForTest(1);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendImageIntent();
@@ -888,6 +896,7 @@
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
         when(sOverrides.resolverListController.getLastChosen())
                 .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
@@ -965,13 +974,16 @@
             List<ResolvedComponentInfo> workResolvedComponentInfos) {
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
         when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 Mockito.isA(List.class),
                 eq(UserHandle.SYSTEM)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index e16d448..42593f6 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -20,11 +20,14 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.mockito.ArgumentMatchers.intThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.usage.IUsageStatsManager;
@@ -48,6 +51,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -78,6 +82,8 @@
         Configuration config = new Configuration();
         config.locale = Locale.getDefault();
         List<ResolveInfo> services = new ArrayList<>();
+        mUsm = new UsageStatsManager(mMockContext, mMockService);
+        when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
         when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(services);
         when(mMockResources.getConfiguration()).thenReturn(config);
         when(mMockContext.getResources()).thenReturn(mMockResources);
@@ -112,8 +118,6 @@
         doAnswer(answer).when(mMockService).reportChooserSelection(
                 anyString(), anyInt(), anyString(), any(), anyString());
         when(mMockContext.getOpPackageName()).thenReturn(refererPackage);
-        mUsm = new UsageStatsManager(mMockContext, mMockService);
-        when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
         mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
                 refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
         mController.sort(new ArrayList<ResolvedComponentInfo>());
@@ -129,8 +133,6 @@
         Intent sendIntent = createSendImageIntent(annotation);
         String refererPackage = "test_referer_package";
         List<ResolvedComponentInfo> resolvedComponents = createResolvedComponentsForTest(10);
-        mUsm = new UsageStatsManager(mMockContext, mMockService);
-        when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
         mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
                 refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
         List<ResolvedComponentInfo> topKList = new ArrayList<>(resolvedComponents);
@@ -151,6 +153,102 @@
                 sortList, topKList);
     }
 
+    @Test
+    public void getResolversForIntent_usesResultsFromPackageManager() {
+        mockStats();
+        List<ResolveInfo> infos = new ArrayList<>();
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+        when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+                any(UserHandle.class))).thenReturn(infos);
+        mController = new ResolverListController(mMockContext, mMockPackageManager,
+                createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+                /* userHandle= */ UserHandle.SYSTEM);
+        List<Intent> intents = new ArrayList<>();
+        intents.add(createActionMainIntent());
+
+        List<ResolvedComponentInfo> resolvers = mController
+                .getResolversForIntent(
+                        /* shouldGetResolvedFilter= */ true,
+                        /* shouldGetActivityMetadata= */ true,
+                        /* shouldGetOnlyDefaultActivities= */ true,
+                        intents);
+
+        assertThat(resolvers, hasSize(1));
+        assertThat(resolvers.get(0).getResolveInfoAt(0), is(infos.get(0)));
+    }
+
+    @Test
+    public void getResolversForIntent_shouldGetOnlyDefaultActivitiesTrue_addsFlag() {
+        mockStats();
+        List<ResolveInfo> infos = new ArrayList<>();
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+        when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+                any(UserHandle.class))).thenReturn(infos);
+        mController = new ResolverListController(mMockContext, mMockPackageManager,
+                createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+                /* userHandle= */ UserHandle.SYSTEM);
+        List<Intent> intents = new ArrayList<>();
+        intents.add(createActionMainIntent());
+
+        mController
+                .getResolversForIntent(
+                        /* shouldGetResolvedFilter= */ true,
+                        /* shouldGetActivityMetadata= */ true,
+                        /* shouldGetOnlyDefaultActivities= */ true,
+                        intents);
+
+        verify(mMockPackageManager).queryIntentActivitiesAsUser(any(),
+                containsFlag(PackageManager.MATCH_DEFAULT_ONLY), any());
+    }
+
+    @Test
+    public void getResolversForIntent_shouldGetOnlyDefaultActivitiesFalse_doesNotAddFlag() {
+        mockStats();
+        List<ResolveInfo> infos = new ArrayList<>();
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+        when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+                any(UserHandle.class))).thenReturn(infos);
+        mController = new ResolverListController(mMockContext, mMockPackageManager,
+                createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+                /* userHandle= */ UserHandle.SYSTEM);
+        List<Intent> intents = new ArrayList<>();
+        intents.add(createActionMainIntent());
+
+        mController
+                .getResolversForIntent(
+                        /* shouldGetResolvedFilter= */ true,
+                        /* shouldGetActivityMetadata= */ true,
+                        /* shouldGetOnlyDefaultActivities= */ false,
+                        intents);
+
+        verify(mMockPackageManager).queryIntentActivitiesAsUser(any(),
+                doesNotContainFlag(PackageManager.MATCH_DEFAULT_ONLY), any());
+    }
+
+    private int containsFlag(int flag) {
+        return intThat(new FlagMatcher(flag, /* contains= */ true));
+    }
+
+    private int doesNotContainFlag(int flag) {
+        return intThat(new FlagMatcher(flag, /* contains= */ false));
+    }
+
+    public static class FlagMatcher implements ArgumentMatcher<Integer> {
+
+        private final int mFlag;
+        private final boolean mContains;
+
+        public FlagMatcher(int flag, boolean contains) {
+            mFlag = flag;
+            mContains = contains;
+        }
+
+        @Override
+        public boolean matches(Integer integer) {
+            return ((integer & mFlag) != 0) == mContains;
+        }
+    }
+
     private UsageStats initStats(String packageName, String action,
                                  String annotation, int count) {
         ArrayMap<String, ArrayMap<String, Integer>> chooserCounts = new ArrayMap<>();
@@ -174,6 +272,24 @@
         return sendIntent;
     }
 
+    private Intent createActionMainIntent() {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_MAIN);
+        sendIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        return sendIntent;
+    }
+
+    private void mockStats() {
+        final List<UsageStats> slices = new ArrayList<>();
+        ParceledListSlice<UsageStats> stats = new ParceledListSlice<>(slices);
+        try {
+            when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString(),
+                    anyInt())).thenReturn(stats);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
     private Integer getCount(
             UsageStatsManager usm, String packageName, String action, String annotation) {
         if (usm == null) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1c13b16..6edf13a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -618,6 +618,17 @@
             android:excludeFromRecents="true"
             android:visibleToInstantApps="true"/>
 
+        <activity
+            android:name=".media.MediaProjectionAppSelectorActivity"
+            android:theme="@style/Theme.SystemUI.MediaProjectionAppSelector"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true"
+            android:documentLaunchMode="never"
+            android:relinquishTaskIdentity="true"
+            android:configChanges=
+                "screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+            android:visibleToInstantApps="true"/>
+
         <!-- started from TvNotificationPanel -->
         <activity
             android:name=".statusbar.tv.notifications.TvNotificationPanelActivity"
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f954bc9..112d903 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -265,6 +265,10 @@
     <style name="Animation.ShutdownUi" parent="@android:style/Animation.Toast">
     </style>
 
+    <style name="Theme.SystemUI.MediaProjectionAppSelector"
+        parent="@*android:style/Theme.DeviceDefault.Chooser">
+    </style>
+
     <!-- Standard animations for hiding and showing the status bar. -->
 
     <style name="Theme.SystemUI" parent="@*android:style/Theme.DeviceDefault.SystemUI">
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ba1e057..137e288 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.lowlightclock.LowLightClockController;
+import com.android.systemui.media.dagger.MediaProjectionModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarComponent;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
@@ -119,6 +120,7 @@
             FalsingModule.class,
             FlagsModule.class,
             LogModule.class,
+            MediaProjectionModule.class,
             PeopleHubModule.class,
             PluginModule.class,
             PrivacyModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
new file mode 100644
index 0000000..6802da3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.systemui.media
+
+import android.app.ActivityOptions
+import android.content.Intent
+import android.media.projection.IMediaProjection
+import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.view.View
+import com.android.internal.app.ChooserActivity
+import com.android.internal.app.chooser.NotSelectableTargetInfo
+import com.android.internal.app.chooser.TargetInfo
+import com.android.systemui.util.AsyncActivityLauncher
+import com.android.systemui.R;
+import javax.inject.Inject
+
+class MediaProjectionAppSelectorActivity @Inject constructor(
+    private val activityLauncher: AsyncActivityLauncher
+) : ChooserActivity() {
+
+    public override fun onCreate(bundle: Bundle?) {
+        val queryIntent = Intent(Intent.ACTION_MAIN)
+            .addCategory(Intent.CATEGORY_LAUNCHER)
+        intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
+
+        // TODO(b/235465652) Use resource lexeme
+        intent.putExtra(Intent.EXTRA_TITLE, "Record or cast an app")
+
+        super.onCreate(bundle)
+
+        // TODO(b/235465652) we should update VisD of the title and add an icon
+        findViewById<View>(R.id.title)?.visibility = View.VISIBLE
+    }
+
+    override fun appliedThemeResId(): Int =
+        R.style.Theme_SystemUI_MediaProjectionAppSelector
+
+    override fun startSelected(which: Int, always: Boolean, filtered: Boolean) {
+        val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter
+        val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return
+        if (targetInfo is NotSelectableTargetInfo) return
+
+        val intent = createIntent(targetInfo)
+
+        val launchToken: IBinder = Binder("media_projection_launch_token")
+        val activityOptions = ActivityOptions.makeBasic()
+        activityOptions.launchCookie = launchToken
+
+        val userHandle = mMultiProfilePagerAdapter.activeListAdapter.userHandle
+
+        // Launch activity asynchronously and wait for the result, launching of an activity
+        // is typically very fast, so we don't show any loaders.
+        // We wait for the activity to be launched to make sure that the window of the activity
+        // is created and ready to be captured.
+        val activityStarted = activityLauncher
+            .startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
+                onTargetActivityLaunched(launchToken)
+            }
+
+        // Rely on the ActivityManager to pop up a dialog regarding app suspension
+        // and return false if suspended
+        if (!targetInfo.isSuspended && activityStarted) {
+            // TODO(b/222078415) track activity launch
+        }
+    }
+
+    private fun createIntent(target: TargetInfo): Intent {
+        val intent = Intent(target.resolvedIntent)
+
+        // Launch the app in a new task, so it won't be in the host's app task
+        intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK
+
+        // Remove activity forward result flag as this activity will
+        // return the media projection session
+        intent.flags = intent.flags and Intent.FLAG_ACTIVITY_FORWARD_RESULT.inv()
+
+        return intent
+    }
+
+    override fun onDestroy() {
+        activityLauncher.destroy()
+        super.onDestroy()
+    }
+
+    override fun onActivityStarted(cti: TargetInfo) {
+        // do nothing
+    }
+
+    private fun onTargetActivityLaunched(launchToken: IBinder) {
+        val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
+        val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
+
+        projection.launchCookie = launchToken
+
+        val intent = Intent()
+        intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
+        setResult(RESULT_OK, intent)
+        setForceSendResultForMediaProjection()
+        finish()
+    }
+
+    override fun shouldGetOnlyDefaultActivities() = false
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 3860409..397bffc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -40,7 +40,10 @@
 import android.util.Log;
 import android.view.Window;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.Utils;
 
@@ -53,6 +56,7 @@
     private String mPackageName;
     private int mUid;
     private IMediaProjectionManager mService;
+    private FeatureFlags mFeatureFlags;
 
     private AlertDialog mDialog;
 
@@ -60,6 +64,7 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        mFeatureFlags = Dependency.get(FeatureFlags.class);
         mPackageName = getCallingPackage();
         IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
         mService = IMediaProjectionManager.Stub.asInterface(b);
@@ -141,14 +146,22 @@
             dialogTitle = getString(R.string.media_projection_dialog_title, appName);
         }
 
-        mDialog = new AlertDialog.Builder(this, R.style.Theme_SystemUI_Dialog)
+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
+                R.style.Theme_SystemUI_Dialog)
                 .setTitle(dialogTitle)
                 .setIcon(R.drawable.ic_media_projection_permission)
                 .setMessage(dialogText)
                 .setPositiveButton(R.string.media_projection_action_text, this)
                 .setNeutralButton(android.R.string.cancel, this)
-                .setOnCancelListener(this)
-                .create();
+                .setOnCancelListener(this);
+
+        if (isPartialScreenSharingEnabled()) {
+            // This is a temporary entry point before we have a new permission dialog
+            // TODO(b/233183090): this activity should be redesigned to have a dropdown selector
+            dialogBuilder.setNegativeButton("App", this);
+        }
+
+        mDialog = dialogBuilder.create();
 
         SystemUIDialog.registerDismissListener(mDialog);
         SystemUIDialog.applyFlags(mDialog);
@@ -177,6 +190,15 @@
             if (which == AlertDialog.BUTTON_POSITIVE) {
                 setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
             }
+
+            if (isPartialScreenSharingEnabled() && which == AlertDialog.BUTTON_NEGATIVE) {
+                IMediaProjection projection = createProjection(mUid, mPackageName);
+                final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
+                intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
+                        projection.asBinder());
+                intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+                startActivity(intent);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Error granting projection permission", e);
             setResult(RESULT_CANCELED);
@@ -188,10 +210,14 @@
         }
     }
 
+    private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
+        return mService.createProjection(uid, packageName,
+                MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
+    }
+
     private Intent getMediaProjectionIntent(int uid, String packageName)
             throws RemoteException {
-        IMediaProjection projection = mService.createProjection(uid, packageName,
-                MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
+        IMediaProjection projection = createProjection(uid, packageName);
         Intent intent = new Intent();
         intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
         return intent;
@@ -201,4 +227,8 @@
     public void onCancel(DialogInterface dialog) {
         finish();
     }
+
+    private boolean isPartialScreenSharingEnabled() {
+        return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
new file mode 100644
index 0000000..e33a1b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.systemui.media.dagger
+
+import android.app.Activity
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class MediaProjectionModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(MediaProjectionAppSelectorActivity::class)
+    abstract fun provideMediaProjectionAppSelectorActivity(
+        activity: MediaProjectionAppSelectorActivity): Activity
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt b/packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt
new file mode 100644
index 0000000..80d0e4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.systemui.util
+
+import android.app.IActivityTaskManager
+import android.app.WaitResult
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Helper class that allows to launch an activity and asynchronously wait
+ * for it to be launched. This class uses application context, so the intent
+ * will be launched with FLAG_ACTIVITY_NEW_TASK.
+ */
+class AsyncActivityLauncher @Inject constructor(
+    private val context: Context,
+    private val activityTaskManager: IActivityTaskManager,
+    @UiBackground private val backgroundExecutor: Executor,
+    @Main private val mainExecutor: Executor
+) {
+
+    private var pendingCallback: ((WaitResult) -> Unit)? = null
+
+    /**
+     * Starts activity and notifies about the result using the provided [callback].
+     * If there is already pending activity launch the call will be ignored.
+     *
+     * @return true if launch has started, false otherwise
+     */
+    fun startActivityAsUser(intent: Intent, userHandle: UserHandle,
+                            activityOptions: Bundle? = null,
+                            callback: (WaitResult) -> Unit): Boolean {
+        if (pendingCallback != null) return false
+
+        pendingCallback = callback
+
+        intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK
+
+        backgroundExecutor.execute {
+            val waitResult = activityTaskManager.startActivityAndWait(
+                /* caller = */ null,
+                /* callingPackage = */ context.packageName,
+                /* callingFeatureId = */ context.attributionTag,
+                /* intent = */ intent,
+                /* resolvedType = */ null,
+                /* resultTo = */ null,
+                /* resultWho = */ null,
+                /* requestCode = */ 0,
+                /* flags = */ 0,
+                /* profilerInfo = */ null,
+                /* options = */ activityOptions,
+                /* userId = */ userHandle.identifier
+            )
+            mainExecutor.execute {
+                pendingCallback?.invoke(waitResult)
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Cancels pending activity launches. It guarantees that the callback won't be fired
+     * but the activity will be launched anyway.
+     */
+    fun destroy() {
+        pendingCallback = null
+    }
+}