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.nene.users; 18 19 import static android.Manifest.permission.CREATE_USERS; 20 import static android.Manifest.permission.INTERACT_ACROSS_USERS; 21 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 22 import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE; 23 import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE; 24 import static android.cts.testapisreflection.TestApisReflectionKt.forceUpdateUserSetupComplete; 25 import static android.cts.testapisreflection.TestApisReflectionKt.getUserType; 26 import static android.os.Build.VERSION_CODES.P; 27 import static android.os.Build.VERSION_CODES.R; 28 import static android.os.Build.VERSION_CODES.S; 29 import static android.os.Build.VERSION_CODES.TIRAMISU; 30 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 31 32 import static com.android.bedstead.nene.users.Users.users; 33 import static com.android.bedstead.nene.utils.Versions.U; 34 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS; 35 import static com.android.bedstead.permissions.CommonPermissions.MODIFY_QUIET_MODE; 36 import static com.android.bedstead.permissions.CommonPermissions.QUERY_USERS; 37 38 import android.annotation.SuppressLint; 39 import android.annotation.TargetApi; 40 import android.app.KeyguardManager; 41 import android.app.admin.DevicePolicyManager; 42 import android.os.Build; 43 import android.os.UserHandle; 44 import android.os.UserManager; 45 import android.util.Log; 46 import android.view.Display; 47 48 import androidx.annotation.Nullable; 49 50 import com.android.bedstead.nene.TestApis; 51 import com.android.bedstead.nene.annotations.Experimental; 52 import com.android.bedstead.nene.devicepolicy.ProfileOwner; 53 import com.android.bedstead.nene.exceptions.AdbException; 54 import com.android.bedstead.nene.exceptions.NeneException; 55 import com.android.bedstead.nene.exceptions.PollValueFailedException; 56 import com.android.bedstead.nene.utils.BlockingBroadcastReceiver; 57 import com.android.bedstead.nene.utils.Poll; 58 import com.android.bedstead.nene.utils.ShellCommand; 59 import com.android.bedstead.nene.utils.ShellCommand.Builder; 60 import com.android.bedstead.nene.utils.ShellCommandUtils; 61 import com.android.bedstead.nene.utils.Versions; 62 import com.android.bedstead.permissions.CommonPermissions; 63 import com.android.bedstead.permissions.PermissionContext; 64 65 import com.google.errorprone.annotations.CanIgnoreReturnValue; 66 67 import java.time.Duration; 68 import java.util.Arrays; 69 import java.util.HashSet; 70 import java.util.Set; 71 72 /** A representation of a User on device which may or may not exist. */ 73 public final class UserReference implements AutoCloseable { 74 75 private static final Set<AdbUser.UserState> RUNNING_STATES = new HashSet<>( 76 Arrays.asList(AdbUser.UserState.RUNNING_LOCKED, 77 AdbUser.UserState.RUNNING_UNLOCKED, 78 AdbUser.UserState.RUNNING_UNLOCKING) 79 ); 80 81 private static final String LOG_TAG = "UserReference"; 82 83 private static final String USER_SETUP_COMPLETE_KEY = "user_setup_complete"; 84 85 private static final String TYPE_PASSWORD = "password"; 86 private static final String TYPE_PIN = "pin"; 87 private static final String TYPE_PATTERN = "pattern"; 88 89 private final int mId; 90 91 private final UserManager mUserManager; 92 93 private Long mSerialNo; 94 private String mName; 95 private UserType mUserType; 96 private Boolean mIsPrimary; 97 private boolean mParentCached = false; 98 private UserReference mParent; 99 private @Nullable String mLockCredential; 100 private @Nullable String mLockType; 101 102 103 /** 104 * Returns a {@link UserReference} equivalent to the passed {@code userHandle}. 105 */ of(UserHandle userHandle)106 public static UserReference of(UserHandle userHandle) { 107 return TestApis.users().find(userHandle.getIdentifier()); 108 } 109 UserReference(int id)110 UserReference(int id) { 111 mId = id; 112 mUserManager = TestApis.context().androidContextAsUser(this) 113 .getSystemService(UserManager.class); 114 } 115 116 /** 117 * The user's id. 118 */ id()119 public int id() { 120 return mId; 121 } 122 123 /** 124 * {@code true} if this is the system user. 125 */ isSystem()126 public boolean isSystem() { 127 return id() == 0; 128 } 129 130 /** 131 * See {@link UserManager#isAdminUser()}. 132 */ isAdmin()133 public boolean isAdmin() { 134 return userInfo().isAdmin(); 135 } 136 137 /** 138 * {@code true} if this is a test user which should not include any user data. 139 */ isForTesting()140 public boolean isForTesting() { 141 if (!Versions.meetsMinimumSdkVersionRequirement(U)) { 142 return false; 143 } 144 145 return userInfo().isForTesting(); 146 } 147 148 /** 149 * {@code true} if this is the main user. 150 */ 151 @SuppressLint("NewApi") 152 @Experimental isMain()153 public boolean isMain() { 154 if (!Versions.meetsMinimumSdkVersionRequirement(U)) { 155 return isSystem(); 156 } 157 158 try (PermissionContext p = 159 TestApis.permissions().withPermission(CommonPermissions.CREATE_USERS)) { 160 return mUserManager.isMainUser(); 161 } 162 } 163 164 /** 165 * Get a {@link UserHandle} for the {@link #id()}. 166 */ userHandle()167 public UserHandle userHandle() { 168 return UserHandle.of(mId); 169 } 170 171 /** 172 * Remove the user from the device. 173 * 174 * <p>If the user does not exist then nothing will happen. If the removal fails for any other 175 * reason, a {@link NeneException} will be thrown. 176 */ 177 @CanIgnoreReturnValue remove()178 public UserReference remove() { 179 Log.i(LOG_TAG, "Trying to remove user " + mId); 180 if (!exists()) { 181 Log.i(LOG_TAG, "User " + mId + " does not exist or removed already."); 182 return this; 183 } 184 185 try { 186 ProfileOwner profileOwner = TestApis.devicePolicy().getProfileOwner(this); 187 if (profileOwner != null && profileOwner.isOrganizationOwned()) { 188 profileOwner.remove(); 189 } 190 191 if (TestApis.users().instrumented().equals(this)) { 192 throw new NeneException("Cannot remove instrumented user"); 193 } 194 195 try { 196 // Expected success string is "Success: removed user" 197 ShellCommand.builder("pm remove-user") 198 .addOperand("-w") // Wait for remove-user to complete 199 .withTimeout(Duration.ofMinutes(1)) 200 .addOperand(mId) 201 .validate(ShellCommandUtils::startsWithSuccess) 202 .execute(); 203 } catch (AdbException e) { 204 throw new NeneException("Could not remove user " + this + ". Logcat: " 205 + TestApis.logcat().dump((l) -> l.contains("UserManagerService")), e); 206 } 207 if (exists()) { 208 // This should never happen 209 throw new NeneException("Failed to remove user " + this); 210 } 211 } catch (NeneException e) { 212 // (b/286380557): Flaky behavior when SafetyCenter tries to remove the user: the user 213 // is seen to be removed even though SafetyCenter throws an exception. 214 boolean userExists = exists(); 215 Log.i(LOG_TAG, 216 "Does user " + id() + " still exist after trying to remove: " 217 + userExists); 218 219 if (userExists) { 220 // A reliable exception, the user was not removed. 221 throw e; 222 } 223 } 224 225 Log.i(LOG_TAG, "Removed user " + mId); 226 return this; 227 } 228 229 /** 230 * Remove the user from device when it is next possible. 231 * 232 * <p>If the user is the current foreground user, removal is deferred until the user is switched 233 * away. Otherwise, it'll be removed immediately. 234 * 235 * <p>If the user does not exist, or setting the user ephemeral fails for any other reason, a 236 * {@link NeneException} will be thrown. 237 */ 238 @Experimental removeWhenPossible()239 public void removeWhenPossible() { 240 try { 241 // Expected success strings are: 242 // ("Success: user %d removed\n", userId) 243 // ("Success: user %d set as ephemeral\n", userId) 244 // ("Success: user %d is already being removed\n", userId) 245 ShellCommand.builder("pm remove-user") 246 .addOperand("--set-ephemeral-if-in-use") 247 .addOperand(mId) 248 .validate(ShellCommandUtils::startsWithSuccess) 249 .execute(); 250 } catch (AdbException e) { 251 throw new NeneException("Could not remove or mark ephemeral user " + this, e); 252 } 253 } 254 255 /** 256 * Starts the user in the background. 257 * 258 * <p>After calling this command, the user will be running unlocked, but not 259 * {@link #isVisible() visible}. 260 * 261 * <p>If the user does not exist, or the start fails for any other reason, a 262 * {@link NeneException} will be thrown. 263 */ 264 @CanIgnoreReturnValue start()265 public UserReference start() { 266 Log.i(LOG_TAG, "Starting user " + mId); 267 return startUser(Display.INVALID_DISPLAY); 268 } 269 270 /** 271 * Starts the user in the background, {@link #isVisible() visible} in the given 272 * display. 273 * 274 * <p>After calling this command, the user will be running unlocked. 275 * 276 * @throws UnsupportedOperationException if the device doesn't 277 * {@link UserManager#isVisibleBackgroundUsersOnDefaultDisplaySupported() support visible 278 * background users} 279 * 280 * @throws NeneException if the user does not exist or the start fails for any other reason 281 */ startVisibleOnDisplay(int displayId)282 public UserReference startVisibleOnDisplay(int displayId) { 283 if (!TestApis.users().isVisibleBackgroundUsersSupported()) { 284 throw new UnsupportedOperationException("Cannot start user " + mId + " on display " 285 + displayId + " as device doesn't support that"); 286 } 287 Log.i(LOG_TAG, "Starting user " + mId + " visible on display " + displayId); 288 return startUser(displayId); 289 } 290 291 //TODO(scottjonathan): Deal with users who won't unlock startUser(int displayId)292 private UserReference startUser(int displayId) { 293 boolean visibleOnDisplay = displayId != Display.INVALID_DISPLAY; 294 295 try { 296 // Expected success string is "Success: user started" 297 Builder builder = ShellCommand.builder("am start-user") 298 .addOperand("-w"); 299 if (visibleOnDisplay) { 300 builder.addOperand("--display").addOperand(displayId); 301 } 302 builder.addOperand(mId) // NOTE: id MUST be the last argument 303 .validate(ShellCommandUtils::startsWithSuccess) 304 .execute(); 305 306 Poll.forValue("User running", this::isRunning) 307 .toBeEqualTo(true) 308 .errorOnFail() 309 .timeout(Duration.ofMinutes(1)) 310 .await(); 311 Poll.forValue("User unlocked", this::isUnlocked) 312 .toBeEqualTo(true) 313 .errorOnFail() 314 .timeout(Duration.ofMinutes(1)) 315 .await(); 316 if (visibleOnDisplay) { 317 Poll.forValue("User visible", this::isVisible) 318 .toBeEqualTo(true) 319 .errorOnFail() 320 .timeout(Duration.ofMinutes(1)) 321 .await(); 322 } 323 } catch (AdbException | PollValueFailedException e) { 324 if (!userInfo().isEnabled()) { 325 throw new NeneException("Could not start user " + this + ". User is not enabled."); 326 } 327 328 throw new NeneException("Could not start user " + this + ". Relevant logcat: " 329 + TestApis.logcat().dump(l -> l.contains("ActivityManager")), e); 330 } 331 332 return this; 333 } 334 335 /** 336 * Stop the user. 337 * 338 * <p>After calling this command, the user will be not running. 339 */ stop()340 public UserReference stop() { 341 try { 342 // Expects no output on success or failure - stderr output on failure 343 ShellCommand.builder("am stop-user") 344 // .addOperand("-w") // Wait for it to stop 345 .addOperand("-f") // Force stop 346 .addOperand(mId) 347 // .withTimeout(Duration.ofMinutes(1)) 348 .allowEmptyOutput(true) 349 .validate(String::isEmpty) 350 .execute(); 351 352 Poll.forValue("User running", this::isRunning) 353 .toBeEqualTo(false) 354 // TODO(b/203630556): Replace stopping with something faster 355 .timeout(Duration.ofMinutes(10)) 356 .errorOnFail() 357 .await(); 358 } catch (AdbException e) { 359 throw new NeneException("Could not stop user " + this, e); 360 } 361 if (isRunning()) { 362 // This should never happen 363 throw new NeneException("Failed to stop user " + this); 364 } 365 366 return this; 367 } 368 369 /** 370 * Make the user the foreground user. 371 * 372 * <p>If the user is a profile, then this will make the parent the foreground user. It will 373 * still return the {@link UserReference} of the profile in that case. 374 */ 375 @CanIgnoreReturnValue switchTo()376 public UserReference switchTo() { 377 UserReference parent = parent(); 378 if (parent != null) { 379 parent.switchTo(); 380 return this; 381 } 382 383 if (TestApis.users().current().equals(this)) { 384 // Already switched to 385 return this; 386 } 387 388 boolean isSdkVersionMinimum_R = Versions.meetsMinimumSdkVersionRequirement(R); 389 try { 390 ShellCommand.builder("am switch-user") 391 .addOperand(isSdkVersionMinimum_R ? "-w" : "") 392 .addOperand(mId) 393 .withTimeout(Duration.ofMinutes(1)) 394 .allowEmptyOutput(true) 395 .validate(String::isEmpty) 396 .execute(); 397 } catch (AdbException e) { 398 String error = getSwitchToUserError(); 399 if (error != null) { 400 throw new NeneException(error); 401 } 402 if (!exists()) { 403 throw new NeneException("Tried to switch to user " + this + " but does not exist"); 404 } 405 // TODO(273229540): It might take a while to fail - we should stream from the 406 // start of the call 407 throw new NeneException("Error switching user to " + this + ". Relevant logcat: " 408 + TestApis.logcat().dump((line) -> line.contains("Cannot switch")), e); 409 } 410 if (isSdkVersionMinimum_R) { 411 Poll.forValue("current user", () -> TestApis.users().current()) 412 .toBeEqualTo(this) 413 .await(); 414 415 if (!TestApis.users().current().equals(this)) { 416 throw new NeneException("Error switching user to " + this 417 + " (current user is " + TestApis.users().current() + "). Relevant logcat: " 418 + TestApis.logcat().dump((line) -> line.contains("ActivityManager"))); 419 } 420 } else { 421 try { 422 Thread.sleep(20000); 423 } catch (InterruptedException e) { 424 Log.e(LOG_TAG, "Interrupted while switching user", e); 425 } 426 } 427 428 return this; 429 } 430 431 /** Get the serial number of the user. */ serialNo()432 public long serialNo() { 433 if (mSerialNo == null) { 434 mSerialNo = TestApis.context().instrumentedContext().getSystemService(UserManager.class) 435 .getSerialNumberForUser(userHandle()); 436 437 if (mSerialNo == -1) { 438 mSerialNo = null; 439 throw new NeneException("User does not exist " + this); 440 } 441 } 442 443 return mSerialNo; 444 } 445 446 /** Get the name of the user. */ name()447 public String name() { 448 if (mName == null) { 449 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 450 mName = adbUser().name(); 451 } else { 452 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 453 mName = TestApis.context().androidContextAsUser(this) 454 .getSystemService(UserManager.class) 455 .getUserName(); 456 } 457 if (mName == null || mName.equals("")) { 458 if (!exists()) { 459 mName = null; 460 throw new NeneException("User does not exist with id " + id()); 461 } 462 } 463 } 464 if (mName == null) { 465 mName = ""; 466 } 467 } 468 469 return mName; 470 } 471 472 /** Is the user running? */ isRunning()473 public boolean isRunning() { 474 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 475 AdbUser adbUser = adbUserOrNull(); 476 if (adbUser == null) { 477 return false; 478 } 479 480 return RUNNING_STATES.contains(adbUser().state()); 481 } 482 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 483 Log.d(LOG_TAG, "isUserRunning(" + this + "): " 484 + mUserManager.isUserRunning(userHandle())); 485 return mUserManager.isUserRunning(userHandle()); 486 } 487 } 488 489 /** Is the user {@link UserManager#isUserVisible() visible}? */ 490 @SuppressLint("NewApi") isVisible()491 public boolean isVisible() { 492 if (!Versions.meetsMinimumSdkVersionRequirement(UPSIDE_DOWN_CAKE)) { 493 // Best effort to define visible as "current user or a profile of the current user" 494 UserReference currentUser = TestApis.users().current(); 495 boolean isIt = currentUser.equals(this) 496 || (isProfile() && currentUser.equals(parent())); 497 Log.d(LOG_TAG, "isUserVisible(" + this + "): returning " + isIt + " as best approach"); 498 return isIt; 499 } 500 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 501 boolean isIt = mUserManager.isUserVisible(); 502 Log.d(LOG_TAG, "isUserVisible(" + this + "): " + isIt); 503 return isIt; 504 } 505 } 506 507 /** Is the user running in the foreground? */ isForeground()508 public boolean isForeground() { 509 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 510 // Best effort to define foreground as "current user" 511 boolean isIt = TestApis.users().current().equals(this); 512 Log.d(LOG_TAG, "isForeground(" + this + "): returning " + isIt + " as best effort"); 513 return isIt; 514 } 515 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 516 boolean isIt = mUserManager.isUserForeground(); 517 Log.d(LOG_TAG, "isUserForeground(" + this + "): " + isIt); 518 return isIt; 519 } 520 } 521 522 /** 523 * Is the user a non-{@link #isProfile() profile} that is running {@link #isVisible()} in the 524 * background? 525 */ isVisibleBagroundNonProfileUser()526 public boolean isVisibleBagroundNonProfileUser() { 527 return isVisible() && !isForeground() && !isProfile(); 528 } 529 530 /** Is the user unlocked? */ isUnlocked()531 public boolean isUnlocked() { 532 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 533 AdbUser adbUser = adbUserOrNull(); 534 if (adbUser == null) { 535 return false; 536 } 537 return adbUser.state().equals(AdbUser.UserState.RUNNING_UNLOCKED); 538 } 539 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 540 Log.d(LOG_TAG, "isUserUnlocked(" + this + "): " 541 + mUserManager.isUserUnlocked(userHandle())); 542 return mUserManager.isUserUnlocked(userHandle()); 543 } 544 } 545 546 /** 547 * Get the user type. 548 */ type()549 public UserType type() { 550 if (mUserType == null) { 551 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 552 mUserType = adbUser().type(); 553 } else { 554 try (PermissionContext p = TestApis.permissions() 555 .withPermission(CREATE_USERS) 556 .withPermissionOnVersionAtLeast(U, QUERY_USERS)) { 557 String userTypeName = getUserType(mUserManager); 558 if (userTypeName.equals("")) { 559 throw new NeneException("User does not exist " + this); 560 } 561 mUserType = TestApis.users().supportedType(userTypeName); 562 } 563 } 564 } 565 return mUserType; 566 } 567 568 /** 569 * Return {@code true} if this is the primary user. 570 */ isPrimary()571 public Boolean isPrimary() { 572 if (mIsPrimary == null) { 573 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 574 mIsPrimary = adbUser().isPrimary(); 575 } else { 576 mIsPrimary = userInfo().isPrimary(); 577 } 578 } 579 580 return mIsPrimary; 581 } 582 583 /** 584 * {@code true} if this user is a profile of another user. 585 * 586 * <p>A non-existing user will return false 587 */ 588 @Experimental isProfile()589 public boolean isProfile() { 590 return exists() && parent() != null; 591 } 592 593 /** 594 * Return the parent of this profile. 595 * 596 * <p>Returns {@code null} if this user is not a profile. 597 */ 598 @Nullable parent()599 public UserReference parent() { 600 if (!mParentCached) { 601 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 602 mParent = adbUser().parent(); 603 } else { 604 try (PermissionContext p = 605 TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 606 UserHandle u = userHandle(); 607 UserHandle parentHandle = mUserManager.getProfileParent(u); 608 if (parentHandle == null) { 609 if (!exists()) { 610 throw new NeneException("User does not exist " + this); 611 } 612 613 mParent = null; 614 } else { 615 mParent = TestApis.users().find(parentHandle); 616 } 617 } 618 } 619 mParentCached = true; 620 } 621 622 return mParent; 623 } 624 625 /** 626 * Return {@code true} if a user with this ID exists. 627 */ exists()628 public boolean exists() { 629 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 630 return TestApis.users().all().stream().anyMatch(u -> u.equals(this)); 631 } 632 return users().anyMatch(ui -> ui.getId() == id()); 633 } 634 635 /** 636 * Sets the value of {@code user_setup_complete} in secure settings to {@code complete}. 637 */ 638 @Experimental setSetupComplete(boolean complete)639 public void setSetupComplete(boolean complete) { 640 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 641 return; 642 } 643 644 if (TestApis.users().system().equals(this) 645 && !TestApis.users().instrumented().equals(this) 646 && TestApis.users().isHeadlessSystemUserMode()) { 647 // We should also copy the setup status onto the instrumented user as DO provisioning 648 // depends on both 649 TestApis.users().instrumented().setSetupComplete(complete); 650 } 651 652 DevicePolicyManager devicePolicyManager = 653 TestApis.context().androidContextAsUser(this) 654 .getSystemService(DevicePolicyManager.class); 655 TestApis.settings().secure().putInt( 656 /* user= */ this, USER_SETUP_COMPLETE_KEY, complete ? 1 : 0); 657 try (PermissionContext p = 658 TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) { 659 forceUpdateUserSetupComplete(devicePolicyManager, id()); 660 } 661 } 662 663 /** 664 * Gets the value of {@code user_setup_complete} from secure settings. 665 */ 666 @Experimental getSetupComplete()667 public boolean getSetupComplete() { 668 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 669 return TestApis.settings().secure() 670 .getInt(/*user= */ this, USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1; 671 } 672 } 673 674 /** 675 * Returns true if the lock screen is completely disabled, i.e. set to None. 676 * Otherwise returns false. 677 */ getScreenLockDisabled()678 public boolean getScreenLockDisabled() { 679 return Boolean.parseBoolean( 680 ShellCommand.builder("cmd lock_settings") 681 .addOperand("get-disabled") 682 .addOption("--user", mId) 683 .executeOrThrowNeneException("Error getting lock screen disabled") 684 .trim()); 685 } 686 687 /** 688 * Sets whether the lock screen is disabled. If the lock screen is secure, 689 * this has no immediate effect. I.e. this can only change between Swipe and None. 690 */ setScreenLockDisabled(boolean disabled)691 public void setScreenLockDisabled(boolean disabled) { 692 ShellCommand.builder("cmd lock_settings") 693 .addOption("set-disabled", disabled) 694 .addOption("--user", mId) 695 .validate(s -> s.startsWith("Lock screen disabled set to " + disabled)) 696 .executeOrThrowNeneException("Error setting lock screen disabled to " + disabled); 697 } 698 699 /** 700 * True if the user has a lock credential (password, pin or pattern set). 701 */ hasLockCredential()702 public boolean hasLockCredential() { 703 try (PermissionContext p = TestApis.permissions().withPermission( 704 INTERACT_ACROSS_USERS_FULL)) { 705 KeyguardManager keyguardManager = TestApis.context().androidContextAsUser(this) 706 .getSystemService(KeyguardManager.class); 707 if (keyguardManager != null) { 708 return keyguardManager.isDeviceSecure(); 709 } else { 710 // keyguardManager isn't available in instant apps 711 return !getScreenLockDisabled(); 712 } 713 } 714 } 715 716 /** 717 * Set a specific type of lock credential for the user. 718 */ setLockCredential( String lockType, String lockCredential, String existingCredential)719 private void setLockCredential( 720 String lockType, String lockCredential, String existingCredential) { 721 String lockTypeSentenceCase = Character.toUpperCase(lockType.charAt(0)) 722 + lockType.substring(1); 723 try { 724 ShellCommand.Builder commandBuilder = ShellCommand.builder("cmd lock_settings") 725 .addOperand("set-" + lockType) 726 .addOption("--user", mId); 727 728 if (existingCredential != null) { 729 commandBuilder.addOption("--old", existingCredential); 730 } else if (mLockCredential != null) { 731 commandBuilder.addOption("--old", mLockCredential); 732 } 733 734 commandBuilder.addOperand(lockCredential) 735 .validate(s -> s.startsWith(lockTypeSentenceCase + " set to")) 736 .execute(); 737 } catch (AdbException e) { 738 if (e.output().contains("old credential was not provided")) { 739 throw new NeneException("Error attempting to set lock credential when there is " 740 + "already one set. Use the version which takes the existing credential"); 741 } 742 743 if (e.output().contains("doesn't satisfy admin policies")) { 744 throw new NeneException(e.output().strip(), e); 745 } 746 747 throw new NeneException("Error setting " + lockType, e); 748 } 749 mLockCredential = lockCredential; 750 mLockType = lockType; 751 } 752 753 /** 754 * Set a password for the user. 755 */ setPassword(String password)756 public void setPassword(String password) { 757 setPassword(password, /* existingCredential= */ null); 758 } 759 760 /** 761 * Set a password for the user. 762 * 763 * <p>If the existing credential was set using TestApis, you do not need to provide it. 764 */ setPassword(String password, String existingCredential)765 public void setPassword(String password, String existingCredential) { 766 setLockCredential(TYPE_PASSWORD, password, existingCredential); 767 } 768 769 /** 770 * Set a pin for the user. 771 */ setPin(String pin)772 public void setPin(String pin) { 773 setPin(pin, /* existingCredential=*/ null); 774 } 775 776 /** 777 * Set a pin for the user. 778 * 779 * <p>If the existing credential was set using TestApis, you do not need to provide it. 780 */ setPin(String pin, String existingCredential)781 public void setPin(String pin, String existingCredential) { 782 setLockCredential(TYPE_PIN, pin, existingCredential); 783 } 784 785 /** 786 * Set a pattern for the user. 787 */ setPattern(String pattern)788 public void setPattern(String pattern) { 789 setPattern(pattern, /* existingCredential= */ null); 790 } 791 792 /** 793 * Set a pattern for the user. 794 * 795 * <p>If the existing credential was set using TestApis, you do not need to provide it. 796 */ setPattern(String pattern, String existingCredential)797 public void setPattern(String pattern, String existingCredential) { 798 setLockCredential(TYPE_PATTERN, pattern, existingCredential); 799 } 800 801 /** 802 * Clear the password for the user, using the lock credential that was last set using 803 * Nene. 804 */ clearPassword()805 public void clearPassword() { 806 clearLockCredential(mLockCredential, TYPE_PASSWORD); 807 } 808 809 /** 810 * Clear password for the user. 811 */ clearPassword(String password)812 public void clearPassword(String password) { 813 clearLockCredential(password, TYPE_PASSWORD); 814 } 815 816 /** 817 * Clear the pin for the user, using the lock credential that was last set using 818 * Nene. 819 */ clearPin()820 public void clearPin() { 821 clearLockCredential(mLockCredential, TYPE_PIN); 822 } 823 824 /** 825 * Clear pin for the user. 826 */ clearPin(String pin)827 public void clearPin(String pin) { 828 clearLockCredential(pin, TYPE_PIN); 829 } 830 831 /** 832 * Clear the pattern for the user, using the lock credential that was last set using 833 * Nene. 834 */ clearPattern()835 public void clearPattern() { 836 clearLockCredential(mLockCredential, TYPE_PATTERN); 837 } 838 839 /** 840 * Clear pin for the user. 841 */ clearPattern(String pattern)842 public void clearPattern(String pattern) { 843 clearLockCredential(pattern, TYPE_PATTERN); 844 } 845 846 /** 847 * Clear the lock credential for the user. 848 */ clearLockCredential(String lockCredential, String lockType)849 private void clearLockCredential(String lockCredential, String lockType) { 850 if (lockCredential == null || lockCredential.length() == 0) return; 851 if (!lockType.equals(mLockType) && mLockType != null) { 852 String lockTypeSentenceCase = Character.toUpperCase(lockType.charAt(0)) 853 + lockType.substring(1); 854 throw new NeneException( 855 "clear" + lockTypeSentenceCase + "() can only be called when set" 856 + lockTypeSentenceCase + " was used to set the lock credential"); 857 } 858 859 try { 860 ShellCommand.builder("cmd lock_settings") 861 .addOperand("clear") 862 .addOption("--old", lockCredential) 863 .addOption("--user", mId) 864 .validate(s -> s.startsWith("Lock credential cleared")) 865 .execute(); 866 } catch (AdbException e) { 867 if (e.output().contains("user has no password")) { 868 // No lock credential anyway, fine 869 mLockCredential = null; 870 mLockType = null; 871 return; 872 } 873 if (e.output().contains("doesn't satisfy admin policies")) { 874 throw new NeneException(e.output().strip(), e); 875 } 876 throw new NeneException("Error clearing lock credential", e); 877 } 878 879 mLockCredential = null; 880 mLockType = null; 881 } 882 883 /** 884 * returns password if password has been set using nene 885 */ password()886 public @Nullable String password() { 887 return lockCredential(TYPE_PASSWORD); 888 } 889 890 /** 891 * returns pin if pin has been set using nene 892 */ pin()893 public @Nullable String pin() { 894 return lockCredential(TYPE_PIN); 895 } 896 897 /** 898 * returns pattern if pattern has been set using nene 899 */ pattern()900 public @Nullable String pattern() { 901 return lockCredential(TYPE_PATTERN); 902 } 903 904 /** 905 * Returns the lock credential for this user if that lock credential was set using Nene. 906 * Where a lock credential can either be a password, pin or pattern. 907 * 908 * <p>If there is a lock credential but the lock credential was not set using the corresponding 909 * Nene method, this will throw an exception. If there is no lock credential set 910 * (regardless off the calling method) this will return {@code null} 911 */ lockCredential(String lockType)912 private @Nullable String lockCredential(String lockType) { 913 if (mLockType != null && !lockType.equals(mLockType)) { 914 String lockTypeSentenceCase = Character.toUpperCase(lockType.charAt(0)) 915 + lockType.substring(1); 916 throw new NeneException(lockType + " not set, as set" + lockTypeSentenceCase + "() has " 917 + "not been called"); 918 } 919 return mLockCredential; 920 } 921 922 /** 923 * Sets quiet mode to {@code enabled}. This will only work for managed profiles with no 924 * credentials set. 925 * 926 * @return {@code false} if user's credential is needed in order to turn off quiet mode, 927 * {@code true} otherwise. 928 */ 929 @CanIgnoreReturnValue 930 @TargetApi(P) 931 @Experimental setQuietMode(boolean enabled)932 public boolean setQuietMode(boolean enabled) { 933 if (!Versions.meetsMinimumSdkVersionRequirement(P)) { 934 return false; 935 } 936 937 if (isQuietModeEnabled() == enabled) { 938 return true; 939 } 940 941 UserReference parent = parent(); 942 if (parent == null) { 943 throw new NeneException("Can't set quiet mode, no parent for user " + this); 944 } 945 946 try (PermissionContext p = TestApis.permissions().withPermission( 947 MODIFY_QUIET_MODE, INTERACT_ACROSS_USERS_FULL)) { 948 BlockingBroadcastReceiver r = BlockingBroadcastReceiver.create( 949 TestApis.context().androidContextAsUser(parent), 950 enabled 951 ? ACTION_MANAGED_PROFILE_UNAVAILABLE 952 : ACTION_MANAGED_PROFILE_AVAILABLE) 953 .register(); 954 try { 955 if (mUserManager.requestQuietModeEnabled(enabled, userHandle())) { 956 r.awaitForBroadcast(); 957 return true; 958 } 959 return false; 960 } finally { 961 r.unregisterQuietly(); 962 } 963 } 964 } 965 966 /** 967 * Returns true if this user is a profile and quiet mode is enabled. Otherwise false. 968 */ 969 @Experimental isQuietModeEnabled()970 public boolean isQuietModeEnabled() { 971 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.N)) { 972 // Quiet mode not supported by < N 973 return false; 974 } 975 return mUserManager.isQuietModeEnabled(userHandle()); 976 } 977 978 @Override equals(Object obj)979 public boolean equals(Object obj) { 980 if (!(obj instanceof UserReference)) { 981 return false; 982 } 983 984 UserReference other = (UserReference) obj; 985 986 return other.id() == id(); 987 } 988 989 @Override hashCode()990 public int hashCode() { 991 return id(); 992 } 993 994 /** See {@link #remove}. */ 995 @Override close()996 public void close() { 997 remove(); 998 } 999 adbUserOrNull()1000 private AdbUser adbUserOrNull() { 1001 return TestApis.users().fetchUser(mId); 1002 } 1003 1004 /** 1005 * Do not use this method except for backwards compatibility. 1006 */ adbUser()1007 private AdbUser adbUser() { 1008 AdbUser user = adbUserOrNull(); 1009 if (user == null) { 1010 throw new NeneException("User does not exist " + this); 1011 } 1012 return user; 1013 } 1014 1015 /** 1016 * Note: This method should not be run on < S. 1017 */ userInfo()1018 private UserInfo userInfo() { 1019 Versions.requireMinimumVersion(S); 1020 1021 return users().filter(ui -> ui.getId() == id()).findFirst() 1022 .orElseThrow(() -> new NeneException("User does not exist " + this)); 1023 } 1024 1025 @Override toString()1026 public String toString() { 1027 try { 1028 return "User{id=" + id() + ", name=" + name() + "}"; 1029 } catch (NeneException e) { 1030 // If the user does not exist we won't be able to get a name 1031 return "User{id=" + id() + "}"; 1032 } 1033 } 1034 1035 /** 1036 * {@code true} if this user can be switched to. 1037 */ canBeSwitchedTo()1038 public boolean canBeSwitchedTo() { 1039 return getSwitchToUserError() == null; 1040 } 1041 1042 /** 1043 * {@code true} if this user can show activities. 1044 */ 1045 @Experimental canShowActivities()1046 public boolean canShowActivities() { 1047 if (!isForeground() && (!isProfile() || !parent().isForeground())) { 1048 return false; 1049 } 1050 1051 return true; 1052 } 1053 1054 /** 1055 * Get the reason this user cannot be switched to. Null if none. 1056 */ getSwitchToUserError()1057 public String getSwitchToUserError() { 1058 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 1059 return null; 1060 } 1061 1062 if (TestApis.users().isHeadlessSystemUserMode() && equals(TestApis.users().system())) { 1063 return "Cannot switch to system user on HSUM devices"; 1064 } 1065 1066 UserInfo userInfo = userInfo(); 1067 if (!userInfo.supportsSwitchTo()) { 1068 return "supportsSwitchTo=false(partial=" + userInfo.getPartial() + ", isEnabled=" 1069 + userInfo.isEnabled() + ", preCreated=" + userInfo.getPreCreated() + ", isFull=" 1070 + userInfo.isFull() + ")"; 1071 } 1072 1073 return null; 1074 } 1075 1076 /** 1077 * checks if user is ephemeral 1078 */ isEphemeral()1079 public boolean isEphemeral() { 1080 return userInfo().isEphemeral(); 1081 } 1082 1083 /** 1084 * checks if user is a guest 1085 */ isGuest()1086 public boolean isGuest() { 1087 return userInfo().isGuest(); 1088 } 1089 1090 /** 1091 * Check if the provided user {@code credential} equals the set credential 1092 * 1093 * @param credential The credential to verify. 1094 * @return {@code true} if the credential matches. 1095 */ lockCredentialEquals(String credential)1096 public boolean lockCredentialEquals(String credential) { 1097 try { 1098 return ShellCommand.builder("cmd lock_settings verify") 1099 .addOperand("--user") 1100 .addOperand(userInfo().getId()) 1101 .addOperand(credential.isEmpty() ? "" : "--old "+credential) 1102 .execute().startsWith("Lock credential verified"); 1103 } catch (AdbException e) { 1104 throw new NeneException("Could not verify user credential"); 1105 } 1106 } 1107 1108 /** Checks if a profile of type {@code userType} can be created. */ 1109 @Experimental 1110 @SuppressWarnings("NewApi") // We include a T version check in the method. canCreateProfile(UserType userType)1111 public boolean canCreateProfile(UserType userType) { 1112 // UserManager#getRemainingCreatableProfileCount is added in T, so we need a version guard. 1113 if (Versions.meetsMinimumSdkVersionRequirement(TIRAMISU)) { 1114 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 1115 return mUserManager.getRemainingCreatableProfileCount(userType.name()) > 0; 1116 } 1117 } 1118 1119 // For S and older versions, we need to keep the previous behavior by returning true here 1120 // so that the check can pass. 1121 Log.d(LOG_TAG, "canCreateProfile pre-T: true"); 1122 return true; 1123 } 1124 } 1125