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