diff options
12 files changed, 486 insertions, 11 deletions
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 2ed0f981692e..737d5e348249 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -265,6 +265,7 @@ public class ResolverListAdapter extends BaseAdapter { return mResolverListController.getResolversForIntent( /* shouldGetResolvedFilter= */ true, mResolverListCommunicator.shouldGetActivityMetadata(), + mResolverListCommunicator.shouldGetOnlyDefaultActivities(), mIntents); } } @@ -727,6 +728,7 @@ public class ResolverListAdapter extends BaseAdapter { protected List<ResolvedComponentInfo> getResolversForUser(UserHandle userHandle) { return mResolverListController.getResolversForIntentAsUser(true, mResolverListCommunicator.shouldGetActivityMetadata(), + mResolverListCommunicator.shouldGetOnlyDefaultActivities(), mIntents, userHandle); } @@ -820,6 +822,12 @@ public class ResolverListAdapter extends BaseAdapter { 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 27573631b2ce..100fcd817812 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -110,17 +110,19 @@ public class ResolverListController { 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 82148422aabf..e8c7ce0b312b 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 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity( @@ -289,6 +290,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test")); @@ -309,6 +311,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -329,6 +332,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -352,6 +356,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -378,6 +383,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -403,6 +409,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -424,6 +431,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -478,6 +486,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -518,6 +527,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -552,6 +562,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(null); Intent sendIntent = createSendTextIntent(); @@ -587,6 +598,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -649,6 +661,7 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); final ChooserActivity activity = @@ -747,6 +762,7 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -958,6 +977,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -997,6 +1017,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -1091,6 +1112,7 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -1157,6 +1180,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -1189,6 +1213,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); @@ -1218,6 +1243,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -1250,6 +1276,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -1284,6 +1311,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); when( @@ -1323,6 +1351,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -1365,6 +1394,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -1409,6 +1439,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -1485,6 +1516,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -1566,6 +1598,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); // Create direct share target @@ -1638,6 +1671,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); // Create direct share target @@ -1745,6 +1779,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -2060,6 +2095,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -2142,6 +2178,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -2229,6 +2266,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -2298,6 +2336,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(resolvedComponentInfos); @@ -2446,6 +2485,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendTextIntent(); @@ -2475,6 +2515,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent sendIntent = createSendTextIntent(); @@ -2528,6 +2569,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); Intent chooserIntent = createChooserIntent(createSendTextIntent(), @@ -2664,6 +2706,7 @@ public class ChooserActivityTest { 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 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when( @@ -3066,6 +3110,7 @@ public class ChooserActivityTest { .getResolversForIntent( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))) .thenReturn(new ArrayList<>(workResolvedComponentInfos)); when( @@ -3075,6 +3120,7 @@ public class ChooserActivityTest { .getResolversForIntentAsUser( Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class), eq(UserHandle.SYSTEM))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); @@ -3131,6 +3177,7 @@ public class ChooserActivityTest { .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 43fba529182e..92c05b0fe9fc 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); waitForIdle(); @@ -171,6 +173,7 @@ public class ResolverActivityTest { when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), + Mockito.anyBoolean(), Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); waitForIdle(); @@ -203,6 +206,7 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 @@ public class ResolverActivityTest { 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 e16d44854516..42593f60094b 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 junit.framework.Assert.assertEquals; 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 com.android.internal.app.ResolverActivity.ResolvedComponentInfo; 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 @@ public class ResolverListControllerTest { 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 @@ public class ResolverListControllerTest { 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 @@ public class ResolverListControllerTest { 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 @@ public class ResolverListControllerTest { 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 @@ public class ResolverListControllerTest { 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 1c13b1660e31..6edf13addbca 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 f954bc9bf17c..112d903c609c 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 ba1e057716f8..137e28888728 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.flags.FlagsModule; 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 @@ import dagger.Provides; 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 000000000000..6802da347c93 --- /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 38604091c409..397bffcaa64c 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.text.style.StyleSpan; 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 @@ public class MediaProjectionPermissionActivity extends Activity private String mPackageName; private int mUid; private IMediaProjectionManager mService; + private FeatureFlags mFeatureFlags; private AlertDialog mDialog; @@ -60,6 +64,7 @@ public class MediaProjectionPermissionActivity extends Activity 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 @@ public class MediaProjectionPermissionActivity extends Activity 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 @@ public class MediaProjectionPermissionActivity extends Activity 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 @@ public class MediaProjectionPermissionActivity extends Activity } } + 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 class MediaProjectionPermissionActivity extends Activity 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 000000000000..e33a1b909d48 --- /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 000000000000..80d0e4b90359 --- /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 + } +} |