Home | History | Annotate | Download | only in pm
      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      */
     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 
     84     public UserManager(Installer installer, File baseUserPath) {
     85         this(Environment.getDataDirectory(), baseUserPath);
     86         mInstaller = installer;
     87     }
     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 
     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      */
    102     int[] getUserIds() {
    103         return mUserIds;
    104     }
    105 
    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 
    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      */
    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      */
    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 
    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 
    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      */
    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 
    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 
    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 
    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      */
    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      */
    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 
    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 
    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