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.os.Build.VERSION_CODES.R;
23 import static android.os.Build.VERSION_CODES.S;
24 
25 import static com.android.bedstead.nene.users.Users.users;
26 
27 import android.content.Intent;
28 import android.content.pm.UserInfo;
29 import android.os.UserHandle;
30 import android.os.UserManager;
31 import android.util.Log;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.bedstead.nene.TestApis;
36 import com.android.bedstead.nene.exceptions.AdbException;
37 import com.android.bedstead.nene.exceptions.NeneException;
38 import com.android.bedstead.nene.permissions.PermissionContext;
39 import com.android.bedstead.nene.utils.Poll;
40 import com.android.bedstead.nene.utils.ShellCommand;
41 import com.android.bedstead.nene.utils.ShellCommandUtils;
42 import com.android.bedstead.nene.utils.Versions;
43 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
44 
45 import java.time.Duration;
46 import java.util.Arrays;
47 import java.util.HashSet;
48 import java.util.Set;
49 
50 /**
51  * A representation of a User on device which may or may not exist.
52  */
53 public class UserReference implements AutoCloseable {
54 
55     private static final Set<AdbUser.UserState> RUNNING_STATES = new HashSet<>(
56             Arrays.asList(AdbUser.UserState.RUNNING_LOCKED,
57                     AdbUser.UserState.RUNNING_UNLOCKED,
58                     AdbUser.UserState.RUNNING_UNLOCKING)
59     );
60 
61     private static final String LOG_TAG = "UserReference";
62 
63     private final int mId;
64 
65     private final UserManager mUserManager;
66 
67     private Long mSerialNo;
68     private String mName;
69     private UserType mUserType;
70     private Boolean mIsPrimary;
71     private boolean mParentCached = false;
72     private UserReference mParent;
73 
UserReference(int id)74     UserReference(int id) {
75         mId = id;
76         mUserManager = TestApis.context().androidContextAsUser(this)
77                 .getSystemService(UserManager.class);
78     }
79 
id()80     public final int id() {
81         return mId;
82     }
83 
84     /**
85      * Get a {@link UserHandle} for the {@link #id()}.
86      */
userHandle()87     public final UserHandle userHandle() {
88         return UserHandle.of(mId);
89     }
90 
91     /**
92      * Remove the user from the device.
93      *
94      * <p>If the user does not exist, or the removal fails for any other reason, a
95      * {@link NeneException} will be thrown.
96      */
remove()97     public final void remove() {
98         try {
99             // Expected success string is "Success: removed user"
100             ShellCommand.builder("pm remove-user")
101                     .addOperand(mId)
102                     .validate(ShellCommandUtils::startsWithSuccess)
103                     .execute();
104 
105             Poll.forValue("User exists", this::exists)
106                     .toBeEqualTo(false)
107                     // TODO(b/203630556): Reduce timeout once we have a faster way of removing users
108                     .timeout(Duration.ofMinutes(1))
109                     .errorOnFail()
110                     .await();
111         } catch (AdbException e) {
112             throw new NeneException("Could not remove user " + this, e);
113         }
114     }
115 
116     /**
117      * Start the user.
118      *
119      * <p>After calling this command, the user will be running unlocked.
120      *
121      * <p>If the user does not exist, or the start fails for any other reason, a
122      * {@link NeneException} will be thrown.
123      */
124     //TODO(scottjonathan): Deal with users who won't unlock
start()125     public UserReference start() {
126         try {
127             // Expected success string is "Success: user started"
128             ShellCommand.builder("am start-user")
129                     .addOperand(mId)
130                     .addOperand("-w")
131                     .validate(ShellCommandUtils::startsWithSuccess)
132                     .execute();
133 
134             Poll.forValue("User running unlocked", () -> isRunning() && isUnlocked())
135                     .toBeEqualTo(true)
136                     .errorOnFail()
137                     .timeout(Duration.ofMinutes(1))
138                     .await();
139         } catch (AdbException e) {
140             throw new NeneException("Could not start user " + this, e);
141         }
142 
143         return this;
144     }
145 
146     /**
147      * Stop the user.
148      *
149      * <p>After calling this command, the user will be not running.
150      */
stop()151     public UserReference stop() {
152         try {
153             // Expects no output on success or failure - stderr output on failure
154             ShellCommand.builder("am stop-user")
155                     .addOperand("-f") // Force stop
156                     .addOperand(mId)
157                     .allowEmptyOutput(true)
158                     .validate(String::isEmpty)
159                     .execute();
160 
161             Poll.forValue("User running", this::isRunning)
162                     .toBeEqualTo(false)
163                     // TODO(b/203630556): Replace stopping with something faster
164                     .timeout(Duration.ofMinutes(10))
165                     .errorOnFail()
166                     .await();
167         } catch (AdbException e) {
168             throw new NeneException("Could not stop user " + this, e);
169         }
170 
171         return this;
172     }
173 
174     /**
175      * Make the user the foreground user.
176      *
177      * <p>If the user is a profile, then this will make the parent the foreground user. It will
178      * still return the {@link UserReference} of the profile in that case.
179      */
switchTo()180     public UserReference switchTo() {
181         UserReference parent = parent();
182         if (parent != null) {
183             parent.switchTo();
184             return this;
185         }
186 
187         if (TestApis.users().current().equals(this)) {
188             // Already switched to
189             return this;
190         }
191 
192         // This is created outside of the try because we don't want to wait for the broadcast
193         // on versions less than R
194         BlockingBroadcastReceiver broadcastReceiver =
195                 new BlockingBroadcastReceiver(TestApis.context().instrumentedContext(),
196                         Intent.ACTION_USER_FOREGROUND,
197                         (intent) ->((UserHandle)
198                                 intent.getParcelableExtra(Intent.EXTRA_USER))
199                                 .getIdentifier() == mId);
200 
201         try {
202             if (Versions.meetsMinimumSdkVersionRequirement(R)) {
203                 try (PermissionContext p =
204                              TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
205                     broadcastReceiver.registerForAllUsers();
206                 }
207             }
208 
209             // Expects no output on success or failure
210             ShellCommand.builder("am switch-user")
211                     .addOperand(mId)
212                     .allowEmptyOutput(true)
213                     .validate(String::isEmpty)
214                     .execute();
215 
216             if (Versions.meetsMinimumSdkVersionRequirement(R)) {
217                 broadcastReceiver.awaitForBroadcast();
218             } else {
219                 Thread.sleep(20000);
220             }
221         } catch (AdbException e) {
222             throw new NeneException("Could not switch to user", e);
223         } catch (InterruptedException e) {
224             Log.e(LOG_TAG, "Interrupted while switching user", e);
225         } finally {
226             broadcastReceiver.unregisterQuietly();
227         }
228 
229         return this;
230     }
231 
232     /** Get the serial number of the user. */
serialNo()233     public long serialNo() {
234         if (mSerialNo == null) {
235             mSerialNo = TestApis.context().instrumentedContext().getSystemService(UserManager.class)
236                     .getSerialNumberForUser(userHandle());
237 
238             if (mSerialNo == -1) {
239                 mSerialNo = null;
240                 throw new NeneException("User does not exist " + this);
241             }
242         }
243 
244         return mSerialNo;
245     }
246 
247     /** Get the name of the user. */
name()248     public String name() {
249         if (mName == null) {
250             if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
251                 mName = adbUser().name();
252             } else {
253                 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) {
254                     mName = TestApis.context().androidContextAsUser(this)
255                             .getSystemService(UserManager.class)
256                             .getUserName();
257                 }
258                 if (mName.equals("")) {
259                     if (!exists()) {
260                         mName = null;
261                         throw new NeneException("User does not exist " + this);
262                     }
263                 }
264             }
265         }
266 
267         return mName;
268     }
269 
270     /** Is the user running? */
isRunning()271     public boolean isRunning() {
272         if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
273             AdbUser adbUser = adbUserOrNull();
274             if (adbUser == null) {
275                 return false;
276             }
277             return RUNNING_STATES.contains(adbUser().state());
278         }
279         try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) {
280             return mUserManager.isUserRunning(userHandle());
281         }
282     }
283 
284     /** Is the user unlocked? */
isUnlocked()285     public boolean isUnlocked() {
286         if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
287             AdbUser adbUser = adbUserOrNull();
288             if (adbUser == null) {
289                 return false;
290             }
291             return adbUser.state().equals(AdbUser.UserState.RUNNING_UNLOCKED);
292         }
293         try (PermissionContext p = TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) {
294             return mUserManager.isUserUnlocked(userHandle());
295         }
296     }
297 
298     /**
299      * Get the user type.
300      */
type()301     public UserType type() {
302         if (mUserType == null) {
303             if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
304                 mUserType = adbUser().type();
305             } else {
306                 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) {
307                     String userTypeName = mUserManager.getUserType();
308                     if (userTypeName.equals("")) {
309                         throw new NeneException("User does not exist " + this);
310                     }
311                     mUserType = TestApis.users().supportedType(userTypeName);
312                 }
313             }
314         }
315         return mUserType;
316     }
317 
318     /**
319      * Return {@code true} if this is the primary user.
320      */
isPrimary()321     public Boolean isPrimary() {
322         if (mIsPrimary == null) {
323             if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
324                 mIsPrimary = adbUser().isPrimary();
325             } else {
326                 mIsPrimary = userInfo().isPrimary();
327             }
328         }
329 
330         return mIsPrimary;
331     }
332 
333     /**
334      * Return the parent of this profile.
335      *
336      * <p>Returns {@code null} if this user is not a profile.
337      */
338     @Nullable
parent()339     public UserReference parent() {
340         if (!mParentCached) {
341             if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
342                 mParent = adbUser().parent();
343             } else {
344                 try (PermissionContext p =
345                              TestApis.permissions().withPermission(INTERACT_ACROSS_USERS)) {
346                     UserHandle parentHandle = mUserManager.getProfileParent(userHandle());
347                     if (parentHandle == null) {
348                         if (!exists()) {
349                             throw new NeneException("User does not exist " + this);
350                         }
351 
352                         mParent = null;
353                     } else {
354                         mParent = TestApis.users().find(parentHandle);
355                     }
356                 }
357             }
358             mParentCached = true;
359         }
360 
361         return mParent;
362     }
363 
364     /**
365      * Return {@code true} if a user with this ID exists.
366      */
exists()367     public boolean exists() {
368         if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
369             return TestApis.users().all().stream().anyMatch(u -> u.equals(this));
370         }
371         return users().anyMatch(ui -> ui.id == id());
372     }
373 
374     @Override
equals(Object obj)375     public boolean equals(Object obj) {
376         if (!(obj instanceof UserReference)) {
377             return false;
378         }
379 
380         UserReference other = (UserReference) obj;
381 
382         return other.id() == id();
383     }
384 
385     @Override
hashCode()386     public int hashCode() {
387         return id();
388     }
389 
390     /** See {@link #remove}. */
391     @Override
close()392     public void close() {
393         remove();
394     }
395 
adbUserOrNull()396     private AdbUser adbUserOrNull() {
397         return TestApis.users().fetchUser(mId);
398     }
399 
adbUser()400     private AdbUser adbUser() {
401         AdbUser user = adbUserOrNull();
402         if (user == null) {
403             throw new NeneException("User does not exist " + this);
404         }
405         return user;
406     }
407 
userInfo()408     private UserInfo userInfo() {
409         return users().filter(ui -> ui.id == id()).findFirst()
410                 .orElseThrow(() -> new NeneException("User does not exist " + this));
411     }
412 
413     @Override
toString()414     public String toString() {
415         return "User{id=" + id() + "}";
416     }
417 }
418