UserManagerService.java revision 258848d2ae04f447ff1c18023fa76b139fcc0862
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 17package com.android.server.pm; 18 19import static android.os.ParcelFileDescriptor.MODE_CREATE; 20import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 21 22import com.android.internal.util.ArrayUtils; 23import com.android.internal.util.FastXmlSerializer; 24 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.PackageManager; 29import android.content.pm.UserInfo; 30import android.os.Binder; 31import android.os.Environment; 32import android.os.FileUtils; 33import android.os.IUserManager; 34import android.os.ParcelFileDescriptor; 35import android.os.Process; 36import android.os.SystemClock; 37import android.os.UserId; 38import android.util.Log; 39import android.util.Slog; 40import android.util.SparseArray; 41import android.util.Xml; 42 43import java.io.BufferedOutputStream; 44import java.io.File; 45import java.io.FileInputStream; 46import java.io.FileNotFoundException; 47import java.io.FileOutputStream; 48import java.io.IOException; 49import java.util.ArrayList; 50import java.util.List; 51 52import org.xmlpull.v1.XmlPullParser; 53import org.xmlpull.v1.XmlPullParserException; 54import org.xmlpull.v1.XmlSerializer; 55 56public class UserManagerService extends IUserManager.Stub { 57 58 private static final String TAG = "UserManagerService"; 59 60 private static final String TAG_NAME = "name"; 61 62 private static final String ATTR_FLAGS = "flags"; 63 64 private static final String ATTR_ICON_PATH = "icon"; 65 66 private static final String ATTR_ID = "id"; 67 68 private static final String TAG_USERS = "users"; 69 70 private static final String TAG_USER = "user"; 71 72 private static final String LOG_TAG = "UserManager"; 73 74 private static final String USER_INFO_DIR = "system" + File.separator + "users"; 75 private static final String USER_LIST_FILENAME = "userlist.xml"; 76 private static final String USER_PHOTO_FILENAME = "photo.png"; 77 78 private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>(); 79 80 private final File mUsersDir; 81 private final File mUserListFile; 82 private int[] mUserIds; 83 private boolean mGuestEnabled; 84 85 private Installer mInstaller; 86 private File mBaseUserPath; 87 private Context mContext; 88 private static UserManagerService sInstance; 89 private PackageManagerService mPm; 90 91 public synchronized static UserManagerService getInstance(Context context) { 92 if (sInstance == null) { 93 sInstance = new UserManagerService(context); 94 } 95 return sInstance; 96 } 97 98 /** 99 * Available for testing purposes. 100 */ 101 UserManagerService(File dataDir, File baseUserPath) { 102 mUsersDir = new File(dataDir, USER_INFO_DIR); 103 mUsersDir.mkdirs(); 104 // Make zeroth user directory, for services to migrate their files to that location 105 File userZeroDir = new File(mUsersDir, "0"); 106 userZeroDir.mkdirs(); 107 mBaseUserPath = baseUserPath; 108 FileUtils.setPermissions(mUsersDir.toString(), 109 FileUtils.S_IRWXU|FileUtils.S_IRWXG 110 |FileUtils.S_IROTH|FileUtils.S_IXOTH, 111 -1, -1); 112 mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); 113 readUserList(); 114 } 115 116 public UserManagerService(Context context) { 117 this(Environment.getDataDirectory(), new File(Environment.getDataDirectory(), "user")); 118 mContext = context; 119 } 120 121 void setInstaller(PackageManagerService pm, Installer installer) { 122 mInstaller = installer; 123 mPm = pm; 124 } 125 126 @Override 127 public List<UserInfo> getUsers() { 128 enforceSystemOrRoot("Only the system can query users"); 129 synchronized (mUsers) { 130 ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); 131 for (int i = 0; i < mUsers.size(); i++) { 132 users.add(mUsers.valueAt(i)); 133 } 134 return users; 135 } 136 } 137 138 @Override 139 public UserInfo getUserInfo(int userId) { 140 enforceSystemOrRoot("Only the system can query user"); 141 synchronized (mUsers) { 142 UserInfo info = mUsers.get(userId); 143 return info; 144 } 145 } 146 147 public boolean exists(int userId) { 148 synchronized (mUsers) { 149 return ArrayUtils.contains(mUserIds, userId); 150 } 151 } 152 153 @Override 154 public void setUserName(int userId, String name) { 155 enforceSystemOrRoot("Only the system can rename users"); 156 synchronized (mUsers) { 157 UserInfo info = mUsers.get(userId); 158 if (name != null && !name.equals(info.name)) { 159 info.name = name; 160 writeUserLocked(info); 161 } 162 } 163 } 164 165 @Override 166 public ParcelFileDescriptor setUserIcon(int userId) { 167 enforceSystemOrRoot("Only the system can update users"); 168 synchronized (mUsers) { 169 UserInfo info = mUsers.get(userId); 170 if (info == null) return null; 171 ParcelFileDescriptor fd = updateIconBitmapLocked(info); 172 if (fd != null) { 173 writeUserLocked(info); 174 } 175 return fd; 176 } 177 } 178 179 @Override 180 public void setGuestEnabled(boolean enable) { 181 enforceSystemOrRoot("Only the system can enable guest users"); 182 synchronized (mUsers) { 183 if (mGuestEnabled != enable) { 184 mGuestEnabled = enable; 185 // Erase any guest user that currently exists 186 for (int i = 0; i < mUsers.size(); i++) { 187 UserInfo user = mUsers.valueAt(i); 188 if (user.isGuest()) { 189 if (!enable) { 190 removeUser(user.id); 191 } 192 return; 193 } 194 } 195 // No guest was found 196 if (enable) { 197 createUser("Guest", UserInfo.FLAG_GUEST); 198 } 199 } 200 } 201 } 202 203 @Override 204 public boolean isGuestEnabled() { 205 synchronized (mUsers) { 206 return mGuestEnabled; 207 } 208 } 209 210 @Override 211 public void wipeUser(int userHandle) { 212 enforceSystemOrRoot("Only the system can wipe users"); 213 // TODO: 214 } 215 216 /** 217 * Enforces that only the system UID or root's UID can call a method exposed 218 * via Binder. 219 * 220 * @param message used as message if SecurityException is thrown 221 * @throws SecurityException if the caller is not system or root 222 */ 223 private static final void enforceSystemOrRoot(String message) { 224 final int uid = Binder.getCallingUid(); 225 if (uid != Process.SYSTEM_UID && uid != 0) { 226 throw new SecurityException(message); 227 } 228 } 229 230 private ParcelFileDescriptor updateIconBitmapLocked(UserInfo info) { 231 try { 232 File dir = new File(mUsersDir, Integer.toString(info.id)); 233 File file = new File(dir, USER_PHOTO_FILENAME); 234 if (!dir.exists()) { 235 dir.mkdir(); 236 FileUtils.setPermissions( 237 dir.getPath(), 238 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, 239 -1, -1); 240 } 241 ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, 242 MODE_CREATE|MODE_READ_WRITE); 243 info.iconPath = file.getAbsolutePath(); 244 return fd; 245 } catch (FileNotFoundException e) { 246 Slog.w(TAG, "Error setting photo for user ", e); 247 } 248 return null; 249 } 250 251 /** 252 * Returns an array of user ids. This array is cached here for quick access, so do not modify or 253 * cache it elsewhere. 254 * @return the array of user ids. 255 */ 256 int[] getUserIds() { 257 return mUserIds; 258 } 259 260 private void readUserList() { 261 synchronized (mUsers) { 262 readUserListLocked(); 263 } 264 } 265 266 private void readUserListLocked() { 267 mGuestEnabled = false; 268 if (!mUserListFile.exists()) { 269 fallbackToSingleUserLocked(); 270 return; 271 } 272 FileInputStream fis = null; 273 try { 274 fis = new FileInputStream(mUserListFile); 275 XmlPullParser parser = Xml.newPullParser(); 276 parser.setInput(fis, null); 277 int type; 278 while ((type = parser.next()) != XmlPullParser.START_TAG 279 && type != XmlPullParser.END_DOCUMENT) { 280 ; 281 } 282 283 if (type != XmlPullParser.START_TAG) { 284 Slog.e(LOG_TAG, "Unable to read user list"); 285 fallbackToSingleUserLocked(); 286 return; 287 } 288 289 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 290 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { 291 String id = parser.getAttributeValue(null, ATTR_ID); 292 UserInfo user = readUser(Integer.parseInt(id)); 293 if (user != null) { 294 mUsers.put(user.id, user); 295 } 296 if (user.isGuest()) { 297 mGuestEnabled = true; 298 } 299 } 300 } 301 updateUserIdsLocked(); 302 } catch (IOException ioe) { 303 fallbackToSingleUserLocked(); 304 } catch (XmlPullParserException pe) { 305 fallbackToSingleUserLocked(); 306 } finally { 307 if (fis != null) { 308 try { 309 fis.close(); 310 } catch (IOException e) { 311 } 312 } 313 } 314 } 315 316 private void fallbackToSingleUserLocked() { 317 // Create the primary user 318 UserInfo primary = new UserInfo(0, "Primary", null, 319 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); 320 mUsers.put(0, primary); 321 updateUserIdsLocked(); 322 323 writeUserListLocked(); 324 writeUserLocked(primary); 325 } 326 327 /* 328 * Writes the user file in this format: 329 * 330 * <user flags="20039023" id="0"> 331 * <name>Primary</name> 332 * </user> 333 */ 334 private void writeUserLocked(UserInfo userInfo) { 335 FileOutputStream fos = null; 336 try { 337 final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); 338 fos = new FileOutputStream(mUserFile); 339 final BufferedOutputStream bos = new BufferedOutputStream(fos); 340 341 // XmlSerializer serializer = XmlUtils.serializerInstance(); 342 final XmlSerializer serializer = new FastXmlSerializer(); 343 serializer.setOutput(bos, "utf-8"); 344 serializer.startDocument(null, true); 345 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 346 347 serializer.startTag(null, TAG_USER); 348 serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); 349 serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); 350 if (userInfo.iconPath != null) { 351 serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath); 352 } 353 354 serializer.startTag(null, TAG_NAME); 355 serializer.text(userInfo.name); 356 serializer.endTag(null, TAG_NAME); 357 358 serializer.endTag(null, TAG_USER); 359 360 serializer.endDocument(); 361 } catch (IOException ioe) { 362 Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); 363 } finally { 364 if (fos != null) { 365 try { 366 fos.close(); 367 } catch (IOException ioe) { 368 } 369 } 370 } 371 } 372 373 /* 374 * Writes the user list file in this format: 375 * 376 * <users> 377 * <user id="0"></user> 378 * <user id="2"></user> 379 * </users> 380 */ 381 private void writeUserListLocked() { 382 FileOutputStream fos = null; 383 try { 384 fos = new FileOutputStream(mUserListFile); 385 final BufferedOutputStream bos = new BufferedOutputStream(fos); 386 387 // XmlSerializer serializer = XmlUtils.serializerInstance(); 388 final XmlSerializer serializer = new FastXmlSerializer(); 389 serializer.setOutput(bos, "utf-8"); 390 serializer.startDocument(null, true); 391 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 392 393 serializer.startTag(null, TAG_USERS); 394 395 for (int i = 0; i < mUsers.size(); i++) { 396 UserInfo user = mUsers.valueAt(i); 397 serializer.startTag(null, TAG_USER); 398 serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); 399 serializer.endTag(null, TAG_USER); 400 } 401 402 serializer.endTag(null, TAG_USERS); 403 404 serializer.endDocument(); 405 } catch (IOException ioe) { 406 Slog.e(LOG_TAG, "Error writing user list"); 407 } finally { 408 if (fos != null) { 409 try { 410 fos.close(); 411 } catch (IOException ioe) { 412 } 413 } 414 } 415 } 416 417 private UserInfo readUser(int id) { 418 int flags = 0; 419 String name = null; 420 String iconPath = null; 421 422 FileInputStream fis = null; 423 try { 424 File userFile = new File(mUsersDir, Integer.toString(id) + ".xml"); 425 fis = new FileInputStream(userFile); 426 XmlPullParser parser = Xml.newPullParser(); 427 parser.setInput(fis, null); 428 int type; 429 while ((type = parser.next()) != XmlPullParser.START_TAG 430 && type != XmlPullParser.END_DOCUMENT) { 431 ; 432 } 433 434 if (type != XmlPullParser.START_TAG) { 435 Slog.e(LOG_TAG, "Unable to read user " + id); 436 return null; 437 } 438 439 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { 440 String storedId = parser.getAttributeValue(null, ATTR_ID); 441 if (Integer.parseInt(storedId) != id) { 442 Slog.e(LOG_TAG, "User id does not match the file name"); 443 return null; 444 } 445 String flagString = parser.getAttributeValue(null, ATTR_FLAGS); 446 flags = Integer.parseInt(flagString); 447 iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH); 448 449 while ((type = parser.next()) != XmlPullParser.START_TAG 450 && type != XmlPullParser.END_DOCUMENT) { 451 } 452 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { 453 type = parser.next(); 454 if (type == XmlPullParser.TEXT) { 455 name = parser.getText(); 456 } 457 } 458 } 459 460 UserInfo userInfo = new UserInfo(id, name, iconPath, flags); 461 return userInfo; 462 463 } catch (IOException ioe) { 464 } catch (XmlPullParserException pe) { 465 } finally { 466 if (fis != null) { 467 try { 468 fis.close(); 469 } catch (IOException e) { 470 } 471 } 472 } 473 return null; 474 } 475 476 @Override 477 public UserInfo createUser(String name, int flags) { 478 enforceSystemOrRoot("Only the system can create users"); 479 int userId = getNextAvailableId(); 480 UserInfo userInfo = new UserInfo(userId, name, null, flags); 481 File userPath = new File(mBaseUserPath, Integer.toString(userId)); 482 if (!createPackageFolders(userId, userPath)) { 483 return null; 484 } 485 synchronized (mUsers) { 486 mUsers.put(userId, userInfo); 487 writeUserListLocked(); 488 writeUserLocked(userInfo); 489 updateUserIdsLocked(); 490 } 491 if (userInfo != null) { 492 Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); 493 addedIntent.putExtra(Intent.EXTRA_USERID, userInfo.id); 494 mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS); 495 } 496 return userInfo; 497 } 498 499 /** 500 * Removes a user and all data directories created for that user. This method should be called 501 * after the user's processes have been terminated. 502 * @param id the user's id 503 */ 504 public boolean removeUser(int userHandle) { 505 enforceSystemOrRoot("Only the system can remove users"); 506 synchronized (mUsers) { 507 return removeUserLocked(userHandle); 508 } 509 } 510 511 private boolean removeUserLocked(int userHandle) { 512 final UserInfo user = mUsers.get(userHandle); 513 if (userHandle == 0 || user == null) { 514 return false; 515 } 516 517 mPm.cleanUpUser(userHandle); 518 519 // Remove this user from the list 520 mUsers.remove(userHandle); 521 // Remove user file 522 File userFile = new File(mUsersDir, userHandle + ".xml"); 523 userFile.delete(); 524 // Update the user list 525 writeUserListLocked(); 526 updateUserIdsLocked(); 527 528 // Let other services shutdown any activity 529 Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED); 530 addedIntent.putExtra(Intent.EXTRA_USERID, userHandle); 531 mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS); 532 533 removePackageFolders(userHandle); 534 return true; 535 } 536 537 public void installPackageForAllUsers(String packageName, int uid) { 538 for (int userId : mUserIds) { 539 // Don't do it for the primary user, it will become recursive. 540 if (userId == 0) 541 continue; 542 mInstaller.createUserData(packageName, UserId.getUid(userId, uid), 543 userId); 544 } 545 } 546 547 public void clearUserDataForAllUsers(String packageName) { 548 for (int userId : mUserIds) { 549 // Don't do it for the primary user, it will become recursive. 550 if (userId == 0) 551 continue; 552 mInstaller.clearUserData(packageName, userId); 553 } 554 } 555 556 public void removePackageForAllUsers(String packageName) { 557 for (int userId : mUserIds) { 558 // Don't do it for the primary user, it will become recursive. 559 if (userId == 0) 560 continue; 561 mInstaller.remove(packageName, userId); 562 } 563 } 564 565 /** 566 * Caches the list of user ids in an array, adjusting the array size when necessary. 567 */ 568 private void updateUserIdsLocked() { 569 if (mUserIds == null || mUserIds.length != mUsers.size()) { 570 mUserIds = new int[mUsers.size()]; 571 } 572 for (int i = 0; i < mUsers.size(); i++) { 573 mUserIds[i] = mUsers.keyAt(i); 574 } 575 } 576 577 /** 578 * Returns the next available user id, filling in any holes in the ids. 579 * TODO: May not be a good idea to recycle ids, in case it results in confusion 580 * for data and battery stats collection, or unexpected cross-talk. 581 * @return 582 */ 583 private int getNextAvailableId() { 584 int i = 0; 585 while (i < Integer.MAX_VALUE) { 586 if (mUsers.indexOfKey(i) < 0) { 587 break; 588 } 589 i++; 590 } 591 return i; 592 } 593 594 private boolean createPackageFolders(int id, File userPath) { 595 // mInstaller may not be available for unit-tests. 596 if (mInstaller == null) return true; 597 598 // Create the user path 599 userPath.mkdir(); 600 FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG 601 | FileUtils.S_IXOTH, -1, -1); 602 603 mInstaller.cloneUserData(0, id, false); 604 605 return true; 606 } 607 608 boolean removePackageFolders(int id) { 609 // mInstaller may not be available for unit-tests. 610 if (mInstaller == null) return true; 611 612 mInstaller.removeUserDataDirs(id); 613 return true; 614 } 615} 616