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