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