• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.pm;
18 
19 import com.android.internal.util.FastXmlSerializer;
20 
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.content.pm.UserInfo;
24 import android.os.Environment;
25 import android.os.FileUtils;
26 import android.os.SystemClock;
27 import android.util.Log;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 import android.util.Xml;
31 
32 import java.io.BufferedOutputStream;
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 import org.xmlpull.v1.XmlPullParser;
41 import org.xmlpull.v1.XmlPullParserException;
42 import org.xmlpull.v1.XmlSerializer;
43 
44 public class UserManager {
45     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 
55     private static final String LOG_TAG = "UserManager";
56 
57     private static final String USER_INFO_DIR = "system" + File.separator + "users";
58     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;
64     private int[] mUserIds;
65 
66     private Installer mInstaller;
67     private File mBaseUserPath;
68 
69     /**
70      * Available for testing purposes.
71      */
UserManager(File dataDir, File baseUserPath)72     UserManager(File dataDir, File baseUserPath) {
73         mUsersDir = new File(dataDir, USER_INFO_DIR);
74         mUsersDir.mkdirs();
75         mBaseUserPath = baseUserPath;
76         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 
UserManager(Installer installer, File baseUserPath)84     public UserManager(Installer installer, File baseUserPath) {
85         this(Environment.getDataDirectory(), baseUserPath);
86         mInstaller = installer;
87     }
88 
getUsers()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 
97     /**
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      */
getUserIds()102     int[] getUserIds() {
103         return mUserIds;
104     }
105 
readUserList()106     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) {
124                 Slog.e(LOG_TAG, "Unable to read user list");
125                 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             }
138             updateUserIds();
139         } catch (IOException ioe) {
140             fallbackToSingleUser();
141         } catch (XmlPullParserException pe) {
142             fallbackToSingleUser();
143         }
144     }
145 
fallbackToSingleUser()146     private void fallbackToSingleUser() {
147         // Create the primary user
148         UserInfo primary = new UserInfo(0, "Primary",
149                 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
150         mUsers.put(0, primary);
151         updateUserIds();
152 
153         writeUserList();
154         writeUser(primary);
155     }
156 
157     /*
158      * Writes the user file in this format:
159      *
160      * <user flags="20039023" id="0">
161      *   <name>Primary</name>
162      * </user>
163      */
writeUser(UserInfo userInfo)164     private void writeUser(UserInfo userInfo) {
165         try {
166             final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
167             final FileOutputStream fos = new FileOutputStream(mUserFile);
168             final BufferedOutputStream bos = new BufferedOutputStream(fos);
169 
170             // XmlSerializer serializer = XmlUtils.serializerInstance();
171             final XmlSerializer serializer = new FastXmlSerializer();
172             serializer.setOutput(bos, "utf-8");
173             serializer.startDocument(null, true);
174             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
175 
176             serializer.startTag(null, TAG_USER);
177             serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
178             serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
179 
180             serializer.startTag(null, TAG_NAME);
181             serializer.text(userInfo.name);
182             serializer.endTag(null, TAG_NAME);
183 
184             serializer.endTag(null, TAG_USER);
185 
186             serializer.endDocument();
187         } catch (IOException ioe) {
188             Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
189         }
190     }
191 
192     /*
193      * Writes the user list file in this format:
194      *
195      * <users>
196      *   <user id="0"></user>
197      *   <user id="2"></user>
198      * </users>
199      */
writeUserList()200     private void writeUserList() {
201         try {
202             final FileOutputStream fos = new FileOutputStream(mUserListFile);
203             final BufferedOutputStream bos = new BufferedOutputStream(fos);
204 
205             // XmlSerializer serializer = XmlUtils.serializerInstance();
206             final XmlSerializer serializer = new FastXmlSerializer();
207             serializer.setOutput(bos, "utf-8");
208             serializer.startDocument(null, true);
209             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
210 
211             serializer.startTag(null, TAG_USERS);
212 
213             for (int i = 0; i < mUsers.size(); i++) {
214                 UserInfo user = mUsers.valueAt(i);
215                 serializer.startTag(null, TAG_USER);
216                 serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
217                 serializer.endTag(null, TAG_USER);
218             }
219 
220             serializer.endTag(null, TAG_USERS);
221 
222             serializer.endDocument();
223         } catch (IOException ioe) {
224             Slog.e(LOG_TAG, "Error writing user list");
225         }
226     }
227 
readUser(int id)228     private UserInfo readUser(int id) {
229         int flags = 0;
230         String name = null;
231 
232         FileInputStream fis = null;
233         try {
234             File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
235             fis = new FileInputStream(userFile);
236             XmlPullParser parser = Xml.newPullParser();
237             parser.setInput(fis, null);
238             int type;
239             while ((type = parser.next()) != XmlPullParser.START_TAG
240                     && type != XmlPullParser.END_DOCUMENT) {
241                 ;
242             }
243 
244             if (type != XmlPullParser.START_TAG) {
245                 Slog.e(LOG_TAG, "Unable to read user " + id);
246                 return null;
247             }
248 
249             if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
250                 String storedId = parser.getAttributeValue(null, ATTR_ID);
251                 if (Integer.parseInt(storedId) != id) {
252                     Slog.e(LOG_TAG, "User id does not match the file name");
253                     return null;
254                 }
255                 String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
256                 flags = Integer.parseInt(flagString);
257 
258                 while ((type = parser.next()) != XmlPullParser.START_TAG
259                         && type != XmlPullParser.END_DOCUMENT) {
260                 }
261                 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
262                     type = parser.next();
263                     if (type == XmlPullParser.TEXT) {
264                         name = parser.getText();
265                     }
266                 }
267             }
268             fis.close();
269 
270             UserInfo userInfo = new UserInfo(id, name, flags);
271             return userInfo;
272 
273         } catch (IOException ioe) {
274         } catch (XmlPullParserException pe) {
275         }
276         return null;
277     }
278 
createUser(String name, int flags, List<ApplicationInfo> apps)279     public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
280         int userId = getNextAvailableId();
281         UserInfo userInfo = new UserInfo(userId, name, flags);
282         File userPath = new File(mBaseUserPath, Integer.toString(userId));
283         if (!createPackageFolders(userId, userPath, apps)) {
284             return null;
285         }
286         mUsers.put(userId, userInfo);
287         writeUserList();
288         writeUser(userInfo);
289         updateUserIds();
290         return userInfo;
291     }
292 
293     /**
294      * Removes a user and all data directories created for that user. This method should be called
295      * after the user's processes have been terminated.
296      * @param id the user's id
297      */
removeUser(int id)298     public void removeUser(int id) {
299         // Remove from the list
300         UserInfo userInfo = mUsers.get(id);
301         if (userInfo != null) {
302             // Remove this user from the list
303             mUsers.remove(id);
304             // Remove user file
305             File userFile = new File(mUsersDir, id + ".xml");
306             userFile.delete();
307             // Update the user list
308             writeUserList();
309             // Remove the data directories for all packages for this user
310             removePackageFolders(id);
311             updateUserIds();
312         }
313     }
314 
installPackageForAllUsers(String packageName, int uid)315     public void installPackageForAllUsers(String packageName, int uid) {
316         for (int userId : mUserIds) {
317             // Don't do it for the primary user, it will become recursive.
318             if (userId == 0)
319                 continue;
320             mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid),
321                     userId);
322         }
323     }
324 
clearUserDataForAllUsers(String packageName)325     public void clearUserDataForAllUsers(String packageName) {
326         for (int userId : mUserIds) {
327             // Don't do it for the primary user, it will become recursive.
328             if (userId == 0)
329                 continue;
330             mInstaller.clearUserData(packageName, userId);
331         }
332     }
333 
removePackageForAllUsers(String packageName)334     public void removePackageForAllUsers(String packageName) {
335         for (int userId : mUserIds) {
336             // Don't do it for the primary user, it will become recursive.
337             if (userId == 0)
338                 continue;
339             mInstaller.remove(packageName, userId);
340         }
341     }
342 
343     /**
344      * Caches the list of user ids in an array, adjusting the array size when necessary.
345      */
updateUserIds()346     private void updateUserIds() {
347         if (mUserIds == null || mUserIds.length != mUsers.size()) {
348             mUserIds = new int[mUsers.size()];
349         }
350         for (int i = 0; i < mUsers.size(); i++) {
351             mUserIds[i] = mUsers.keyAt(i);
352         }
353     }
354 
355     /**
356      * Returns the next available user id, filling in any holes in the ids.
357      * @return
358      */
getNextAvailableId()359     private int getNextAvailableId() {
360         int i = 0;
361         while (i < Integer.MAX_VALUE) {
362             if (mUsers.indexOfKey(i) < 0) {
363                 break;
364             }
365             i++;
366         }
367         return i;
368     }
369 
createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps)370     private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
371         // mInstaller may not be available for unit-tests.
372         if (mInstaller == null || apps == null) return true;
373 
374         final long startTime = SystemClock.elapsedRealtime();
375         // Create the user path
376         userPath.mkdir();
377         FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
378                 | FileUtils.S_IXOTH, -1, -1);
379 
380         // Create the individual data directories
381         for (ApplicationInfo app : apps) {
382             if (app.uid > android.os.Process.FIRST_APPLICATION_UID
383                     && app.uid < PackageManager.PER_USER_RANGE) {
384                 mInstaller.createUserData(app.packageName,
385                         PackageManager.getUid(id, app.uid), id);
386             }
387         }
388         final long stopTime = SystemClock.elapsedRealtime();
389         Log.i(LOG_TAG,
390                 "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
391         return true;
392     }
393 
removePackageFolders(int id)394     private boolean removePackageFolders(int id) {
395         // mInstaller may not be available for unit-tests.
396         if (mInstaller == null) return true;
397 
398         mInstaller.removeUserDataDirs(id);
399         return true;
400     }
401 }
402