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