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