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