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