• 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.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
20 import static android.app.admin.DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED;
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.pm.PackageManager.MATCH_DEFAULT_ONLY;
24 import static android.os.UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE;
25 
26 import static com.android.bedstead.harrier.UserType.PRIMARY_USER;
27 import static com.android.bedstead.harrier.UserType.WORK_PROFILE;
28 import static com.android.bedstead.remotedpc.RemoteDpc.DPC_COMPONENT_NAME;
29 import static com.android.queryable.queries.ActivityQuery.activity;
30 import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
31 
32 import static com.google.common.truth.Truth.assertWithMessage;
33 
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.pm.ResolveInfo;
38 
39 import com.android.bedstead.harrier.BedsteadJUnit4;
40 import com.android.bedstead.harrier.DeviceState;
41 import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
42 import com.android.bedstead.harrier.annotations.Postsubmit;
43 import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
44 import com.android.bedstead.nene.TestApis;
45 import com.android.bedstead.nene.permissions.PermissionContext;
46 import com.android.bedstead.nene.users.UserReference;
47 import com.android.bedstead.remotedpc.RemoteDpc;
48 import com.android.bedstead.testapp.TestApp;
49 import com.android.bedstead.testapp.TestAppInstance;
50 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
51 
52 import org.junit.ClassRule;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 
57 import java.util.List;
58 import java.util.stream.Collectors;
59 
60 @RunWith(BedsteadJUnit4.class)
61 public final class CrossProfileSharingTest {
62     @ClassRule
63     @Rule
64     public static final DeviceState sDeviceState = new DeviceState();
65 
66     private static final TestApp sTestApp = sDeviceState.testApps().query()
67             .whereActivities().contains(
68                     activity().intentFilters().contains(
69                             intentFilter().actions().contains("com.android.testapp.SOME_ACTION"),
70                             intentFilter().actions().contains("android.intent.action.PICK"),
71                             intentFilter().actions().contains("android.intent.action.SEND_MULTIPLE")
72                     )).get();
73 
74     // Known action that is handled in the opposite profile, used to query forwarder activity.
75     private static final String CROSS_PROFILE_ACTION = "com.android.testapp.SOME_ACTION";
76 
77     // TODO(b/191640667): use parametrization instead of looping once available.
78     // These are the data sharing intents which can be forwarded to the primary profile.
79     private static final Intent[] OPENING_INTENTS = {
80             new Intent(Intent.ACTION_GET_CONTENT).setType("*/*").addCategory(
81                     Intent.CATEGORY_OPENABLE),
82             new Intent(Intent.ACTION_OPEN_DOCUMENT).setType("*/*").addCategory(
83                     Intent.CATEGORY_OPENABLE),
84             new Intent(Intent.ACTION_PICK).setType("*/*").addCategory(
85                     Intent.CATEGORY_DEFAULT),
86             new Intent(Intent.ACTION_PICK).addCategory(Intent.CATEGORY_DEFAULT)
87     };
88 
89     // These are the data sharing intents which can be forwarded to the managed profile.
90     private static final Intent[] SHARING_INTENTS = {
91             new Intent(Intent.ACTION_SEND).setType("*/*"),
92             new Intent(Intent.ACTION_SEND_MULTIPLE).setType("*/*")
93     };
94 
95     /**
96      * Test sharing initiated from the profile side i.e. user tries to pick up personal data within
97      * a work app when DISALLOW_SHARE_INTO_MANAGED_PROFILE is enforced.
98      */
99     @Test
100     @Postsubmit(reason = "new test")
101     @RequireRunOnWorkProfile
openingPersonalFromProfile_disallowShareIntoProfile_restrictionApplied()102     public void openingPersonalFromProfile_disallowShareIntoProfile_restrictionApplied() {
103         ResolveInfo toPersonalForwarderInfo = getWorkToPersonalForwarder();
104 
105         // Enforce the restriction and wait for it to be applied.
106         setSharingIntoProfileEnabled(false);
107         // Test app handles android.intent.action.PICK just in case no other app does.
108         try (TestAppInstance testAppParent =
109                      sTestApp.install(sDeviceState.primaryUser())) {
110             // Verify that the intents don't resolve into cross-profile forwarder.
111             assertCrossProfileIntentsResolvability(OPENING_INTENTS,
112                     toPersonalForwarderInfo, /* expectForwardable */ false);
113         } finally {
114             // Restore default state.
115             setSharingIntoProfileEnabled(true);
116         }
117     }
118 
119     /**
120      * Test sharing initiated from the profile side i.e. user tries to pick up personal data within
121      * a work app when DISALLOW_SHARE_INTO_MANAGED_PROFILE is NOT enforced.
122      */
123     @Test
124     @Postsubmit(reason = "new test")
125     @RequireRunOnWorkProfile
openingPersonalFromProfile_disallowShareIntoProfile_restrictionRemoved()126     public void openingPersonalFromProfile_disallowShareIntoProfile_restrictionRemoved() {
127         ResolveInfo workToPersonalForwarder = getWorkToPersonalForwarder();
128 
129         // Enforce the restriction and wait for it to be applied, then remove it and wait again.
130         setSharingIntoProfileEnabled(false);
131         setSharingIntoProfileEnabled(true);
132 
133         // Test app handles android.intent.action.PICK just in case no other app does.
134         try (TestAppInstance testAppParent =
135                      sTestApp.install(sDeviceState.primaryUser())) {
136             // Verify that the intents resolve into cross-profile forwarder.
137             assertCrossProfileIntentsResolvability(
138                     OPENING_INTENTS, workToPersonalForwarder, /* expectForwardable */ true);
139         }
140     }
141 
142     @Test
143     @Postsubmit(reason = "new test")
144     @EnsureHasWorkProfile(forUser = PRIMARY_USER)
sharingFromPersonalToWork_disallowShareIntoProfile_restrictionApplied()145     public void sharingFromPersonalToWork_disallowShareIntoProfile_restrictionApplied() {
146         ResolveInfo personalToWorkForwarder = getPersonalToWorkForwarder();
147 
148         // Enforce the restriction and wait for it to be applied.
149         setSharingIntoProfileEnabled(false);
150 
151         try {
152             // Verify that sharing intent doesn't get resolve into profile forwarder.
153             assertCrossProfileIntentsResolvability(
154                     SHARING_INTENTS, personalToWorkForwarder, /* expectForwardable */ false);
155         } finally {
156             setSharingIntoProfileEnabled(true);
157         }
158 
159     }
160 
161     @Test
162     @Postsubmit(reason = "new test")
163     @EnsureHasWorkProfile(forUser = PRIMARY_USER)
sharingFromPersonalToWork_disallowShareIntoProfile_restrictionRemoved()164     public void sharingFromPersonalToWork_disallowShareIntoProfile_restrictionRemoved() {
165         try (TestAppInstance testApp = sTestApp.install(sDeviceState.workProfile())) {
166             ResolveInfo personalToWorkForwarder = getPersonalToWorkForwarder();
167 
168             // Enforce the restriction and wait for it to be applied, then remove it and wait again.
169             setSharingIntoProfileEnabled(false);
170             setSharingIntoProfileEnabled(true);
171 
172             // Verify that sharing intent gets resolved into profile forwarder successfully.
173             assertCrossProfileIntentsResolvability(
174                     SHARING_INTENTS, personalToWorkForwarder, /* expectForwardable */ true);
175         }
176     }
177 
getPersonalToWorkForwarder()178     private ResolveInfo getPersonalToWorkForwarder() {
179         return getResolveInfo(sDeviceState.workProfile(), FLAG_MANAGED_CAN_ACCESS_PARENT);
180     }
181 
getWorkToPersonalForwarder()182     private ResolveInfo getWorkToPersonalForwarder() {
183         return getResolveInfo(sDeviceState.primaryUser(), FLAG_PARENT_CAN_ACCESS_MANAGED);
184     }
185 
186     /**
187      * Finds ResolveInfo for a system activity that forwards cross-profile intent by resolving an
188      * intent that it handled in one profile from the other profile.
189      * TODO(b/198759180): replace it with a @TestApi
190      */
getResolveInfo(UserReference targetProfile, int direction)191     private ResolveInfo getResolveInfo(UserReference targetProfile, int direction) {
192         ResolveInfo forwarderInfo;
193         try (TestAppInstance testApp = sTestApp.install(targetProfile)) {
194             // Set up cross profile intent filters so we can resolve these to find out framework's
195             // intent forwarder activity as ground truth
196             sDeviceState.profileOwner(WORK_PROFILE).devicePolicyManager()
197                     .addCrossProfileIntentFilter(DPC_COMPONENT_NAME,
198                             new IntentFilter(CROSS_PROFILE_ACTION), direction);
199             try {
200                 forwarderInfo = getCrossProfileIntentForwarder(new Intent(CROSS_PROFILE_ACTION));
201             } finally {
202                 sDeviceState.profileOwner(WORK_PROFILE).devicePolicyManager()
203                         .clearCrossProfileIntentFilters(DPC_COMPONENT_NAME);
204             }
205         }
206         return forwarderInfo;
207     }
208 
getCrossProfileIntentForwarder(Intent intent)209     private ResolveInfo getCrossProfileIntentForwarder(Intent intent) {
210         List<ResolveInfo> result = TestApis.context().instrumentedContext().getPackageManager()
211                 .queryIntentActivities(intent, MATCH_DEFAULT_ONLY)
212                 .stream().filter(ResolveInfo::isCrossProfileIntentForwarderActivity)
213                 .collect(Collectors.toList());
214 
215         assertWithMessage("Failed to get intent forwarder component")
216                 .that(result.size()).isEqualTo(1);
217 
218         return result.get(0);
219     }
220 
setSharingIntoProfileEnabled(boolean enabled)221     private void setSharingIntoProfileEnabled(boolean enabled) {
222         RemoteDpc remoteDpc = sDeviceState.profileOwner(WORK_PROFILE);
223         IntentFilter filter = new IntentFilter(ACTION_DATA_SHARING_RESTRICTION_APPLIED);
224         Context remoteCtx = TestApis.context().androidContextAsUser(remoteDpc.user());
225         try (PermissionContext permissionContext =
226                      TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL);
227              BlockingBroadcastReceiver receiver =
228                      BlockingBroadcastReceiver.create(remoteCtx, filter).register()) {
229             if (enabled) {
230                 remoteDpc.devicePolicyManager().clearUserRestriction(
231                         DPC_COMPONENT_NAME, DISALLOW_SHARE_INTO_MANAGED_PROFILE);
232             } else {
233                 remoteDpc.devicePolicyManager().addUserRestriction(
234                         DPC_COMPONENT_NAME, DISALLOW_SHARE_INTO_MANAGED_PROFILE);
235             }
236         }
237     }
238 
assertCrossProfileIntentsResolvability( Intent[] intents, ResolveInfo expectedForwarder, boolean expectForwardable)239     private void assertCrossProfileIntentsResolvability(
240             Intent[] intents, ResolveInfo expectedForwarder, boolean expectForwardable) {
241         for (Intent intent : intents) {
242             List<ResolveInfo> resolveInfoList = TestApis.context().instrumentedContext()
243                     .getPackageManager().queryIntentActivities(intent, MATCH_DEFAULT_ONLY);
244             if (expectForwardable) {
245                 assertWithMessage(String.format(
246                         "Expect %s to be forwardable, but resolve list does not contain expected "
247                                 + "intent forwarder %s", intent, expectedForwarder))
248                         .that(containsResolveInfo(resolveInfoList, expectedForwarder)).isTrue();
249             } else {
250                 assertWithMessage(String.format(
251                         "Expect %s not to be forwardable, but resolve list contains intent "
252                                 + "forwarder %s", intent, expectedForwarder))
253                         .that(containsResolveInfo(resolveInfoList, expectedForwarder)).isFalse();
254             }
255         }
256     }
257 
containsResolveInfo(List<ResolveInfo> list, ResolveInfo info)258     private boolean containsResolveInfo(List<ResolveInfo> list, ResolveInfo info) {
259         for (ResolveInfo entry : list) {
260             if (entry.activityInfo.packageName.equals(info.activityInfo.packageName)
261                     && entry.activityInfo.name.equals(info.activityInfo.name)) {
262                 return true;
263             }
264         }
265         return false;
266     }
267 }
268