blob: 5eacf4a342b0d2d5f499547b5c4ad473befa7e9b [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
19import com.android.internal.util.FastXmlSerializer;
20
Amith Yamasani0b285492011-04-14 17:35:23 -070021import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageManager;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070023import android.content.pm.UserInfo;
24import android.os.Environment;
25import android.os.FileUtils;
Amith Yamasani0b285492011-04-14 17:35:23 -070026import android.os.SystemClock;
Amith Yamasani742a6712011-05-04 14:49:28 -070027import android.os.UserId;
Amith Yamasani0b285492011-04-14 17:35:23 -070028import android.util.Log;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070029import android.util.Slog;
30import android.util.SparseArray;
31import android.util.Xml;
32
33import java.io.BufferedOutputStream;
34import java.io.File;
35import java.io.FileInputStream;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.List;
40
41import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlPullParserException;
43import org.xmlpull.v1.XmlSerializer;
44
Amith Yamasani0b285492011-04-14 17:35:23 -070045public class UserManager {
Amith Yamasani4b2e9342011-03-31 12:38:53 -070046 private static final String TAG_NAME = "name";
47
48 private static final String ATTR_FLAGS = "flags";
49
50 private static final String ATTR_ID = "id";
51
52 private static final String TAG_USERS = "users";
53
54 private static final String TAG_USER = "user";
55
Amith Yamasani0b285492011-04-14 17:35:23 -070056 private static final String LOG_TAG = "UserManager";
Amith Yamasani4b2e9342011-03-31 12:38:53 -070057
Amith Yamasani0b285492011-04-14 17:35:23 -070058 private static final String USER_INFO_DIR = "system" + File.separator + "users";
Amith Yamasani4b2e9342011-03-31 12:38:53 -070059 private static final String USER_LIST_FILENAME = "userlist.xml";
60
61 private SparseArray<UserInfo> mUsers;
62
63 private final File mUsersDir;
64 private final File mUserListFile;
Amith Yamasani0b285492011-04-14 17:35:23 -070065 private int[] mUserIds;
66
67 private Installer mInstaller;
68 private File mBaseUserPath;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070069
70 /**
71 * Available for testing purposes.
72 */
Amith Yamasani0b285492011-04-14 17:35:23 -070073 UserManager(File dataDir, File baseUserPath) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -070074 mUsersDir = new File(dataDir, USER_INFO_DIR);
75 mUsersDir.mkdirs();
Amith Yamasani0b285492011-04-14 17:35:23 -070076 mBaseUserPath = baseUserPath;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070077 FileUtils.setPermissions(mUsersDir.toString(),
78 FileUtils.S_IRWXU|FileUtils.S_IRWXG
79 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
80 -1, -1);
81 mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
82 readUserList();
83 }
84
Amith Yamasani0b285492011-04-14 17:35:23 -070085 public UserManager(Installer installer, File baseUserPath) {
86 this(Environment.getDataDirectory(), baseUserPath);
87 mInstaller = installer;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070088 }
89
90 public List<UserInfo> getUsers() {
91 ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
92 for (int i = 0; i < mUsers.size(); i++) {
93 users.add(mUsers.valueAt(i));
94 }
95 return users;
96 }
97
Amith Yamasani0b285492011-04-14 17:35:23 -070098 /**
99 * Returns an array of user ids. This array is cached here for quick access, so do not modify or
100 * cache it elsewhere.
101 * @return the array of user ids.
102 */
103 int[] getUserIds() {
104 return mUserIds;
105 }
106
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700107 private void readUserList() {
108 mUsers = new SparseArray<UserInfo>();
109 if (!mUserListFile.exists()) {
110 fallbackToSingleUser();
111 return;
112 }
113 FileInputStream fis = null;
114 try {
115 fis = new FileInputStream(mUserListFile);
116 XmlPullParser parser = Xml.newPullParser();
117 parser.setInput(fis, null);
118 int type;
119 while ((type = parser.next()) != XmlPullParser.START_TAG
120 && type != XmlPullParser.END_DOCUMENT) {
121 ;
122 }
123
124 if (type != XmlPullParser.START_TAG) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700125 Slog.e(LOG_TAG, "Unable to read user list");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700126 fallbackToSingleUser();
127 return;
128 }
129
130 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
131 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
132 String id = parser.getAttributeValue(null, ATTR_ID);
133 UserInfo user = readUser(Integer.parseInt(id));
134 if (user != null) {
135 mUsers.put(user.id, user);
136 }
137 }
138 }
Amith Yamasani0b285492011-04-14 17:35:23 -0700139 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700140 } catch (IOException ioe) {
141 fallbackToSingleUser();
142 } catch (XmlPullParserException pe) {
143 fallbackToSingleUser();
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800144 } finally {
145 if (fis != null) {
146 try {
147 fis.close();
148 } catch (IOException e) {
149 }
150 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700151 }
152 }
153
154 private void fallbackToSingleUser() {
155 // Create the primary user
156 UserInfo primary = new UserInfo(0, "Primary",
157 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
158 mUsers.put(0, primary);
Amith Yamasani0b285492011-04-14 17:35:23 -0700159 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700160
161 writeUserList();
162 writeUser(primary);
163 }
164
165 /*
166 * Writes the user file in this format:
167 *
168 * <user flags="20039023" id="0">
169 * <name>Primary</name>
170 * </user>
171 */
172 private void writeUser(UserInfo userInfo) {
Amith Yamasani742a6712011-05-04 14:49:28 -0700173 FileOutputStream fos = null;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700174 try {
175 final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
Amith Yamasani742a6712011-05-04 14:49:28 -0700176 fos = new FileOutputStream(mUserFile);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700177 final BufferedOutputStream bos = new BufferedOutputStream(fos);
178
179 // XmlSerializer serializer = XmlUtils.serializerInstance();
180 final XmlSerializer serializer = new FastXmlSerializer();
181 serializer.setOutput(bos, "utf-8");
182 serializer.startDocument(null, true);
183 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
184
185 serializer.startTag(null, TAG_USER);
186 serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
187 serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
188
189 serializer.startTag(null, TAG_NAME);
190 serializer.text(userInfo.name);
191 serializer.endTag(null, TAG_NAME);
192
193 serializer.endTag(null, TAG_USER);
194
195 serializer.endDocument();
196 } catch (IOException ioe) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700197 Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
Amith Yamasani742a6712011-05-04 14:49:28 -0700198 } finally {
199 if (fos != null) {
200 try {
201 fos.close();
202 } catch (IOException ioe) {
203 }
204 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700205 }
206 }
207
208 /*
209 * Writes the user list file in this format:
210 *
211 * <users>
212 * <user id="0"></user>
213 * <user id="2"></user>
214 * </users>
215 */
216 private void writeUserList() {
Amith Yamasani742a6712011-05-04 14:49:28 -0700217 FileOutputStream fos = null;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700218 try {
Amith Yamasani742a6712011-05-04 14:49:28 -0700219 fos = new FileOutputStream(mUserListFile);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700220 final BufferedOutputStream bos = new BufferedOutputStream(fos);
221
222 // XmlSerializer serializer = XmlUtils.serializerInstance();
223 final XmlSerializer serializer = new FastXmlSerializer();
224 serializer.setOutput(bos, "utf-8");
225 serializer.startDocument(null, true);
226 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
227
228 serializer.startTag(null, TAG_USERS);
229
230 for (int i = 0; i < mUsers.size(); i++) {
231 UserInfo user = mUsers.valueAt(i);
232 serializer.startTag(null, TAG_USER);
233 serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
234 serializer.endTag(null, TAG_USER);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700235 }
236
237 serializer.endTag(null, TAG_USERS);
238
239 serializer.endDocument();
240 } catch (IOException ioe) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700241 Slog.e(LOG_TAG, "Error writing user list");
Amith Yamasani742a6712011-05-04 14:49:28 -0700242 } finally {
243 if (fos != null) {
244 try {
245 fos.close();
246 } catch (IOException ioe) {
247 }
248 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700249 }
250 }
251
252 private UserInfo readUser(int id) {
253 int flags = 0;
254 String name = null;
255
256 FileInputStream fis = null;
257 try {
258 File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
259 fis = new FileInputStream(userFile);
260 XmlPullParser parser = Xml.newPullParser();
261 parser.setInput(fis, null);
262 int type;
263 while ((type = parser.next()) != XmlPullParser.START_TAG
264 && type != XmlPullParser.END_DOCUMENT) {
265 ;
266 }
267
268 if (type != XmlPullParser.START_TAG) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700269 Slog.e(LOG_TAG, "Unable to read user " + id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700270 return null;
271 }
272
273 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
274 String storedId = parser.getAttributeValue(null, ATTR_ID);
275 if (Integer.parseInt(storedId) != id) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700276 Slog.e(LOG_TAG, "User id does not match the file name");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700277 return null;
278 }
279 String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
280 flags = Integer.parseInt(flagString);
281
282 while ((type = parser.next()) != XmlPullParser.START_TAG
283 && type != XmlPullParser.END_DOCUMENT) {
284 }
285 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
286 type = parser.next();
287 if (type == XmlPullParser.TEXT) {
288 name = parser.getText();
289 }
290 }
291 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700292
293 UserInfo userInfo = new UserInfo(id, name, flags);
294 return userInfo;
295
296 } catch (IOException ioe) {
297 } catch (XmlPullParserException pe) {
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800298 } finally {
299 if (fis != null) {
300 try {
301 fis.close();
302 } catch (IOException e) {
303 }
304 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700305 }
306 return null;
307 }
308
Amith Yamasani0b285492011-04-14 17:35:23 -0700309 public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
310 int userId = getNextAvailableId();
311 UserInfo userInfo = new UserInfo(userId, name, flags);
312 File userPath = new File(mBaseUserPath, Integer.toString(userId));
313 if (!createPackageFolders(userId, userPath, apps)) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700314 return null;
315 }
Amith Yamasani0b285492011-04-14 17:35:23 -0700316 mUsers.put(userId, userInfo);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700317 writeUserList();
318 writeUser(userInfo);
Amith Yamasani0b285492011-04-14 17:35:23 -0700319 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700320 return userInfo;
321 }
322
Amith Yamasani0b285492011-04-14 17:35:23 -0700323 /**
324 * Removes a user and all data directories created for that user. This method should be called
325 * after the user's processes have been terminated.
326 * @param id the user's id
327 */
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700328 public void removeUser(int id) {
329 // Remove from the list
330 UserInfo userInfo = mUsers.get(id);
331 if (userInfo != null) {
332 // Remove this user from the list
333 mUsers.remove(id);
334 // Remove user file
335 File userFile = new File(mUsersDir, id + ".xml");
336 userFile.delete();
Amith Yamasani0b285492011-04-14 17:35:23 -0700337 // Update the user list
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700338 writeUserList();
Amith Yamasani0b285492011-04-14 17:35:23 -0700339 // Remove the data directories for all packages for this user
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700340 removePackageFolders(id);
Amith Yamasani0b285492011-04-14 17:35:23 -0700341 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700342 }
343 }
344
Amith Yamasani0b285492011-04-14 17:35:23 -0700345 public void installPackageForAllUsers(String packageName, int uid) {
346 for (int userId : mUserIds) {
347 // Don't do it for the primary user, it will become recursive.
348 if (userId == 0)
349 continue;
Amith Yamasani742a6712011-05-04 14:49:28 -0700350 mInstaller.createUserData(packageName, UserId.getUid(userId, uid),
Amith Yamasani0b285492011-04-14 17:35:23 -0700351 userId);
352 }
353 }
354
355 public void clearUserDataForAllUsers(String packageName) {
356 for (int userId : mUserIds) {
357 // Don't do it for the primary user, it will become recursive.
358 if (userId == 0)
359 continue;
360 mInstaller.clearUserData(packageName, userId);
361 }
362 }
363
364 public void removePackageForAllUsers(String packageName) {
365 for (int userId : mUserIds) {
366 // Don't do it for the primary user, it will become recursive.
367 if (userId == 0)
368 continue;
369 mInstaller.remove(packageName, userId);
370 }
371 }
372
373 /**
374 * Caches the list of user ids in an array, adjusting the array size when necessary.
375 */
376 private void updateUserIds() {
377 if (mUserIds == null || mUserIds.length != mUsers.size()) {
378 mUserIds = new int[mUsers.size()];
379 }
380 for (int i = 0; i < mUsers.size(); i++) {
381 mUserIds[i] = mUsers.keyAt(i);
382 }
383 }
384
385 /**
386 * Returns the next available user id, filling in any holes in the ids.
Amith Yamasani742a6712011-05-04 14:49:28 -0700387 * TODO: May not be a good idea to recycle ids, in case it results in confusion
388 * for data and battery stats collection, or unexpected cross-talk.
Amith Yamasani0b285492011-04-14 17:35:23 -0700389 * @return
390 */
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700391 private int getNextAvailableId() {
392 int i = 0;
393 while (i < Integer.MAX_VALUE) {
394 if (mUsers.indexOfKey(i) < 0) {
395 break;
396 }
397 i++;
398 }
399 return i;
400 }
401
Amith Yamasani0b285492011-04-14 17:35:23 -0700402 private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
403 // mInstaller may not be available for unit-tests.
404 if (mInstaller == null || apps == null) return true;
405
406 final long startTime = SystemClock.elapsedRealtime();
407 // Create the user path
408 userPath.mkdir();
409 FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
410 | FileUtils.S_IXOTH, -1, -1);
411
Amith Yamasani742a6712011-05-04 14:49:28 -0700412 mInstaller.cloneUserData(0, id, false);
413
Amith Yamasani0b285492011-04-14 17:35:23 -0700414 final long stopTime = SystemClock.elapsedRealtime();
415 Log.i(LOG_TAG,
416 "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700417 return true;
418 }
419
420 private boolean removePackageFolders(int id) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700421 // mInstaller may not be available for unit-tests.
422 if (mInstaller == null) return true;
423
424 mInstaller.removeUserDataDirs(id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700425 return true;
426 }
427}