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