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