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 com.android.bedstead.remotedpc; 18 19 import static android.os.UserManager.DISALLOW_DEBUGGING_FEATURES; 20 import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES; 21 22 import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME; 23 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS; 24 25 import android.app.admin.DevicePolicyManager; 26 import android.app.admin.ManagedProfileProvisioningParams; 27 import android.app.admin.ProvisioningException; 28 import android.content.ComponentName; 29 import android.os.Build; 30 import android.os.UserHandle; 31 import android.util.Log; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.bedstead.nene.TestApis; 36 import com.android.bedstead.nene.annotations.Experimental; 37 import com.android.bedstead.nene.devicepolicy.DeviceOwner; 38 import com.android.bedstead.nene.devicepolicy.DevicePolicyController; 39 import com.android.bedstead.nene.devicepolicy.ProfileOwner; 40 import com.android.bedstead.nene.exceptions.NeneException; 41 import com.android.bedstead.nene.users.UserReference; 42 import com.android.bedstead.nene.utils.Versions; 43 import com.android.bedstead.permissions.PermissionContext; 44 import com.android.bedstead.testapp.TestApp; 45 import com.android.bedstead.testapp.TestAppProvider; 46 import com.android.bedstead.testapp.TestAppQueryBuilder; 47 48 /** Entry point to RemoteDPC. */ 49 public class RemoteDpc extends RemotePolicyManager { 50 51 public static final String REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX = "com.android.cts.RemoteDPC"; 52 private static final String TEST_APP_CLASS_NAME = 53 "com.android.bedstead.testapp.BaseTestAppDeviceAdminReceiver"; 54 private static final String LOG_TAG = "RemoteDpc"; 55 56 private static final DevicePolicyManager sDevicePolicyManager = 57 TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class); 58 private static final TestAppProvider sTestAppProvider = new TestAppProvider(); 59 60 private boolean mShouldRemoveUserWhenRemoved = false; 61 62 public static final String TAG = "RemoteDpc"; 63 64 /** 65 * Get the {@link RemoteDpc} instance for the Device Owner. 66 * 67 * <p>This will return {@code null} if there is no Device Owner or it is not a RemoteDPC app. 68 */ 69 @Nullable deviceOwner()70 public static RemoteDpc deviceOwner() { 71 DeviceOwner deviceOwner = TestApis.devicePolicy().getDeviceOwner(); 72 if (!isRemoteDpc(deviceOwner)) { 73 return null; 74 } 75 76 TestApp remoteDpcTestApp = new TestAppProvider().query().wherePackageName() 77 .isEqualTo(deviceOwner.componentName().getPackageName()) 78 .get(); 79 return new RemoteDpc(remoteDpcTestApp, deviceOwner); 80 } 81 82 /** 83 * Get the {@link RemoteDpc} instance for the Profile Owner of the current user. 84 * 85 * <p>This will return null if there is no Profile Owner or it is not a RemoteDPC app. 86 */ 87 @Nullable profileOwner()88 public static RemoteDpc profileOwner() { 89 return profileOwner(TestApis.users().instrumented()); 90 } 91 92 /** 93 * Get the {@link RemoteDpc} instance for the Profile Owner of the given {@code profile}. 94 * 95 * <p>This will return null if there is no Profile Owner or it is not a RemoteDPC app. 96 */ 97 @Nullable profileOwner(UserHandle profile)98 public static RemoteDpc profileOwner(UserHandle profile) { 99 if (profile == null) { 100 throw new NullPointerException(); 101 } 102 103 return profileOwner(TestApis.users().find(profile)); 104 } 105 106 /** 107 * Get the {@link RemoteDpc} instance for the Profile Owner of the given {@code profile}. 108 * 109 * <p>This will return null if there is no Profile Owner or it is not a RemoteDPC app. 110 */ 111 @Nullable profileOwner(UserReference profile)112 public static RemoteDpc profileOwner(UserReference profile) { 113 if (profile == null) { 114 throw new NullPointerException(); 115 } 116 117 ProfileOwner profileOwner = TestApis.devicePolicy().getProfileOwner(profile); 118 if (!isRemoteDpc(profileOwner)) { 119 return null; 120 } 121 122 TestApp remoteDpcTestApp = new TestAppProvider().query().wherePackageName() 123 .isEqualTo(profileOwner.componentName().getPackageName()) 124 .get(); 125 return new RemoteDpc(remoteDpcTestApp, profileOwner); 126 } 127 128 /** 129 * Get the most specific {@link RemoteDpc} instance for the current user. 130 * 131 * <p>If the user has a RemoteDPC Profile Owner, this will refer to that. If it does not but 132 * has a RemoteDPC Device Owner it will refer to that. Otherwise it will return null. 133 */ 134 @Nullable any()135 public static RemoteDpc any() { 136 return any(TestApis.users().instrumented()); 137 } 138 139 /** 140 * Get the most specific {@link RemoteDpc} instance for the current user. 141 * 142 * <p>If the user has a RemoteDPC Profile Owner, this will refer to that. If it does not but 143 * has a RemoteDPC Device Owner it will refer to that. Otherwise it will return null. 144 */ 145 @Nullable any(UserHandle user)146 public static RemoteDpc any(UserHandle user) { 147 if (user == null) { 148 throw new NullPointerException(); 149 } 150 151 return any(TestApis.users().find(user)); 152 } 153 154 /** 155 * Get the most specific {@link RemoteDpc} instance for the current user. 156 * 157 * <p>If the user has a RemoteDPC Profile Owner, this will refer to that. If it does not but 158 * has a RemoteDPC Device Owner it will refer to that. Otherwise it will return null. 159 */ 160 @Nullable any(UserReference user)161 public static RemoteDpc any(UserReference user) { 162 RemoteDpc remoteDPC = profileOwner(user); 163 if (remoteDPC != null) { 164 return remoteDPC; 165 } 166 return deviceOwner(); 167 } 168 169 /** 170 * Get the {@link RemoteDpc} controller for the given {@link DevicePolicyController}. 171 */ forDevicePolicyController(DevicePolicyController controller)172 public static RemoteDpc forDevicePolicyController(DevicePolicyController controller) { 173 if (controller == null) { 174 throw new NullPointerException(); 175 } 176 177 if (isRemoteDpc(controller)) { 178 TestApp remoteDpcTestApp = new TestAppProvider().query().wherePackageName() 179 .isEqualTo(controller.componentName().getPackageName()) 180 .get(); 181 182 return new RemoteDpc(remoteDpcTestApp, controller); 183 } 184 185 throw new IllegalStateException("DevicePolicyController is not a RemoteDPC: " 186 + controller); 187 } 188 189 /** 190 * Set RemoteDPC as the Device Owner. 191 */ setAsDeviceOwner()192 public static RemoteDpc setAsDeviceOwner() { 193 return setAsDeviceOwner(new TestAppProvider().query().wherePackageName() 194 .isEqualTo(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX)); 195 } 196 197 /** 198 * Sets RemoteDPC as the Device Owner on the system user based on TestAppQuery 199 */ setAsDeviceOwner(TestAppQueryBuilder dpcQuery)200 public static RemoteDpc setAsDeviceOwner(TestAppQueryBuilder dpcQuery) { 201 return setAsDeviceOwner(dpcQuery, TestApis.users().system()); 202 } 203 204 /** 205 * Sets RemoteDPC as the Device Owner on the given user based on TestAppQuery 206 */ setAsDeviceOwner(TestAppQueryBuilder dpcQuery, UserReference user)207 public static RemoteDpc setAsDeviceOwner(TestAppQueryBuilder dpcQuery, UserReference user) { 208 // We make sure that the query has RemoteDpc filter specified, 209 // this is useful for the case where the user calls the method directly 210 // and does not specify the RemoteDpc filter. 211 dpcQuery = enforceRemoteDpcPackageFilter(dpcQuery); 212 213 DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner(); 214 if (matchesRemoteDpcQuery(currentDeviceOwner, dpcQuery)) { 215 return RemoteDpc.forDevicePolicyController(currentDeviceOwner); 216 } 217 218 if (currentDeviceOwner != null) { 219 currentDeviceOwner.remove(); 220 } 221 222 TestApp testApp = dpcQuery.get(); 223 testApp.install(user); 224 Log.i(LOG_TAG, "Installing RemoteDPC app: " + testApp.packageName()); 225 ComponentName componentName = 226 new ComponentName(testApp.packageName(), TEST_APP_CLASS_NAME); 227 DeviceOwner deviceOwner = TestApis.devicePolicy().setDeviceOwner(componentName, user); 228 return new RemoteDpc(testApp, deviceOwner); 229 } 230 231 /** 232 * Set any RemoteDPC as the Profile Owner of the instrumented user. 233 */ setAsProfileOwner()234 public static RemoteDpc setAsProfileOwner() { 235 return setAsProfileOwner(TestApis.users().instrumented()); 236 } 237 238 /** 239 * Set RemoteDPC that matches the query as the Profile Owner of the instrumented user. 240 */ setAsProfileOwner(TestAppQueryBuilder dpcQuery)241 public static RemoteDpc setAsProfileOwner(TestAppQueryBuilder dpcQuery) { 242 return setAsProfileOwner(TestApis.users().instrumented(), dpcQuery); 243 } 244 245 /** 246 * Set any RemoteDPC as the Profile Owner. 247 */ setAsProfileOwner(UserHandle user)248 public static RemoteDpc setAsProfileOwner(UserHandle user) { 249 if (user == null) { 250 throw new NullPointerException(); 251 } 252 253 TestAppQueryBuilder anyRemoteDpcQuery = new TestAppProvider().query() 254 .wherePackageName().startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX); 255 return setAsProfileOwner(TestApis.users().find(user), anyRemoteDpcQuery); 256 } 257 258 /** 259 * Set RemoteDPC that matches the query as the Profile Owner. 260 */ setAsProfileOwner( UserHandle user, TestAppQueryBuilder dpcQuery)261 public static RemoteDpc setAsProfileOwner( 262 UserHandle user, TestAppQueryBuilder dpcQuery) { 263 if (user == null) { 264 throw new NullPointerException(); 265 } 266 return setAsProfileOwner(TestApis.users().find(user), dpcQuery); 267 } 268 269 /** 270 * Set RemoteDPC as the Profile Owner. 271 * 272 * <p>If called for Android versions prior to Q, an exception will be thrown if the user is not 273 * the instrumented user. 274 */ setAsProfileOwner(UserReference user)275 public static RemoteDpc setAsProfileOwner(UserReference user) { 276 if (user == null) { 277 throw new NullPointerException(); 278 } 279 280 TestAppQueryBuilder anyRemoteDpcQuery = new TestAppProvider().query() 281 .wherePackageName().startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX); 282 return setAsProfileOwner(user, anyRemoteDpcQuery); 283 } 284 285 /** 286 * Set RemoteDPC that matches the query as the Profile Owner. 287 * 288 * <p>If called for Android versions prior to Q, an exception will be thrown if the user is not 289 * the instrumented user. 290 */ setAsProfileOwner( UserReference user, TestAppQueryBuilder dpcQuery)291 public static RemoteDpc setAsProfileOwner( 292 UserReference user, TestAppQueryBuilder dpcQuery) { 293 // We make sure that the query has RemoteDpc filter specified, 294 // this is useful for the case where the user calls the method directly 295 // and does not specify the RemoteDpc filter. 296 dpcQuery = enforceRemoteDpcPackageFilter(dpcQuery); 297 298 if (user == null) { 299 throw new NullPointerException(); 300 } 301 302 if (!user.equals(TestApis.users().instrumented())) { 303 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.Q)) { 304 throw new NeneException("Cannot use RemoteDPC across users prior to Q"); 305 } 306 } 307 308 ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user); 309 if (matchesRemoteDpcQuery(currentProfileOwner, dpcQuery)) { 310 return RemoteDpc.forDevicePolicyController(currentProfileOwner); 311 } 312 313 return setAsProfileOwner(user, dpcQuery.get()); 314 } 315 316 /** 317 * Set specific RemoteDPC {@link TestApp} as the Profile Owner. 318 * 319 * <p>If called for Android versions prior to Q, an exception will be thrown if the user is not 320 * the instrumented user. 321 */ setAsProfileOwner( UserReference user, TestApp dpcTestApp)322 public static RemoteDpc setAsProfileOwner( 323 UserReference user, TestApp dpcTestApp) { 324 if (!dpcTestApp.pkg().packageName().startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX)) { 325 throw new IllegalArgumentException("setAsProfileOwner test app must be a RemoteDPC"); 326 } 327 328 if (user == null) { 329 throw new NullPointerException(); 330 } 331 332 if (!user.equals(TestApis.users().instrumented())) { 333 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.Q)) { 334 throw new NeneException("Cannot use RemoteDPC across users prior to Q"); 335 } 336 } 337 338 ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user); 339 340 if (currentProfileOwner != null) { 341 currentProfileOwner.remove(); 342 } 343 344 // TODO(274125850): Figure out the core reason these users are stopped 345 if (!user.isRunning()) { 346 user.start(); 347 } 348 349 if (!dpcTestApp.installedOnUser(user)) { 350 Log.i(LOG_TAG, "Installing RemoteDPC app: " + dpcTestApp.packageName()); 351 dpcTestApp.install(user); 352 } 353 354 ComponentName componentName = 355 new ComponentName(dpcTestApp.packageName(), TEST_APP_CLASS_NAME); 356 RemoteDpc remoteDpc = new RemoteDpc( 357 dpcTestApp, 358 TestApis.devicePolicy().setProfileOwner(user, componentName)); 359 360 try { // workaround for: b/391462951 361 // DISALLOW_INSTALL_UNKNOWN_SOURCES causes verification failures in work profiles 362 remoteDpc.devicePolicyManager().clearUserRestriction( 363 remoteDpc.componentName(), 364 DISALLOW_INSTALL_UNKNOWN_SOURCES 365 ); 366 } catch (Exception e) { 367 Log.e(TAG, "unable to clear user restriction: " 368 + DISALLOW_INSTALL_UNKNOWN_SOURCES, e); 369 } 370 371 // DISALLOW_DEBUGGING_FEATURES is being added to newly-created work profile by default due 372 // to b/382064697 . This would have impacted certain CTS test flows when they interact with 373 // the work profile via ADB (for example installing an app into the work profile). 374 // Remove DISALLOW_DEBUGGING_FEATURES here to reduce the potential impact. 375 remoteDpc 376 .devicePolicyManager() 377 .clearUserRestriction(remoteDpc.componentName(), DISALLOW_DEBUGGING_FEATURES); 378 379 return remoteDpc; 380 } 381 382 /** 383 * Create a work profile of the instrumented user with RemoteDpc as the profile owner. 384 * 385 * <p>If autoclosed, the user will be removed along with the dpc. 386 * 387 * <p>If called for Android versions prior to Q an exception will be thrown 388 */ 389 @Experimental createWorkProfile()390 public static RemoteDpc createWorkProfile() { 391 return createWorkProfile(TestApis.users().instrumented()); 392 } 393 394 /** 395 * Create a work profile of the instrumented user with RemoteDpc as the profile owner. 396 * 397 * <p>If autoclosed, the user will be removed along with the dpc. 398 * 399 * <p>If called for Android versions prior to Q an exception will be thrown 400 */ 401 @Experimental createWorkProfile(TestAppQueryBuilder dpcQuery)402 public static RemoteDpc createWorkProfile(TestAppQueryBuilder dpcQuery) { 403 return createWorkProfile(TestApis.users().instrumented(), dpcQuery); 404 } 405 406 /** 407 * Create a work profile with RemoteDpc as the profile owner. 408 * 409 * <p>If autoclosed, the user will be removed along with the dpc. 410 * 411 * <p>If called for Android versions prior to Q an exception will be thrown 412 */ 413 @Experimental createWorkProfile(UserReference parent)414 public static RemoteDpc createWorkProfile(UserReference parent) { 415 return createWorkProfile(parent, new TestAppProvider().query().wherePackageName() 416 .isEqualTo(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX)); 417 } 418 419 /** 420 * Create a work profile with RemoteDpc as the profile owner. 421 * 422 * <p>If autoclosed, the user will be removed along with the dpc. 423 * 424 * <p>If called for Android versions prior to Q an exception will be thrown 425 */ 426 @Experimental createWorkProfile(UserReference parent, TestAppQueryBuilder dpcQuery)427 public static RemoteDpc createWorkProfile(UserReference parent, TestAppQueryBuilder dpcQuery) { 428 // It'd be ideal if this method could be in TestApis.devicePolicy() but the dependency 429 // direction wouldn't allow it 430 if (parent == null) { 431 throw new NullPointerException(); 432 } 433 434 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.Q)) { 435 throw new NeneException("Cannot use RemoteDPC across users prior to Q"); 436 } 437 438 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) { 439 UserReference profile = TestApis.users().createUser() 440 .type(TestApis.users().supportedType(MANAGED_PROFILE_TYPE_NAME)) 441 .parent(parent) 442 .createAndStart(); 443 444 return setAsProfileOwner(profile, dpcQuery); 445 } 446 447 boolean removeFromParent = false; 448 TestApp testApp = dpcQuery.get(); 449 if (!testApp.installedOnUser(parent)) { 450 Log.i(LOG_TAG, "Installing RemoteDPC app: " + testApp.packageName()); 451 testApp.install(parent); 452 } 453 try { 454 RemoteDpc dpc = forDevicePolicyController(TestApis.devicePolicy().getProfileOwner( 455 createAndProvisionManagedProfile(testApp))); 456 457 dpc.devicePolicyManager().setProfileEnabled(dpc.componentName()); 458 459 dpc.mShouldRemoveUserWhenRemoved = true; 460 return dpc; 461 462 } catch (ProvisioningException e) { 463 throw new NeneException("Error provisioning work profile", e); 464 } finally { 465 if (removeFromParent) { 466 testApp.uninstall(parent); 467 } 468 } 469 } 470 createAndProvisionManagedProfile(TestApp testApp)471 private static UserHandle createAndProvisionManagedProfile(TestApp testApp) throws ProvisioningException { 472 ManagedProfileProvisioningParams provisioningParams = 473 new ManagedProfileProvisioningParams.Builder( 474 new ComponentName(testApp.packageName(), TEST_APP_CLASS_NAME), 475 "RemoteDPC" 476 ).build(); 477 try (PermissionContext p = TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) { 478 UserHandle managedProfile = sDevicePolicyManager.createManagedProfile(provisioningParams); 479 sDevicePolicyManager.finalizeWorkProfileProvisioning(managedProfile, null); 480 return managedProfile; 481 } 482 } 483 484 /** 485 * Check if the RemoteDpc matches the query 486 */ matchesRemoteDpcQuery( DevicePolicyController devicePolicyController, TestAppQueryBuilder dpcQuery)487 public static boolean matchesRemoteDpcQuery( 488 DevicePolicyController devicePolicyController, 489 TestAppQueryBuilder dpcQuery) { 490 if (isRemoteDpc(devicePolicyController)) { 491 RemoteDpc remoteDpc = RemoteDpc.forDevicePolicyController(devicePolicyController); 492 return dpcQuery.matches(remoteDpc.testApp()); 493 } 494 return false; 495 } 496 497 /** 498 * Check if dpc is a RemoteDpc 499 */ isRemoteDpc(DevicePolicyController controller)500 public static boolean isRemoteDpc(DevicePolicyController controller) { 501 return controller != null 502 && controller.componentName().getPackageName() 503 .startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX) 504 && controller.componentName().getClassName().equals(TEST_APP_CLASS_NAME); 505 } 506 enforceRemoteDpcPackageFilter( TestAppQueryBuilder dpcQuery)507 private static TestAppQueryBuilder enforceRemoteDpcPackageFilter( 508 TestAppQueryBuilder dpcQuery) { 509 return dpcQuery.wherePackageName() 510 .startsWith(REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX) 511 .allowInternalBedsteadTestApps(); 512 } 513 514 private final DevicePolicyController mDevicePolicyController; 515 RemoteDpc(TestApp remoteDpcTestApp, DevicePolicyController devicePolicyController)516 RemoteDpc(TestApp remoteDpcTestApp, DevicePolicyController devicePolicyController) { 517 super(remoteDpcTestApp, devicePolicyController == null ? null 518 : devicePolicyController.user()); 519 mDevicePolicyController = devicePolicyController; 520 } 521 522 /** 523 * Get the {@link DevicePolicyController} for this instance of RemoteDPC. 524 */ devicePolicyController()525 public DevicePolicyController devicePolicyController() { 526 return mDevicePolicyController; 527 } 528 529 /** 530 * Remove RemoteDPC as Device Owner or Profile Owner and uninstall the APK from the user. 531 */ remove()532 public void remove() { 533 if (mShouldRemoveUserWhenRemoved) { 534 mDevicePolicyController.user().remove(); 535 } else { 536 mDevicePolicyController.remove(); 537 TestApis.packages().find(mDevicePolicyController.componentName().getPackageName()) 538 .uninstall(mDevicePolicyController.user()); 539 } 540 } 541 542 @Override close()543 public void close() { 544 remove(); 545 } 546 547 /** 548 * Get the {@link ComponentName} of the DPC. 549 */ 550 @Override componentName()551 public ComponentName componentName() { 552 return mDevicePolicyController.componentName(); 553 } 554 555 @Override hashCode()556 public int hashCode() { 557 return mDevicePolicyController.hashCode(); 558 } 559 560 @Override equals(Object obj)561 public boolean equals(Object obj) { 562 if (!(obj instanceof RemoteDpc)) { 563 return false; 564 } 565 566 RemoteDpc other = (RemoteDpc) obj; 567 return other.mDevicePolicyController.equals(mDevicePolicyController); 568 } 569 570 @Override toString()571 public String toString() { 572 return "RemoteDpc{" 573 + "devicePolicyController=" + mDevicePolicyController 574 + ", testApp=" + super.toString() 575 + '}'; 576 } 577 } 578