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