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