• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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