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