From 0b285499db739ba50f2f839d633e763c70e67f96 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Thu, 14 Apr 2011 17:35:23 -0700 Subject: Plumbing in PackageManager and installd for multi-user support. - Create /data/user directory and symlink /data/user/0 -> /data/data for backward compatibility - Create data directories for all packages for new user - Remove data directories when removing a user - Create data directories for all users when a package is created - Clear / Remove data for multiple users - Fixed a bug in verifying the location of a system app - pm commands for createUser and removeUser (will be disabled later) - symlink duplicate lib directories to the original lib directory Change-Id: Id9fdfcf0e62406a8896aa811314dfc08d5f6ed95 --- cmds/installd/commands.c | 45 ++- cmds/installd/installd.c | 63 +++- cmds/installd/installd.h | 12 +- cmds/installd/utils.c | 72 ++++ cmds/pm/src/com/android/commands/pm/Pm.java | 72 +++- .../android/app/ApplicationPackageManager.java | 13 +- core/java/android/content/pm/IPackageManager.aidl | 4 + core/java/android/content/pm/PackageManager.java | 46 ++- core/java/android/content/pm/PackageParser.java | 1 + core/java/android/content/pm/UserInfo.aidl | 20 + core/java/android/content/pm/UserInfo.java | 3 +- services/java/com/android/server/pm/Installer.java | 26 +- .../android/server/pm/PackageManagerService.java | 68 +++- .../java/com/android/server/pm/UserDetails.java | 305 ---------------- .../java/com/android/server/pm/UserManager.java | 401 +++++++++++++++++++++ .../src/com/android/server/pm/UserDetailsTest.java | 106 ------ .../src/com/android/server/pm/UserManagerTest.java | 107 ++++++ 17 files changed, 914 insertions(+), 450 deletions(-) create mode 100644 core/java/android/content/pm/UserInfo.aidl delete mode 100644 services/java/com/android/server/pm/UserDetails.java create mode 100644 services/java/com/android/server/pm/UserManager.java delete mode 100644 services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java create mode 100644 services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 80ba1e9..9a93137 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -67,15 +67,15 @@ int install(const char *pkgname, uid_t uid, gid_t gid) return 0; } -int uninstall(const char *pkgname) +int uninstall(const char *pkgname, uid_t persona) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) return -1; - /* delete contents AND directory, no exceptions */ - return delete_dir_contents(pkgdir, 1, 0); + /* delete contents AND directory, no exceptions */ + return delete_dir_contents(pkgdir, 1, NULL); } int renamepkg(const char *oldpkgname, const char *newpkgname) @@ -95,17 +95,48 @@ int renamepkg(const char *oldpkgname, const char *newpkgname) return 0; } -int delete_user_data(const char *pkgname) +int delete_user_data(const char *pkgname, uid_t persona) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) return -1; - /* delete contents, excluding "lib", but not the directory itself */ + /* delete contents, excluding "lib", but not the directory itself */ return delete_dir_contents(pkgdir, 0, "lib"); } +int make_user_data(const char *pkgname, uid_t uid, uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + char real_libdir[PKG_PATH_MAX]; + + // Create the data dir for the package + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) { + return -1; + } + if (mkdir(pkgdir, 0751) < 0) { + LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); + return -errno; + } + if (chown(pkgdir, uid, uid) < 0) { + LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -errno; + } + return 0; +} + +int delete_persona(uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + + if (create_persona_path(pkgdir, persona)) + return -1; + + return delete_dir_contents(pkgdir, 1, NULL); +} + int delete_cache(const char *pkgname) { char cachedir[PKG_PATH_MAX]; diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index e0d0f97..c062d36 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -49,7 +49,7 @@ static int do_rm_dex(char **arg, char reply[REPLY_MAX]) static int do_remove(char **arg, char reply[REPLY_MAX]) { - return uninstall(arg[0]); /* pkgname */ + return uninstall(arg[0], atoi(arg[1])); /* pkgname, userid */ } static int do_rename(char **arg, char reply[REPLY_MAX]) @@ -92,7 +92,17 @@ static int do_get_size(char **arg, char reply[REPLY_MAX]) static int do_rm_user_data(char **arg, char reply[REPLY_MAX]) { - return delete_user_data(arg[0]); /* pkgname */ + return delete_user_data(arg[0], atoi(arg[1])); /* pkgname, userid */ +} + +static int do_mk_user_data(char **arg, char reply[REPLY_MAX]) +{ + return make_user_data(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, userid */ +} + +static int do_rm_user(char **arg, char reply[REPLY_MAX]) +{ + return delete_persona(atoi(arg[0])); /* userid */ } static int do_movefiles(char **arg, char reply[REPLY_MAX]) @@ -122,16 +132,18 @@ struct cmdinfo cmds[] = { { "dexopt", 3, do_dexopt }, { "movedex", 2, do_move_dex }, { "rmdex", 1, do_rm_dex }, - { "remove", 1, do_remove }, + { "remove", 2, do_remove }, { "rename", 2, do_rename }, { "freecache", 1, do_free_cache }, { "rmcache", 1, do_rm_cache }, { "protect", 2, do_protect }, { "getsize", 3, do_get_size }, - { "rmuserdata", 1, do_rm_user_data }, + { "rmuserdata", 2, do_rm_user_data }, { "movefiles", 0, do_movefiles }, { "linklib", 2, do_linklib }, { "unlinklib", 1, do_unlinklib }, + { "mkuserdata", 3, do_mk_user_data }, + { "rmuser", 1, do_rm_user }, }; static int readx(int s, void *_buf, int count) @@ -286,14 +298,50 @@ int initialize_globals() { return -1; } + // append "app/" to dirs[0] + char *system_app_path = build_string2(android_system_dirs.dirs[0].path, APP_SUBDIR); + android_system_dirs.dirs[0].path = system_app_path; + android_system_dirs.dirs[0].len = strlen(system_app_path); + // vendor // TODO replace this with an environment variable (doesn't exist yet) - android_system_dirs.dirs[1].path = "/vendor/"; + android_system_dirs.dirs[1].path = "/vendor/app/"; android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path); return 0; } +int initialize_directories() { + // /data/user + char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX); + // /data/data + char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX); + // /data/user/0 + char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX, + "0"); + int ret = -1; + if (user_data_dir != NULL && primary_data_dir != NULL && legacy_data_dir != NULL) { + ret = 0; + // Make the /data/user directory if necessary + if (access(user_data_dir, R_OK) < 0) { + if (mkdir(user_data_dir, 0755) < 0) { + return -1; + } + if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) { + return -1; + } + } + // Make the /data/user/0 symlink to /data/data if necessary + if (access(primary_data_dir, R_OK) < 0) { + ret = symlink(legacy_data_dir, primary_data_dir); + } + free(user_data_dir); + free(legacy_data_dir); + free(primary_data_dir); + } + return ret; +} + int main(const int argc, const char *argv[]) { char buf[BUFFER_MAX]; struct sockaddr addr; @@ -305,6 +353,11 @@ int main(const int argc, const char *argv[]) { exit(1); } + if (initialize_directories() < 0) { + LOGE("Could not create directories; exiting.\n"); + exit(1); + } + lsocket = android_get_control_socket(SOCKET_PATH); if (lsocket < 0) { LOGE("Failed to get socket from environment: %s\n", strerror(errno)); diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index cbca135..e5f6739 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -102,6 +102,9 @@ int create_pkg_path(char path[PKG_PATH_MAX], const char *postfix, uid_t persona); +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona); + int is_valid_package_name(const char* pkgname); int create_cache_path(char path[PKG_PATH_MAX], const char *src); @@ -124,12 +127,17 @@ int validate_apk_path(const char *path); int append_and_increment(char** dst, const char* src, size_t* dst_size); +char *build_string2(char *s1, char *s2); +char *build_string3(char *s1, char *s2, char *s3); + /* commands.c */ int install(const char *pkgname, uid_t uid, gid_t gid); -int uninstall(const char *pkgname); +int uninstall(const char *pkgname, uid_t persona); int renamepkg(const char *oldpkgname, const char *newpkgname); -int delete_user_data(const char *pkgname); +int delete_user_data(const char *pkgname, uid_t persona); +int make_user_data(const char *pkgname, uid_t uid, uid_t persona); +int delete_persona(uid_t persona); int delete_cache(const char *pkgname); int move_dex(const char *src, const char *dst); int rm_dex(const char *path); diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index f37a6fb..3099b83 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -96,6 +96,46 @@ int create_pkg_path(char path[PKG_PATH_MAX], } /** + * Create the path name for user data for a certain persona. + * Returns 0 on success, and -1 on failure. + */ +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona) +{ + size_t uid_len; + char* persona_prefix; + if (persona == 0) { + persona_prefix = PRIMARY_USER_PREFIX; + uid_len = 0; + } else { + persona_prefix = SECONDARY_USER_PREFIX; + uid_len = snprintf(NULL, 0, "%d", persona); + } + + char *dst = path; + size_t dst_size = PKG_PATH_MAX; + + if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0 + || append_and_increment(&dst, persona_prefix, &dst_size) < 0) { + LOGE("Error building prefix for user path"); + return -1; + } + + if (persona != 0) { + if (dst_size < uid_len + 1) { + LOGE("Error building user path"); + return -1; + } + int ret = snprintf(dst, dst_size, "%d", persona); + if (ret < 0 || (size_t) ret != uid_len) { + LOGE("Error appending persona id to path"); + return -1; + } + } + return 0; +} + +/** * Checks whether the package name is valid. Returns -1 on error and * 0 on success. */ @@ -408,3 +448,35 @@ int append_and_increment(char** dst, const char* src, size_t* dst_size) { *dst_size -= ret; return 0; } + +char *build_string2(char *s1, char *s2) { + if (s1 == NULL || s2 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len = len_s1 + len_s2 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + + return result; +} + +char *build_string3(char *s1, char *s2, char *s3) { + if (s1 == NULL || s2 == NULL || s3 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len_s3 = strlen(s3); + int len = len_s1 + len_s2 + len_s3 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + strcpy(result + len_s1 + len_s2, s3); + + return result; +} diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index d058e38..78a450c 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -35,9 +35,9 @@ import android.content.pm.PermissionInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.provider.Settings; import java.io.File; import java.lang.reflect.Field; @@ -60,6 +60,7 @@ public final class Pm { private static final String PM_NOT_RUNNING_ERR = "Error: Could not access the Package Manager. Is the system running?"; + private static final int ROOT_UID = 0; public static void main(String[] args) { new Pm().run(args); @@ -127,6 +128,16 @@ public final class Pm { return; } + if ("createUser".equals(op)) { + runCreateUser(); + return; + } + + if ("removeUser".equals(op)) { + runRemoveUser(); + return; + } + try { if (args.length == 1) { if (args[0].equalsIgnoreCase("-l")) { @@ -763,6 +774,63 @@ public final class Pm { } } + public void runCreateUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: createUser must be run as root"); + return; + } + String name; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user name specified."); + showUsage(); + return; + } + name = arg; + try { + if (mPm.createUser(name, 0) == null) { + System.err.println("Error: couldn't create user."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + + } + + public void runRemoveUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: removeUser must be run as root"); + return; + } + int userId; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user id specified."); + showUsage(); + return; + } + try { + userId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + System.err.println("Error: user id has to be a number."); + showUsage(); + return; + } + try { + if (!mPm.removeUser(userId)) { + System.err.println("Error: couldn't remove user."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { boolean finished; boolean result; @@ -1006,6 +1074,8 @@ public final class Pm { System.err.println(" pm enable PACKAGE_OR_COMPONENT"); System.err.println(" pm disable PACKAGE_OR_COMPONENT"); System.err.println(" pm setInstallLocation [0/auto] [1/internal] [2/external]"); + System.err.println(" pm createUser USER_NAME"); + System.err.println(" pm removeUser USER_ID"); System.err.println(""); System.err.println("The list packages command prints all packages, optionally only"); System.err.println("those whose package name contains the text in FILTER. Options:"); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ef8ba8e..85918cf 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1113,7 +1113,11 @@ final class ApplicationPackageManager extends PackageManager { */ @Override public UserInfo createUser(String name, int flags) { - // TODO + try { + return mPM.createUser(name, flags); + } catch (RemoteException e) { + // Should never happen! + } return null; } @@ -1136,8 +1140,11 @@ final class ApplicationPackageManager extends PackageManager { */ @Override public boolean removeUser(int id) { - // TODO: - return false; + try { + return mPM.removeUser(id); + } catch (RemoteException e) { + return false; + } } /** diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index fbf8f92..11cd446 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -35,6 +35,7 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.net.Uri; import android.content.IntentSender; @@ -329,4 +330,7 @@ interface IPackageManager { boolean setInstallLocation(int loc); int getInstallLocation(); + + UserInfo createUser(in String name, int flags); + boolean removeUser(int userId); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 99c4c7f..ff817c1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -662,10 +662,15 @@ public abstract class PackageManager { public static final int MOVE_EXTERNAL_MEDIA = 0x00000002; /** - * Feature for {@link #getSystemAvailableFeatures} and - * {@link #hasSystemFeature}: The device's audio pipeline is low-latency, - * more suitable for audio applications sensitive to delays or lag in - * sound input or output. + * Range of IDs allocated for a user. + * @hide + */ + public static final int PER_USER_RANGE = 100000; + + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's + * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or + * lag in sound input or output. */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency"; @@ -2387,4 +2392,37 @@ public abstract class PackageManager { * @hide */ public abstract void updateUserFlags(int id, int flags); + + /** + * Checks to see if the user id is the same for the two uids, i.e., they belong to the same + * user. + * @hide + */ + public static boolean isSameUser(int uid1, int uid2) { + return getUserId(uid1) == getUserId(uid2); + } + + /** + * Returns the user id for a given uid. + * @hide + */ + public static int getUserId(int uid) { + return uid / PER_USER_RANGE; + } + + /** + * Returns the uid that is composed from the userId and the appId. + * @hide + */ + public static int getUid(int userId, int appId) { + return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); + } + + /** + * Returns the app id (or base uid) for a given uid, stripping out the user id from it. + * @hide + */ + public static int getAppId(int uid) { + return uid % PER_USER_RANGE; + } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 67cd4a2..ebda40e 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -24,6 +24,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; diff --git a/core/java/android/content/pm/UserInfo.aidl b/core/java/android/content/pm/UserInfo.aidl new file mode 100644 index 0000000..2e7cb8f --- /dev/null +++ b/core/java/android/content/pm/UserInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 2011, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.content.pm; + +parcelable UserInfo; diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 3704d3a..ba5331c 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -74,8 +74,7 @@ public class UserInfo implements Parcelable { @Override public String toString() { - return "UserInfo{" - + id + ":" + name + ":" + Integer.toHexString(flags) + "}"; + return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}"; } public int describeContents() { diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index da3ebaf..d10aa97 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -225,10 +225,12 @@ class Installer { return execute(builder.toString()); } - public int remove(String name) { + public int remove(String name, int userId) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } @@ -248,10 +250,30 @@ class Installer { return execute(builder.toString()); } - public int clearUserData(String name) { + public int createUserData(String name, int uid, int userId) { + StringBuilder builder = new StringBuilder("mkuserdata"); + builder.append(' '); + builder.append(name); + builder.append(' '); + builder.append(uid); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int removeUserDataDirs(int userId) { + StringBuilder builder = new StringBuilder("rmuser"); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int clearUserData(String name, int userId) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index a9d49b4..6e1093f 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -65,6 +65,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; +import android.content.pm.UserInfo; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -208,6 +209,9 @@ public class PackageManagerService extends IPackageManager.Stub { // This is where all application persistent data goes. final File mAppDataDir; + // This is where all application persistent data goes for secondary users. + final File mUserAppDataDir; + // This is the object monitoring the framework dir. final FileObserver mFrameworkInstallObserver; @@ -359,6 +363,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; + final UserManager mUserManager; + final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { @@ -797,8 +803,11 @@ public class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); + mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); + mUserManager = new UserManager(mInstaller, mUserAppDataDir); + if (mInstaller == null) { // Make sure these dirs exist, when we are running in // the simulator. @@ -806,6 +815,7 @@ public class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mUserAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } @@ -974,7 +984,8 @@ public class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - mInstaller.remove(ps.name); + mInstaller.remove(ps.name, 0); + mUserManager.removePackageForAllUsers(ps.name); } } } @@ -1059,10 +1070,12 @@ public class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - int retCode = mInstaller.remove(ps.name); + int retCode = mInstaller.remove(ps.name, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); + } else { + mUserManager.removePackageForAllUsers(ps.name); } } else { //for emulator @@ -1510,7 +1523,8 @@ public class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.flags = ps.pkgFlags; ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; ps.pkg.applicationInfo.sourceDir = ps.codePathString; - ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); + ps.pkg.applicationInfo.dataDir = + getDataPathForPackage(ps.pkg.packageName, 0).getPath(); ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; ps.pkg.mSetEnabled = ps.enabled; ps.pkg.mSetStopped = ps.stopped; @@ -2836,11 +2850,15 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - private File getDataPathForPackage(PackageParser.Package pkg) { - final File dataPath = new File(mAppDataDir, pkg.packageName); - return dataPath; + File getDataPathForUser(int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId); } - + + private File getDataPathForPackage(String packageName, int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + + userId + File.separator + packageName); + } + private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) { File scanFile = new File(pkg.mScanPath); @@ -3162,7 +3180,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. - dataPath = getDataPathForPackage(pkg); + dataPath = getDataPathForPackage(pkg.packageName, 0); boolean uidError = false; @@ -3178,8 +3196,11 @@ public class PackageManagerService extends IPackageManager.Stub { // If this is a system app, we can at least delete its // current data so the application will still work. if (mInstaller != null) { - int ret = mInstaller.remove(pkgName); + int ret = mInstaller.remove(pkgName, 0); if (ret >= 0) { + // TODO: Kill the processes first + // Remove the data directories for all users + mUserManager.removePackageForAllUsers(pkgName); // Old data gone! String msg = "System package " + pkg.packageName + " has changed from uid: " @@ -3199,6 +3220,9 @@ public class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, + pkg.applicationInfo.uid); } } if (!recovered) { @@ -3235,11 +3259,13 @@ public class PackageManagerService extends IPackageManager.Stub { if (mInstaller != null) { int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); - if(ret < 0) { + if (ret < 0) { // Error from installer mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); } else { dataPath.mkdirs(); if (dataPath.exists()) { @@ -5703,7 +5729,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; - boolean dataDirExists = getDataPathForPackage(pkg).exists(); + boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); res.name = pkgName; synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { @@ -6390,11 +6416,14 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (mInstaller != null) { - int retCode = mInstaller.remove(packageName); + int retCode = mInstaller.remove(packageName, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); // we don't consider this to be a failure of the core package deletion + } else { + // TODO: Kill the processes first + mUserManager.removePackageForAllUsers(packageName); } } else { // for simulator @@ -6654,7 +6683,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } if (mInstaller != null) { - int retCode = mInstaller.clearUserData(packageName); + int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -8015,4 +8044,17 @@ public class PackageManagerService extends IPackageManager.Stub { android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, PackageHelper.APP_INSTALL_AUTO); } + + public UserInfo createUser(String name, int flags) { + UserInfo userInfo = mUserManager.createUser(name, flags, getInstalledApplications(0)); + return userInfo; + } + + public boolean removeUser(int userId) { + if (userId == 0) { + return false; + } + mUserManager.removeUser(userId); + return true; + } } diff --git a/services/java/com/android/server/pm/UserDetails.java b/services/java/com/android/server/pm/UserDetails.java deleted file mode 100644 index 2aeed7c..0000000 --- a/services/java/com/android/server/pm/UserDetails.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm; - -import com.android.internal.util.FastXmlSerializer; - -import android.content.pm.UserInfo; -import android.os.Environment; -import android.os.FileUtils; -import android.util.Slog; -import android.util.SparseArray; -import android.util.Xml; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -public class UserDetails { - private static final String TAG_NAME = "name"; - - private static final String ATTR_FLAGS = "flags"; - - private static final String ATTR_ID = "id"; - - private static final String TAG_USERS = "users"; - - private static final String TAG_USER = "user"; - - private static final String TAG = "UserDetails"; - - private static final String USER_INFO_DIR = "system/users"; - private static final String USER_LIST_FILENAME = "userlist.xml"; - - private SparseArray mUsers; - - private final File mUsersDir; - private final File mUserListFile; - - /** - * Available for testing purposes. - */ - UserDetails(File dataDir) { - mUsersDir = new File(dataDir, USER_INFO_DIR); - mUsersDir.mkdirs(); - FileUtils.setPermissions(mUsersDir.toString(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG - |FileUtils.S_IROTH|FileUtils.S_IXOTH, - -1, -1); - mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); - readUserList(); - } - - public UserDetails() { - this(Environment.getDataDirectory()); - } - - public List getUsers() { - ArrayList users = new ArrayList(mUsers.size()); - for (int i = 0; i < mUsers.size(); i++) { - users.add(mUsers.valueAt(i)); - } - return users; - } - - private void readUserList() { - mUsers = new SparseArray(); - if (!mUserListFile.exists()) { - fallbackToSingleUser(); - return; - } - FileInputStream fis = null; - try { - fis = new FileInputStream(mUserListFile); - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(fis, null); - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - Slog.e(TAG, "Unable to read user list"); - fallbackToSingleUser(); - return; - } - - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { - String id = parser.getAttributeValue(null, ATTR_ID); - UserInfo user = readUser(Integer.parseInt(id)); - if (user != null) { - mUsers.put(user.id, user); - } - } - } - } catch (IOException ioe) { - fallbackToSingleUser(); - } catch (XmlPullParserException pe) { - fallbackToSingleUser(); - } - } - - private void fallbackToSingleUser() { - // Create the primary user - UserInfo primary = new UserInfo(0, "Primary", - UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); - mUsers.put(0, primary); - - writeUserList(); - writeUser(primary); - } - - /* - * Writes the user file in this format: - * - * - * Primary - * - */ - private void writeUser(UserInfo userInfo) { - try { - final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); - final FileOutputStream fos = new FileOutputStream(mUserFile); - final BufferedOutputStream bos = new BufferedOutputStream(fos); - - // XmlSerializer serializer = XmlUtils.serializerInstance(); - final XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(bos, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, TAG_USER); - serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); - serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); - - serializer.startTag(null, TAG_NAME); - serializer.text(userInfo.name); - serializer.endTag(null, TAG_NAME); - - serializer.endTag(null, TAG_USER); - - serializer.endDocument(); - } catch (IOException ioe) { - Slog.e(TAG, "Error writing user info " + userInfo.id + "\n" + ioe); - } - } - - /* - * Writes the user list file in this format: - * - * - * - * - * - */ - private void writeUserList() { - try { - final FileOutputStream fos = new FileOutputStream(mUserListFile); - final BufferedOutputStream bos = new BufferedOutputStream(fos); - - // XmlSerializer serializer = XmlUtils.serializerInstance(); - final XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(bos, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, TAG_USERS); - - for (int i = 0; i < mUsers.size(); i++) { - UserInfo user = mUsers.valueAt(i); - serializer.startTag(null, TAG_USER); - serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); - serializer.endTag(null, TAG_USER); - Slog.e(TAG, "Wrote user " + user.id + " to userlist.xml"); - } - - serializer.endTag(null, TAG_USERS); - - serializer.endDocument(); - } catch (IOException ioe) { - Slog.e(TAG, "Error writing user list"); - } - } - - private UserInfo readUser(int id) { - int flags = 0; - String name = null; - - FileInputStream fis = null; - try { - File userFile = new File(mUsersDir, Integer.toString(id) + ".xml"); - fis = new FileInputStream(userFile); - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(fis, null); - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - Slog.e(TAG, "Unable to read user " + id); - return null; - } - - if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { - String storedId = parser.getAttributeValue(null, ATTR_ID); - if (Integer.parseInt(storedId) != id) { - Slog.e(TAG, "User id does not match the file name"); - return null; - } - String flagString = parser.getAttributeValue(null, ATTR_FLAGS); - flags = Integer.parseInt(flagString); - - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - } - if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { - type = parser.next(); - if (type == XmlPullParser.TEXT) { - name = parser.getText(); - } - } - } - fis.close(); - - UserInfo userInfo = new UserInfo(id, name, flags); - return userInfo; - - } catch (IOException ioe) { - } catch (XmlPullParserException pe) { - } - return null; - } - - public UserInfo createUser(String name, int flags) { - int id = getNextAvailableId(); - UserInfo userInfo = new UserInfo(id, name, flags); - if (!createPackageFolders(id)) { - return null; - } - mUsers.put(id, userInfo); - writeUserList(); - writeUser(userInfo); - return userInfo; - } - - public void removeUser(int id) { - // Remove from the list - UserInfo userInfo = mUsers.get(id); - if (userInfo != null) { - // Remove this user from the list - mUsers.remove(id); - // Remove user file - File userFile = new File(mUsersDir, id + ".xml"); - userFile.delete(); - writeUserList(); - removePackageFolders(id); - } - } - - private int getNextAvailableId() { - int i = 0; - while (i < Integer.MAX_VALUE) { - if (mUsers.indexOfKey(i) < 0) { - break; - } - i++; - } - return i; - } - - private boolean createPackageFolders(int id) { - // TODO: Create data directories for all the packages for a new user, w/ specified user id. - return true; - } - - private boolean removePackageFolders(int id) { - // TODO: Remove all the data directories for the specified user. - return true; - } -} diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java new file mode 100644 index 0000000..76fa5ab --- /dev/null +++ b/services/java/com/android/server/pm/UserManager.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import com.android.internal.util.FastXmlSerializer; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Environment; +import android.os.FileUtils; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +public class UserManager { + private static final String TAG_NAME = "name"; + + private static final String ATTR_FLAGS = "flags"; + + private static final String ATTR_ID = "id"; + + private static final String TAG_USERS = "users"; + + private static final String TAG_USER = "user"; + + private static final String LOG_TAG = "UserManager"; + + private static final String USER_INFO_DIR = "system" + File.separator + "users"; + private static final String USER_LIST_FILENAME = "userlist.xml"; + + private SparseArray mUsers; + + private final File mUsersDir; + private final File mUserListFile; + private int[] mUserIds; + + private Installer mInstaller; + private File mBaseUserPath; + + /** + * Available for testing purposes. + */ + UserManager(File dataDir, File baseUserPath) { + mUsersDir = new File(dataDir, USER_INFO_DIR); + mUsersDir.mkdirs(); + mBaseUserPath = baseUserPath; + FileUtils.setPermissions(mUsersDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); + mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); + readUserList(); + } + + public UserManager(Installer installer, File baseUserPath) { + this(Environment.getDataDirectory(), baseUserPath); + mInstaller = installer; + } + + public List getUsers() { + ArrayList users = new ArrayList(mUsers.size()); + for (int i = 0; i < mUsers.size(); i++) { + users.add(mUsers.valueAt(i)); + } + return users; + } + + /** + * Returns an array of user ids. This array is cached here for quick access, so do not modify or + * cache it elsewhere. + * @return the array of user ids. + */ + int[] getUserIds() { + return mUserIds; + } + + private void readUserList() { + mUsers = new SparseArray(); + if (!mUserListFile.exists()) { + fallbackToSingleUser(); + return; + } + FileInputStream fis = null; + try { + fis = new FileInputStream(mUserListFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user list"); + fallbackToSingleUser(); + return; + } + + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String id = parser.getAttributeValue(null, ATTR_ID); + UserInfo user = readUser(Integer.parseInt(id)); + if (user != null) { + mUsers.put(user.id, user); + } + } + } + updateUserIds(); + } catch (IOException ioe) { + fallbackToSingleUser(); + } catch (XmlPullParserException pe) { + fallbackToSingleUser(); + } + } + + private void fallbackToSingleUser() { + // Create the primary user + UserInfo primary = new UserInfo(0, "Primary", + UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); + mUsers.put(0, primary); + updateUserIds(); + + writeUserList(); + writeUser(primary); + } + + /* + * Writes the user file in this format: + * + * + * Primary + * + */ + private void writeUser(UserInfo userInfo) { + try { + final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); + final FileOutputStream fos = new FileOutputStream(mUserFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); + serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); + + serializer.startTag(null, TAG_NAME); + serializer.text(userInfo.name); + serializer.endTag(null, TAG_NAME); + + serializer.endTag(null, TAG_USER); + + serializer.endDocument(); + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); + } + } + + /* + * Writes the user list file in this format: + * + * + * + * + * + */ + private void writeUserList() { + try { + final FileOutputStream fos = new FileOutputStream(mUserListFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USERS); + + for (int i = 0; i < mUsers.size(); i++) { + UserInfo user = mUsers.valueAt(i); + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); + serializer.endTag(null, TAG_USER); + } + + serializer.endTag(null, TAG_USERS); + + serializer.endDocument(); + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error writing user list"); + } + } + + private UserInfo readUser(int id) { + int flags = 0; + String name = null; + + FileInputStream fis = null; + try { + File userFile = new File(mUsersDir, Integer.toString(id) + ".xml"); + fis = new FileInputStream(userFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user " + id); + return null; + } + + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String storedId = parser.getAttributeValue(null, ATTR_ID); + if (Integer.parseInt(storedId) != id) { + Slog.e(LOG_TAG, "User id does not match the file name"); + return null; + } + String flagString = parser.getAttributeValue(null, ATTR_FLAGS); + flags = Integer.parseInt(flagString); + + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + name = parser.getText(); + } + } + } + fis.close(); + + UserInfo userInfo = new UserInfo(id, name, flags); + return userInfo; + + } catch (IOException ioe) { + } catch (XmlPullParserException pe) { + } + return null; + } + + public UserInfo createUser(String name, int flags, List apps) { + int userId = getNextAvailableId(); + UserInfo userInfo = new UserInfo(userId, name, flags); + File userPath = new File(mBaseUserPath, Integer.toString(userId)); + if (!createPackageFolders(userId, userPath, apps)) { + return null; + } + mUsers.put(userId, userInfo); + writeUserList(); + writeUser(userInfo); + updateUserIds(); + return userInfo; + } + + /** + * Removes a user and all data directories created for that user. This method should be called + * after the user's processes have been terminated. + * @param id the user's id + */ + public void removeUser(int id) { + // Remove from the list + UserInfo userInfo = mUsers.get(id); + if (userInfo != null) { + // Remove this user from the list + mUsers.remove(id); + // Remove user file + File userFile = new File(mUsersDir, id + ".xml"); + userFile.delete(); + // Update the user list + writeUserList(); + // Remove the data directories for all packages for this user + removePackageFolders(id); + updateUserIds(); + } + } + + public void installPackageForAllUsers(String packageName, int uid) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), + userId); + } + } + + public void clearUserDataForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.clearUserData(packageName, userId); + } + } + + public void removePackageForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.remove(packageName, userId); + } + } + + /** + * Caches the list of user ids in an array, adjusting the array size when necessary. + */ + private void updateUserIds() { + if (mUserIds == null || mUserIds.length != mUsers.size()) { + mUserIds = new int[mUsers.size()]; + } + for (int i = 0; i < mUsers.size(); i++) { + mUserIds[i] = mUsers.keyAt(i); + } + } + + /** + * Returns the next available user id, filling in any holes in the ids. + * @return + */ + private int getNextAvailableId() { + int i = 0; + while (i < Integer.MAX_VALUE) { + if (mUsers.indexOfKey(i) < 0) { + break; + } + i++; + } + return i; + } + + private boolean createPackageFolders(int id, File userPath, final List apps) { + // mInstaller may not be available for unit-tests. + if (mInstaller == null || apps == null) return true; + + final long startTime = SystemClock.elapsedRealtime(); + // Create the user path + userPath.mkdir(); + FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG + | FileUtils.S_IXOTH, -1, -1); + + // Create the individual data directories + for (ApplicationInfo app : apps) { + if (app.uid > android.os.Process.FIRST_APPLICATION_UID + && app.uid < PackageManager.PER_USER_RANGE) { + mInstaller.createUserData(app.packageName, + PackageManager.getUid(id, app.uid), id); + } + } + final long stopTime = SystemClock.elapsedRealtime(); + Log.i(LOG_TAG, + "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); + return true; + } + + private boolean removePackageFolders(int id) { + // mInstaller may not be available for unit-tests. + if (mInstaller == null) return true; + + mInstaller.removeUserDataDirs(id); + return true; + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java deleted file mode 100644 index 7b77aac..0000000 --- a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm; - -import com.android.server.pm.UserDetails; - -import android.content.pm.UserInfo; -import android.os.Debug; -import android.os.Environment; -import android.test.AndroidTestCase; - -import java.util.List; - -/** Test {@link UserDetails} functionality. */ -public class UserDetailsTest extends AndroidTestCase { - - UserDetails mDetails = null; - - @Override - public void setUp() throws Exception { - mDetails = new UserDetails(Environment.getExternalStorageDirectory()); - } - - @Override - public void tearDown() throws Exception { - List users = mDetails.getUsers(); - // Remove all except the primary user - for (UserInfo user : users) { - if (!user.isPrimary()) { - mDetails.removeUser(user.id); - } - } - } - - public void testHasPrimary() throws Exception { - assertTrue(findUser(0)); - } - - public void testAddUser() throws Exception { - final UserDetails details = mDetails; - - UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST); - assertTrue(userInfo != null); - - List list = details.getUsers(); - boolean found = false; - for (UserInfo user : list) { - if (user.id == userInfo.id && user.name.equals("Guest 1") - && user.isGuest() - && !user.isAdmin() - && !user.isPrimary()) { - found = true; - } - } - assertTrue(found); - } - - public void testAdd2Users() throws Exception { - final UserDetails details = mDetails; - - UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST); - UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN); - - assertTrue(user1 != null); - assertTrue(user2 != null); - - assertTrue(findUser(0)); - assertTrue(findUser(user1.id)); - assertTrue(findUser(user2.id)); - } - - public void testRemoveUser() throws Exception { - final UserDetails details = mDetails; - - UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST); - - details.removeUser(userInfo.id); - - assertFalse(findUser(userInfo.id)); - } - - private boolean findUser(int id) { - List list = mDetails.getUsers(); - - for (UserInfo user : list) { - if (user.id == id) { - return true; - } - } - return false; - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java new file mode 100644 index 0000000..e8188e7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import com.android.server.pm.UserManager; + +import android.content.pm.UserInfo; +import android.os.Debug; +import android.os.Environment; +import android.test.AndroidTestCase; + +import java.util.List; + +/** Test {@link UserManager} functionality. */ +public class UserManagerTest extends AndroidTestCase { + + UserManager mUserManager = null; + + @Override + public void setUp() throws Exception { + mUserManager = new UserManager(Environment.getExternalStorageDirectory(), + Environment.getExternalStorageDirectory()); + } + + @Override + public void tearDown() throws Exception { + List users = mUserManager.getUsers(); + // Remove all except the primary user + for (UserInfo user : users) { + if (!user.isPrimary()) { + mUserManager.removeUser(user.id); + } + } + } + + public void testHasPrimary() throws Exception { + assertTrue(findUser(0)); + } + + public void testAddUser() throws Exception { + final UserManager details = mUserManager; + + UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); + assertTrue(userInfo != null); + + List list = details.getUsers(); + boolean found = false; + for (UserInfo user : list) { + if (user.id == userInfo.id && user.name.equals("Guest 1") + && user.isGuest() + && !user.isAdmin() + && !user.isPrimary()) { + found = true; + } + } + assertTrue(found); + } + + public void testAdd2Users() throws Exception { + final UserManager details = mUserManager; + + UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); + UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN, null); + + assertTrue(user1 != null); + assertTrue(user2 != null); + + assertTrue(findUser(0)); + assertTrue(findUser(user1.id)); + assertTrue(findUser(user2.id)); + } + + public void testRemoveUser() throws Exception { + final UserManager details = mUserManager; + + UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); + + details.removeUser(userInfo.id); + + assertFalse(findUser(userInfo.id)); + } + + private boolean findUser(int id) { + List list = mUserManager.getUsers(); + + for (UserInfo user : list) { + if (user.id == id) { + return true; + } + } + return false; + } +} -- cgit v1.1