blob: 959e570589d635b033b3dd8ef8775900b3203266 [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 Yamasani483f3b02012-03-13 16:08:00 -070076 // Make zeroth user directory, for services to migrate their files to that location
77 File userZeroDir = new File(mUsersDir, "0");
78 userZeroDir.mkdirs();
Amith Yamasani0b285492011-04-14 17:35:23 -070079 mBaseUserPath = baseUserPath;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070080 FileUtils.setPermissions(mUsersDir.toString(),
81 FileUtils.S_IRWXU|FileUtils.S_IRWXG
82 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
83 -1, -1);
84 mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
85 readUserList();
86 }
87
Amith Yamasani0b285492011-04-14 17:35:23 -070088 public UserManager(Installer installer, File baseUserPath) {
89 this(Environment.getDataDirectory(), baseUserPath);
90 mInstaller = installer;
Amith Yamasani4b2e9342011-03-31 12:38:53 -070091 }
92
93 public List<UserInfo> getUsers() {
94 ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
95 for (int i = 0; i < mUsers.size(); i++) {
96 users.add(mUsers.valueAt(i));
97 }
98 return users;
99 }
100
Amith Yamasani0b285492011-04-14 17:35:23 -0700101 /**
102 * Returns an array of user ids. This array is cached here for quick access, so do not modify or
103 * cache it elsewhere.
104 * @return the array of user ids.
105 */
106 int[] getUserIds() {
107 return mUserIds;
108 }
109
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700110 private void readUserList() {
111 mUsers = new SparseArray<UserInfo>();
112 if (!mUserListFile.exists()) {
113 fallbackToSingleUser();
114 return;
115 }
116 FileInputStream fis = null;
117 try {
118 fis = new FileInputStream(mUserListFile);
119 XmlPullParser parser = Xml.newPullParser();
120 parser.setInput(fis, null);
121 int type;
122 while ((type = parser.next()) != XmlPullParser.START_TAG
123 && type != XmlPullParser.END_DOCUMENT) {
124 ;
125 }
126
127 if (type != XmlPullParser.START_TAG) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700128 Slog.e(LOG_TAG, "Unable to read user list");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700129 fallbackToSingleUser();
130 return;
131 }
132
133 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
134 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
135 String id = parser.getAttributeValue(null, ATTR_ID);
136 UserInfo user = readUser(Integer.parseInt(id));
137 if (user != null) {
138 mUsers.put(user.id, user);
139 }
140 }
141 }
Amith Yamasani0b285492011-04-14 17:35:23 -0700142 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700143 } catch (IOException ioe) {
144 fallbackToSingleUser();
145 } catch (XmlPullParserException pe) {
146 fallbackToSingleUser();
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800147 } finally {
148 if (fis != null) {
149 try {
150 fis.close();
151 } catch (IOException e) {
152 }
153 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700154 }
155 }
156
157 private void fallbackToSingleUser() {
158 // Create the primary user
159 UserInfo primary = new UserInfo(0, "Primary",
160 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
161 mUsers.put(0, primary);
Amith Yamasani0b285492011-04-14 17:35:23 -0700162 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700163
164 writeUserList();
165 writeUser(primary);
166 }
167
168 /*
169 * Writes the user file in this format:
170 *
171 * <user flags="20039023" id="0">
172 * <name>Primary</name>
173 * </user>
174 */
175 private void writeUser(UserInfo userInfo) {
Amith Yamasani742a6712011-05-04 14:49:28 -0700176 FileOutputStream fos = null;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700177 try {
178 final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
Amith Yamasani742a6712011-05-04 14:49:28 -0700179 fos = new FileOutputStream(mUserFile);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700180 final BufferedOutputStream bos = new BufferedOutputStream(fos);
181
182 // XmlSerializer serializer = XmlUtils.serializerInstance();
183 final XmlSerializer serializer = new FastXmlSerializer();
184 serializer.setOutput(bos, "utf-8");
185 serializer.startDocument(null, true);
186 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
187
188 serializer.startTag(null, TAG_USER);
189 serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
190 serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
191
192 serializer.startTag(null, TAG_NAME);
193 serializer.text(userInfo.name);
194 serializer.endTag(null, TAG_NAME);
195
196 serializer.endTag(null, TAG_USER);
197
198 serializer.endDocument();
199 } catch (IOException ioe) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700200 Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
Amith Yamasani742a6712011-05-04 14:49:28 -0700201 } finally {
202 if (fos != null) {
203 try {
204 fos.close();
205 } catch (IOException ioe) {
206 }
207 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700208 }
209 }
210
211 /*
212 * Writes the user list file in this format:
213 *
214 * <users>
215 * <user id="0"></user>
216 * <user id="2"></user>
217 * </users>
218 */
219 private void writeUserList() {
Amith Yamasani742a6712011-05-04 14:49:28 -0700220 FileOutputStream fos = null;
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700221 try {
Amith Yamasani742a6712011-05-04 14:49:28 -0700222 fos = new FileOutputStream(mUserListFile);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700223 final BufferedOutputStream bos = new BufferedOutputStream(fos);
224
225 // XmlSerializer serializer = XmlUtils.serializerInstance();
226 final XmlSerializer serializer = new FastXmlSerializer();
227 serializer.setOutput(bos, "utf-8");
228 serializer.startDocument(null, true);
229 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
230
231 serializer.startTag(null, TAG_USERS);
232
233 for (int i = 0; i < mUsers.size(); i++) {
234 UserInfo user = mUsers.valueAt(i);
235 serializer.startTag(null, TAG_USER);
236 serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
237 serializer.endTag(null, TAG_USER);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700238 }
239
240 serializer.endTag(null, TAG_USERS);
241
242 serializer.endDocument();
243 } catch (IOException ioe) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700244 Slog.e(LOG_TAG, "Error writing user list");
Amith Yamasani742a6712011-05-04 14:49:28 -0700245 } finally {
246 if (fos != null) {
247 try {
248 fos.close();
249 } catch (IOException ioe) {
250 }
251 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700252 }
253 }
254
255 private UserInfo readUser(int id) {
256 int flags = 0;
257 String name = null;
258
259 FileInputStream fis = null;
260 try {
261 File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
262 fis = new FileInputStream(userFile);
263 XmlPullParser parser = Xml.newPullParser();
264 parser.setInput(fis, null);
265 int type;
266 while ((type = parser.next()) != XmlPullParser.START_TAG
267 && type != XmlPullParser.END_DOCUMENT) {
268 ;
269 }
270
271 if (type != XmlPullParser.START_TAG) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700272 Slog.e(LOG_TAG, "Unable to read user " + id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700273 return null;
274 }
275
276 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
277 String storedId = parser.getAttributeValue(null, ATTR_ID);
278 if (Integer.parseInt(storedId) != id) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700279 Slog.e(LOG_TAG, "User id does not match the file name");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700280 return null;
281 }
282 String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
283 flags = Integer.parseInt(flagString);
284
285 while ((type = parser.next()) != XmlPullParser.START_TAG
286 && type != XmlPullParser.END_DOCUMENT) {
287 }
288 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
289 type = parser.next();
290 if (type == XmlPullParser.TEXT) {
291 name = parser.getText();
292 }
293 }
294 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700295
296 UserInfo userInfo = new UserInfo(id, name, flags);
297 return userInfo;
298
299 } catch (IOException ioe) {
300 } catch (XmlPullParserException pe) {
Dianne Hackbornbfd89b32011-12-15 18:22:54 -0800301 } finally {
302 if (fis != null) {
303 try {
304 fis.close();
305 } catch (IOException e) {
306 }
307 }
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700308 }
309 return null;
310 }
311
Amith Yamasani0b285492011-04-14 17:35:23 -0700312 public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
313 int userId = getNextAvailableId();
314 UserInfo userInfo = new UserInfo(userId, name, flags);
315 File userPath = new File(mBaseUserPath, Integer.toString(userId));
316 if (!createPackageFolders(userId, userPath, apps)) {
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700317 return null;
318 }
Amith Yamasani0b285492011-04-14 17:35:23 -0700319 mUsers.put(userId, userInfo);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700320 writeUserList();
321 writeUser(userInfo);
Amith Yamasani0b285492011-04-14 17:35:23 -0700322 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700323 return userInfo;
324 }
325
Amith Yamasani0b285492011-04-14 17:35:23 -0700326 /**
327 * Removes a user and all data directories created for that user. This method should be called
328 * after the user's processes have been terminated.
329 * @param id the user's id
330 */
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700331 public void removeUser(int id) {
332 // Remove from the list
333 UserInfo userInfo = mUsers.get(id);
334 if (userInfo != null) {
335 // Remove this user from the list
336 mUsers.remove(id);
337 // Remove user file
338 File userFile = new File(mUsersDir, id + ".xml");
339 userFile.delete();
Amith Yamasani0b285492011-04-14 17:35:23 -0700340 // Update the user list
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700341 writeUserList();
Amith Yamasani0b285492011-04-14 17:35:23 -0700342 // Remove the data directories for all packages for this user
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700343 removePackageFolders(id);
Amith Yamasani0b285492011-04-14 17:35:23 -0700344 updateUserIds();
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700345 }
346 }
347
Amith Yamasani0b285492011-04-14 17:35:23 -0700348 public void installPackageForAllUsers(String packageName, int uid) {
349 for (int userId : mUserIds) {
350 // Don't do it for the primary user, it will become recursive.
351 if (userId == 0)
352 continue;
Amith Yamasani742a6712011-05-04 14:49:28 -0700353 mInstaller.createUserData(packageName, UserId.getUid(userId, uid),
Amith Yamasani0b285492011-04-14 17:35:23 -0700354 userId);
355 }
356 }
357
358 public void clearUserDataForAllUsers(String packageName) {
359 for (int userId : mUserIds) {
360 // Don't do it for the primary user, it will become recursive.
361 if (userId == 0)
362 continue;
363 mInstaller.clearUserData(packageName, userId);
364 }
365 }
366
367 public void removePackageForAllUsers(String packageName) {
368 for (int userId : mUserIds) {
369 // Don't do it for the primary user, it will become recursive.
370 if (userId == 0)
371 continue;
372 mInstaller.remove(packageName, userId);
373 }
374 }
375
376 /**
377 * Caches the list of user ids in an array, adjusting the array size when necessary.
378 */
379 private void updateUserIds() {
380 if (mUserIds == null || mUserIds.length != mUsers.size()) {
381 mUserIds = new int[mUsers.size()];
382 }
383 for (int i = 0; i < mUsers.size(); i++) {
384 mUserIds[i] = mUsers.keyAt(i);
385 }
386 }
387
388 /**
389 * Returns the next available user id, filling in any holes in the ids.
Amith Yamasani742a6712011-05-04 14:49:28 -0700390 * TODO: May not be a good idea to recycle ids, in case it results in confusion
391 * for data and battery stats collection, or unexpected cross-talk.
Amith Yamasani0b285492011-04-14 17:35:23 -0700392 * @return
393 */
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700394 private int getNextAvailableId() {
395 int i = 0;
396 while (i < Integer.MAX_VALUE) {
397 if (mUsers.indexOfKey(i) < 0) {
398 break;
399 }
400 i++;
401 }
402 return i;
403 }
404
Amith Yamasani0b285492011-04-14 17:35:23 -0700405 private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
406 // mInstaller may not be available for unit-tests.
407 if (mInstaller == null || apps == null) return true;
408
409 final long startTime = SystemClock.elapsedRealtime();
410 // Create the user path
411 userPath.mkdir();
412 FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
413 | FileUtils.S_IXOTH, -1, -1);
414
Amith Yamasani742a6712011-05-04 14:49:28 -0700415 mInstaller.cloneUserData(0, id, false);
416
Amith Yamasani0b285492011-04-14 17:35:23 -0700417 final long stopTime = SystemClock.elapsedRealtime();
418 Log.i(LOG_TAG,
419 "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700420 return true;
421 }
422
423 private boolean removePackageFolders(int id) {
Amith Yamasani0b285492011-04-14 17:35:23 -0700424 // mInstaller may not be available for unit-tests.
425 if (mInstaller == null) return true;
426
427 mInstaller.removeUserDataDirs(id);
Amith Yamasani4b2e9342011-03-31 12:38:53 -0700428 return true;
429 }
430}