1 /* 2 * Copyright (C) 2011 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.server.pm; 18 19 import com.android.internal.util.FastXmlSerializer; 20 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.PackageManager; 23 import android.content.pm.UserInfo; 24 import android.os.Environment; 25 import android.os.FileUtils; 26 import android.os.SystemClock; 27 import android.util.Log; 28 import android.util.Slog; 29 import android.util.SparseArray; 30 import android.util.Xml; 31 32 import java.io.BufferedOutputStream; 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.io.FileOutputStream; 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.List; 39 40 import org.xmlpull.v1.XmlPullParser; 41 import org.xmlpull.v1.XmlPullParserException; 42 import org.xmlpull.v1.XmlSerializer; 43 44 public class UserManager { 45 private static final String TAG_NAME = "name"; 46 47 private static final String ATTR_FLAGS = "flags"; 48 49 private static final String ATTR_ID = "id"; 50 51 private static final String TAG_USERS = "users"; 52 53 private static final String TAG_USER = "user"; 54 55 private static final String LOG_TAG = "UserManager"; 56 57 private static final String USER_INFO_DIR = "system" + File.separator + "users"; 58 private static final String USER_LIST_FILENAME = "userlist.xml"; 59 60 private SparseArray<UserInfo> mUsers; 61 62 private final File mUsersDir; 63 private final File mUserListFile; 64 private int[] mUserIds; 65 66 private Installer mInstaller; 67 private File mBaseUserPath; 68 69 /** 70 * Available for testing purposes. 71 */ 72 UserManager(File dataDir, File baseUserPath) { 73 mUsersDir = new File(dataDir, USER_INFO_DIR); 74 mUsersDir.mkdirs(); 75 mBaseUserPath = baseUserPath; 76 FileUtils.setPermissions(mUsersDir.toString(), 77 FileUtils.S_IRWXU|FileUtils.S_IRWXG 78 |FileUtils.S_IROTH|FileUtils.S_IXOTH, 79 -1, -1); 80 mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); 81 readUserList(); 82 } 83 84 public UserManager(Installer installer, File baseUserPath) { 85 this(Environment.getDataDirectory(), baseUserPath); 86 mInstaller = installer; 87 } 88 89 public List<UserInfo> getUsers() { 90 ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); 91 for (int i = 0; i < mUsers.size(); i++) { 92 users.add(mUsers.valueAt(i)); 93 } 94 return users; 95 } 96 97 /** 98 * Returns an array of user ids. This array is cached here for quick access, so do not modify or 99 * cache it elsewhere. 100 * @return the array of user ids. 101 */ 102 int[] getUserIds() { 103 return mUserIds; 104 } 105 106 private void readUserList() { 107 mUsers = new SparseArray<UserInfo>(); 108 if (!mUserListFile.exists()) { 109 fallbackToSingleUser(); 110 return; 111 } 112 FileInputStream fis = null; 113 try { 114 fis = new FileInputStream(mUserListFile); 115 XmlPullParser parser = Xml.newPullParser(); 116 parser.setInput(fis, null); 117 int type; 118 while ((type = parser.next()) != XmlPullParser.START_TAG 119 && type != XmlPullParser.END_DOCUMENT) { 120 ; 121 } 122 123 if (type != XmlPullParser.START_TAG) { 124 Slog.e(LOG_TAG, "Unable to read user list"); 125 fallbackToSingleUser(); 126 return; 127 } 128 129 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 130 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { 131 String id = parser.getAttributeValue(null, ATTR_ID); 132 UserInfo user = readUser(Integer.parseInt(id)); 133 if (user != null) { 134 mUsers.put(user.id, user); 135 } 136 } 137 } 138 updateUserIds(); 139 } catch (IOException ioe) { 140 fallbackToSingleUser(); 141 } catch (XmlPullParserException pe) { 142 fallbackToSingleUser(); 143 } 144 } 145 146 private void fallbackToSingleUser() { 147 // Create the primary user 148 UserInfo primary = new UserInfo(0, "Primary", 149 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); 150 mUsers.put(0, primary); 151 updateUserIds(); 152 153 writeUserList(); 154 writeUser(primary); 155 } 156 157 /* 158 * Writes the user file in this format: 159 * 160 * <user flags="20039023" id="0"> 161 * <name>Primary</name> 162 * </user> 163 */ 164 private void writeUser(UserInfo userInfo) { 165 try { 166 final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); 167 final FileOutputStream fos = new FileOutputStream(mUserFile); 168 final BufferedOutputStream bos = new BufferedOutputStream(fos); 169 170 // XmlSerializer serializer = XmlUtils.serializerInstance(); 171 final XmlSerializer serializer = new FastXmlSerializer(); 172 serializer.setOutput(bos, "utf-8"); 173 serializer.startDocument(null, true); 174 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 175 176 serializer.startTag(null, TAG_USER); 177 serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); 178 serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); 179 180 serializer.startTag(null, TAG_NAME); 181 serializer.text(userInfo.name); 182 serializer.endTag(null, TAG_NAME); 183 184 serializer.endTag(null, TAG_USER); 185 186 serializer.endDocument(); 187 } catch (IOException ioe) { 188 Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); 189 } 190 } 191 192 /* 193 * Writes the user list file in this format: 194 * 195 * <users> 196 * <user id="0"></user> 197 * <user id="2"></user> 198 * </users> 199 */ 200 private void writeUserList() { 201 try { 202 final FileOutputStream fos = new FileOutputStream(mUserListFile); 203 final BufferedOutputStream bos = new BufferedOutputStream(fos); 204 205 // XmlSerializer serializer = XmlUtils.serializerInstance(); 206 final XmlSerializer serializer = new FastXmlSerializer(); 207 serializer.setOutput(bos, "utf-8"); 208 serializer.startDocument(null, true); 209 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 210 211 serializer.startTag(null, TAG_USERS); 212 213 for (int i = 0; i < mUsers.size(); i++) { 214 UserInfo user = mUsers.valueAt(i); 215 serializer.startTag(null, TAG_USER); 216 serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); 217 serializer.endTag(null, TAG_USER); 218 } 219 220 serializer.endTag(null, TAG_USERS); 221 222 serializer.endDocument(); 223 } catch (IOException ioe) { 224 Slog.e(LOG_TAG, "Error writing user list"); 225 } 226 } 227 228 private UserInfo readUser(int id) { 229 int flags = 0; 230 String name = null; 231 232 FileInputStream fis = null; 233 try { 234 File userFile = new File(mUsersDir, Integer.toString(id) + ".xml"); 235 fis = new FileInputStream(userFile); 236 XmlPullParser parser = Xml.newPullParser(); 237 parser.setInput(fis, null); 238 int type; 239 while ((type = parser.next()) != XmlPullParser.START_TAG 240 && type != XmlPullParser.END_DOCUMENT) { 241 ; 242 } 243 244 if (type != XmlPullParser.START_TAG) { 245 Slog.e(LOG_TAG, "Unable to read user " + id); 246 return null; 247 } 248 249 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { 250 String storedId = parser.getAttributeValue(null, ATTR_ID); 251 if (Integer.parseInt(storedId) != id) { 252 Slog.e(LOG_TAG, "User id does not match the file name"); 253 return null; 254 } 255 String flagString = parser.getAttributeValue(null, ATTR_FLAGS); 256 flags = Integer.parseInt(flagString); 257 258 while ((type = parser.next()) != XmlPullParser.START_TAG 259 && type != XmlPullParser.END_DOCUMENT) { 260 } 261 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { 262 type = parser.next(); 263 if (type == XmlPullParser.TEXT) { 264 name = parser.getText(); 265 } 266 } 267 } 268 fis.close(); 269 270 UserInfo userInfo = new UserInfo(id, name, flags); 271 return userInfo; 272 273 } catch (IOException ioe) { 274 } catch (XmlPullParserException pe) { 275 } 276 return null; 277 } 278 279 public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) { 280 int userId = getNextAvailableId(); 281 UserInfo userInfo = new UserInfo(userId, name, flags); 282 File userPath = new File(mBaseUserPath, Integer.toString(userId)); 283 if (!createPackageFolders(userId, userPath, apps)) { 284 return null; 285 } 286 mUsers.put(userId, userInfo); 287 writeUserList(); 288 writeUser(userInfo); 289 updateUserIds(); 290 return userInfo; 291 } 292 293 /** 294 * Removes a user and all data directories created for that user. This method should be called 295 * after the user's processes have been terminated. 296 * @param id the user's id 297 */ 298 public void removeUser(int id) { 299 // Remove from the list 300 UserInfo userInfo = mUsers.get(id); 301 if (userInfo != null) { 302 // Remove this user from the list 303 mUsers.remove(id); 304 // Remove user file 305 File userFile = new File(mUsersDir, id + ".xml"); 306 userFile.delete(); 307 // Update the user list 308 writeUserList(); 309 // Remove the data directories for all packages for this user 310 removePackageFolders(id); 311 updateUserIds(); 312 } 313 } 314 315 public void installPackageForAllUsers(String packageName, int uid) { 316 for (int userId : mUserIds) { 317 // Don't do it for the primary user, it will become recursive. 318 if (userId == 0) 319 continue; 320 mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), 321 userId); 322 } 323 } 324 325 public void clearUserDataForAllUsers(String packageName) { 326 for (int userId : mUserIds) { 327 // Don't do it for the primary user, it will become recursive. 328 if (userId == 0) 329 continue; 330 mInstaller.clearUserData(packageName, userId); 331 } 332 } 333 334 public void removePackageForAllUsers(String packageName) { 335 for (int userId : mUserIds) { 336 // Don't do it for the primary user, it will become recursive. 337 if (userId == 0) 338 continue; 339 mInstaller.remove(packageName, userId); 340 } 341 } 342 343 /** 344 * Caches the list of user ids in an array, adjusting the array size when necessary. 345 */ 346 private void updateUserIds() { 347 if (mUserIds == null || mUserIds.length != mUsers.size()) { 348 mUserIds = new int[mUsers.size()]; 349 } 350 for (int i = 0; i < mUsers.size(); i++) { 351 mUserIds[i] = mUsers.keyAt(i); 352 } 353 } 354 355 /** 356 * Returns the next available user id, filling in any holes in the ids. 357 * @return 358 */ 359 private int getNextAvailableId() { 360 int i = 0; 361 while (i < Integer.MAX_VALUE) { 362 if (mUsers.indexOfKey(i) < 0) { 363 break; 364 } 365 i++; 366 } 367 return i; 368 } 369 370 private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) { 371 // mInstaller may not be available for unit-tests. 372 if (mInstaller == null || apps == null) return true; 373 374 final long startTime = SystemClock.elapsedRealtime(); 375 // Create the user path 376 userPath.mkdir(); 377 FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG 378 | FileUtils.S_IXOTH, -1, -1); 379 380 // Create the individual data directories 381 for (ApplicationInfo app : apps) { 382 if (app.uid > android.os.Process.FIRST_APPLICATION_UID 383 && app.uid < PackageManager.PER_USER_RANGE) { 384 mInstaller.createUserData(app.packageName, 385 PackageManager.getUid(id, app.uid), id); 386 } 387 } 388 final long stopTime = SystemClock.elapsedRealtime(); 389 Log.i(LOG_TAG, 390 "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); 391 return true; 392 } 393 394 private boolean removePackageFolders(int id) { 395 // mInstaller may not be available for unit-tests. 396 if (mInstaller == null) return true; 397 398 mInstaller.removeUserDataDirs(id); 399 return true; 400 } 401 } 402