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 
25 import androidx.annotation.CheckResult;
26 import androidx.annotation.Nullable;
27 
28 import com.android.bedstead.nene.TestApis;
29 import com.android.bedstead.nene.exceptions.AdbException;
30 import com.android.bedstead.nene.exceptions.NeneException;
31 import com.android.bedstead.nene.utils.ShellCommand;
32 import com.android.bedstead.nene.utils.ShellCommandUtils;
33 
34 import java.util.UUID;
35 
36 /**
37  * Builder for creating a new Android User.
38  */
39 public class UserBuilder {
40 
41     private final TestApis mTestApis;
42     private String mName;
43     private @Nullable UserType mType;
44     private @Nullable UserReference mParent;
45 
UserBuilder(TestApis testApis)46     UserBuilder(TestApis testApis) {
47         mTestApis = testApis;
48     }
49 
50     /**
51      * Set the user's name.
52      */
53     @CheckResult
name(String name)54     public UserBuilder name(String name) {
55         if (name == null) {
56             throw new NullPointerException();
57         }
58         mName = name;
59         return this;
60     }
61 
62     /**
63      * Set the {@link UserType}.
64      *
65      * <p>Defaults to android.os.usertype.full.SECONDARY
66      */
67     @CheckResult
type(UserType type)68     public UserBuilder type(UserType type) {
69         if (type == null) {
70             // We don't want to allow null to be passed in explicitly as that would cause subtle
71             // bugs when chaining with .supportedType() which can return null
72             throw new NullPointerException("Can not set type to null");
73         }
74         mType = type;
75         return this;
76     }
77 
78     /**
79      * Set the parent of the new user.
80      *
81      * <p>This should only be set if the {@link #type(UserType)} is a profile.
82      */
83     @CheckResult
parent(UserReference parent)84     public UserBuilder parent(UserReference parent) {
85         mParent = parent;
86         return this;
87     }
88 
89     /** Create the user. */
create()90     public UserReference create() {
91         if (mName == null) {
92             mName = UUID.randomUUID().toString();
93         }
94 
95         ShellCommand.Builder commandBuilder = ShellCommand.builder("pm create-user");
96 
97         if (mType != null) {
98             if (mType.baseType().contains(UserType.BaseType.SYSTEM)) {
99                 throw new NeneException(
100                         "Can not create additional system users " + this);
101             }
102 
103             if (mType.baseType().contains(UserType.BaseType.PROFILE)) {
104                 if (mParent == null) {
105                     throw new NeneException("When creating a profile, the parent user must be"
106                             + " specified");
107                 }
108 
109                 commandBuilder.addOption("--profileOf", mParent.id());
110             } else if (mParent != null) {
111                 throw new NeneException("A parent should only be specified when create profiles");
112             }
113 
114             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
115                 if (mType.name().equals(MANAGED_PROFILE_TYPE_NAME)) {
116                     if (mParent.id() != SYSTEM_USER_ID) {
117                         // On R, this error will be thrown when we execute the command
118                         throw new NeneException(
119                                 "Can not create managed profiles of users other than the "
120                                         + "system user"
121                         );
122                     }
123 
124                     commandBuilder.addOperand("--managed");
125                 } else if (!mType.name().equals(SECONDARY_USER_TYPE_NAME)) {
126                     // This shouldn't be reachable as before R we can't fetch a list of user types
127                     //  so the only supported ones are system/managed profile/secondary
128                     throw new NeneException(
129                             "Can not create users of type " + mType + " on this device");
130                 }
131             } else {
132                 commandBuilder.addOption("--user-type", mType.name());
133             }
134         }
135 
136         commandBuilder.addOperand(mName);
137 
138         // Expected success string is e.g. "Success: created user id 14"
139         try {
140             int userId =
141                     commandBuilder.validate(ShellCommandUtils::startsWithSuccess)
142                             .executeAndParseOutput(
143                                     (output) -> Integer.parseInt(output.split("id ")[1].trim()));
144             return new UnresolvedUser(mTestApis, userId);
145         } catch (AdbException e) {
146             throw new NeneException("Could not create user " + this, e);
147         }
148     }
149 
150     /**
151      * Create the user and start it.
152      *
153      * <p>Equivalent of calling {@link #create()} and then {@link User#start()}.
154      */
createAndStart()155     public UserReference createAndStart() {
156         return create().start();
157     }
158 
159     @Override
toString()160     public String toString() {
161         return new StringBuilder("UserBuilder{")
162             .append("name=").append(mName)
163             .append(", type=").append(mType)
164             .append("}")
165             .toString();
166     }
167 }
168