• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.devicepolicy.cts;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED;
20 import static android.app.admin.DevicePolicyManager.EXTRA_RESTRICTION;
21 import static android.app.admin.DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT;
22 import static android.app.admin.DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED;
23 import static android.content.Intent.ACTION_SEND;
24 import static android.content.Intent.CATEGORY_DEFAULT;
25 import static android.content.Intent.EXTRA_TEXT;
26 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
27 import static android.os.UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE;
28 
29 import static com.android.bedstead.enterprise.EnterpriseDeviceStateExtensionsKt.dpc;
30 import static com.android.bedstead.enterprise.EnterpriseDeviceStateExtensionsKt.profileOwner;
31 import static com.android.bedstead.enterprise.EnterpriseDeviceStateExtensionsKt.workProfile;
32 import static com.android.bedstead.harrier.UserType.WORK_PROFILE;
33 import static com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_CROSS_PROFILE_COPY_PASTE;
34 import static com.android.bedstead.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL;
35 import static com.android.bedstead.testapps.TestAppsDeviceStateExtensionsKt.testApps;
36 import static com.android.queryable.queries.ActivityQuery.activity;
37 import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
38 
39 import static com.google.common.truth.Truth.assertThat;
40 import static com.google.common.truth.Truth.assertWithMessage;
41 
42 import android.content.ComponentName;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.content.pm.ActivityInfo;
47 import android.content.pm.PackageManager;
48 import android.content.pm.ResolveInfo;
49 
50 import com.android.bedstead.enterprise.annotations.EnsureDoesNotHaveUserRestriction;
51 import com.android.bedstead.enterprise.annotations.EnsureHasUserRestriction;
52 import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
53 import com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile;
54 import com.android.bedstead.harrier.BedsteadJUnit4;
55 import com.android.bedstead.harrier.DeviceState;
56 import com.android.bedstead.harrier.annotations.Postsubmit;
57 import com.android.bedstead.nene.TestApis;
58 import com.android.bedstead.nene.exceptions.NeneException;
59 import com.android.bedstead.nene.packages.ComponentReference;
60 import com.android.bedstead.nene.users.UserReference;
61 import com.android.bedstead.nene.utils.BlockingBroadcastReceiver;
62 import com.android.bedstead.nene.utils.Poll;
63 import com.android.bedstead.nene.utils.ResolveInfoWrapper;
64 import com.android.bedstead.permissions.annotations.EnsureHasPermission;
65 import com.android.bedstead.remotedpc.RemoteDpc;
66 import com.android.bedstead.testapp.TestApp;
67 import com.android.bedstead.testapp.TestAppInstance;
68 import com.android.compatibility.common.util.ApiTest;
69 
70 import org.junit.ClassRule;
71 import org.junit.Rule;
72 import org.junit.Test;
73 import org.junit.runner.RunWith;
74 
75 import java.util.List;
76 import java.util.stream.Collectors;
77 
78 @RunWith(BedsteadJUnit4.class)
79 public final class CrossProfileSharingTest {
80     @ClassRule
81     @Rule
82     public static final DeviceState sDeviceState = new DeviceState();
83 
84     private static final TestApp sTestApp = testApps(sDeviceState).query()
85             .whereActivities().contains(
86                     activity().where().intentFilters().contains(
87                             intentFilter().where().actions().contains("com.android.testapp.SOME_ACTION"),
88                             intentFilter().where().actions().contains("android.intent.action.PICK"),
89                             intentFilter().where().actions().contains("android.intent.action.SEND_MULTIPLE")
90                     )).get();
91 
92     // Known action that is handled in the opposite profile, used to query forwarder activity.
93     private static final String CROSS_PROFILE_ACTION = "com.android.testapp.SOME_ACTION";
94 
95     // TODO(b/191640667): use parametrization instead of looping once available.
96     // These are the data sharing intents which can be forwarded to the primary profile.
97     private static final Intent[] OPENING_INTENTS = {
98             new Intent(Intent.ACTION_GET_CONTENT).setType("*/*").addCategory(
99                     Intent.CATEGORY_OPENABLE),
100             new Intent(Intent.ACTION_OPEN_DOCUMENT).setType("*/*").addCategory(
101                     Intent.CATEGORY_OPENABLE),
102             new Intent(Intent.ACTION_PICK).setType("*/*").addCategory(
103                     Intent.CATEGORY_DEFAULT),
104             new Intent(Intent.ACTION_PICK).addCategory(Intent.CATEGORY_DEFAULT)
105     };
106 
107     // These are the data sharing intents which can be forwarded to the managed profile.
108     private static final Intent[] SHARING_INTENTS = {
109             new Intent(ACTION_SEND).setType("*/*"),
110             new Intent(Intent.ACTION_SEND_MULTIPLE).setType("*/*")
111     };
112 
113     private static final IntentFilter SEND_INTENT_FILTER =
114             IntentFilter.create(ACTION_SEND, /* dataType= */ "*/*");
115     static {
116         SEND_INTENT_FILTER.addCategory(CATEGORY_DEFAULT);
117     }
118 
119     private static final Intent SEND_INTENT =
120             new Intent(ACTION_SEND).setType("text/plain").addCategory(CATEGORY_DEFAULT)
121                     .putExtra(EXTRA_TEXT, "example");
122     private static final Intent CHOOSER_INTENT =
123             Intent.createChooser(SEND_INTENT, /* title= */ null);
124 
125     /**
126      * Test sharing initiated from the profile side i.e. user tries to pick up personal data within
127      * a work app when DISALLOW_SHARE_INTO_MANAGED_PROFILE is enforced.
128      */
129     @Test
130     @Postsubmit(reason = "new test")
131     @RequireRunOnWorkProfile
132     @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
openingPersonalFromProfile_disallowShareIntoProfile_restrictionApplied()133     public void openingPersonalFromProfile_disallowShareIntoProfile_restrictionApplied() {
134         ResolveInfo toPersonalForwarderInfo = getWorkToPersonalForwarder();
135 
136         // Enforce the restriction and wait for it to be applied.
137         setSharingIntoProfileEnabled(false);
138         // Test app handles android.intent.action.PICK just in case no other app does.
139         try (TestAppInstance testAppParent =
140                      sTestApp.install(sDeviceState.primaryUser())) {
141             // Verify that the intents don't resolve into cross-profile forwarder.
142             assertCrossProfileIntentsResolvability(OPENING_INTENTS,
143                     toPersonalForwarderInfo, /* expectForwardable */ false);
144         } finally {
145             // Restore default state.
146             setSharingIntoProfileEnabled(true);
147         }
148     }
149 
150     /**
151      * Test sharing initiated from the profile side i.e. user tries to pick up personal data within
152      * a work app when DISALLOW_SHARE_INTO_MANAGED_PROFILE is NOT enforced.
153      */
154     @Test
155     @Postsubmit(reason = "new test")
156     @RequireRunOnWorkProfile
157     @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
openingPersonalFromProfile_disallowShareIntoProfile_restrictionRemoved()158     public void openingPersonalFromProfile_disallowShareIntoProfile_restrictionRemoved() {
159         ResolveInfo workToPersonalForwarder = getWorkToPersonalForwarder();
160 
161         // Enforce the restriction and wait for it to be applied, then remove it and wait again.
162         setSharingIntoProfileEnabled(false);
163         setSharingIntoProfileEnabled(true);
164 
165         // Test app handles android.intent.action.PICK just in case no other app does.
166         try (TestAppInstance testAppParent =
167                      sTestApp.install(sDeviceState.primaryUser())) {
168             // Verify that the intents resolve into cross-profile forwarder.
169             assertCrossProfileIntentsResolvability(
170                     OPENING_INTENTS, workToPersonalForwarder, /* expectForwardable */ true);
171         }
172     }
173 
174     @Test
175     @Postsubmit(reason = "new test")
176     @EnsureHasWorkProfile
177     @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
sharingFromPersonalToWork_disallowShareIntoProfile_restrictionApplied()178     public void sharingFromPersonalToWork_disallowShareIntoProfile_restrictionApplied() {
179         ResolveInfo personalToWorkForwarder = getPersonalToWorkForwarder();
180 
181         // Enforce the restriction and wait for it to be applied.
182         setSharingIntoProfileEnabled(false);
183 
184         try {
185             // Verify that sharing intent doesn't get resolve into profile forwarder.
186             assertCrossProfileIntentsResolvability(
187                     SHARING_INTENTS, personalToWorkForwarder, /* expectForwardable */ false);
188         } finally {
189             setSharingIntoProfileEnabled(true);
190         }
191 
192     }
193 
194     @Test
195     @Postsubmit(reason = "new test")
196     @EnsureHasWorkProfile
197     @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
sharingFromPersonalToWork_disallowShareIntoProfile_restrictionRemoved()198     public void sharingFromPersonalToWork_disallowShareIntoProfile_restrictionRemoved() {
199         try (TestAppInstance testApp = sTestApp.install(workProfile(sDeviceState))) {
200             ResolveInfo personalToWorkForwarder = getPersonalToWorkForwarder();
201 
202             // Enforce the restriction and wait for it to be applied, then remove it and wait again.
203             setSharingIntoProfileEnabled(false);
204             setSharingIntoProfileEnabled(true);
205 
206             // Verify that sharing intent gets resolved into profile forwarder successfully.
207             assertCrossProfileIntentsResolvability(
208                     SHARING_INTENTS, personalToWorkForwarder, /* expectForwardable */ true);
209         }
210     }
211 
212     @ApiTest(apis = "android.app.admin.DevicePolicyManager#addCrossProfileIntentFilter")
213     @Postsubmit(reason = "new test")
214     @Test
215     @EnsureHasWorkProfile(dpcIsPrimary = true)
addCrossProfileIntentFilter_switchToOtherProfile_chooserActivityLaunched()216     public void addCrossProfileIntentFilter_switchToOtherProfile_chooserActivityLaunched() {
217         try {
218             IntentFilter sendIntentFilter = IntentFilter.create(ACTION_SEND, /* dataType= */ "*/*");
219             sendIntentFilter.addCategory(CATEGORY_DEFAULT);
220             dpc(sDeviceState).devicePolicyManager().addCrossProfileIntentFilter(
221                     dpc(sDeviceState).componentName(), sendIntentFilter,
222                     FLAG_MANAGED_CAN_ACCESS_PARENT | FLAG_PARENT_CAN_ACCESS_MANAGED);
223             Intent switchToOtherProfileIntent = getSwitchToOtherProfileIntent();
224 
225             TestApis.context().instrumentedContext().startActivity(switchToOtherProfileIntent);
226 
227             ComponentReference chooserActivityComponentReference = TestApis.activities()
228                     .getResolvedActivityOfIntent(CHOOSER_INTENT,
229                             PackageManager.MATCH_DEFAULT_ONLY);
230 
231             if (chooserActivityComponentReference == null) {
232                 throw new NeneException("Unable to resolve activity of Intent: " + CHOOSER_INTENT);
233             }
234 
235             Poll.forValue("Chooser activity launched",
236                             () -> TestApis.activities().foregroundActivity().componentName())
237                     .toBeEqualTo(chooserActivityComponentReference.componentName())
238                     .errorOnFail()
239                     .await();
240         } finally {
241             dpc(sDeviceState).devicePolicyManager().clearCrossProfileIntentFilters(
242                     dpc(sDeviceState).componentName());
243         }
244     }
245 
246     @ApiTest(apis = {"android.app.admin.DevicePolicyManager#createAdminSupportIntent",
247             "android.os.UserManager#DISALLOW_CROSS_PROFILE_COPY_PASTE"})
248     @Postsubmit(reason = "new test")
249     @EnsureHasUserRestriction(DISALLOW_CROSS_PROFILE_COPY_PASTE)
250     @Test
createAdminSupportIntent_disallowCrossProfileCopyPaste_createsIntent()251     public void createAdminSupportIntent_disallowCrossProfileCopyPaste_createsIntent() {
252         Intent intent = TestApis.devicePolicy().createAdminSupportIntent(
253                 DISALLOW_CROSS_PROFILE_COPY_PASTE);
254 
255         assertThat(intent.getStringExtra(EXTRA_RESTRICTION))
256                 .isEqualTo(DISALLOW_CROSS_PROFILE_COPY_PASTE);
257     }
258 
259     @ApiTest(apis = {"android.app.admin.DevicePolicyManager#createAdminSupportIntent",
260             "android.os.UserManager#DISALLOW_CROSS_PROFILE_COPY_PASTE"})
261     @Postsubmit(reason = "new test")
262     @EnsureDoesNotHaveUserRestriction(DISALLOW_CROSS_PROFILE_COPY_PASTE)
263     @Test
createAdminSupportIntent_allowCrossProfileCopyPaste_doesNotCreate()264     public void createAdminSupportIntent_allowCrossProfileCopyPaste_doesNotCreate() {
265         Intent intent = TestApis.devicePolicy().createAdminSupportIntent(
266                 DISALLOW_CROSS_PROFILE_COPY_PASTE);
267 
268         assertThat(intent).isNull();
269     }
270 
getSwitchToOtherProfileIntent()271     private Intent getSwitchToOtherProfileIntent() {
272         ResolveInfoWrapper switchToOtherProfileResolveInfo = getSwitchToOtherProfileResolveInfo(
273         );
274         assertWithMessage("Could not retrieve the switch to other profile resolve info.")
275                 .that(switchToOtherProfileResolveInfo)
276                 .isNotNull();
277 
278         Intent switchToOtherProfileIntent = new Intent(CHOOSER_INTENT);
279         ActivityInfo activityInfo = switchToOtherProfileResolveInfo.activityInfo();
280         switchToOtherProfileIntent.setComponent(
281                 new ComponentName(activityInfo.packageName, activityInfo.name));
282         switchToOtherProfileIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
283         return switchToOtherProfileIntent;
284     }
285 
286     /**
287      * Returns the ResolveInfo of the "Switch Profiles" dialog if one exists for this intent
288      * (otherwise null).
289      */
getSwitchToOtherProfileResolveInfo()290     private ResolveInfoWrapper getSwitchToOtherProfileResolveInfo() {
291         // match == 0 means that this intent actually doesn't match to any activity on this profile,
292         // that means it should be on the other profile.
293 
294         return TestApis.packages().queryIntentActivities(CrossProfileSharingTest.SEND_INTENT,
295                         android.content.pm.PackageManager.MATCH_DEFAULT_ONLY)
296                 .stream()
297                 .filter(r -> r.match() == 0)
298                 .findFirst()
299                 .orElse(null);
300     }
301 
getPersonalToWorkForwarder()302     private ResolveInfo getPersonalToWorkForwarder() {
303         return getResolveInfo(workProfile(sDeviceState), FLAG_MANAGED_CAN_ACCESS_PARENT);
304     }
305 
getWorkToPersonalForwarder()306     private ResolveInfo getWorkToPersonalForwarder() {
307         return getResolveInfo(sDeviceState.primaryUser(), FLAG_PARENT_CAN_ACCESS_MANAGED);
308     }
309 
310     /**
311      * Finds ResolveInfo for a system activity that forwards cross-profile intent by resolving an
312      * intent that it handled in one profile from the other profile.
313      * TODO(b/198759180): replace it with a @TestApi
314      */
getResolveInfo(UserReference targetProfile, int direction)315     private ResolveInfo getResolveInfo(UserReference targetProfile, int direction) {
316         ResolveInfo forwarderInfo;
317         try (TestAppInstance testApp = sTestApp.install(targetProfile)) {
318             // Set up cross profile intent filters so we can resolve these to find out framework's
319             // intent forwarder activity as ground truth
320             profileOwner(sDeviceState, WORK_PROFILE).devicePolicyManager()
321                     .addCrossProfileIntentFilter(profileOwner(sDeviceState, WORK_PROFILE)
322                                     .componentName(),
323                             new IntentFilter(CROSS_PROFILE_ACTION), direction);
324             try {
325                 forwarderInfo = getCrossProfileIntentForwarder(new Intent(CROSS_PROFILE_ACTION));
326             } finally {
327                 profileOwner(sDeviceState, WORK_PROFILE).devicePolicyManager()
328                         .clearCrossProfileIntentFilters(
329                                 profileOwner(sDeviceState, WORK_PROFILE).componentName());
330             }
331         }
332         return forwarderInfo;
333     }
334 
getCrossProfileIntentForwarder(Intent intent)335     private ResolveInfo getCrossProfileIntentForwarder(Intent intent) {
336         List<ResolveInfo> result = TestApis.context().instrumentedContext().getPackageManager()
337                 .queryIntentActivities(intent, MATCH_DEFAULT_ONLY)
338                 .stream().filter(ResolveInfo::isCrossProfileIntentForwarderActivity)
339                 .collect(Collectors.toList());
340 
341         assertWithMessage("Failed to get intent forwarder component")
342                 .that(result.size()).isEqualTo(1);
343 
344         return result.get(0);
345     }
346 
setSharingIntoProfileEnabled(boolean enabled)347     private void setSharingIntoProfileEnabled(boolean enabled) {
348         RemoteDpc remoteDpc = profileOwner(sDeviceState, WORK_PROFILE);
349         IntentFilter filter = new IntentFilter(ACTION_DATA_SHARING_RESTRICTION_APPLIED);
350         Context remoteCtx = TestApis.context().androidContextAsUser(remoteDpc.user());
351         try (BlockingBroadcastReceiver receiver =
352                      BlockingBroadcastReceiver.create(remoteCtx, filter).register()) {
353             if (enabled) {
354                 remoteDpc.devicePolicyManager().clearUserRestriction(
355                         remoteDpc.componentName(), DISALLOW_SHARE_INTO_MANAGED_PROFILE);
356             } else {
357                 remoteDpc.devicePolicyManager().addUserRestriction(
358                         remoteDpc.componentName(), DISALLOW_SHARE_INTO_MANAGED_PROFILE);
359             }
360         }
361     }
362 
assertCrossProfileIntentsResolvability( Intent[] intents, ResolveInfo expectedForwarder, boolean expectForwardable)363     private void assertCrossProfileIntentsResolvability(
364             Intent[] intents, ResolveInfo expectedForwarder, boolean expectForwardable) {
365         for (Intent intent : intents) {
366             List<ResolveInfo> resolveInfoList = TestApis.context().instrumentedContext()
367                     .getPackageManager().queryIntentActivities(intent, MATCH_DEFAULT_ONLY);
368             if (expectForwardable) {
369                 assertWithMessage(String.format(
370                         "Expect %s to be forwardable, but resolve list does not contain expected "
371                                 + "intent forwarder %s", intent, expectedForwarder))
372                         .that(containsResolveInfo(resolveInfoList, expectedForwarder)).isTrue();
373             } else {
374                 assertWithMessage(String.format(
375                         "Expect %s not to be forwardable, but resolve list contains intent "
376                                 + "forwarder %s", intent, expectedForwarder))
377                         .that(containsResolveInfo(resolveInfoList, expectedForwarder)).isFalse();
378             }
379         }
380     }
381 
containsResolveInfo(List<ResolveInfo> list, ResolveInfo info)382     private boolean containsResolveInfo(List<ResolveInfo> list, ResolveInfo info) {
383         for (ResolveInfo entry : list) {
384             if (entry.activityInfo.packageName.equals(info.activityInfo.packageName)
385                     && entry.activityInfo.name.equals(info.activityInfo.name)) {
386                 return true;
387             }
388         }
389         return false;
390     }
391 }
392