blob: 26877288966ad7d384381a33ad7db2f53407d50f [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;
27import android.util.Log;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070028import android.util.Slog;
29import android.util.SparseArray;
30import android.util.Xml;
31
32import java.io.BufferedOutputStream;
33import java.io.File;
34import java.io.FileInputStream;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.util.ArrayList;
38import java.util.List;
39
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
Amith Yamasani0b285492011-04-14 17:35:23 -070044public class UserManager {
Amith Yamasani4b2e9342011-03-31 12:38:53 -070045 private static final String TAG_NAME = "name";
46
47 private static final String ATTR_FLAGS = "flags";
48
49 private static final String ATTR_ID = "id";
50
51 private static final String TAG_USERS = "users";
52
53 private static final String TAG_USER = "user";
54
Amith Yamasani0b285492011-04-14 17:35:23 -070055 private static final String LOG_TAG = "UserManager";
Amith Yamasani4b2e9342011-03-31 12:38:53 -070056
Amith Yamasani0b285492011-04-14 17:35:23 -070057 private static final String USER_INFO_DIR = "system" + File.separator + "users";
Amith Yamasani4b2e9342011-03-31 12:38:53 -070058 private static final String USER_LIST_FILENAME = "userlist.xml";
59
60 private SparseArray<UserInfo> mUsers;
61
62 private final File mUsersDir;
63 private final File mUserListFile;
Amith Yamasani0b285492011-04-14 17:35:23 -070064 private int[] mUserIds;
65
66 private Installer mInstaller;
67 private File mBaseUserPath;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070068
69 /**
70 * Available for testing purposes.
71 */
Amith Yamasani0b285492011-04-14 17:35:23 -070072 UserManager(File dataDir, File baseUserPath) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -070073 mUsersDir = new File(dataDir, USER_INFO_DIR);
74 mUsersDir.mkdirs();
Amith Yamasani0b285492011-04-14 17:35:23 -070075 mBaseUserPath = baseUserPath;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070076 FileUtils.setPermissions(mUsersDir.toString(),
77 FileUtils.S_IRWXU|FileUtils.S_IRWXG
78 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
79 -1, -1);
80 mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
81 readUserList();
82 }
83
Amith Yamasani0b285492011-04-14 17:35:23 -070084 public UserManager(Installer installer, File baseUserPath) {
85 this(Environment.getDataDirectory(), baseUserPath);
86 mInstaller = installer;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070087 }
88
89 public List<UserInfo> getUsers() {
90 ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
91 for (int i = 0; i < mUsers.size(); i++) {
92 users.add(mUsers.valueAt(i));
93 }
94 return users;
95 }
96
Amith Yamasani0b285492011-04-14 17:35:23 -070097 /**
98 * Returns an array of user ids. This array is cached here for quick access, so do not modify or
99 * cache it elsewhere.
100 * @return the array of user ids.
101 */
102 int[] getUserIds() {
103 return mUserIds;
104 }
105
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700106 private void readUserList() {
107 mUsers = new SparseArray<UserInfo>();
108 if (!mUserListFile.exists()) {
109 fallbackToSingleUser();
110 return;
111 }
112 FileInputStream fis = null;
113 try {
114 fis = new FileInputStream(mUserListFile);
115 XmlPullParser parser = Xml.newPullParser();
116 parser.setInput(fis, null);
117 int type;
118 while ((type = parser.next()) != XmlPullParser.START_TAG
119 && type != XmlPullParser.END_DOCUMENT) {
120 ;
121 }
122
123 if (type != XmlPullParser.START_TAG) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700124 Slog.e(LOG_TAG, "Unable to read user list");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700125 fallbackToSingleUser();
126 return;
127 }
128
129 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
130 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
131 String id = parser.getAttributeValue(null, ATTR_ID);
132 UserInfo user = readUser(Integer.parseInt(id));
133 if (user != null) {
134 mUsers.put(user.id, user);
135 }
136 }
137 }
Amith Yamasani0b285492011-04-14 17:35:23 -0700138 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700139 } catch (IOException ioe) {
140 fallbackToSingleUser();
141 } catch (XmlPullParserException pe) {
142 fallbackToSingleUser();
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800143 } finally {
144 if (fis != null) {
145 try {
146 fis.close();
147 } catch (IOException e) {
148 }
149 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700150 }
151 }
152
153 private void fallbackToSingleUser() {
154 // Create the primary user
155 UserInfo primary = new UserInfo(0, "Primary",
156 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
157 mUsers.put(0, primary);
Amith Yamasani0b285492011-04-14 17:35:23 -0700158 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700159
160 writeUserList();
161 writeUser(primary);
162 }
163
164 /*
165 * Writes the user file in this format:
166 *
167 * <user flags="20039023" id="0">
168 * <name>Primary</name>
169 * </user>
170 */
171 private void writeUser(UserInfo userInfo) {
172 try {
173 final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
174 final FileOutputStream fos = new FileOutputStream(mUserFile);
175 final BufferedOutputStream bos = new BufferedOutputStream(fos);
176
177 // XmlSerializer serializer = XmlUtils.serializerInstance();
178 final XmlSerializer serializer = new FastXmlSerializer();
179 serializer.setOutput(bos, "utf-8");
180 serializer.startDocument(null, true);
181 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
182
183 serializer.startTag(null, TAG_USER);
184 serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
185 serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
186
187 serializer.startTag(null, TAG_NAME);
188 serializer.text(userInfo.name);
189 serializer.endTag(null, TAG_NAME);
190
191 serializer.endTag(null, TAG_USER);
192
193 serializer.endDocument();
194 } catch (IOException ioe) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700195 Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700196 }
197 }
198
199 /*
200 * Writes the user list file in this format:
201 *
202 * <users>
203 * <user id="0"></user>
204 * <user id="2"></user>
205 * </users>
206 */
207 private void writeUserList() {
208 try {
209 final FileOutputStream fos = new FileOutputStream(mUserListFile);
210 final BufferedOutputStream bos = new BufferedOutputStream(fos);
211
212 // XmlSerializer serializer = XmlUtils.serializerInstance();
213 final XmlSerializer serializer = new FastXmlSerializer();
214 serializer.setOutput(bos, "utf-8");
215 serializer.startDocument(null, true);
216 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
217
218 serializer.startTag(null, TAG_USERS);
219
220 for (int i = 0; i < mUsers.size(); i++) {
221 UserInfo user = mUsers.valueAt(i);
222 serializer.startTag(null, TAG_USER);
223 serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
224 serializer.endTag(null, TAG_USER);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700225 }
226
227 serializer.endTag(null, TAG_USERS);
228
229 serializer.endDocument();
230 } catch (IOException ioe) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700231 Slog.e(LOG_TAG, "Error writing user list");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700232 }
233 }
234
235 private UserInfo readUser(int id) {
236 int flags = 0;
237 String name = null;
238
239 FileInputStream fis = null;
240 try {
241 File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
242 fis = new FileInputStream(userFile);
243 XmlPullParser parser = Xml.newPullParser();
244 parser.setInput(fis, null);
245 int type;
246 while ((type = parser.next()) != XmlPullParser.START_TAG
247 && type != XmlPullParser.END_DOCUMENT) {
248 ;
249 }
250
251 if (type != XmlPullParser.START_TAG) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700252 Slog.e(LOG_TAG, "Unable to read user " + id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700253 return null;
254 }
255
256 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
257 String storedId = parser.getAttributeValue(null, ATTR_ID);
258 if (Integer.parseInt(storedId) != id) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700259 Slog.e(LOG_TAG, "User id does not match the file name");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700260 return null;
261 }
262 String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
263 flags = Integer.parseInt(flagString);
264
265 while ((type = parser.next()) != XmlPullParser.START_TAG
266 && type != XmlPullParser.END_DOCUMENT) {
267 }
268 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
269 type = parser.next();
270 if (type == XmlPullParser.TEXT) {
271 name = parser.getText();
272 }
273 }
274 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700275
276 UserInfo userInfo = new UserInfo(id, name, flags);
277 return userInfo;
278
279 } catch (IOException ioe) {
280 } catch (XmlPullParserException pe) {
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800281 } finally {
282 if (fis != null) {
283 try {
284 fis.close();
285 } catch (IOException e) {
286 }
287 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700288 }
289 return null;
290 }
291
Amith Yamasani0b285492011-04-14 17:35:23 -0700292 public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
293 int userId = getNextAvailableId();
294 UserInfo userInfo = new UserInfo(userId, name, flags);
295 File userPath = new File(mBaseUserPath, Integer.toString(userId));
296 if (!createPackageFolders(userId, userPath, apps)) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700297 return null;
298 }
Amith Yamasani0b285492011-04-14 17:35:23 -0700299 mUsers.put(userId, userInfo);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700300 writeUserList();
301 writeUser(userInfo);
Amith Yamasani0b285492011-04-14 17:35:23 -0700302 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700303 return userInfo;
304 }
305
Amith Yamasani0b285492011-04-14 17:35:23 -0700306 /**
307 * Removes a user and all data directories created for that user. This method should be called
308 * after the user's processes have been terminated.
309 * @param id the user's id
310 */
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700311 public void removeUser(int id) {
312 // Remove from the list
313 UserInfo userInfo = mUsers.get(id);
314 if (userInfo != null) {
315 // Remove this user from the list
316 mUsers.remove(id);
317 // Remove user file
318 File userFile = new File(mUsersDir, id + ".xml");
319 userFile.delete();
Amith Yamasani0b285492011-04-14 17:35:23 -0700320 // Update the user list
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700321 writeUserList();
Amith Yamasani0b285492011-04-14 17:35:23 -0700322 // Remove the data directories for all packages for this user
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700323 removePackageFolders(id);
Amith Yamasani0b285492011-04-14 17:35:23 -0700324 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700325 }
326 }
327
Amith Yamasani0b285492011-04-14 17:35:23 -0700328 public void installPackageForAllUsers(String packageName, int uid) {
329 for (int userId : mUserIds) {
330 // Don't do it for the primary user, it will become recursive.
331 if (userId == 0)
332 continue;
333 mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid),
334 userId);
335 }
336 }
337
338 public void clearUserDataForAllUsers(String packageName) {
339 for (int userId : mUserIds) {
340 // Don't do it for the primary user, it will become recursive.
341 if (userId == 0)
342 continue;
343 mInstaller.clearUserData(packageName, userId);
344 }
345 }
346
347 public void removePackageForAllUsers(String packageName) {
348 for (int userId : mUserIds) {
349 // Don't do it for the primary user, it will become recursive.
350 if (userId == 0)
351 continue;
352 mInstaller.remove(packageName, userId);
353 }
354 }
355
356 /**
357 * Caches the list of user ids in an array, adjusting the array size when necessary.
358 */
359 private void updateUserIds() {
360 if (mUserIds == null || mUserIds.length != mUsers.size()) {
361 mUserIds = new int[mUsers.size()];
362 }
363 for (int i = 0; i < mUsers.size(); i++) {
364 mUserIds[i] = mUsers.keyAt(i);
365 }
366 }
367
368 /**
369 * Returns the next available user id, filling in any holes in the ids.
370 * @return
371 */
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700372 private int getNextAvailableId() {
373 int i = 0;
374 while (i < Integer.MAX_VALUE) {
375 if (mUsers.indexOfKey(i) < 0) {
376 break;
377 }
378 i++;
379 }
380 return i;
381 }
382
Amith Yamasani0b285492011-04-14 17:35:23 -0700383 private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
384 // mInstaller may not be available for unit-tests.
385 if (mInstaller == null || apps == null) return true;
386
387 final long startTime = SystemClock.elapsedRealtime();
388 // Create the user path
389 userPath.mkdir();
390 FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
391 | FileUtils.S_IXOTH, -1, -1);
392
393 // Create the individual data directories
394 for (ApplicationInfo app : apps) {
395 if (app.uid > android.os.Process.FIRST_APPLICATION_UID
396 && app.uid < PackageManager.PER_USER_RANGE) {
397 mInstaller.createUserData(app.packageName,
398 PackageManager.getUid(id, app.uid), id);
399 }
400 }
401 final long stopTime = SystemClock.elapsedRealtime();
402 Log.i(LOG_TAG,
403 "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700404 return true;
405 }
406
407 private boolean removePackageFolders(int id) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700408 // mInstaller may not be available for unit-tests.
409 if (mInstaller == null) return true;
410
411 mInstaller.removeUserDataDirs(id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700412 return true;
413 }
414}