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 28 import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS; 29 import static com.android.bedstead.nene.permissions.CommonPermissions.MODIFY_QUIET_MODE; 30 import static com.android.bedstead.nene.users.Users.users; 31 32 import android.annotation.TargetApi; 33 import android.app.KeyguardManager; 34 import android.app.admin.DevicePolicyManager; 35 import android.content.Intent; 36 import android.content.pm.UserInfo; 37 import android.os.UserHandle; 38 import android.os.UserManager; 39 import android.util.Log; 40 41 import androidx.annotation.Nullable; 42 43 import com.android.bedstead.nene.TestApis; 44 import com.android.bedstead.nene.annotations.Experimental; 45 import com.android.bedstead.nene.exceptions.AdbException; 46 import com.android.bedstead.nene.exceptions.NeneException; 47 import com.android.bedstead.nene.permissions.PermissionContext; 48 import com.android.bedstead.nene.utils.Poll; 49 import com.android.bedstead.nene.utils.ShellCommand; 50 import com.android.bedstead.nene.utils.ShellCommandUtils; 51 import com.android.bedstead.nene.utils.Versions; 52 import com.android.compatibility.common.util.BlockingBroadcastReceiver; 53 54 import java.time.Duration; 55 import java.util.Arrays; 56 import java.util.HashSet; 57 import java.util.Set; 58 59 /** 60 * A representation of a User on device which may or may not exist. 61 */ 62 public class UserReference implements AutoCloseable { 63 64 private static final Set<AdbUser.UserState> RUNNING_STATES = new HashSet<>( 65 Arrays.asList(AdbUser.UserState.RUNNING_LOCKED, 66 AdbUser.UserState.RUNNING_UNLOCKED, 67 AdbUser.UserState.RUNNING_UNLOCKING) 68 ); 69 70 private static final String LOG_TAG = "UserReference"; 71 72 private static final String USER_SETUP_COMPLETE_KEY = "user_setup_complete"; 73 74 private final int mId; 75 76 private final UserManager mUserManager; 77 78 private Long mSerialNo; 79 private String mName; 80 private UserType mUserType; 81 private Boolean mIsPrimary; 82 private boolean mParentCached = false; 83 private UserReference mParent; 84 private @Nullable String mPassword; 85 86 /** 87 * Returns a {@link UserReference} equivalent to the passed {@code userHandle}. 88 */ of(UserHandle userHandle)89 public static UserReference of(UserHandle userHandle) { 90 return TestApis.users().find(userHandle.getIdentifier()); 91 } 92 UserReference(int id)93 UserReference(int id) { 94 mId = id; 95 mUserManager = TestApis.context().androidContextAsUser(this) 96 .getSystemService(UserManager.class); 97 } 98 id()99 public final int id() { 100 return mId; 101 } 102 103 /** 104 * Get a {@link UserHandle} for the {@link #id()}. 105 */ userHandle()106 public final UserHandle userHandle() { 107 return UserHandle.of(mId); 108 } 109 110 /** 111 * Remove the user from the device. 112 * 113 * <p>If the user does not exist, or the removal fails for any other reason, a 114 * {@link NeneException} will be thrown. 115 */ remove()116 public final void remove() { 117 try { 118 // Expected success string is "Success: removed user" 119 ShellCommand.builder("pm remove-user") 120 .addOperand(mId) 121 .validate(ShellCommandUtils::startsWithSuccess) 122 .execute(); 123 124 Poll.forValue("User exists", this::exists) 125 .toBeEqualTo(false) 126 // TODO(b/203630556): Reduce timeout once we have a faster way of removing users 127 .timeout(Duration.ofMinutes(1)) 128 .errorOnFail() 129 .await(); 130 } catch (AdbException e) { 131 throw new NeneException("Could not remove user " + this, e); 132 } 133 } 134 135 /** 136 * Start the user. 137 * 138 * <p>After calling this command, the user will be running unlocked. 139 * 140 * <p>If the user does not exist, or the start fails for any other reason, a 141 * {@link NeneException} will be thrown. 142 */ 143 //TODO(scottjonathan): Deal with users who won't unlock start()144 public UserReference start() { 145 try { 146 // Expected success string is "Success: user started" 147 ShellCommand.builder("am start-user") 148 .addOperand(mId) 149 .addOperand("-w") 150 .validate(ShellCommandUtils::startsWithSuccess) 151 .execute(); 152 153 Poll.forValue("User running unlocked", () -> isRunning() && isUnlocked()) 154 .toBeEqualTo(true) 155 .errorOnFail() 156 .timeout(Duration.ofMinutes(1)) 157 .await(); 158 } catch (AdbException e) { 159 throw new NeneException("Could not start user " + this, e); 160 } 161 162 return this; 163 } 164 165 /** 166 * Stop the user. 167 * 168 * <p>After calling this command, the user will be not running. 169 */ stop()170 public UserReference stop() { 171 try { 172 // Expects no output on success or failure - stderr output on failure 173 ShellCommand.builder("am stop-user") 174 .addOperand("-f") // Force stop 175 .addOperand(mId) 176 .allowEmptyOutput(true) 177 .validate(String::isEmpty) 178 .execute(); 179 180 Poll.forValue("User running", this::isRunning) 181 .toBeEqualTo(false) 182 // TODO(b/203630556): Replace stopping with something faster 183 .timeout(Duration.ofMinutes(10)) 184 .errorOnFail() 185 .await(); 186 } catch (AdbException e) { 187 throw new NeneException("Could not stop user " + this, e); 188 } 189 190 return this; 191 } 192 193 /** 194 * Make the user the foreground user. 195 * 196 * <p>If the user is a profile, then this will make the parent the foreground user. It will 197 * still return the {@link UserReference} of the profile in that case. 198 */ switchTo()199 public UserReference switchTo() { 200 UserReference parent = parent(); 201 if (parent != null) { 202 parent.switchTo(); 203 return this; 204 } 205 206 if (TestApis.users().current().equals(this)) { 207 // Already switched to 208 return this; 209 } 210 211 // This is created outside of the try because we don't want to wait for the broadcast 212 // on versions less than R 213 BlockingBroadcastReceiver broadcastReceiver = 214 new BlockingBroadcastReceiver(TestApis.context().instrumentedContext(), 215 Intent.ACTION_USER_FOREGROUND, 216 (intent) ->((UserHandle) 217 intent.getParcelableExtra(Intent.EXTRA_USER)) 218 .getIdentifier() == mId); 219 220 try { 221 if (Versions.meetsMinimumSdkVersionRequirement(R)) { 222 try (PermissionContext p = 223 TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) { 224 broadcastReceiver.registerForAllUsers(); 225 } 226 } 227 228 // Expects no output on success or failure 229 ShellCommand.builder("am switch-user") 230 .addOperand(mId) 231 .allowEmptyOutput(true) 232 .validate(String::isEmpty) 233 .execute(); 234 235 if (Versions.meetsMinimumSdkVersionRequirement(R)) { 236 broadcastReceiver.awaitForBroadcast(); 237 } else { 238 Thread.sleep(20000); 239 } 240 } catch (AdbException e) { 241 throw new NeneException("Could not switch to user", e); 242 } catch (InterruptedException e) { 243 Log.e(LOG_TAG, "Interrupted while switching user", e); 244 } finally { 245 broadcastReceiver.unregisterQuietly(); 246 } 247 248 return this; 249 } 250 251 /** Get the serial number of the user. */ serialNo()252 public long serialNo() { 253 if (mSerialNo == null) { 254 mSerialNo = TestApis.context().instrumentedContext().getSystemService(UserManager.class) 255 .getSerialNumberForUser(userHandle()); 256 257 if (mSerialNo == -1) { 258 mSerialNo = null; 259 throw new NeneException("User does not exist " + this); 260 } 261 } 262 263 return mSerialNo; 264 } 265 266 /** Get the name of the user. */ name()267 public String name() { 268 if (mName == null) { 269 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 270 mName = adbUser().name(); 271 } else { 272 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 273 mName = TestApis.context().androidContextAsUser(this) 274 .getSystemService(UserManager.class) 275 .getUserName(); 276 } 277 if (mName == null || mName.equals("")) { 278 if (!exists()) { 279 mName = null; 280 throw new NeneException("User does not exist " + this); 281 } 282 } 283 } 284 if (mName == null) { 285 mName = ""; 286 } 287 } 288 289 return mName; 290 } 291 292 /** Is the user running? */ isRunning()293 public boolean isRunning() { 294 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 295 AdbUser adbUser = adbUserOrNull(); 296 if (adbUser == null) { 297 return false; 298 } 299 return RUNNING_STATES.contains(adbUser().state()); 300 } 301 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 302 return mUserManager.isUserRunning(userHandle()); 303 } 304 } 305 306 /** Is the user unlocked? */ isUnlocked()307 public boolean isUnlocked() { 308 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 309 AdbUser adbUser = adbUserOrNull(); 310 if (adbUser == null) { 311 return false; 312 } 313 return adbUser.state().equals(AdbUser.UserState.RUNNING_UNLOCKED); 314 } 315 try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 316 return mUserManager.isUserUnlocked(userHandle()); 317 } 318 } 319 320 /** 321 * Get the user type. 322 */ type()323 public UserType type() { 324 if (mUserType == null) { 325 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 326 mUserType = adbUser().type(); 327 } else { 328 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 329 String userTypeName = mUserManager.getUserType(); 330 if (userTypeName.equals("")) { 331 throw new NeneException("User does not exist " + this); 332 } 333 mUserType = TestApis.users().supportedType(userTypeName); 334 } 335 } 336 } 337 return mUserType; 338 } 339 340 /** 341 * Return {@code true} if this is the primary user. 342 */ isPrimary()343 public Boolean isPrimary() { 344 if (mIsPrimary == null) { 345 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 346 mIsPrimary = adbUser().isPrimary(); 347 } else { 348 mIsPrimary = userInfo().isPrimary(); 349 } 350 } 351 352 return mIsPrimary; 353 } 354 355 /** 356 * Return the parent of this profile. 357 * 358 * <p>Returns {@code null} if this user is not a profile. 359 */ 360 @Nullable parent()361 public UserReference parent() { 362 if (!mParentCached) { 363 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 364 mParent = adbUser().parent(); 365 } else { 366 try (PermissionContext p = 367 TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) { 368 UserHandle parentHandle = mUserManager.getProfileParent(userHandle()); 369 if (parentHandle == null) { 370 if (!exists()) { 371 throw new NeneException("User does not exist " + this); 372 } 373 374 mParent = null; 375 } else { 376 mParent = TestApis.users().find(parentHandle); 377 } 378 } 379 } 380 mParentCached = true; 381 } 382 383 return mParent; 384 } 385 386 /** 387 * Return {@code true} if a user with this ID exists. 388 */ exists()389 public boolean exists() { 390 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 391 return TestApis.users().all().stream().anyMatch(u -> u.equals(this)); 392 } 393 return users().anyMatch(ui -> ui.id == id()); 394 } 395 396 /** 397 * Sets the value of {@code user_setup_complete} in secure settings to {@code complete}. 398 */ 399 @Experimental setSetupComplete(boolean complete)400 public void setSetupComplete(boolean complete) { 401 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 402 return; 403 } 404 DevicePolicyManager devicePolicyManager = 405 TestApis.context().androidContextAsUser(this) 406 .getSystemService(DevicePolicyManager.class); 407 TestApis.settings().secure().putInt( 408 /* user= */ this, USER_SETUP_COMPLETE_KEY, complete ? 1 : 0); 409 try (PermissionContext p = 410 TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) { 411 devicePolicyManager.forceUpdateUserSetupComplete(id()); 412 } 413 } 414 415 /** 416 * Gets the value of {@code user_setup_complete} from secure settings. 417 */ 418 @Experimental getSetupComplete()419 public boolean getSetupComplete() { 420 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 421 return TestApis.settings().secure() 422 .getInt(/*user= */ this, USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1; 423 } 424 } 425 426 /** 427 * True if the user has a password. 428 */ hasPassword()429 public boolean hasPassword() { 430 return TestApis.context().androidContextAsUser(this) 431 .getSystemService(KeyguardManager.class).isDeviceSecure(); 432 } 433 434 /** 435 * Set a password for the user. 436 */ setPassword(String password)437 public void setPassword(String password) { 438 try { 439 ShellCommand.builder("cmd lock_settings") 440 .addOperand("set-password") 441 .addOperand(password) 442 .addOption("--user", mId) 443 .validate(s -> s.startsWith("Password set to ")) 444 .execute(); 445 } catch (AdbException e) { 446 throw new NeneException("Error setting password", e); 447 } 448 mPassword = password; 449 } 450 451 /** 452 * Clear the password for the user, using the password that was last set using Nene. 453 */ clearPassword()454 public void clearPassword() { 455 if (mPassword == null) { 456 throw new NeneException( 457 "clearPassword() can only be called when setPassword was used to set the" 458 + " password"); 459 } 460 clearPassword(mPassword); 461 } 462 463 /** 464 * Clear the password for the user. 465 */ clearPassword(String password)466 public void clearPassword(String password) { 467 468 try { 469 ShellCommand.builder("cmd lock_settings") 470 .addOperand("clear") 471 .addOption("--old", password) 472 .addOption("--user", mId) 473 .validate(s -> s.startsWith("Lock credential cleared")) 474 .execute(); 475 } catch (AdbException e) { 476 if (e.output().contains("user has no password")) { 477 // No password anyway, fine 478 mPassword = null; 479 return; 480 } 481 throw new NeneException("Error clearing password", e); 482 } 483 484 mPassword = null; 485 } 486 487 /** 488 * Returns the password for this user if that password was set using Nene. 489 * 490 * 491 * <p>If there is no password, or the password was not set using Nene, then this will 492 * return {@code null}. 493 */ password()494 public @Nullable String password() { 495 return mPassword; 496 } 497 498 /** 499 * Sets quiet mode to {@code enabled}. This will only work for managed profiles with no 500 * credentials set. 501 * 502 * @return {@code false} if user's credential is needed in order to turn off quiet mode, 503 * {@code true} otherwise. 504 */ 505 @TargetApi(P) 506 @Experimental setQuietMode(boolean enabled)507 public boolean setQuietMode(boolean enabled) { 508 if (!Versions.meetsMinimumSdkVersionRequirement(P)) { 509 return false; 510 } 511 try (PermissionContext p = TestApis.permissions().withPermission(MODIFY_QUIET_MODE)) { 512 BlockingBroadcastReceiver r = BlockingBroadcastReceiver.create( 513 TestApis.context().instrumentedContext(), 514 enabled 515 ? ACTION_MANAGED_PROFILE_UNAVAILABLE 516 : ACTION_MANAGED_PROFILE_AVAILABLE) 517 .register(); 518 try { 519 if (mUserManager.requestQuietModeEnabled(enabled, userHandle())) { 520 r.awaitForBroadcast(); 521 return true; 522 } 523 return false; 524 } finally { 525 r.unregisterQuietly(); 526 } 527 } 528 } 529 530 @Override equals(Object obj)531 public boolean equals(Object obj) { 532 if (!(obj instanceof UserReference)) { 533 return false; 534 } 535 536 UserReference other = (UserReference) obj; 537 538 return other.id() == id(); 539 } 540 541 @Override hashCode()542 public int hashCode() { 543 return id(); 544 } 545 546 /** See {@link #remove}. */ 547 @Override close()548 public void close() { 549 remove(); 550 } 551 adbUserOrNull()552 private AdbUser adbUserOrNull() { 553 return TestApis.users().fetchUser(mId); 554 } 555 adbUser()556 private AdbUser adbUser() { 557 AdbUser user = adbUserOrNull(); 558 if (user == null) { 559 throw new NeneException("User does not exist " + this); 560 } 561 return user; 562 } 563 userInfo()564 private UserInfo userInfo() { 565 return users().filter(ui -> ui.id == id()).findFirst() 566 .orElseThrow(() -> new NeneException("User does not exist " + this)); 567 } 568 569 @Override toString()570 public String toString() { 571 return "User{id=" + id() + "}"; 572 } 573 } 574