• 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.WORK_PROFILE;
27 import static com.android.queryable.queries.ActivityQuery.activity;
28 import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
29 
30 import static com.google.common.truth.Truth.assertWithMessage;
31 
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.ResolveInfo;
36 
37 import com.android.bedstead.harrier.BedsteadJUnit4;
38 import com.android.bedstead.harrier.DeviceState;
39 import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
40 import com.android.bedstead.harrier.annotations.Postsubmit;
41 import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
42 import com.android.bedstead.nene.TestApis;
43 import com.android.bedstead.nene.permissions.PermissionContext;
44 import com.android.bedstead.nene.users.UserReference;
45 import com.android.bedstead.remotedpc.RemoteDpc;
46 import com.android.bedstead.testapp.TestApp;
47 import com.android.bedstead.testapp.TestAppInstance;
48 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
49 
50 import org.junit.ClassRule;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 import java.util.List;
56 import java.util.stream.Collectors;
57 
58 @RunWith(BedsteadJUnit4.class)
59 public final class CrossProfileSharingTest {
60     @ClassRule
61     @Rule
62     public static final DeviceState sDeviceState = new DeviceState();
63 
64     private static final TestApp sTestApp = sDeviceState.testApps().query()
65             .whereActivities().contains(
66                     activity().where().intentFilters().contains(
67                             intentFilter().where().actions().contains("com.android.testapp.SOME_ACTION"),
68                             intentFilter().where().actions().contains("android.intent.action.PICK"),
69                             intentFilter().where().actions().contains("android.intent.action.SEND_MULTIPLE")
70                     )).get();
71 
72     // Known action that is handled in the opposite profile, used to query forwarder activity.
73     private static final String CROSS_PROFILE_ACTION = "com.android.testapp.SOME_ACTION";
74 
75     // TODO(b/191640667): use parametrization instead of looping once available.
76     // These are the data sharing intents which can be forwarded to the primary profile.
77     private static final Intent[] OPENING_INTENTS = {
78             new Intent(Intent.ACTION_GET_CONTENT).setType("*/*").addCategory(
79                     Intent.CATEGORY_OPENABLE),
80             new Intent(Intent.ACTION_OPEN_DOCUMENT).setType("*/*").addCategory(
81                     Intent.CATEGORY_OPENABLE),
82             new Intent(Intent.ACTION_PICK).setType("*/*").addCategory(
83                     Intent.CATEGORY_DEFAULT),
84             new Intent(Intent.ACTION_PICK).addCategory(Intent.CATEGORY_DEFAULT)
85     };
86 
87     // These are the data sharing intents which can be forwarded to the managed profile.
88     private static final Intent[] SHARING_INTENTS = {
89             new Intent(Intent.ACTION_SEND).setType("*/*"),
90             new Intent(Intent.ACTION_SEND_MULTIPLE).setType("*/*")
91     };
92 
93     /**
94      * Test sharing initiated from the profile side i.e. user tries to pick up personal data within
95      * a work app when DISALLOW_SHARE_INTO_MANAGED_PROFILE is enforced.
96      */
97     @Test
98     @Postsubmit(reason = "new test")
99     @RequireRunOnWorkProfile
openingPersonalFromProfile_disallowShareIntoProfile_restrictionApplied()100     public void openingPersonalFromProfile_disallowShareIntoProfile_restrictionApplied() {
101         ResolveInfo toPersonalForwarderInfo = getWorkToPersonalForwarder();
102 
103         // Enforce the restriction and wait for it to be applied.
104         setSharingIntoProfileEnabled(false);
105         // Test app handles android.intent.action.PICK just in case no other app does.
106         try (TestAppInstance testAppParent =
107                      sTestApp.install(sDeviceState.primaryUser())) {
108             // Verify that the intents don't resolve into cross-profile forwarder.
109             assertCrossProfileIntentsResolvability(OPENING_INTENTS,
110                     toPersonalForwarderInfo, /* expectForwardable */ false);
111         } finally {
112             // Restore default state.
113             setSharingIntoProfileEnabled(true);
114         }
115     }
116 
117     /**
118      * Test sharing initiated from the profile side i.e. user tries to pick up personal data within
119      * a work app when DISALLOW_SHARE_INTO_MANAGED_PROFILE is NOT enforced.
120      */
121     @Test
122     @Postsubmit(reason = "new test")
123     @RequireRunOnWorkProfile
openingPersonalFromProfile_disallowShareIntoProfile_restrictionRemoved()124     public void openingPersonalFromProfile_disallowShareIntoProfile_restrictionRemoved() {
125         ResolveInfo workToPersonalForwarder = getWorkToPersonalForwarder();
126 
127         // Enforce the restriction and wait for it to be applied, then remove it and wait again.
128         setSharingIntoProfileEnabled(false);
129         setSharingIntoProfileEnabled(true);
130 
131         // Test app handles android.intent.action.PICK just in case no other app does.
132         try (TestAppInstance testAppParent =
133                      sTestApp.install(sDeviceState.primaryUser())) {
134             // Verify that the intents resolve into cross-profile forwarder.
135             assertCrossProfileIntentsResolvability(
136                     OPENING_INTENTS, workToPersonalForwarder, /* expectForwardable */ true);
137         }
138     }
139 
140     @Test
141     @Postsubmit(reason = "new test")
142     @EnsureHasWorkProfile
sharingFromPersonalToWork_disallowShareIntoProfile_restrictionApplied()143     public void sharingFromPersonalToWork_disallowShareIntoProfile_restrictionApplied() {
144         ResolveInfo personalToWorkForwarder = getPersonalToWorkForwarder();
145 
146         // Enforce the restriction and wait for it to be applied.
147         setSharingIntoProfileEnabled(false);
148 
149         try {
150             // Verify that sharing intent doesn't get resolve into profile forwarder.
151             assertCrossProfileIntentsResolvability(
152                     SHARING_INTENTS, personalToWorkForwarder, /* expectForwardable */ false);
153         } finally {
154             setSharingIntoProfileEnabled(true);
155         }
156 
157     }
158 
159     @Test
160     @Postsubmit(reason = "new test")
161     @EnsureHasWorkProfile
sharingFromPersonalToWork_disallowShareIntoProfile_restrictionRemoved()162     public void sharingFromPersonalToWork_disallowShareIntoProfile_restrictionRemoved() {
163         try (TestAppInstance testApp = sTestApp.install(sDeviceState.workProfile())) {
164             ResolveInfo personalToWorkForwarder = getPersonalToWorkForwarder();
165 
166             // Enforce the restriction and wait for it to be applied, then remove it and wait again.
167             setSharingIntoProfileEnabled(false);
168             setSharingIntoProfileEnabled(true);
169 
170             // Verify that sharing intent gets resolved into profile forwarder successfully.
171             assertCrossProfileIntentsResolvability(
172                     SHARING_INTENTS, personalToWorkForwarder, /* expectForwardable */ true);
173         }
174     }
175 
getPersonalToWorkForwarder()176     private ResolveInfo getPersonalToWorkForwarder() {
177         return getResolveInfo(sDeviceState.workProfile(), FLAG_MANAGED_CAN_ACCESS_PARENT);
178     }
179 
getWorkToPersonalForwarder()180     private ResolveInfo getWorkToPersonalForwarder() {
181         return getResolveInfo(sDeviceState.primaryUser(), FLAG_PARENT_CAN_ACCESS_MANAGED);
182     }
183 
184     /**
185      * Finds ResolveInfo for a system activity that forwards cross-profile intent by resolving an
186      * intent that it handled in one profile from the other profile.
187      * TODO(b/198759180): replace it with a @TestApi
188      */
getResolveInfo(UserReference targetProfile, int direction)189     private ResolveInfo getResolveInfo(UserReference targetProfile, int direction) {
190         ResolveInfo forwarderInfo;
191         try (TestAppInstance testApp = sTestApp.install(targetProfile)) {
192             // Set up cross profile intent filters so we can resolve these to find out framework's
193             // intent forwarder activity as ground truth
194             sDeviceState.profileOwner(WORK_PROFILE).devicePolicyManager()
195                     .addCrossProfileIntentFilter(sDeviceState.profileOwner(WORK_PROFILE)
196                                     .componentName(),
197                             new IntentFilter(CROSS_PROFILE_ACTION), direction);
198             try {
199                 forwarderInfo = getCrossProfileIntentForwarder(new Intent(CROSS_PROFILE_ACTION));
200             } finally {
201                 sDeviceState.profileOwner(WORK_PROFILE).devicePolicyManager()
202                         .clearCrossProfileIntentFilters(
203                                 sDeviceState.profileOwner(WORK_PROFILE).componentName());
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                         remoteDpc.componentName(), DISALLOW_SHARE_INTO_MANAGED_PROFILE);
232             } else {
233                 remoteDpc.devicePolicyManager().addUserRestriction(
234                         remoteDpc.componentName(), 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