blob: 4e9e6660d10127bffb9b58424a6dfe82f924583a [file] [log] [blame]
Amith Yamasani4b2e9342011-03-31 12:38:53 -07001/*
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
Amith Yamasani13593602012-03-22 16:16:17 -070019import com.android.internal.util.ArrayUtils;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070020import com.android.internal.util.FastXmlSerializer;
21
Amith Yamasani0b285492011-04-14 17:35:23 -070022import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageManager;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070024import android.content.pm.UserInfo;
25import android.os.Environment;
26import android.os.FileUtils;
Amith Yamasani0b285492011-04-14 17:35:23 -070027import android.os.SystemClock;
Amith Yamasani742a6712011-05-04 14:49:28 -070028import android.os.UserId;
Amith Yamasani0b285492011-04-14 17:35:23 -070029import android.util.Log;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070030import android.util.Slog;
31import android.util.SparseArray;
32import android.util.Xml;
33
34import java.io.BufferedOutputStream;
35import java.io.File;
36import java.io.FileInputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.util.ArrayList;
40import java.util.List;
41
42import org.xmlpull.v1.XmlPullParser;
43import org.xmlpull.v1.XmlPullParserException;
44import org.xmlpull.v1.XmlSerializer;
45
Amith Yamasani0b285492011-04-14 17:35:23 -070046public class UserManager {
Amith Yamasani4b2e9342011-03-31 12:38:53 -070047 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
Amith Yamasani0b285492011-04-14 17:35:23 -070057 private static final String LOG_TAG = "UserManager";
Amith Yamasani4b2e9342011-03-31 12:38:53 -070058
Amith Yamasani0b285492011-04-14 17:35:23 -070059 private static final String USER_INFO_DIR = "system" + File.separator + "users";
Amith Yamasani4b2e9342011-03-31 12:38:53 -070060 private static final String USER_LIST_FILENAME = "userlist.xml";
61
Amith Yamasani13593602012-03-22 16:16:17 -070062 private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
Amith Yamasani4b2e9342011-03-31 12:38:53 -070063
64 private final File mUsersDir;
65 private final File mUserListFile;
Amith Yamasani0b285492011-04-14 17:35:23 -070066 private int[] mUserIds;
67
68 private Installer mInstaller;
69 private File mBaseUserPath;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070070
71 /**
72 * Available for testing purposes.
73 */
Amith Yamasani0b285492011-04-14 17:35:23 -070074 UserManager(File dataDir, File baseUserPath) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -070075 mUsersDir = new File(dataDir, USER_INFO_DIR);
76 mUsersDir.mkdirs();
Amith Yamasani483f3b02012-03-13 16:08:00 -070077 // Make zeroth user directory, for services to migrate their files to that location
78 File userZeroDir = new File(mUsersDir, "0");
79 userZeroDir.mkdirs();
Amith Yamasani0b285492011-04-14 17:35:23 -070080 mBaseUserPath = baseUserPath;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070081 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
Amith Yamasani0b285492011-04-14 17:35:23 -070089 public UserManager(Installer installer, File baseUserPath) {
90 this(Environment.getDataDirectory(), baseUserPath);
91 mInstaller = installer;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070092 }
93
94 public List<UserInfo> getUsers() {
Amith Yamasani13593602012-03-22 16:16:17 -070095 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;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700101 }
Amith Yamasani13593602012-03-22 16:16:17 -0700102 }
103
104 public UserInfo getUser(int userId) {
105 synchronized (mUsers) {
106 UserInfo info = mUsers.get(userId);
107 return info;
108 }
109 }
110
111 public boolean exists(int userId) {
112 synchronized (mUsers) {
113 return ArrayUtils.contains(mUserIds, userId);
114 }
115 }
116
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 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700125 }
126
Amith Yamasani0b285492011-04-14 17:35:23 -0700127 /**
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 */
132 int[] getUserIds() {
133 return mUserIds;
134 }
135
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700136 private void readUserList() {
Amith Yamasani13593602012-03-22 16:16:17 -0700137 synchronized (mUsers) {
138 readUserListLocked();
139 }
140 }
141
142 private void readUserListLocked() {
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700143 if (!mUserListFile.exists()) {
Amith Yamasani13593602012-03-22 16:16:17 -0700144 fallbackToSingleUserLocked();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700145 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) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700159 Slog.e(LOG_TAG, "Unable to read user list");
Amith Yamasani13593602012-03-22 16:16:17 -0700160 fallbackToSingleUserLocked();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700161 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 }
Amith Yamasani13593602012-03-22 16:16:17 -0700173 updateUserIdsLocked();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700174 } catch (IOException ioe) {
Amith Yamasani13593602012-03-22 16:16:17 -0700175 fallbackToSingleUserLocked();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700176 } catch (XmlPullParserException pe) {
Amith Yamasani13593602012-03-22 16:16:17 -0700177 fallbackToSingleUserLocked();
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800178 } finally {
179 if (fis != null) {
180 try {
181 fis.close();
182 } catch (IOException e) {
183 }
184 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700185 }
186 }
187
Amith Yamasani13593602012-03-22 16:16:17 -0700188 private void fallbackToSingleUserLocked() {
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700189 // Create the primary user
190 UserInfo primary = new UserInfo(0, "Primary",
191 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
192 mUsers.put(0, primary);
Amith Yamasani13593602012-03-22 16:16:17 -0700193 updateUserIdsLocked();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700194
Amith Yamasani13593602012-03-22 16:16:17 -0700195 writeUserListLocked();
196 writeUserLocked(primary);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700197 }
198
199 /*
200 * Writes the user file in this format:
201 *
202 * <user flags="20039023" id="0">
203 * <name>Primary</name>
204 * </user>
205 */
Amith Yamasani13593602012-03-22 16:16:17 -0700206 private void writeUserLocked(UserInfo userInfo) {
Amith Yamasani742a6712011-05-04 14:49:28 -0700207 FileOutputStream fos = null;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700208 try {
209 final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
Amith Yamasani742a6712011-05-04 14:49:28 -0700210 fos = new FileOutputStream(mUserFile);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700211 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) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700231 Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
Amith Yamasani742a6712011-05-04 14:49:28 -0700232 } finally {
233 if (fos != null) {
234 try {
235 fos.close();
236 } catch (IOException ioe) {
237 }
238 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700239 }
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 */
Amith Yamasani13593602012-03-22 16:16:17 -0700250 private void writeUserListLocked() {
Amith Yamasani742a6712011-05-04 14:49:28 -0700251 FileOutputStream fos = null;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700252 try {
Amith Yamasani742a6712011-05-04 14:49:28 -0700253 fos = new FileOutputStream(mUserListFile);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700254 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);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700269 }
270
271 serializer.endTag(null, TAG_USERS);
272
273 serializer.endDocument();
274 } catch (IOException ioe) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700275 Slog.e(LOG_TAG, "Error writing user list");
Amith Yamasani742a6712011-05-04 14:49:28 -0700276 } finally {
277 if (fos != null) {
278 try {
279 fos.close();
280 } catch (IOException ioe) {
281 }
282 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700283 }
284 }
285
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) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700303 Slog.e(LOG_TAG, "Unable to read user " + id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700304 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) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700310 Slog.e(LOG_TAG, "User id does not match the file name");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700311 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 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700326
327 UserInfo userInfo = new UserInfo(id, name, flags);
328 return userInfo;
329
330 } catch (IOException ioe) {
331 } catch (XmlPullParserException pe) {
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800332 } finally {
333 if (fis != null) {
334 try {
335 fis.close();
336 } catch (IOException e) {
337 }
338 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700339 }
340 return null;
341 }
342
Amith Yamasani13593602012-03-22 16:16:17 -0700343 public UserInfo createUser(String name, int flags) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700344 int userId = getNextAvailableId();
345 UserInfo userInfo = new UserInfo(userId, name, flags);
346 File userPath = new File(mBaseUserPath, Integer.toString(userId));
Amith Yamasani13593602012-03-22 16:16:17 -0700347 if (!createPackageFolders(userId, userPath)) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700348 return null;
349 }
Amith Yamasani13593602012-03-22 16:16:17 -0700350 synchronized (mUsers) {
351 mUsers.put(userId, userInfo);
352 writeUserListLocked();
353 writeUserLocked(userInfo);
354 updateUserIdsLocked();
355 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700356 return userInfo;
357 }
358
Amith Yamasani0b285492011-04-14 17:35:23 -0700359 /**
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 */
Amith Yamasani13593602012-03-22 16:16:17 -0700364 public boolean removeUser(int id) {
365 synchronized (mUsers) {
366 return removeUserLocked(id);
367 }
368 }
369
370 private boolean removeUserLocked(int id) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700371 // 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();
Amith Yamasani0b285492011-04-14 17:35:23 -0700379 // Update the user list
Amith Yamasani13593602012-03-22 16:16:17 -0700380 writeUserListLocked();
381 updateUserIdsLocked();
382 return true;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700383 }
Amith Yamasani13593602012-03-22 16:16:17 -0700384 return false;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700385 }
386
Amith Yamasani0b285492011-04-14 17:35:23 -0700387 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;
Amith Yamasani742a6712011-05-04 14:49:28 -0700392 mInstaller.createUserData(packageName, UserId.getUid(userId, uid),
Amith Yamasani0b285492011-04-14 17:35:23 -0700393 userId);
394 }
395 }
396
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
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 */
Amith Yamasani13593602012-03-22 16:16:17 -0700418 private void updateUserIdsLocked() {
Amith Yamasani0b285492011-04-14 17:35:23 -0700419 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.
Amith Yamasani742a6712011-05-04 14:49:28 -0700429 * 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.
Amith Yamasani0b285492011-04-14 17:35:23 -0700431 * @return
432 */
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700433 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
Amith Yamasani13593602012-03-22 16:16:17 -0700444 private boolean createPackageFolders(int id, File userPath) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700445 // mInstaller may not be available for unit-tests.
Amith Yamasani13593602012-03-22 16:16:17 -0700446 if (mInstaller == null) return true;
Amith Yamasani0b285492011-04-14 17:35:23 -0700447
Amith Yamasani0b285492011-04-14 17:35:23 -0700448 // 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
Amith Yamasani742a6712011-05-04 14:49:28 -0700453 mInstaller.cloneUserData(0, id, false);
454
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700455 return true;
456 }
457
Amith Yamasani13593602012-03-22 16:16:17 -0700458 boolean removePackageFolders(int id) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700459 // mInstaller may not be available for unit-tests.
460 if (mInstaller == null) return true;
461
462 mInstaller.removeUserDataDirs(id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700463 return true;
464 }
465}