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 com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
20 import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
21 import static com.android.bedstead.nene.users.Users.SYSTEM_USER_ID;
22 
23 import android.os.Build;
24 import android.util.Log;
25 
26 import androidx.annotation.CheckResult;
27 import androidx.annotation.Nullable;
28 
29 import com.android.bedstead.nene.TestApis;
30 import com.android.bedstead.nene.exceptions.AdbException;
31 import com.android.bedstead.nene.exceptions.NeneException;
32 import com.android.bedstead.nene.utils.ShellCommand;
33 import com.android.bedstead.nene.utils.ShellCommandUtils;
34 import com.android.bedstead.nene.utils.Versions;
35 
36 import java.util.UUID;
37 
38 /**
39  * Builder for creating a new Android User.
40  */
41 public final class UserBuilder {
42 
43     private String mName;
44     private @Nullable UserType mType;
45     private @Nullable UserReference mParent;
46     private boolean mForTesting = true;
47     private boolean mEphemeral = false;
48 
49     private static final String LOG_TAG = "UserBuilder";
50 
UserBuilder()51     UserBuilder() {
52     }
53 
54     /**
55      * Set the user's name.
56      */
57     @CheckResult
name(String name)58     public UserBuilder name(String name) {
59         if (name == null) {
60             throw new NullPointerException();
61         }
62         mName = name;
63         return this;
64     }
65 
66     /**
67      * Set the {@link UserType}.
68      *
69      * <p>Defaults to android.os.usertype.full.SECONDARY
70      */
71     @CheckResult
type(UserType type)72     public UserBuilder type(UserType type) {
73         if (type == null) {
74             // We don't want to allow null to be passed in explicitly as that would cause subtle
75             // bugs when chaining with .supportedType() which can return null
76             throw new NullPointerException("Can not set type to null");
77         }
78         mType = type;
79         return this;
80     }
81 
82     /**
83      * Set the {@link UserType}.
84      *
85      * <p>Defaults to android.os.usertype.full.SECONDARY
86      */
87     @CheckResult
type(String typeName)88     public UserBuilder type(String typeName) {
89         if (typeName == null) {
90             // We don't want to allow null to be passed in explicitly as that would cause subtle
91             // bugs when chaining with .supportedType() which can return null
92             throw new NullPointerException("Can not set type to null");
93         }
94         return type(TestApis.users().supportedType(typeName));
95     }
96 
97     /**
98      * Set if this user should be marked as for-testing.
99      *
100      * <p>This means it should not contain human user data - and will ensure it does not block
101      * usage of some test functionality
102      *
103      * <p>This defaults to true
104      */
105     @CheckResult
forTesting(boolean forTesting)106     public UserBuilder forTesting(boolean forTesting) {
107         mForTesting = forTesting;
108         return this;
109     }
110 
111     /**
112      * Set if this user is ephemeral.
113      *
114      * <p>This defaults to false
115      */
116     @CheckResult
ephemeral(boolean ephemeral)117     public UserBuilder ephemeral(boolean ephemeral) {
118         mEphemeral = ephemeral;
119         return this;
120     }
121 
122     /**
123      * Set the parent of the new user.
124      *
125      * <p>This should only be set if the {@link #type(UserType)} is a profile.
126      */
127     @CheckResult
parent(UserReference parent)128     public UserBuilder parent(UserReference parent) {
129         mParent = parent;
130         return this;
131     }
132 
133     /** Create the user. */
create()134     public UserReference create() {
135         if (mName == null) {
136             mName = UUID.randomUUID().toString();
137         }
138 
139         ShellCommand.Builder commandBuilder = ShellCommand.builder("pm create-user");
140 
141         if (mType != null) {
142             if (mType.baseType().contains(UserType.BaseType.SYSTEM)) {
143                 throw new NeneException(
144                         "Can not create additional system users " + this);
145             }
146 
147             if (mType.baseType().contains(UserType.BaseType.PROFILE)) {
148                 if (mParent == null) {
149                     throw new NeneException("When creating a profile, the parent user must be"
150                             + " specified");
151                 }
152 
153                 commandBuilder.addOption("--profileOf", mParent.id());
154             } else if (mParent != null) {
155                 throw new NeneException("A parent should only be specified when create profiles");
156             }
157 
158             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
159                 if (mType.name().equals(MANAGED_PROFILE_TYPE_NAME)) {
160                     if (mParent.id() != SYSTEM_USER_ID) {
161                         // On R, this error will be thrown when we execute the command
162                         throw new NeneException(
163                                 "Can not create managed profiles of users other than the "
164                                         + "system user"
165                         );
166                     }
167 
168                     commandBuilder.addOperand("--managed");
169                 } else if (!mType.name().equals(SECONDARY_USER_TYPE_NAME)) {
170                     // This shouldn't be reachable as before R we can't fetch a list of user types
171                     //  so the only supported ones are system/managed profile/secondary
172                     throw new NeneException(
173                             "Can not create users of type " + mType + " on this device");
174                 }
175             } else {
176                 commandBuilder.addOption("--user-type", mType.name());
177             }
178         }
179 
180         if (Versions.meetsMinimumSdkVersionRequirement(Versions.U) && mForTesting) {
181             // Marking all created users as test users means we don't block changing device
182             // management states
183             commandBuilder.addOperand("--for-testing");
184         }
185 
186         if (mEphemeral) {
187             commandBuilder.addOperand("--ephemeral");
188         }
189 
190         commandBuilder.addOperand(mName);
191 
192         // Expected success string is e.g. "Success: created user id 14"
193         try {
194 
195             Log.d(LOG_TAG, "Creating user with command " + commandBuilder);
196             int userId =
197                     commandBuilder.validate(ShellCommandUtils::startsWithSuccess)
198                             .executeAndParseOutput(
199                                     (output) -> Integer.parseInt(output.split("id ")[1].trim()));
200             return TestApis.users().find(userId);
201         } catch (AdbException e) {
202             throw new NeneException("Could not create user " + this, e);
203         }
204     }
205 
206     /**
207      * Create the user and start it.
208      *
209      * <p>Equivalent of calling {@link #create()} and then {@link User#start()}.
210      */
createAndStart()211     public UserReference createAndStart() {
212         return create().start();
213     }
214 
215     @Override
toString()216     public String toString() {
217         return new StringBuilder("UserBuilder{")
218             .append("name=").append(mName)
219             .append(", type=").append(mType)
220             .append("}")
221             .toString();
222     }
223 }
224