[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
+ }
+}