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.TIRAMISU; 28 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 29 30 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS; 31 import static com.android.bedstead.permissions.CommonPermissions.MODIFY_QUIET_MODE; 32 import static com.android.bedstead.permissions.CommonPermissions.QUERY_USERS; 33 import static com.android.bedstead.nene.users.Users.users; 34 import static com.android.bedstead.nene.utils.Versions.U; 35 36 import android.annotation.SuppressLint; 37 import android.annotation.TargetApi; 38 import android.app.KeyguardManager; 39 import android.app.admin.DevicePolicyManager; 40 import android.content.Intent; 41 import android.cts.testapisreflection.TestApisReflectionKt; 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.permissions.CommonPermissions; 57 import com.android.bedstead.permissions.PermissionContext; 58 import com.android.bedstead.nene.utils.Poll; 59 import com.android.bedstead.nene.utils.ShellCommand; 60 import com.android.bedstead.nene.utils.ShellCommand.Builder; 61 import com.android.bedstead.nene.utils.ShellCommandUtils; 62 import com.android.bedstead.nene.utils.Versions; 63 import com.android.bedstead.nene.utils.BlockingBroadcastReceiver; 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 String unused = 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 String unused = 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 */ switchTo()375 public UserReference switchTo() { 376 UserReference parent = parent(); 377 if (parent != null) { 378 UserReference unused = parent.switchTo(); 379 return this; 380 } 381 382 if (TestApis.users().current().equals(this)) { 383 // Already switched to 384 return this; 385 } 386 387 boolean isSdkVersionMinimum_R = Versions.meetsMinimumSdkVersionRequirement(R); 388 try { 389 ShellCommand.builder("am switch-user") 390 .addOperand(isSdkVersionMinimum_R ? "-w" : "") 391 .addOperand(mId) 392 .withTimeout(Duration.ofMinutes(1)) 393 .allowEmptyOutput(true) 394 .validate(String::isEmpty) 395 .execute(); 396 } catch (AdbException e) { 397 String error = getSwitchToUserError(); 398 if (error != null) { 399 throw new NeneException(error); 400 } 401 if (!exists()) { 402 throw new NeneException("Tried to switch to user " + this + " but does not exist"); 403 } 404 // TODO(273229540): It might take a while to fail - we should stream from the 405 // start of the call 406 throw new NeneException("Error switching user to " + this + ". Relevant logcat: " 407 + TestApis.logcat().dump((line) -> line.contains("Cannot switch")), e); 408 } 409 if (isSdkVersionMinimum_R) { 410 Poll.forValue("current user", () -> TestApis.users().current()) 411 .toBeEqualTo(this) 412 .await(); 413 414 if (!TestApis.users().current().equals(this)) { 415 throw new NeneException("Error switching user to " + this 416 + " (current user is " + TestApis.users().current() + "). Relevant logcat: " 417 + TestApis.logcat().dump((line) -> line.contains("ActivityManager"))); 418 } 419 } else { 420 try { 421 Thread.sleep(20000); 422 } catch (InterruptedException e) { 423 Log.e(LOG_TAG, "Interrupted while switching user", e); 424 } 425 } 426 427 return this; 428 } 429 430 /** Get the serial number of the user. */ serialNo()431 public long serialNo() { 432 if (mSerialNo == null) { 433 mSerialNo = TestApis.context().instrumentedContext().getSystemService(UserManager.class) 434 .getSerialNumberForUser(userHandle()); 435 436 if (mSerialNo == -1) { 437 mSerialNo = null; 438 throw new NeneException("User does not exist " + this); 439 } 440 } 441 442 return mSerialNo; 443 } 444 445 /** Get the name of the user. */ name()446 public String name() { 447 if (mName == null) { 448 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 449 mName = adbUser().name(); 450 } else { 451 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 452 mName = TestApis.context().androidContextAsUser(this) 453 .getSystemService(UserManager.class) 454 .getUserName(); 455 } 456 if (mName == null || mName.equals("")) { 457 if (!exists()) { 458 mName = null; 459 throw new NeneException("User does not exist with id " + id()); 460 } 461 } 462 } 463 if (mName == null) { 464 mName = ""; 465 } 466 } 467 468 return mName; 469 } 470 471 /** Is the user running? */ isRunning()472 public boolean isRunning() { 473 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 474 AdbUser adbUser = adbUserOrNull(); 475 if (adbUser == null) { 476 return false; 477 } 478 479 return RUNNING_STATES.contains(adbUser().state()); 480 } 481 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 482 Log.d(LOG_TAG, "isUserRunning(" + this + "): " 483 + mUserManager.isUserRunning(userHandle())); 484 return mUserManager.isUserRunning(userHandle()); 485 } 486 } 487 488 /** Is the user {@link UserManager#isUserVisible() visible}? */ 489 @SuppressLint("NewApi") isVisible()490 public boolean isVisible() { 491 if (!Versions.meetsMinimumSdkVersionRequirement(UPSIDE_DOWN_CAKE)) { 492 // Best effort to define visible as "current user or a profile of the current user" 493 UserReference currentUser = TestApis.users().current(); 494 boolean isIt = currentUser.equals(this) 495 || (isProfile() && currentUser.equals(parent())); 496 Log.d(LOG_TAG, "isUserVisible(" + this + "): returning " + isIt + " as best approach"); 497 return isIt; 498 } 499 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 500 boolean isIt = mUserManager.isUserVisible(); 501 Log.d(LOG_TAG, "isUserVisible(" + this + "): " + isIt); 502 return isIt; 503 } 504 } 505 506 /** Is the user running in the foreground? */ isForeground()507 public boolean isForeground() { 508 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 509 // Best effort to define foreground as "current user" 510 boolean isIt = TestApis.users().current().equals(this); 511 Log.d(LOG_TAG, "isForeground(" + this + "): returning " + isIt + " as best effort"); 512 return isIt; 513 } 514 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 515 boolean isIt = mUserManager.isUserForeground(); 516 Log.d(LOG_TAG, "isUserForeground(" + this + "): " + isIt); 517 return isIt; 518 } 519 } 520 521 /** 522 * Is the user a non-{@link #isProfile() profile} that is running {@link #isVisible()} in the 523 * background? 524 */ isVisibleBagroundNonProfileUser()525 public boolean isVisibleBagroundNonProfileUser() { 526 return isVisible() && !isForeground() && !isProfile(); 527 } 528 529 /** Is the user unlocked? */ isUnlocked()530 public boolean isUnlocked() { 531 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 532 AdbUser adbUser = adbUserOrNull(); 533 if (adbUser == null) { 534 return false; 535 } 536 return adbUser.state().equals(AdbUser.UserState.RUNNING_UNLOCKED); 537 } 538 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 539 Log.d(LOG_TAG, "isUserUnlocked(" + this + "): " 540 + mUserManager.isUserUnlocked(userHandle())); 541 return mUserManager.isUserUnlocked(userHandle()); 542 } 543 } 544 545 /** 546 * Get the user type. 547 */ type()548 public UserType type() { 549 if (mUserType == null) { 550 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 551 mUserType = adbUser().type(); 552 } else { 553 try (PermissionContext p = TestApis.permissions() 554 .withPermission(CREATE_USERS) 555 .withPermissionOnVersionAtLeast(U, QUERY_USERS)) { 556 String userTypeName = TestApisReflectionKt.getUserType(mUserManager); 557 if (userTypeName.equals("")) { 558 throw new NeneException("User does not exist " + this); 559 } 560 mUserType = TestApis.users().supportedType(userTypeName); 561 } 562 } 563 } 564 return mUserType; 565 } 566 567 /** 568 * Return {@code true} if this is the primary user. 569 */ isPrimary()570 public Boolean isPrimary() { 571 if (mIsPrimary == null) { 572 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 573 mIsPrimary = adbUser().isPrimary(); 574 } else { 575 mIsPrimary = userInfo().isPrimary(); 576 } 577 } 578 579 return mIsPrimary; 580 } 581 582 /** 583 * {@code true} if this user is a profile of another user. 584 * 585 * <p>A non-existing user will return false 586 */ 587 @Experimental isProfile()588 public boolean isProfile() { 589 return exists() && parent() != null; 590 } 591 592 /** 593 * Return the parent of this profile. 594 * 595 * <p>Returns {@code null} if this user is not a profile. 596 */ 597 @Nullable parent()598 public UserReference parent() { 599 if (!mParentCached) { 600 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 601 mParent = adbUser().parent(); 602 } else { 603 try (PermissionContext p = 604 TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 605 UserHandle u = userHandle(); 606 UserHandle parentHandle = mUserManager.getProfileParent(u); 607 if (parentHandle == null) { 608 if (!exists()) { 609 throw new NeneException("User does not exist " + this); 610 } 611 612 mParent = null; 613 } else { 614 mParent = TestApis.users().find(parentHandle); 615 } 616 } 617 } 618 mParentCached = true; 619 } 620 621 return mParent; 622 } 623 624 /** 625 * Return {@code true} if a user with this ID exists. 626 */ exists()627 public boolean exists() { 628 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 629 return TestApis.users().all().stream().anyMatch(u -> u.equals(this)); 630 } 631 return users().anyMatch(ui -> ui.getId() == id()); 632 } 633 634 /** 635 * Sets the value of {@code user_setup_complete} in secure settings to {@code complete}. 636 */ 637 @Experimental setSetupComplete(boolean complete)638 public void setSetupComplete(boolean complete) { 639 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 640 return; 641 } 642 643 if (TestApis.users().system().equals(this) 644 && !TestApis.users().instrumented().equals(this) 645 && TestApis.users().isHeadlessSystemUserMode()) { 646 // We should also copy the setup status onto the instrumented user as DO provisioning 647 // depends on both 648 TestApis.users().instrumented().setSetupComplete(complete); 649 } 650 651 DevicePolicyManager devicePolicyManager = 652 TestApis.context().androidContextAsUser(this) 653 .getSystemService(DevicePolicyManager.class); 654 TestApis.settings().secure().putInt( 655 /* user= */ this, USER_SETUP_COMPLETE_KEY, complete ? 1 : 0); 656 try (PermissionContext p = 657 TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) { 658 TestApisReflectionKt.forceUpdateUserSetupComplete(devicePolicyManager, id()); 659 } 660 } 661 662 /** 663 * Gets the value of {@code user_setup_complete} from secure settings. 664 */ 665 @Experimental getSetupComplete()666 public boolean getSetupComplete() { 667 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 668 return TestApis.settings().secure() 669 .getInt(/*user= */ this, USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1; 670 } 671 } 672 673 /** 674 * True if the user has a lock credential (password, pin or pattern set). 675 */ hasLockCredential()676 public boolean hasLockCredential() { 677 return TestApis.context().androidContextAsUser(this) 678 .getSystemService(KeyguardManager.class).isDeviceSecure(); 679 } 680 681 /** 682 * Set a specific type of lock credential for the user. 683 */ setLockCredential( String lockType, String lockCredential, String existingCredential)684 private void setLockCredential( 685 String lockType, String lockCredential, String existingCredential) { 686 String lockTypeSentenceCase = Character.toUpperCase(lockType.charAt(0)) 687 + lockType.substring(1); 688 try { 689 ShellCommand.Builder commandBuilder = ShellCommand.builder("cmd lock_settings") 690 .addOperand("set-" + lockType) 691 .addOption("--user", mId); 692 693 if (existingCredential != null) { 694 ShellCommand.Builder unused = commandBuilder.addOption("--old", existingCredential); 695 } else if (mLockCredential != null) { 696 ShellCommand.Builder unused = commandBuilder.addOption("--old", mLockCredential); 697 } 698 699 String unused = commandBuilder.addOperand(lockCredential) 700 .validate(s -> s.startsWith(lockTypeSentenceCase + " set to")) 701 .execute(); 702 } catch (AdbException e) { 703 if (e.output().contains("null or empty")) { 704 throw new NeneException("Error attempting to set lock credential when there is " 705 + "already one set. Use the version which takes the existing credential"); 706 } 707 708 if (e.output().contains("doesn't satisfy admin policies")) { 709 throw new NeneException(e.output().strip(), e); 710 } 711 712 throw new NeneException("Error setting " + lockType, e); 713 } 714 mLockCredential = lockCredential; 715 mLockType = lockType; 716 } 717 718 /** 719 * Set a password for the user. 720 */ setPassword(String password)721 public void setPassword(String password) { 722 setPassword(password, /* existingCredential= */ null); 723 } 724 725 /** 726 * Set a password for the user. 727 * 728 * <p>If the existing credential was set using TestApis, you do not need to provide it. 729 */ setPassword(String password, String existingCredential)730 public void setPassword(String password, String existingCredential) { 731 setLockCredential(TYPE_PASSWORD, password, existingCredential); 732 } 733 734 /** 735 * Set a pin for the user. 736 */ setPin(String pin)737 public void setPin(String pin) { 738 setPin(pin, /* existingCredential=*/ null); 739 } 740 741 /** 742 * Set a pin for the user. 743 * 744 * <p>If the existing credential was set using TestApis, you do not need to provide it. 745 */ setPin(String pin, String existingCredential)746 public void setPin(String pin, String existingCredential) { 747 setLockCredential(TYPE_PIN, pin, existingCredential); 748 } 749 750 /** 751 * Set a pattern for the user. 752 */ setPattern(String pattern)753 public void setPattern(String pattern) { 754 setPattern(pattern, /* existingCredential= */ null); 755 } 756 757 /** 758 * Set a pattern for the user. 759 * 760 * <p>If the existing credential was set using TestApis, you do not need to provide it. 761 */ setPattern(String pattern, String existingCredential)762 public void setPattern(String pattern, String existingCredential) { 763 setLockCredential(TYPE_PATTERN, pattern, existingCredential); 764 } 765 766 /** 767 * Clear the password for the user, using the lock credential that was last set using 768 * Nene. 769 */ clearPassword()770 public void clearPassword() { 771 clearLockCredential(mLockCredential, TYPE_PASSWORD); 772 } 773 774 /** 775 * Clear password for the user. 776 */ clearPassword(String password)777 public void clearPassword(String password) { 778 clearLockCredential(password, TYPE_PASSWORD); 779 } 780 781 /** 782 * Clear the pin for the user, using the lock credential that was last set using 783 * Nene. 784 */ clearPin()785 public void clearPin() { 786 clearLockCredential(mLockCredential, TYPE_PIN); 787 } 788 789 /** 790 * Clear pin for the user. 791 */ clearPin(String pin)792 public void clearPin(String pin) { 793 clearLockCredential(pin, TYPE_PIN); 794 } 795 796 /** 797 * Clear the pattern for the user, using the lock credential that was last set using 798 * Nene. 799 */ clearPattern()800 public void clearPattern() { 801 clearLockCredential(mLockCredential, TYPE_PATTERN); 802 } 803 804 /** 805 * Clear pin for the user. 806 */ clearPattern(String pattern)807 public void clearPattern(String pattern) { 808 clearLockCredential(pattern, TYPE_PATTERN); 809 } 810 811 /** 812 * Clear the lock credential for the user. 813 */ clearLockCredential(String lockCredential, String lockType)814 private void clearLockCredential(String lockCredential, String lockType) { 815 if (lockCredential == null || lockCredential.length() == 0) return; 816 if (!lockType.equals(mLockType) && mLockType != null) { 817 String lockTypeSentenceCase = Character.toUpperCase(lockType.charAt(0)) 818 + lockType.substring(1); 819 throw new NeneException( 820 "clear" + lockTypeSentenceCase + "() can only be called when set" 821 + lockTypeSentenceCase + " was used to set the lock credential"); 822 } 823 824 try { 825 String unused = ShellCommand.builder("cmd lock_settings") 826 .addOperand("clear") 827 .addOption("--old", lockCredential) 828 .addOption("--user", mId) 829 .validate(s -> s.startsWith("Lock credential cleared")) 830 .execute(); 831 } catch (AdbException e) { 832 if (e.output().contains("user has no password")) { 833 // No lock credential anyway, fine 834 mLockCredential = null; 835 mLockType = null; 836 return; 837 } 838 if (e.output().contains("doesn't satisfy admin policies")) { 839 throw new NeneException(e.output().strip(), e); 840 } 841 throw new NeneException("Error clearing lock credential", e); 842 } 843 844 mLockCredential = null; 845 mLockType = null; 846 } 847 848 /** 849 * returns password if password has been set using nene 850 */ password()851 public @Nullable String password() { 852 return lockCredential(TYPE_PASSWORD); 853 } 854 855 /** 856 * returns pin if pin has been set using nene 857 */ pin()858 public @Nullable String pin() { 859 return lockCredential(TYPE_PIN); 860 } 861 862 /** 863 * returns pattern if pattern has been set using nene 864 */ pattern()865 public @Nullable String pattern() { 866 return lockCredential(TYPE_PATTERN); 867 } 868 869 /** 870 * Returns the lock credential for this user if that lock credential was set using Nene. 871 * Where a lock credential can either be a password, pin or pattern. 872 * 873 * <p>If there is a lock credential but the lock credential was not set using the corresponding 874 * Nene method, this will throw an exception. If there is no lock credential set 875 * (regardless off the calling method) this will return {@code null} 876 */ lockCredential(String lockType)877 private @Nullable String lockCredential(String lockType) { 878 if (mLockType != null && !lockType.equals(mLockType)) { 879 String lockTypeSentenceCase = Character.toUpperCase(lockType.charAt(0)) 880 + lockType.substring(1); 881 throw new NeneException(lockType + " not set, as set" + lockTypeSentenceCase + "() has " 882 + "not been called"); 883 } 884 return mLockCredential; 885 } 886 887 /** 888 * Sets quiet mode to {@code enabled}. This will only work for managed profiles with no 889 * credentials set. 890 * 891 * @return {@code false} if user's credential is needed in order to turn off quiet mode, 892 * {@code true} otherwise. 893 */ 894 @TargetApi(P) 895 @Experimental setQuietMode(boolean enabled)896 public boolean setQuietMode(boolean enabled) { 897 if (!Versions.meetsMinimumSdkVersionRequirement(P)) { 898 return false; 899 } 900 901 if (isQuietModeEnabled() == enabled) { 902 return true; 903 } 904 905 UserReference parent = parent(); 906 if (parent == null) { 907 throw new NeneException("Can't set quiet mode, no parent for user " + this); 908 } 909 910 try (PermissionContext p = TestApis.permissions().withPermission( 911 MODIFY_QUIET_MODE, INTERACT_ACROSS_USERS_FULL)) { 912 BlockingBroadcastReceiver r = BlockingBroadcastReceiver.create( 913 TestApis.context().androidContextAsUser(parent), 914 enabled 915 ? ACTION_MANAGED_PROFILE_UNAVAILABLE 916 : ACTION_MANAGED_PROFILE_AVAILABLE) 917 .register(); 918 try { 919 if (mUserManager.requestQuietModeEnabled(enabled, userHandle())) { 920 Intent unused = r.awaitForBroadcast(); 921 return true; 922 } 923 return false; 924 } finally { 925 r.unregisterQuietly(); 926 } 927 } 928 } 929 930 /** 931 * Returns true if this user is a profile and quiet mode is enabled. Otherwise false. 932 */ 933 @Experimental isQuietModeEnabled()934 public boolean isQuietModeEnabled() { 935 if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.N)) { 936 // Quiet mode not supported by < N 937 return false; 938 } 939 return mUserManager.isQuietModeEnabled(userHandle()); 940 } 941 942 @Override equals(Object obj)943 public boolean equals(Object obj) { 944 if (!(obj instanceof UserReference)) { 945 return false; 946 } 947 948 UserReference other = (UserReference) obj; 949 950 return other.id() == id(); 951 } 952 953 @Override hashCode()954 public int hashCode() { 955 return id(); 956 } 957 958 /** See {@link #remove}. */ 959 @Override close()960 public void close() { 961 UserReference unused = remove(); 962 } 963 adbUserOrNull()964 private AdbUser adbUserOrNull() { 965 return TestApis.users().fetchUser(mId); 966 } 967 968 /** 969 * Do not use this method except for backwards compatibility. 970 */ adbUser()971 private AdbUser adbUser() { 972 AdbUser user = adbUserOrNull(); 973 if (user == null) { 974 throw new NeneException("User does not exist " + this); 975 } 976 return user; 977 } 978 979 /** 980 * Note: This method should not be run on < S. 981 */ userInfo()982 private UserInfo userInfo() { 983 Versions.requireMinimumVersion(S); 984 985 return users().filter(ui -> ui.getId() == id()).findFirst() 986 .orElseThrow(() -> new NeneException("User does not exist " + this)); 987 } 988 989 @Override toString()990 public String toString() { 991 try { 992 return "User{id=" + id() + ", name=" + name() + "}"; 993 } catch (NeneException e) { 994 // If the user does not exist we won't be able to get a name 995 return "User{id=" + id() + "}"; 996 } 997 } 998 999 /** 1000 * {@code true} if this user can be switched to. 1001 */ canBeSwitchedTo()1002 public boolean canBeSwitchedTo() { 1003 return getSwitchToUserError() == null; 1004 } 1005 1006 /** 1007 * {@code true} if this user can show activities. 1008 */ 1009 @Experimental canShowActivities()1010 public boolean canShowActivities() { 1011 if (!isForeground() && (!isProfile() || !parent().isForeground())) { 1012 return false; 1013 } 1014 1015 return true; 1016 } 1017 1018 /** 1019 * Get the reason this user cannot be switched to. Null if none. 1020 */ getSwitchToUserError()1021 public String getSwitchToUserError() { 1022 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 1023 return null; 1024 } 1025 1026 if (TestApis.users().isHeadlessSystemUserMode() && equals(TestApis.users().system())) { 1027 return "Cannot switch to system user on HSUM devices"; 1028 } 1029 1030 UserInfo userInfo = userInfo(); 1031 if (!userInfo.supportsSwitchTo()) { 1032 return "supportsSwitchTo=false(partial=" + userInfo.getPartial() + ", isEnabled=" 1033 + userInfo.isEnabled() + ", preCreated=" + userInfo.getPreCreated() + ", isFull=" 1034 + userInfo.isFull() + ")"; 1035 } 1036 1037 return null; 1038 } 1039 1040 /** 1041 * checks if user is ephemeral 1042 */ isEphemeral()1043 public boolean isEphemeral() { 1044 return userInfo().isEphemeral(); 1045 } 1046 1047 /** 1048 * checks if user is a guest 1049 */ isGuest()1050 public boolean isGuest() { 1051 return userInfo().isGuest(); 1052 } 1053 1054 /** 1055 * Check if the provided user {@code credential} equals the set credential 1056 * 1057 * @param credential The credential to verify. 1058 * @return {@code true} if the credential matches. 1059 */ lockCredentialEquals(String credential)1060 public boolean lockCredentialEquals(String credential) { 1061 try { 1062 return ShellCommand.builder("cmd lock_settings verify") 1063 .addOperand("--user") 1064 .addOperand(userInfo().getId()) 1065 .addOperand(credential.isEmpty() ? "" : "--old "+credential) 1066 .execute().startsWith("Lock credential verified"); 1067 } catch (AdbException e) { 1068 throw new NeneException("Could not verify user credential"); 1069 } 1070 } 1071 1072 /** Checks if a profile of type {@code userType} can be created. */ 1073 @Experimental 1074 @SuppressWarnings("NewApi") // We include a T version check in the method. canCreateProfile(UserType userType)1075 public boolean canCreateProfile(UserType userType) { 1076 // UserManager#getRemainingCreatableProfileCount is added in T, so we need a version guard. 1077 if (Versions.meetsMinimumSdkVersionRequirement(TIRAMISU)) { 1078 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 1079 return mUserManager.getRemainingCreatableProfileCount(userType.name()) > 0; 1080 } 1081 } 1082 1083 // For S and older versions, we need to keep the previous behavior by returning true here 1084 // so that the check can pass. 1085 Log.d(LOG_TAG, "canCreateProfile pre-T: true"); 1086 return true; 1087 } 1088 } 1089