Merge "[Partial screen sharing] App selector initial version" into tm-qpr-dev

This commit is contained in:
Nick Chameyev
2022-07-05 15:02:58 +00:00
committed by Android (Google) Code Review
12 changed files with 486 additions and 11 deletions

View File

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

View File

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

View File

@@ -264,6 +264,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -287,6 +288,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -307,6 +309,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -327,6 +330,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -350,6 +354,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -376,6 +381,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -401,6 +407,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -422,6 +429,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -476,6 +484,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -516,6 +525,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -550,6 +560,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -585,6 +596,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -647,6 +659,7 @@ public class ChooserActivityTest {
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -686,6 +699,7 @@ public class ChooserActivityTest {
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -718,6 +732,7 @@ public class ChooserActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -745,6 +760,7 @@ public class ChooserActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -774,6 +790,7 @@ public class ChooserActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -844,6 +861,7 @@ public class ChooserActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -920,6 +938,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -956,6 +975,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -995,6 +1015,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1089,6 +1110,7 @@ public class ChooserActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -1122,6 +1144,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1155,6 +1178,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1187,6 +1211,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1216,6 +1241,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1248,6 +1274,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1282,6 +1309,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1321,6 +1349,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1363,6 +1392,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1407,6 +1437,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1483,6 +1514,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1564,6 +1596,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1636,6 +1669,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -1743,6 +1777,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2058,6 +2093,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2140,6 +2176,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2227,6 +2264,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2296,6 +2334,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2444,6 +2483,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2473,6 +2513,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2526,6 +2567,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -2663,6 +2705,7 @@ public class ChooserActivityTest {
createResolvedComponentsForTest(4);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
@@ -3055,6 +3098,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -3064,6 +3108,7 @@ public class ChooserActivityTest {
.getInstance()
.workResolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
@@ -3073,6 +3118,7 @@ public class ChooserActivityTest {
.getInstance()
.workResolverListController
.getResolversForIntentAsUser(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class),
@@ -3129,6 +3175,7 @@ public class ChooserActivityTest {
.getInstance()
.resolverListController
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))

View File

@@ -95,6 +95,7 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -126,6 +127,7 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
waitForIdle();
@@ -170,6 +172,7 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
waitForIdle();
@@ -202,6 +205,7 @@ public class ResolverActivityTest {
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(sOverrides.resolverListController.getLastChosen())
@@ -272,6 +276,7 @@ public class ResolverActivityTest {
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
@@ -316,6 +321,7 @@ public class ResolverActivityTest {
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(sOverrides.resolverListController.getLastChosen())
@@ -806,6 +812,7 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
@@ -830,6 +837,7 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTest(1);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
@@ -887,6 +895,7 @@ public class ResolverActivityTest {
createResolvedComponentsForTest(2);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(sOverrides.resolverListController.getLastChosen())
@@ -964,13 +973,16 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> personalResolvedComponentInfos,
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)))

View File

@@ -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) {

View File

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

View File

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

View File

@@ -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,

View File

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

View File

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

View File

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

View File

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