UserManagerService.java revision 07a0ede729f9b2f472b659b67b4cbc3602aa289a
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.app.ActivityManager;
26import android.app.ActivityManagerNative;
27import android.app.IStopUserCallback;
28import android.content.Context;
29import android.content.Intent;
30import android.content.pm.PackageManager;
31import android.content.pm.UserInfo;
32import android.os.Binder;
33import android.os.Environment;
34import android.os.FileUtils;
35import android.os.IUserManager;
36import android.os.ParcelFileDescriptor;
37import android.os.Process;
38import android.os.RemoteException;
39import android.os.UserHandle;
40import android.util.AtomicFile;
41import android.util.Slog;
42import android.util.SparseArray;
43import android.util.Xml;
44
45import java.io.BufferedOutputStream;
46import java.io.File;
47import java.io.FileInputStream;
48import java.io.FileNotFoundException;
49import java.io.FileOutputStream;
50import java.io.IOException;
51import java.util.ArrayList;
52import java.util.List;
53
54import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56import org.xmlpull.v1.XmlSerializer;
57
58public class UserManagerService extends IUserManager.Stub {
59
60    private static final String LOG_TAG = "UserManagerService";
61
62    private static final String TAG_NAME = "name";
63    private static final String ATTR_FLAGS = "flags";
64    private static final String ATTR_ICON_PATH = "icon";
65    private static final String ATTR_ID = "id";
66    private static final String ATTR_SERIAL_NO = "serialNumber";
67    private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
68    private static final String TAG_USERS = "users";
69    private static final String TAG_USER = "user";
70
71    private static final String USER_INFO_DIR = "system" + File.separator + "users";
72    private static final String USER_LIST_FILENAME = "userlist.xml";
73    private static final String USER_PHOTO_FILENAME = "photo.png";
74
75    private final Context mContext;
76    private final PackageManagerService mPm;
77    private final Object mInstallLock;
78    private final Object mPackagesLock;
79
80    private final File mUsersDir;
81    private final File mUserListFile;
82    private final File mBaseUserPath;
83
84    private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
85
86    private final int mUserLimit;
87
88    private int[] mUserIds;
89    private boolean mGuestEnabled;
90    private int mNextSerialNumber;
91
92    private static UserManagerService sInstance;
93
94    public static UserManagerService getInstance() {
95        synchronized (UserManagerService.class) {
96            return sInstance;
97        }
98    }
99
100    /**
101     * Available for testing purposes.
102     */
103    UserManagerService(File dataDir, File baseUserPath) {
104        this(null, null, new Object(), new Object(), dataDir, baseUserPath);
105    }
106
107    /**
108     * Called by package manager to create the service.  This is closely
109     * associated with the package manager, and the given lock is the
110     * package manager's own lock.
111     */
112    UserManagerService(Context context, PackageManagerService pm,
113            Object installLock, Object packagesLock) {
114        this(context, pm, installLock, packagesLock,
115                Environment.getDataDirectory(),
116                new File(Environment.getDataDirectory(), "user"));
117    }
118
119    /**
120     * Available for testing purposes.
121     */
122    private UserManagerService(Context context, PackageManagerService pm,
123            Object installLock, Object packagesLock,
124            File dataDir, File baseUserPath) {
125        synchronized (UserManagerService.class) {
126            mContext = context;
127            mPm = pm;
128            mInstallLock = installLock;
129            mPackagesLock = packagesLock;
130            mUserLimit = mContext.getResources().getInteger(
131                    com.android.internal.R.integer.config_multiuserMaximumUsers);
132            mUsersDir = new File(dataDir, USER_INFO_DIR);
133            mUsersDir.mkdirs();
134            // Make zeroth user directory, for services to migrate their files to that location
135            File userZeroDir = new File(mUsersDir, "0");
136            userZeroDir.mkdirs();
137            mBaseUserPath = baseUserPath;
138            FileUtils.setPermissions(mUsersDir.toString(),
139                    FileUtils.S_IRWXU|FileUtils.S_IRWXG
140                    |FileUtils.S_IROTH|FileUtils.S_IXOTH,
141                    -1, -1);
142            mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
143            readUserList();
144            sInstance = this;
145        }
146    }
147
148    @Override
149    public List<UserInfo> getUsers() {
150        checkManageUsersPermission("query users");
151        synchronized (mPackagesLock) {
152            ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
153            for (int i = 0; i < mUsers.size(); i++) {
154                users.add(mUsers.valueAt(i));
155            }
156            return users;
157        }
158    }
159
160    @Override
161    public UserInfo getUserInfo(int userId) {
162        checkManageUsersPermission("query user");
163        synchronized (mPackagesLock) {
164            return getUserInfoLocked(userId);
165        }
166    }
167
168    /*
169     * Should be locked on mUsers before calling this.
170     */
171    private UserInfo getUserInfoLocked(int userId) {
172        return mUsers.get(userId);
173    }
174
175    public boolean exists(int userId) {
176        synchronized (mPackagesLock) {
177            return ArrayUtils.contains(mUserIds, userId);
178        }
179    }
180
181    @Override
182    public void setUserName(int userId, String name) {
183        checkManageUsersPermission("rename users");
184        synchronized (mPackagesLock) {
185            UserInfo info = mUsers.get(userId);
186            if (name != null && !name.equals(info.name)) {
187                info.name = name;
188                writeUserLocked(info);
189            }
190        }
191    }
192
193    @Override
194    public ParcelFileDescriptor setUserIcon(int userId) {
195        checkManageUsersPermission("update users");
196        synchronized (mPackagesLock) {
197            UserInfo info = mUsers.get(userId);
198            if (info == null) return null;
199            ParcelFileDescriptor fd = updateIconBitmapLocked(info);
200            if (fd != null) {
201                writeUserLocked(info);
202            }
203            return fd;
204        }
205    }
206
207    @Override
208    public void setGuestEnabled(boolean enable) {
209        checkManageUsersPermission("enable guest users");
210        synchronized (mPackagesLock) {
211            if (mGuestEnabled != enable) {
212                mGuestEnabled = enable;
213                // Erase any guest user that currently exists
214                for (int i = 0; i < mUsers.size(); i++) {
215                    UserInfo user = mUsers.valueAt(i);
216                    if (user.isGuest()) {
217                        if (!enable) {
218                            removeUser(user.id);
219                        }
220                        return;
221                    }
222                }
223                // No guest was found
224                if (enable) {
225                    createUser("Guest", UserInfo.FLAG_GUEST);
226                }
227            }
228        }
229    }
230
231    @Override
232    public boolean isGuestEnabled() {
233        synchronized (mPackagesLock) {
234            return mGuestEnabled;
235        }
236    }
237
238    @Override
239    public void wipeUser(int userHandle) {
240        checkManageUsersPermission("wipe user");
241        // TODO:
242    }
243
244    public void makeInitialized(int userId) {
245        checkManageUsersPermission("makeInitialized");
246        synchronized (mPackagesLock) {
247            UserInfo info = mUsers.get(userId);
248            if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
249                info.flags |= UserInfo.FLAG_INITIALIZED;
250                writeUserLocked(info);
251            }
252        }
253    }
254
255    /**
256     * Check if we've hit the limit of how many users can be created.
257     */
258    private boolean isUserLimitReachedLocked() {
259        int nUsers = mUsers.size();
260        return nUsers >= mUserLimit;
261    }
262
263    /**
264     * Enforces that only the system UID or root's UID or apps that have the
265     * {@link android.Manifest.permission.MANAGE_USERS MANAGE_USERS}
266     * permission can make certain calls to the UserManager.
267     *
268     * @param message used as message if SecurityException is thrown
269     * @throws SecurityException if the caller is not system or root
270     */
271    private static final void checkManageUsersPermission(String message) {
272        final int uid = Binder.getCallingUid();
273        if (uid != Process.SYSTEM_UID && uid != 0
274                && ActivityManager.checkComponentPermission(
275                        android.Manifest.permission.MANAGE_USERS,
276                        uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
277            throw new SecurityException("You need MANAGE_USERS permission to: " + message);
278        }
279    }
280
281    private ParcelFileDescriptor updateIconBitmapLocked(UserInfo info) {
282        try {
283            File dir = new File(mUsersDir, Integer.toString(info.id));
284            File file = new File(dir, USER_PHOTO_FILENAME);
285            if (!dir.exists()) {
286                dir.mkdir();
287                FileUtils.setPermissions(
288                        dir.getPath(),
289                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
290                        -1, -1);
291            }
292            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
293                    MODE_CREATE|MODE_READ_WRITE);
294            info.iconPath = file.getAbsolutePath();
295            return fd;
296        } catch (FileNotFoundException e) {
297            Slog.w(LOG_TAG, "Error setting photo for user ", e);
298        }
299        return null;
300    }
301
302    /**
303     * Returns an array of user ids. This array is cached here for quick access, so do not modify or
304     * cache it elsewhere.
305     * @return the array of user ids.
306     */
307    public int[] getUserIds() {
308        synchronized (mPackagesLock) {
309            return mUserIds;
310        }
311    }
312
313    int[] getUserIdsLPr() {
314        return mUserIds;
315    }
316
317    private void readUserList() {
318        synchronized (mPackagesLock) {
319            readUserListLocked();
320        }
321    }
322
323    private void readUserListLocked() {
324        mGuestEnabled = false;
325        if (!mUserListFile.exists()) {
326            fallbackToSingleUserLocked();
327            return;
328        }
329        FileInputStream fis = null;
330        AtomicFile userListFile = new AtomicFile(mUserListFile);
331        try {
332            fis = userListFile.openRead();
333            XmlPullParser parser = Xml.newPullParser();
334            parser.setInput(fis, null);
335            int type;
336            while ((type = parser.next()) != XmlPullParser.START_TAG
337                    && type != XmlPullParser.END_DOCUMENT) {
338                ;
339            }
340
341            if (type != XmlPullParser.START_TAG) {
342                Slog.e(LOG_TAG, "Unable to read user list");
343                fallbackToSingleUserLocked();
344                return;
345            }
346
347            mNextSerialNumber = -1;
348            if (parser.getName().equals(TAG_USERS)) {
349                String lastSerialNumber = parser.getAttributeValue(null, ATTR_NEXT_SERIAL_NO);
350                if (lastSerialNumber != null) {
351                    mNextSerialNumber = Integer.parseInt(lastSerialNumber);
352                }
353            }
354
355            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
356                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
357                    String id = parser.getAttributeValue(null, ATTR_ID);
358                    UserInfo user = readUser(Integer.parseInt(id));
359                    if (user != null) {
360                        mUsers.put(user.id, user);
361                        if (user.isGuest()) {
362                            mGuestEnabled = true;
363                        }
364                        if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
365                            mNextSerialNumber = user.id + 1;
366                        }
367                    }
368                }
369            }
370            updateUserIdsLocked();
371        } catch (IOException ioe) {
372            fallbackToSingleUserLocked();
373        } catch (XmlPullParserException pe) {
374            fallbackToSingleUserLocked();
375        } finally {
376            if (fis != null) {
377                try {
378                    fis.close();
379                } catch (IOException e) {
380                }
381            }
382        }
383    }
384
385    private void fallbackToSingleUserLocked() {
386        // Create the primary user
387        UserInfo primary = new UserInfo(0, "Primary", null,
388                UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
389        mUsers.put(0, primary);
390        updateUserIdsLocked();
391
392        writeUserListLocked();
393        writeUserLocked(primary);
394    }
395
396    /*
397     * Writes the user file in this format:
398     *
399     * <user flags="20039023" id="0">
400     *   <name>Primary</name>
401     * </user>
402     */
403    private void writeUserLocked(UserInfo userInfo) {
404        FileOutputStream fos = null;
405        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + ".xml"));
406        try {
407            fos = userFile.startWrite();
408            final BufferedOutputStream bos = new BufferedOutputStream(fos);
409
410            // XmlSerializer serializer = XmlUtils.serializerInstance();
411            final XmlSerializer serializer = new FastXmlSerializer();
412            serializer.setOutput(bos, "utf-8");
413            serializer.startDocument(null, true);
414            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
415
416            serializer.startTag(null, TAG_USER);
417            serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
418            serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
419            serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
420            if (userInfo.iconPath != null) {
421                serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
422            }
423
424            serializer.startTag(null, TAG_NAME);
425            serializer.text(userInfo.name);
426            serializer.endTag(null, TAG_NAME);
427
428            serializer.endTag(null, TAG_USER);
429
430            serializer.endDocument();
431            userFile.finishWrite(fos);
432        } catch (Exception ioe) {
433            Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
434            userFile.failWrite(fos);
435        }
436    }
437
438    /*
439     * Writes the user list file in this format:
440     *
441     * <users nextSerialNumber="3">
442     *   <user id="0"></user>
443     *   <user id="2"></user>
444     * </users>
445     */
446    private void writeUserListLocked() {
447        FileOutputStream fos = null;
448        AtomicFile userListFile = new AtomicFile(mUserListFile);
449        try {
450            fos = userListFile.startWrite();
451            final BufferedOutputStream bos = new BufferedOutputStream(fos);
452
453            // XmlSerializer serializer = XmlUtils.serializerInstance();
454            final XmlSerializer serializer = new FastXmlSerializer();
455            serializer.setOutput(bos, "utf-8");
456            serializer.startDocument(null, true);
457            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
458
459            serializer.startTag(null, TAG_USERS);
460            serializer.attribute(null, ATTR_NEXT_SERIAL_NO, Integer.toString(mNextSerialNumber));
461
462            for (int i = 0; i < mUsers.size(); i++) {
463                UserInfo user = mUsers.valueAt(i);
464                serializer.startTag(null, TAG_USER);
465                serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
466                serializer.endTag(null, TAG_USER);
467            }
468
469            serializer.endTag(null, TAG_USERS);
470
471            serializer.endDocument();
472            userListFile.finishWrite(fos);
473        } catch (Exception e) {
474            userListFile.failWrite(fos);
475            Slog.e(LOG_TAG, "Error writing user list");
476        }
477    }
478
479    private UserInfo readUser(int id) {
480        int flags = 0;
481        int serialNumber = id;
482        String name = null;
483        String iconPath = null;
484
485        FileInputStream fis = null;
486        try {
487            AtomicFile userFile =
488                    new AtomicFile(new File(mUsersDir, Integer.toString(id) + ".xml"));
489            fis = userFile.openRead();
490            XmlPullParser parser = Xml.newPullParser();
491            parser.setInput(fis, null);
492            int type;
493            while ((type = parser.next()) != XmlPullParser.START_TAG
494                    && type != XmlPullParser.END_DOCUMENT) {
495                ;
496            }
497
498            if (type != XmlPullParser.START_TAG) {
499                Slog.e(LOG_TAG, "Unable to read user " + id);
500                return null;
501            }
502
503            if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
504                String storedId = parser.getAttributeValue(null, ATTR_ID);
505                if (Integer.parseInt(storedId) != id) {
506                    Slog.e(LOG_TAG, "User id does not match the file name");
507                    return null;
508                }
509                String serialNumberValue = parser.getAttributeValue(null, ATTR_SERIAL_NO);
510                if (serialNumberValue != null) {
511                    serialNumber = Integer.parseInt(serialNumberValue);
512                }
513                String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
514                flags = Integer.parseInt(flagString);
515                iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
516
517                while ((type = parser.next()) != XmlPullParser.START_TAG
518                        && type != XmlPullParser.END_DOCUMENT) {
519                }
520                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
521                    type = parser.next();
522                    if (type == XmlPullParser.TEXT) {
523                        name = parser.getText();
524                    }
525                }
526            }
527
528            UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
529            userInfo.serialNumber = serialNumber;
530            return userInfo;
531
532        } catch (IOException ioe) {
533        } catch (XmlPullParserException pe) {
534        } finally {
535            if (fis != null) {
536                try {
537                    fis.close();
538                } catch (IOException e) {
539                }
540            }
541        }
542        return null;
543    }
544
545    @Override
546    public UserInfo createUser(String name, int flags) {
547        checkManageUsersPermission("Only the system can create users");
548
549        final long ident = Binder.clearCallingIdentity();
550        final UserInfo userInfo;
551        try {
552            synchronized (mInstallLock) {
553                synchronized (mPackagesLock) {
554                    if (isUserLimitReachedLocked()) return null;
555                    int userId = getNextAvailableIdLocked();
556                    userInfo = new UserInfo(userId, name, null, flags);
557                    File userPath = new File(mBaseUserPath, Integer.toString(userId));
558                    userInfo.serialNumber = mNextSerialNumber++;
559                    mUsers.put(userId, userInfo);
560                    writeUserListLocked();
561                    writeUserLocked(userInfo);
562                    updateUserIdsLocked();
563                    mPm.createNewUserLILPw(userId, userPath);
564                }
565            }
566            if (userInfo != null) {
567                Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
568                addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
569                mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
570                        android.Manifest.permission.MANAGE_USERS);
571            }
572        } finally {
573            Binder.restoreCallingIdentity(ident);
574        }
575        return userInfo;
576    }
577
578    /**
579     * Removes a user and all data directories created for that user. This method should be called
580     * after the user's processes have been terminated.
581     * @param id the user's id
582     */
583    public boolean removeUser(int userHandle) {
584        checkManageUsersPermission("Only the system can remove users");
585        final UserInfo user;
586        synchronized (mPackagesLock) {
587            user = mUsers.get(userHandle);
588            if (userHandle == 0 || user == null) {
589                return false;
590            }
591        }
592
593        int res;
594        try {
595            res = ActivityManagerNative.getDefault().stopUser(userHandle,
596                    new IStopUserCallback.Stub() {
597                        @Override
598                        public void userStopped(int userId) {
599                            finishRemoveUser(userId);
600                        }
601                        @Override
602                        public void userStopAborted(int userId) {
603                        }
604            });
605        } catch (RemoteException e) {
606            return false;
607        }
608
609        return res == ActivityManager.USER_OP_SUCCESS;
610    }
611
612    void finishRemoveUser(int userHandle) {
613        synchronized (mInstallLock) {
614            synchronized (mPackagesLock) {
615                // Cleanup package manager settings
616                mPm.cleanUpUserLILPw(userHandle);
617
618                // Remove this user from the list
619                mUsers.remove(userHandle);
620                // Remove user file
621                AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + ".xml"));
622                userFile.delete();
623                // Update the user list
624                writeUserListLocked();
625                updateUserIdsLocked();
626                removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
627            }
628        }
629
630        // Let other services shutdown any activity
631        long ident = Binder.clearCallingIdentity();
632        try {
633            Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
634            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
635            mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
636                    android.Manifest.permission.MANAGE_USERS);
637        } finally {
638            Binder.restoreCallingIdentity(ident);
639        }
640    }
641
642    private void removeDirectoryRecursive(File parent) {
643        if (parent.isDirectory()) {
644            String[] files = parent.list();
645            for (String filename : files) {
646                File child = new File(parent, filename);
647                removeDirectoryRecursive(child);
648            }
649        }
650        parent.delete();
651    }
652
653    @Override
654    public int getUserSerialNumber(int userHandle) {
655        synchronized (mPackagesLock) {
656            if (!exists(userHandle)) return -1;
657            return getUserInfoLocked(userHandle).serialNumber;
658        }
659    }
660
661    @Override
662    public int getUserHandle(int userSerialNumber) {
663        synchronized (mPackagesLock) {
664            for (int userId : mUserIds) {
665                if (getUserInfoLocked(userId).serialNumber == userSerialNumber) return userId;
666            }
667            // Not found
668            return -1;
669        }
670    }
671
672    /**
673     * Caches the list of user ids in an array, adjusting the array size when necessary.
674     */
675    private void updateUserIdsLocked() {
676        int[] newUsers = new int[mUsers.size()];
677        for (int i = 0; i < mUsers.size(); i++) {
678            newUsers[i] = mUsers.keyAt(i);
679        }
680        mUserIds = newUsers;
681    }
682
683    /**
684     * Returns the next available user id, filling in any holes in the ids.
685     * TODO: May not be a good idea to recycle ids, in case it results in confusion
686     * for data and battery stats collection, or unexpected cross-talk.
687     * @return
688     */
689    private int getNextAvailableIdLocked() {
690        synchronized (mPackagesLock) {
691            int i = 10;
692            while (i < Integer.MAX_VALUE) {
693                if (mUsers.indexOfKey(i) < 0) {
694                    break;
695                }
696                i++;
697            }
698            return i;
699        }
700    }
701}
702