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