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