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