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