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