UserManagerService.java revision 1d31d5ba129007fc13bd0ed49a3e3cd60e748e7c
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 = openIconBitmapLocked(info, true /* write */);
200            if (fd != null) {
201                writeUserLocked(info);
202            }
203            return fd;
204        }
205    }
206
207    @Override
208    public ParcelFileDescriptor getUserIcon(int userId) {
209        checkManageUsersPermission("read users");
210        synchronized (mPackagesLock) {
211            UserInfo info = mUsers.get(userId);
212            if (info == null || info.iconPath == null) return null;
213            ParcelFileDescriptor fd = openIconBitmapLocked(info, false /* read */);
214            return fd;
215        }
216    }
217
218    @Override
219    public void setGuestEnabled(boolean enable) {
220        checkManageUsersPermission("enable guest users");
221        synchronized (mPackagesLock) {
222            if (mGuestEnabled != enable) {
223                mGuestEnabled = enable;
224                // Erase any guest user that currently exists
225                for (int i = 0; i < mUsers.size(); i++) {
226                    UserInfo user = mUsers.valueAt(i);
227                    if (user.isGuest()) {
228                        if (!enable) {
229                            removeUser(user.id);
230                        }
231                        return;
232                    }
233                }
234                // No guest was found
235                if (enable) {
236                    createUser("Guest", UserInfo.FLAG_GUEST);
237                }
238            }
239        }
240    }
241
242    @Override
243    public boolean isGuestEnabled() {
244        synchronized (mPackagesLock) {
245            return mGuestEnabled;
246        }
247    }
248
249    @Override
250    public void wipeUser(int userHandle) {
251        checkManageUsersPermission("wipe user");
252        // TODO:
253    }
254
255    public void makeInitialized(int userId) {
256        checkManageUsersPermission("makeInitialized");
257        synchronized (mPackagesLock) {
258            UserInfo info = mUsers.get(userId);
259            if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
260                info.flags |= UserInfo.FLAG_INITIALIZED;
261                writeUserLocked(info);
262            }
263        }
264    }
265
266    /**
267     * Check if we've hit the limit of how many users can be created.
268     */
269    private boolean isUserLimitReachedLocked() {
270        int nUsers = mUsers.size();
271        return nUsers >= mUserLimit;
272    }
273
274    /**
275     * Enforces that only the system UID or root's UID or apps that have the
276     * {@link android.Manifest.permission.MANAGE_USERS MANAGE_USERS}
277     * permission can make certain calls to the UserManager.
278     *
279     * @param message used as message if SecurityException is thrown
280     * @throws SecurityException if the caller is not system or root
281     */
282    private static final void checkManageUsersPermission(String message) {
283        final int uid = Binder.getCallingUid();
284        if (uid != Process.SYSTEM_UID && uid != 0
285                && ActivityManager.checkComponentPermission(
286                        android.Manifest.permission.MANAGE_USERS,
287                        uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
288            throw new SecurityException("You need MANAGE_USERS permission to: " + message);
289        }
290    }
291
292    private ParcelFileDescriptor openIconBitmapLocked(UserInfo info, boolean toWrite) {
293        try {
294            File dir = new File(mUsersDir, Integer.toString(info.id));
295            File file = new File(dir, USER_PHOTO_FILENAME);
296            if (!dir.exists()) {
297                dir.mkdir();
298                FileUtils.setPermissions(
299                        dir.getPath(),
300                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
301                        -1, -1);
302            }
303            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
304                    toWrite ? MODE_CREATE|MODE_READ_WRITE : MODE_READ_WRITE);
305            if (toWrite) {
306                info.iconPath = file.getAbsolutePath();
307            }
308            return fd;
309        } catch (FileNotFoundException e) {
310            Slog.w(LOG_TAG, "Error setting photo for user ", e);
311        }
312        return null;
313    }
314
315    /**
316     * Returns an array of user ids. This array is cached here for quick access, so do not modify or
317     * cache it elsewhere.
318     * @return the array of user ids.
319     */
320    public int[] getUserIds() {
321        synchronized (mPackagesLock) {
322            return mUserIds;
323        }
324    }
325
326    int[] getUserIdsLPr() {
327        return mUserIds;
328    }
329
330    private void readUserList() {
331        synchronized (mPackagesLock) {
332            readUserListLocked();
333        }
334    }
335
336    private void readUserListLocked() {
337        mGuestEnabled = false;
338        if (!mUserListFile.exists()) {
339            fallbackToSingleUserLocked();
340            return;
341        }
342        FileInputStream fis = null;
343        AtomicFile userListFile = new AtomicFile(mUserListFile);
344        try {
345            fis = userListFile.openRead();
346            XmlPullParser parser = Xml.newPullParser();
347            parser.setInput(fis, null);
348            int type;
349            while ((type = parser.next()) != XmlPullParser.START_TAG
350                    && type != XmlPullParser.END_DOCUMENT) {
351                ;
352            }
353
354            if (type != XmlPullParser.START_TAG) {
355                Slog.e(LOG_TAG, "Unable to read user list");
356                fallbackToSingleUserLocked();
357                return;
358            }
359
360            mNextSerialNumber = -1;
361            if (parser.getName().equals(TAG_USERS)) {
362                String lastSerialNumber = parser.getAttributeValue(null, ATTR_NEXT_SERIAL_NO);
363                if (lastSerialNumber != null) {
364                    mNextSerialNumber = Integer.parseInt(lastSerialNumber);
365                }
366            }
367
368            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
369                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
370                    String id = parser.getAttributeValue(null, ATTR_ID);
371                    UserInfo user = readUser(Integer.parseInt(id));
372                    if (user != null) {
373                        mUsers.put(user.id, user);
374                        if (user.isGuest()) {
375                            mGuestEnabled = true;
376                        }
377                        if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
378                            mNextSerialNumber = user.id + 1;
379                        }
380                    }
381                }
382            }
383            updateUserIdsLocked();
384        } catch (IOException ioe) {
385            fallbackToSingleUserLocked();
386        } catch (XmlPullParserException pe) {
387            fallbackToSingleUserLocked();
388        } finally {
389            if (fis != null) {
390                try {
391                    fis.close();
392                } catch (IOException e) {
393                }
394            }
395        }
396    }
397
398    private void fallbackToSingleUserLocked() {
399        // Create the primary user
400        UserInfo primary = new UserInfo(0, "Primary", null,
401                UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
402        mUsers.put(0, primary);
403        updateUserIdsLocked();
404
405        writeUserListLocked();
406        writeUserLocked(primary);
407    }
408
409    /*
410     * Writes the user file in this format:
411     *
412     * <user flags="20039023" id="0">
413     *   <name>Primary</name>
414     * </user>
415     */
416    private void writeUserLocked(UserInfo userInfo) {
417        FileOutputStream fos = null;
418        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + ".xml"));
419        try {
420            fos = userFile.startWrite();
421            final BufferedOutputStream bos = new BufferedOutputStream(fos);
422
423            // XmlSerializer serializer = XmlUtils.serializerInstance();
424            final XmlSerializer serializer = new FastXmlSerializer();
425            serializer.setOutput(bos, "utf-8");
426            serializer.startDocument(null, true);
427            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
428
429            serializer.startTag(null, TAG_USER);
430            serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
431            serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
432            serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
433            if (userInfo.iconPath != null) {
434                serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
435            }
436
437            serializer.startTag(null, TAG_NAME);
438            serializer.text(userInfo.name);
439            serializer.endTag(null, TAG_NAME);
440
441            serializer.endTag(null, TAG_USER);
442
443            serializer.endDocument();
444            userFile.finishWrite(fos);
445        } catch (Exception ioe) {
446            Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
447            userFile.failWrite(fos);
448        }
449    }
450
451    /*
452     * Writes the user list file in this format:
453     *
454     * <users nextSerialNumber="3">
455     *   <user id="0"></user>
456     *   <user id="2"></user>
457     * </users>
458     */
459    private void writeUserListLocked() {
460        FileOutputStream fos = null;
461        AtomicFile userListFile = new AtomicFile(mUserListFile);
462        try {
463            fos = userListFile.startWrite();
464            final BufferedOutputStream bos = new BufferedOutputStream(fos);
465
466            // XmlSerializer serializer = XmlUtils.serializerInstance();
467            final XmlSerializer serializer = new FastXmlSerializer();
468            serializer.setOutput(bos, "utf-8");
469            serializer.startDocument(null, true);
470            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
471
472            serializer.startTag(null, TAG_USERS);
473            serializer.attribute(null, ATTR_NEXT_SERIAL_NO, Integer.toString(mNextSerialNumber));
474
475            for (int i = 0; i < mUsers.size(); i++) {
476                UserInfo user = mUsers.valueAt(i);
477                serializer.startTag(null, TAG_USER);
478                serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
479                serializer.endTag(null, TAG_USER);
480            }
481
482            serializer.endTag(null, TAG_USERS);
483
484            serializer.endDocument();
485            userListFile.finishWrite(fos);
486        } catch (Exception e) {
487            userListFile.failWrite(fos);
488            Slog.e(LOG_TAG, "Error writing user list");
489        }
490    }
491
492    private UserInfo readUser(int id) {
493        int flags = 0;
494        int serialNumber = id;
495        String name = null;
496        String iconPath = null;
497
498        FileInputStream fis = null;
499        try {
500            AtomicFile userFile =
501                    new AtomicFile(new File(mUsersDir, Integer.toString(id) + ".xml"));
502            fis = userFile.openRead();
503            XmlPullParser parser = Xml.newPullParser();
504            parser.setInput(fis, null);
505            int type;
506            while ((type = parser.next()) != XmlPullParser.START_TAG
507                    && type != XmlPullParser.END_DOCUMENT) {
508                ;
509            }
510
511            if (type != XmlPullParser.START_TAG) {
512                Slog.e(LOG_TAG, "Unable to read user " + id);
513                return null;
514            }
515
516            if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
517                String storedId = parser.getAttributeValue(null, ATTR_ID);
518                if (Integer.parseInt(storedId) != id) {
519                    Slog.e(LOG_TAG, "User id does not match the file name");
520                    return null;
521                }
522                String serialNumberValue = parser.getAttributeValue(null, ATTR_SERIAL_NO);
523                if (serialNumberValue != null) {
524                    serialNumber = Integer.parseInt(serialNumberValue);
525                }
526                String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
527                flags = Integer.parseInt(flagString);
528                iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
529
530                while ((type = parser.next()) != XmlPullParser.START_TAG
531                        && type != XmlPullParser.END_DOCUMENT) {
532                }
533                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
534                    type = parser.next();
535                    if (type == XmlPullParser.TEXT) {
536                        name = parser.getText();
537                    }
538                }
539            }
540
541            UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
542            userInfo.serialNumber = serialNumber;
543            return userInfo;
544
545        } catch (IOException ioe) {
546        } catch (XmlPullParserException pe) {
547        } finally {
548            if (fis != null) {
549                try {
550                    fis.close();
551                } catch (IOException e) {
552                }
553            }
554        }
555        return null;
556    }
557
558    @Override
559    public UserInfo createUser(String name, int flags) {
560        checkManageUsersPermission("Only the system can create users");
561
562        final long ident = Binder.clearCallingIdentity();
563        final UserInfo userInfo;
564        try {
565            synchronized (mInstallLock) {
566                synchronized (mPackagesLock) {
567                    if (isUserLimitReachedLocked()) return null;
568                    int userId = getNextAvailableIdLocked();
569                    userInfo = new UserInfo(userId, name, null, flags);
570                    File userPath = new File(mBaseUserPath, Integer.toString(userId));
571                    userInfo.serialNumber = mNextSerialNumber++;
572                    mUsers.put(userId, userInfo);
573                    writeUserListLocked();
574                    writeUserLocked(userInfo);
575                    updateUserIdsLocked();
576                    mPm.createNewUserLILPw(userId, userPath);
577                }
578            }
579            if (userInfo != null) {
580                Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
581                addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
582                mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
583                        android.Manifest.permission.MANAGE_USERS);
584            }
585        } finally {
586            Binder.restoreCallingIdentity(ident);
587        }
588        return userInfo;
589    }
590
591    /**
592     * Removes a user and all data directories created for that user. This method should be called
593     * after the user's processes have been terminated.
594     * @param id the user's id
595     */
596    public boolean removeUser(int userHandle) {
597        checkManageUsersPermission("Only the system can remove users");
598        final UserInfo user;
599        synchronized (mPackagesLock) {
600            user = mUsers.get(userHandle);
601            if (userHandle == 0 || user == null) {
602                return false;
603            }
604        }
605
606        int res;
607        try {
608            res = ActivityManagerNative.getDefault().stopUser(userHandle,
609                    new IStopUserCallback.Stub() {
610                        @Override
611                        public void userStopped(int userId) {
612                            finishRemoveUser(userId);
613                        }
614                        @Override
615                        public void userStopAborted(int userId) {
616                        }
617            });
618        } catch (RemoteException e) {
619            return false;
620        }
621
622        return res == ActivityManager.USER_OP_SUCCESS;
623    }
624
625    void finishRemoveUser(int userHandle) {
626        synchronized (mInstallLock) {
627            synchronized (mPackagesLock) {
628                // Cleanup package manager settings
629                mPm.cleanUpUserLILPw(userHandle);
630
631                // Remove this user from the list
632                mUsers.remove(userHandle);
633                // Remove user file
634                AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + ".xml"));
635                userFile.delete();
636                // Update the user list
637                writeUserListLocked();
638                updateUserIdsLocked();
639                removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
640            }
641        }
642
643        // Let other services shutdown any activity
644        long ident = Binder.clearCallingIdentity();
645        try {
646            Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
647            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
648            mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
649                    android.Manifest.permission.MANAGE_USERS);
650        } finally {
651            Binder.restoreCallingIdentity(ident);
652        }
653    }
654
655    private void removeDirectoryRecursive(File parent) {
656        if (parent.isDirectory()) {
657            String[] files = parent.list();
658            for (String filename : files) {
659                File child = new File(parent, filename);
660                removeDirectoryRecursive(child);
661            }
662        }
663        parent.delete();
664    }
665
666    @Override
667    public int getUserSerialNumber(int userHandle) {
668        synchronized (mPackagesLock) {
669            if (!exists(userHandle)) return -1;
670            return getUserInfoLocked(userHandle).serialNumber;
671        }
672    }
673
674    @Override
675    public int getUserHandle(int userSerialNumber) {
676        synchronized (mPackagesLock) {
677            for (int userId : mUserIds) {
678                if (getUserInfoLocked(userId).serialNumber == userSerialNumber) return userId;
679            }
680            // Not found
681            return -1;
682        }
683    }
684
685    /**
686     * Caches the list of user ids in an array, adjusting the array size when necessary.
687     */
688    private void updateUserIdsLocked() {
689        int[] newUsers = new int[mUsers.size()];
690        for (int i = 0; i < mUsers.size(); i++) {
691            newUsers[i] = mUsers.keyAt(i);
692        }
693        mUserIds = newUsers;
694    }
695
696    /**
697     * Returns the next available user id, filling in any holes in the ids.
698     * TODO: May not be a good idea to recycle ids, in case it results in confusion
699     * for data and battery stats collection, or unexpected cross-talk.
700     * @return
701     */
702    private int getNextAvailableIdLocked() {
703        synchronized (mPackagesLock) {
704            int i = 10;
705            while (i < Integer.MAX_VALUE) {
706                if (mUsers.indexOfKey(i) < 0) {
707                    break;
708                }
709                i++;
710            }
711            return i;
712        }
713    }
714}
715