UserManagerService.java revision 258848d2ae04f447ff1c18023fa76b139fcc0862
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
17package com.android.server.pm;
18
19import static android.os.ParcelFileDescriptor.MODE_CREATE;
20import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
21
22import com.android.internal.util.ArrayUtils;
23import com.android.internal.util.FastXmlSerializer;
24
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.UserInfo;
30import android.os.Binder;
31import android.os.Environment;
32import android.os.FileUtils;
33import android.os.IUserManager;
34import android.os.ParcelFileDescriptor;
35import android.os.Process;
36import android.os.SystemClock;
37import android.os.UserId;
38import android.util.Log;
39import android.util.Slog;
40import android.util.SparseArray;
41import android.util.Xml;
42
43import java.io.BufferedOutputStream;
44import java.io.File;
45import java.io.FileInputStream;
46import java.io.FileNotFoundException;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.util.ArrayList;
50import java.util.List;
51
52import org.xmlpull.v1.XmlPullParser;
53import org.xmlpull.v1.XmlPullParserException;
54import org.xmlpull.v1.XmlSerializer;
55
56public class UserManagerService extends IUserManager.Stub {
57
58    private static final String TAG = "UserManagerService";
59
60    private static final String TAG_NAME = "name";
61
62    private static final String ATTR_FLAGS = "flags";
63
64    private static final String ATTR_ICON_PATH = "icon";
65
66    private static final String ATTR_ID = "id";
67
68    private static final String TAG_USERS = "users";
69
70    private static final String TAG_USER = "user";
71
72    private static final String LOG_TAG = "UserManager";
73
74    private static final String USER_INFO_DIR = "system" + File.separator + "users";
75    private static final String USER_LIST_FILENAME = "userlist.xml";
76    private static final String USER_PHOTO_FILENAME = "photo.png";
77
78    private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
79
80    private final File mUsersDir;
81    private final File mUserListFile;
82    private int[] mUserIds;
83    private boolean mGuestEnabled;
84
85    private Installer mInstaller;
86    private File mBaseUserPath;
87    private Context mContext;
88    private static UserManagerService sInstance;
89    private PackageManagerService mPm;
90
91    public synchronized static UserManagerService getInstance(Context context) {
92        if (sInstance == null) {
93            sInstance = new UserManagerService(context);
94        }
95        return sInstance;
96    }
97
98    /**
99     * Available for testing purposes.
100     */
101    UserManagerService(File dataDir, File baseUserPath) {
102        mUsersDir = new File(dataDir, USER_INFO_DIR);
103        mUsersDir.mkdirs();
104        // Make zeroth user directory, for services to migrate their files to that location
105        File userZeroDir = new File(mUsersDir, "0");
106        userZeroDir.mkdirs();
107        mBaseUserPath = baseUserPath;
108        FileUtils.setPermissions(mUsersDir.toString(),
109                FileUtils.S_IRWXU|FileUtils.S_IRWXG
110                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
111                -1, -1);
112        mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
113        readUserList();
114    }
115
116    public UserManagerService(Context context) {
117        this(Environment.getDataDirectory(), new File(Environment.getDataDirectory(), "user"));
118        mContext = context;
119    }
120
121    void setInstaller(PackageManagerService pm, Installer installer) {
122        mInstaller = installer;
123        mPm = pm;
124    }
125
126    @Override
127    public List<UserInfo> getUsers() {
128        enforceSystemOrRoot("Only the system can query users");
129        synchronized (mUsers) {
130            ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
131            for (int i = 0; i < mUsers.size(); i++) {
132                users.add(mUsers.valueAt(i));
133            }
134            return users;
135        }
136    }
137
138    @Override
139    public UserInfo getUserInfo(int userId) {
140        enforceSystemOrRoot("Only the system can query user");
141        synchronized (mUsers) {
142            UserInfo info = mUsers.get(userId);
143            return info;
144        }
145    }
146
147    public boolean exists(int userId) {
148        synchronized (mUsers) {
149            return ArrayUtils.contains(mUserIds, userId);
150        }
151    }
152
153    @Override
154    public void setUserName(int userId, String name) {
155        enforceSystemOrRoot("Only the system can rename users");
156        synchronized (mUsers) {
157            UserInfo info = mUsers.get(userId);
158            if (name != null && !name.equals(info.name)) {
159                info.name = name;
160                writeUserLocked(info);
161            }
162        }
163    }
164
165    @Override
166    public ParcelFileDescriptor setUserIcon(int userId) {
167        enforceSystemOrRoot("Only the system can update users");
168        synchronized (mUsers) {
169            UserInfo info = mUsers.get(userId);
170            if (info == null) return null;
171            ParcelFileDescriptor fd = updateIconBitmapLocked(info);
172            if (fd != null) {
173                writeUserLocked(info);
174            }
175            return fd;
176        }
177    }
178
179    @Override
180    public void setGuestEnabled(boolean enable) {
181        enforceSystemOrRoot("Only the system can enable guest users");
182        synchronized (mUsers) {
183            if (mGuestEnabled != enable) {
184                mGuestEnabled = enable;
185                // Erase any guest user that currently exists
186                for (int i = 0; i < mUsers.size(); i++) {
187                    UserInfo user = mUsers.valueAt(i);
188                    if (user.isGuest()) {
189                        if (!enable) {
190                            removeUser(user.id);
191                        }
192                        return;
193                    }
194                }
195                // No guest was found
196                if (enable) {
197                    createUser("Guest", UserInfo.FLAG_GUEST);
198                }
199            }
200        }
201    }
202
203    @Override
204    public boolean isGuestEnabled() {
205        synchronized (mUsers) {
206            return mGuestEnabled;
207        }
208    }
209
210    @Override
211    public void wipeUser(int userHandle) {
212        enforceSystemOrRoot("Only the system can wipe users");
213        // TODO:
214    }
215
216    /**
217     * Enforces that only the system UID or root's UID can call a method exposed
218     * via Binder.
219     *
220     * @param message used as message if SecurityException is thrown
221     * @throws SecurityException if the caller is not system or root
222     */
223    private static final void enforceSystemOrRoot(String message) {
224        final int uid = Binder.getCallingUid();
225        if (uid != Process.SYSTEM_UID && uid != 0) {
226            throw new SecurityException(message);
227        }
228    }
229
230    private ParcelFileDescriptor updateIconBitmapLocked(UserInfo info) {
231        try {
232            File dir = new File(mUsersDir, Integer.toString(info.id));
233            File file = new File(dir, USER_PHOTO_FILENAME);
234            if (!dir.exists()) {
235                dir.mkdir();
236                FileUtils.setPermissions(
237                        dir.getPath(),
238                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
239                        -1, -1);
240            }
241            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
242                    MODE_CREATE|MODE_READ_WRITE);
243            info.iconPath = file.getAbsolutePath();
244            return fd;
245        } catch (FileNotFoundException e) {
246            Slog.w(TAG, "Error setting photo for user ", e);
247        }
248        return null;
249    }
250
251    /**
252     * Returns an array of user ids. This array is cached here for quick access, so do not modify or
253     * cache it elsewhere.
254     * @return the array of user ids.
255     */
256    int[] getUserIds() {
257        return mUserIds;
258    }
259
260    private void readUserList() {
261        synchronized (mUsers) {
262            readUserListLocked();
263        }
264    }
265
266    private void readUserListLocked() {
267        mGuestEnabled = false;
268        if (!mUserListFile.exists()) {
269            fallbackToSingleUserLocked();
270            return;
271        }
272        FileInputStream fis = null;
273        try {
274            fis = new FileInputStream(mUserListFile);
275            XmlPullParser parser = Xml.newPullParser();
276            parser.setInput(fis, null);
277            int type;
278            while ((type = parser.next()) != XmlPullParser.START_TAG
279                    && type != XmlPullParser.END_DOCUMENT) {
280                ;
281            }
282
283            if (type != XmlPullParser.START_TAG) {
284                Slog.e(LOG_TAG, "Unable to read user list");
285                fallbackToSingleUserLocked();
286                return;
287            }
288
289            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
290                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
291                    String id = parser.getAttributeValue(null, ATTR_ID);
292                    UserInfo user = readUser(Integer.parseInt(id));
293                    if (user != null) {
294                        mUsers.put(user.id, user);
295                    }
296                    if (user.isGuest()) {
297                        mGuestEnabled = true;
298                    }
299                }
300            }
301            updateUserIdsLocked();
302        } catch (IOException ioe) {
303            fallbackToSingleUserLocked();
304        } catch (XmlPullParserException pe) {
305            fallbackToSingleUserLocked();
306        } finally {
307            if (fis != null) {
308                try {
309                    fis.close();
310                } catch (IOException e) {
311                }
312            }
313        }
314    }
315
316    private void fallbackToSingleUserLocked() {
317        // Create the primary user
318        UserInfo primary = new UserInfo(0, "Primary", null,
319                UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
320        mUsers.put(0, primary);
321        updateUserIdsLocked();
322
323        writeUserListLocked();
324        writeUserLocked(primary);
325    }
326
327    /*
328     * Writes the user file in this format:
329     *
330     * <user flags="20039023" id="0">
331     *   <name>Primary</name>
332     * </user>
333     */
334    private void writeUserLocked(UserInfo userInfo) {
335        FileOutputStream fos = null;
336        try {
337            final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
338            fos = new FileOutputStream(mUserFile);
339            final BufferedOutputStream bos = new BufferedOutputStream(fos);
340
341            // XmlSerializer serializer = XmlUtils.serializerInstance();
342            final XmlSerializer serializer = new FastXmlSerializer();
343            serializer.setOutput(bos, "utf-8");
344            serializer.startDocument(null, true);
345            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
346
347            serializer.startTag(null, TAG_USER);
348            serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
349            serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
350            if (userInfo.iconPath != null) {
351                serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
352            }
353
354            serializer.startTag(null, TAG_NAME);
355            serializer.text(userInfo.name);
356            serializer.endTag(null, TAG_NAME);
357
358            serializer.endTag(null, TAG_USER);
359
360            serializer.endDocument();
361        } catch (IOException ioe) {
362            Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
363        } finally {
364            if (fos != null) {
365                try {
366                    fos.close();
367                } catch (IOException ioe) {
368                }
369            }
370        }
371    }
372
373    /*
374     * Writes the user list file in this format:
375     *
376     * <users>
377     *   <user id="0"></user>
378     *   <user id="2"></user>
379     * </users>
380     */
381    private void writeUserListLocked() {
382        FileOutputStream fos = null;
383        try {
384            fos = new FileOutputStream(mUserListFile);
385            final BufferedOutputStream bos = new BufferedOutputStream(fos);
386
387            // XmlSerializer serializer = XmlUtils.serializerInstance();
388            final XmlSerializer serializer = new FastXmlSerializer();
389            serializer.setOutput(bos, "utf-8");
390            serializer.startDocument(null, true);
391            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
392
393            serializer.startTag(null, TAG_USERS);
394
395            for (int i = 0; i < mUsers.size(); i++) {
396                UserInfo user = mUsers.valueAt(i);
397                serializer.startTag(null, TAG_USER);
398                serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
399                serializer.endTag(null, TAG_USER);
400            }
401
402            serializer.endTag(null, TAG_USERS);
403
404            serializer.endDocument();
405        } catch (IOException ioe) {
406            Slog.e(LOG_TAG, "Error writing user list");
407        } finally {
408            if (fos != null) {
409                try {
410                    fos.close();
411                } catch (IOException ioe) {
412                }
413            }
414        }
415    }
416
417    private UserInfo readUser(int id) {
418        int flags = 0;
419        String name = null;
420        String iconPath = null;
421
422        FileInputStream fis = null;
423        try {
424            File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
425            fis = new FileInputStream(userFile);
426            XmlPullParser parser = Xml.newPullParser();
427            parser.setInput(fis, null);
428            int type;
429            while ((type = parser.next()) != XmlPullParser.START_TAG
430                    && type != XmlPullParser.END_DOCUMENT) {
431                ;
432            }
433
434            if (type != XmlPullParser.START_TAG) {
435                Slog.e(LOG_TAG, "Unable to read user " + id);
436                return null;
437            }
438
439            if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
440                String storedId = parser.getAttributeValue(null, ATTR_ID);
441                if (Integer.parseInt(storedId) != id) {
442                    Slog.e(LOG_TAG, "User id does not match the file name");
443                    return null;
444                }
445                String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
446                flags = Integer.parseInt(flagString);
447                iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
448
449                while ((type = parser.next()) != XmlPullParser.START_TAG
450                        && type != XmlPullParser.END_DOCUMENT) {
451                }
452                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
453                    type = parser.next();
454                    if (type == XmlPullParser.TEXT) {
455                        name = parser.getText();
456                    }
457                }
458            }
459
460            UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
461            return userInfo;
462
463        } catch (IOException ioe) {
464        } catch (XmlPullParserException pe) {
465        } finally {
466            if (fis != null) {
467                try {
468                    fis.close();
469                } catch (IOException e) {
470                }
471            }
472        }
473        return null;
474    }
475
476    @Override
477    public UserInfo createUser(String name, int flags) {
478        enforceSystemOrRoot("Only the system can create users");
479        int userId = getNextAvailableId();
480        UserInfo userInfo = new UserInfo(userId, name, null, flags);
481        File userPath = new File(mBaseUserPath, Integer.toString(userId));
482        if (!createPackageFolders(userId, userPath)) {
483            return null;
484        }
485        synchronized (mUsers) {
486            mUsers.put(userId, userInfo);
487            writeUserListLocked();
488            writeUserLocked(userInfo);
489            updateUserIdsLocked();
490        }
491        if (userInfo != null) {
492            Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
493            addedIntent.putExtra(Intent.EXTRA_USERID, userInfo.id);
494            mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
495        }
496        return userInfo;
497    }
498
499    /**
500     * Removes a user and all data directories created for that user. This method should be called
501     * after the user's processes have been terminated.
502     * @param id the user's id
503     */
504    public boolean removeUser(int userHandle) {
505        enforceSystemOrRoot("Only the system can remove users");
506        synchronized (mUsers) {
507            return removeUserLocked(userHandle);
508        }
509    }
510
511    private boolean removeUserLocked(int userHandle) {
512        final UserInfo user = mUsers.get(userHandle);
513        if (userHandle == 0 || user == null) {
514            return false;
515        }
516
517        mPm.cleanUpUser(userHandle);
518
519        // Remove this user from the list
520        mUsers.remove(userHandle);
521        // Remove user file
522        File userFile = new File(mUsersDir, userHandle + ".xml");
523        userFile.delete();
524        // Update the user list
525        writeUserListLocked();
526        updateUserIdsLocked();
527
528        // Let other services shutdown any activity
529        Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
530        addedIntent.putExtra(Intent.EXTRA_USERID, userHandle);
531        mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
532
533        removePackageFolders(userHandle);
534        return true;
535    }
536
537    public void installPackageForAllUsers(String packageName, int uid) {
538        for (int userId : mUserIds) {
539            // Don't do it for the primary user, it will become recursive.
540            if (userId == 0)
541                continue;
542            mInstaller.createUserData(packageName, UserId.getUid(userId, uid),
543                    userId);
544        }
545    }
546
547    public void clearUserDataForAllUsers(String packageName) {
548        for (int userId : mUserIds) {
549            // Don't do it for the primary user, it will become recursive.
550            if (userId == 0)
551                continue;
552            mInstaller.clearUserData(packageName, userId);
553        }
554    }
555
556    public void removePackageForAllUsers(String packageName) {
557        for (int userId : mUserIds) {
558            // Don't do it for the primary user, it will become recursive.
559            if (userId == 0)
560                continue;
561            mInstaller.remove(packageName, userId);
562        }
563    }
564
565    /**
566     * Caches the list of user ids in an array, adjusting the array size when necessary.
567     */
568    private void updateUserIdsLocked() {
569        if (mUserIds == null || mUserIds.length != mUsers.size()) {
570            mUserIds = new int[mUsers.size()];
571        }
572        for (int i = 0; i < mUsers.size(); i++) {
573            mUserIds[i] = mUsers.keyAt(i);
574        }
575    }
576
577    /**
578     * Returns the next available user id, filling in any holes in the ids.
579     * TODO: May not be a good idea to recycle ids, in case it results in confusion
580     * for data and battery stats collection, or unexpected cross-talk.
581     * @return
582     */
583    private int getNextAvailableId() {
584        int i = 0;
585        while (i < Integer.MAX_VALUE) {
586            if (mUsers.indexOfKey(i) < 0) {
587                break;
588            }
589            i++;
590        }
591        return i;
592    }
593
594    private boolean createPackageFolders(int id, File userPath) {
595        // mInstaller may not be available for unit-tests.
596        if (mInstaller == null) return true;
597
598        // Create the user path
599        userPath.mkdir();
600        FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
601                | FileUtils.S_IXOTH, -1, -1);
602
603        mInstaller.cloneUserData(0, id, false);
604
605        return true;
606    }
607
608    boolean removePackageFolders(int id) {
609        // mInstaller may not be available for unit-tests.
610        if (mInstaller == null) return true;
611
612        mInstaller.removeUserDataDirs(id);
613        return true;
614    }
615}
616