• 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.ArrayUtils;
20 import com.android.internal.util.FastXmlSerializer;
21 
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.UserInfo;
25 import android.os.Environment;
26 import android.os.FileUtils;
27 import android.os.SystemClock;
28 import android.os.UserId;
29 import android.util.Log;
30 import android.util.Slog;
31 import android.util.SparseArray;
32 import android.util.Xml;
33 
34 import java.io.BufferedOutputStream;
35 import java.io.File;
36 import java.io.FileInputStream;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 import org.xmlpull.v1.XmlPullParser;
43 import org.xmlpull.v1.XmlPullParserException;
44 import org.xmlpull.v1.XmlSerializer;
45 
46 public class UserManager {
47     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 
57     private static final String LOG_TAG = "UserManager";
58 
59     private static final String USER_INFO_DIR = "system" + File.separator + "users";
60     private static final String USER_LIST_FILENAME = "userlist.xml";
61 
62     private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
63 
64     private final File mUsersDir;
65     private final File mUserListFile;
66     private int[] mUserIds;
67 
68     private Installer mInstaller;
69     private File mBaseUserPath;
70 
71     /**
72      * Available for testing purposes.
73      */
UserManager(File dataDir, File baseUserPath)74     UserManager(File dataDir, File baseUserPath) {
75         mUsersDir = new File(dataDir, USER_INFO_DIR);
76         mUsersDir.mkdirs();
77         // Make zeroth user directory, for services to migrate their files to that location
78         File userZeroDir = new File(mUsersDir, "0");
79         userZeroDir.mkdirs();
80         mBaseUserPath = baseUserPath;
81         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 
UserManager(Installer installer, File baseUserPath)89     public UserManager(Installer installer, File baseUserPath) {
90         this(Environment.getDataDirectory(), baseUserPath);
91         mInstaller = installer;
92     }
93 
getUsers()94     public List<UserInfo> getUsers() {
95         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;
101         }
102     }
103 
getUser(int userId)104     public UserInfo getUser(int userId) {
105         synchronized (mUsers) {
106             UserInfo info = mUsers.get(userId);
107             return info;
108         }
109     }
110 
exists(int userId)111     public boolean exists(int userId) {
112         synchronized (mUsers) {
113             return ArrayUtils.contains(mUserIds, userId);
114         }
115     }
116 
updateUserName(int userId, String name)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         }
125     }
126 
127     /**
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      */
getUserIds()132     int[] getUserIds() {
133         return mUserIds;
134     }
135 
readUserList()136     private void readUserList() {
137         synchronized (mUsers) {
138             readUserListLocked();
139         }
140     }
141 
readUserListLocked()142     private void readUserListLocked() {
143         if (!mUserListFile.exists()) {
144             fallbackToSingleUserLocked();
145             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) {
159                 Slog.e(LOG_TAG, "Unable to read user list");
160                 fallbackToSingleUserLocked();
161                 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             }
173             updateUserIdsLocked();
174         } catch (IOException ioe) {
175             fallbackToSingleUserLocked();
176         } catch (XmlPullParserException pe) {
177             fallbackToSingleUserLocked();
178         } finally {
179             if (fis != null) {
180                 try {
181                     fis.close();
182                 } catch (IOException e) {
183                 }
184             }
185         }
186     }
187 
fallbackToSingleUserLocked()188     private void fallbackToSingleUserLocked() {
189         // Create the primary user
190         UserInfo primary = new UserInfo(0, "Primary",
191                 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
192         mUsers.put(0, primary);
193         updateUserIdsLocked();
194 
195         writeUserListLocked();
196         writeUserLocked(primary);
197     }
198 
199     /*
200      * Writes the user file in this format:
201      *
202      * <user flags="20039023" id="0">
203      *   <name>Primary</name>
204      * </user>
205      */
writeUserLocked(UserInfo userInfo)206     private void writeUserLocked(UserInfo userInfo) {
207         FileOutputStream fos = null;
208         try {
209             final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
210             fos = new FileOutputStream(mUserFile);
211             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) {
231             Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
232         } finally {
233             if (fos != null) {
234                 try {
235                     fos.close();
236                 } catch (IOException ioe) {
237                 }
238             }
239         }
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      */
writeUserListLocked()250     private void writeUserListLocked() {
251         FileOutputStream fos = null;
252         try {
253             fos = new FileOutputStream(mUserListFile);
254             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);
269             }
270 
271             serializer.endTag(null, TAG_USERS);
272 
273             serializer.endDocument();
274         } catch (IOException ioe) {
275             Slog.e(LOG_TAG, "Error writing user list");
276         } finally {
277             if (fos != null) {
278                 try {
279                     fos.close();
280                 } catch (IOException ioe) {
281                 }
282             }
283         }
284     }
285 
readUser(int id)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) {
303                 Slog.e(LOG_TAG, "Unable to read user " + id);
304                 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) {
310                     Slog.e(LOG_TAG, "User id does not match the file name");
311                     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             }
326 
327             UserInfo userInfo = new UserInfo(id, name, flags);
328             return userInfo;
329 
330         } catch (IOException ioe) {
331         } catch (XmlPullParserException pe) {
332         } finally {
333             if (fis != null) {
334                 try {
335                     fis.close();
336                 } catch (IOException e) {
337                 }
338             }
339         }
340         return null;
341     }
342 
createUser(String name, int flags)343     public UserInfo createUser(String name, int flags) {
344         int userId = getNextAvailableId();
345         UserInfo userInfo = new UserInfo(userId, name, flags);
346         File userPath = new File(mBaseUserPath, Integer.toString(userId));
347         if (!createPackageFolders(userId, userPath)) {
348             return null;
349         }
350         synchronized (mUsers) {
351             mUsers.put(userId, userInfo);
352             writeUserListLocked();
353             writeUserLocked(userInfo);
354             updateUserIdsLocked();
355         }
356         return userInfo;
357     }
358 
359     /**
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      */
removeUser(int id)364     public boolean removeUser(int id) {
365         synchronized (mUsers) {
366             return removeUserLocked(id);
367         }
368     }
369 
removeUserLocked(int id)370     private boolean removeUserLocked(int id) {
371         // 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();
379             // Update the user list
380             writeUserListLocked();
381             updateUserIdsLocked();
382             return true;
383         }
384         return false;
385     }
386 
installPackageForAllUsers(String packageName, int uid)387     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;
392             mInstaller.createUserData(packageName, UserId.getUid(userId, uid),
393                     userId);
394         }
395     }
396 
clearUserDataForAllUsers(String packageName)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 
removePackageForAllUsers(String packageName)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      */
updateUserIdsLocked()418     private void updateUserIdsLocked() {
419         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.
429      * 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.
431      * @return
432      */
getNextAvailableId()433     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 
createPackageFolders(int id, File userPath)444     private boolean createPackageFolders(int id, File userPath) {
445         // mInstaller may not be available for unit-tests.
446         if (mInstaller == null) return true;
447 
448         // 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 
453         mInstaller.cloneUserData(0, id, false);
454 
455         return true;
456     }
457 
removePackageFolders(int id)458     boolean removePackageFolders(int id) {
459         // mInstaller may not be available for unit-tests.
460         if (mInstaller == null) return true;
461 
462         mInstaller.removeUserDataDirs(id);
463         return true;
464     }
465 }
466