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