User创建基本流程
以创建manage profile user的代码为例。
frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
private void createProfile(String profileName) throws ProvisioningException {
ProvisionLogger.logd("Creating managed profile with name " + profileName);
mManagedProfileOrUserInfo = mUserManager.createProfileForUser(profileName,
UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_DISABLED,
Process.myUserHandle().getIdentifier());
...
}
跳转到UserManagerService.java中
public UserInfo createProfileForUser(String name, int flags, int userId) {
checkManageOrCreateUsersPermission(flags);
return createUserInternal(name, flags, userId);
}
private UserInfo createUserInternal(String name, int flags, int parentId) {
if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) {
Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
return null;
}
return createUserInternalUnchecked(name, flags, parentId);
}
private UserInfo createUserInternalUnchecked(String name, int flags, int parentId) {
if (ActivityManager.isLowRamDeviceStatic()) {
return null;
}
final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
//依据传入的参数isManagedProfile会为true
final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final boolean isDemo = (flags & UserInfo.FLAG_DEMO) != 0;
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo;
UserData userData;
final int userId;
try {
synchronized (mPackagesLock) {
UserData parent = null;
if (parentId != UserHandle.USER_NULL) {
synchronized (mUsersLock) {
parent = getUserDataLU(parentId);
}
if (parent == null) return null;
}
if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
return null;
}
...
userId = getNextAvailableId(); //获取下一个可用的id,是个递增的数字
Environment.getUserSystemDirectory(userId).mkdirs(); //创建user目录
...
synchronized (mUsersLock) {
...
userInfo = new UserInfo(userId, name, null, flags); //创建新的UserInfo
//序列号,user相关目录的inode都会有值为serialNumber的属性
userInfo.serialNumber = mNextSerialNumber++;
long now = System.currentTimeMillis();
userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
userInfo.partial = true; //表明User是创建或者删除中,不完备
userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
userData = new UserData(); //创建UserData
userData.info = userInfo;
mUsers.put(userId, userData);
}
writeUserLP(userData); //用户和用户列表存储数据到xml中
writeUserListLP();
if (parent != null) {
if (isManagedProfile) {
if (parent.info.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.info.profileGroupId = parent.info.id;
writeUserLP(parent);
}
标记当前用户和新建用户的profileGroupId都为当前用户的userId,同属一个组
userInfo.profileGroupId = parent.info.profileGroupId;
}
...
}
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);
//生成设备加密用的user key。
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
//mPm是PackageManager,确保了用户数据目录的建立
mPm.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
//PMS相关工作,比较多,后面具体分析
mPm.createNewUser(userId);
userInfo.partial = false; //创建完毕,partial可以设置成false了
synchronized (mPackagesLock) {
writeUserLP(userData); //再次保存数据
}
updateUserIds(); 更新数组mUserIds
...
mPm.onNewUserCreated(userId);//赋予基本权限,并可能弹出危险权限提示UI
//发送ACTION_USER_ADDED广播
Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS);
MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED : TRON_USER_CREATED, 1);
} finally {
Binder.restoreCallingIdentity(ident);
}
return userInfo;
}
可以看出和创建普通user没啥大区别,明显的的区别是设置了当前user和新建user的profileGroupId属性,manage profile user是必须有parent user的,不可能单独存在。
PMS在创建User流程中的工作
prepareUserData
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void prepareUserData(int userId, int userSerial, int flags) {
synchronized (mInstallLock) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
}
}
}
枚举所有的内部可写存储,然后转到prepareUserDataLI,顺道先看下getWritablePrivateVolumes的代码
frameworks/base/core/java/android/os/storage/StorageManager.java
public @NonNull List<VolumeInfo> getWritablePrivateVolumes() {
...
final ArrayList<VolumeInfo> res = new ArrayList<>();
for (VolumeInfo vol : mMountService.getVolumes(0)) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
res.add(vol);
}
}
return res;
...
}
Volume的实例都在frameworks/base/services/core/java/com/android/server/MountService.java中创建
private void addInternalVolumeLocked() {
// Create a stub volume that represents internal storage
final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
VolumeInfo.TYPE_PRIVATE, null, null);
internal.state = VolumeInfo.STATE_MOUNTED;
internal.path = Environment.getDataDirectory().getAbsolutePath();
mVolumes.put(internal.id, internal);
}
本文不会涉及MountService的具体流程,通过addInternalVolumeLocked可以看出VolumeInfo.TYPE_PRIVATE实际就是指内部存储,不对外开放。
回到PackageManagerService,继续看prepareUserDataLI
private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
boolean allowRecover) {
// Prepare storage and verify that serial numbers are consistent; if
// there's a mismatch we need to destroy to avoid leaking data
final StorageManager storage = mContext.getSystemService(StorageManager.class);
try {
//第一段
storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
//第二段
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
UserManagerService.enforceSerialNumber(
Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
UserManagerService.enforceSerialNumber(
Environment.getDataSystemDeDirectory(userId), userSerial);
}
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
UserManagerService.enforceSerialNumber(
Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
UserManagerService.enforceSerialNumber(
Environment.getDataSystemCeDirectory(userId), userSerial);
}
}
//第三段
synchronized (mInstallLock) {
mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
}
} catch (Exception e) {
logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
+ " because we failed to prepare: " + e);
destroyUserDataLI(volumeUuid, userId,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
if (allowRecover) {
// Try one last time; if we fail again we're really in trouble
prepareUserDataLI(volumeUuid, userId, userSerial, flags, false);
}
}
}
第一段
代码会走到MountService.java
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
...
mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
userId, serialNumber, flags);
...
}
mCryptConnector是连接通过socket连接cryptd daemon, 然后运行cryptfs中的prepare_user_storage方法。中间流程不再叙述,最后跳转到system/vold/Ext4Crypt.cpp的e4crypt_prepare_user_storage
bool e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id, int serial,
int flags) {
LOG(DEBUG) << "e4crypt_prepare_user_storage for volume " << escape_null(volume_uuid)
<< ", user " << user_id << ", serial " << serial << ", flags " << flags;
if (flags & FLAG_STORAGE_DE) {
// DE_sys key
...
//路径地址,实际上就是/data/user_de/
auto user_de_path = android::vold::BuildDataUserDePath(volume_uuid, user_id);
...
#if MANAGE_MISC_DIRS
if (!prepare_dir(misc_legacy_path, 0750, multiuser_get_uid(user_id, AID_SYSTEM),
multiuser_get_uid(user_id, AID_EVERYBODY))) return false;
#endif
...
//创建目录并设置目录权限
if (!prepare_dir(user_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;
// For now, FBE is only supported on internal storage
if (e4crypt_is_native() && volume_uuid == nullptr) {
//使用FBE会走这里
std::string de_raw_ref;
if (!lookup_key_ref(s_de_key_raw_refs, user_id, &de_raw_ref)) return false;
if (!ensure_policy(de_raw_ref, system_de_path)) return false;
if (!ensure_policy(de_raw_ref, misc_de_path)) return false;
if (!ensure_policy(de_raw_ref, user_de_path)) return false;
}
}
if (flags & FLAG_STORAGE_CE) {
...
}
return true;
}
prepare_dir最终会调用到系统C库system/core/libcutils/fs.c中的fs_prepare_path_impl,代码中省略了很多其它路径,因为原理是相同的
e4crypt_is_native方法在system/extras/ext4_utils/ext4_crypt.cpp
bool e4crypt_is_native() {
char value[PROPERTY_VALUE_MAX];
property_get("ro.crypto.type", value, "none");
return !strcmp(value, "file");
}
查看手机e4crypt_is_native是返回false的,系统属性是block。
FBE是file-based encryption,加密方式的一种,文件加密详细见http://blog.youkuaiyun.com/myfriend0/article/details/77094890,文中也解释了CE和DE是什么
凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用。
设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用。
CE和DE流程类似,不贴代码了。
第一段代码的作用就是创建user加密所需相应目录和设置权限。注意最后并没有地方使用serial参数
第二段
先看两个if语句中的判断条件,flag是满足的,那么就要看mOnlyCore,mOnlyCore是在PackageManagerService构造方法中初始化的,而构造方法唯一使用的地方是main方法。
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
ServiceManager.addService("package", m);
return m;
}
main方法被frameworks/base/services/java/com/android/server/SystemServer.java调用
private void startBootstrapServices() {
...
mIsAlarmBoot = SystemProperties.getBoolean("ro.alarm_boot", false);
if (ENCRYPTING_STATE.equals(cryptState)) {
Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
mOnlyCore = true;
} else if (ENCRYPTED_STATE.equals(cryptState)) {
Slog.w(TAG, "Device encrypted - only parsing core apps");
mOnlyCore = true;
} else if (mIsAlarmBoot) {
// power off alarm mode is similar to encryption mode. Only power off alarm
// applications will be parsed by packageParser. Some services or settings are
// not necessary to power off alarm mode. So reuse mOnlyCore for power off alarm
// mode.
mOnlyCore = true;
}
...
}
看出在设备加密或关机闹钟特殊情况下mOnlyCore为true,在一般情况下mOnlyCore是false的,所以prepareUserDataLI的头两个if语句都是满足条件的。
然后第二段代码中剩下的唯一核心方法就是enforceSerialNumber
public static void enforceSerialNumber(File file, int serialNumber) throws IOException {
...
final int foundSerial = getSerialNumber(file);
Slog.v(LOG_TAG, "Found " + file + " with serial number " + foundSerial);
if (foundSerial == -1) {
Slog.d(LOG_TAG, "Serial number missing on " + file + "; assuming current is valid");
try {
setSerialNumber(file, serialNumber);
} catch (IOException e) {
Slog.w(LOG_TAG, "Failed to set serial number on " + file, e);
}
} else if (foundSerial != serialNumber) {
throw new IOException("Found serial number " + foundSerial
+ " doesn't match expected " + serialNumber);
}
}
private static int getSerialNumber(File file) throws IOException {
try {
final byte[] buf = new byte[256];
final int len = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL, buf);
final String serial = new String(buf, 0, len);
try {
return Integer.parseInt(serial);
} catch (NumberFormatException e) {
throw new IOException("Bad serial number: " + serial);
}
} catch (ErrnoException e) {
if (e.errno == OsConstants.ENODATA) {
return -1;
} else {
throw e.rethrowAsIOException();
}
}
}
getxattr参见https://baike.baidu.com/item/getxattr/1370228?fr=aladdin,是从inode中获取属性。
第一段代码并没有使用userinfo中的serialNumber,所以第一次走到getSerialNumber返回的肯定是-1,enforceSerialNumber肯定会走setSerialNumber。
private static void setSerialNumber(File file, int serialNumber)
throws IOException {
try {
final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
设置serialNumber到inode的属性列表中。
第二段代码是往user加密所需相应目录inode的属性中加入了key为user.serial,value为userinfo的serialNumber。
第三段
mInstaller类似与MountService中的mCryptConnector,通过socket连接installd daemon完成工作。installd代码位于frameworks/native/cmds/installd
执行了installd中的create_user_data方法,代码在commands.cpp
int create_user_data(const char *uuid, userid_t userid, int user_serial ATTRIBUTE_UNUSED,
int flags) {
if (flags & FLAG_STORAGE_DE) {
if (uuid == nullptr) {
return ensure_config_user_dirs(userid);
}
}
return 0;
}
两个if的条件都是满足的
utils.cpp
int ensure_config_user_dirs(userid_t userid) {
// writable by system, readable by any app within the same user
const int uid = multiuser_get_uid(userid, AID_SYSTEM);
const int gid = multiuser_get_uid(userid, AID_EVERYBODY);
// Ensure /data/misc/user/<userid> exists
auto path = create_data_misc_legacy_path(userid);
return fs_prepare_dir(path.c_str(), 0750, uid, gid);
}
创建了/data/misc/user/<userid>目录并设置了新的权限,注意这段代码e4crypt_prepare_user_storage中已经覆盖。
不过MANAGE_MISC_DIRS为0,所以e4crypt_prepare_user_storage中的相同代码其实没有走。
另一个注意点就是uid和gid是一句userid生成的,所以该目录的linux层面权限检查会更严格。
createNewUser
void createNewUser(int userId) {
synchronized (mInstallLock) {
mSettings.createNewUserLI(this, mInstaller, userId); //第一段
}
synchronized (mPackages) {
scheduleWritePackageRestrictionsLocked(userId); //第二段
scheduleWritePackageListLocked(userId);//第三段
applyFactoryDefaultBrowserLPw(userId);//第四段
primeDomainVerificationsLPw(userId);//第五段
}
}
第一段
void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
int userHandle) {
...
for (int i = 0; i < packagesCount; i++) {
if (names[i] == null) {
continue;
}
// TODO: triage flags!
final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
installer.createAppData(volumeUuids[i], names[i], userHandle, flags, appIds[i],
seinfos[i], targetSdkVersions[i]);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to prepare app data", e);
}
}
synchronized (mPackages) {
applyDefaultPreferredAppsLPw(service, userHandle);
}
}
create_app_data位于frameworks/native/cmds/installd/commands.cpp
int create_app_data(const char *uuid, const char *pkgname, userid_t userid, int flags,
appid_t appid, const char* seinfo, int target_sdk_version) {
uid_t uid = multiuser_get_uid(userid, appid);
mode_t target_mode = target_sdk_version >= MIN_RESTRICTED_HOME_SDK_VERSION ? 0700 : 0751;
if (flags & FLAG_STORAGE_CE) {
auto path = create_data_user_ce_package_path(uuid, userid, pkgname);
//创建cache和code_cache目录
if (prepare_app_dir(path, target_mode, uid) ||
prepare_app_dir(path, "cache", 0771, uid) ||
prepare_app_dir(path, "code_cache", 0771, uid)) {
return -1;
}
// Consider restorecon over contents if label changed
//restorecon命令用来恢复SELinux文件属性即恢复文件的安全上下文。参见http://man.linuxde.net/restorecon
if (restorecon_app_data_lazy(path, seinfo, uid) ||
restorecon_app_data_lazy(path, "cache", seinfo, uid) ||
restorecon_app_data_lazy(path, "code_cache", seinfo, uid)) {
return -1;
}
// Remember inode numbers of cache directories so that we can clear
// contents while CE storage is locked
//标准C库方法,往inode中写属性
if (write_path_inode(path, "cache", kXattrInodeCache) ||
write_path_inode(path, "code_cache", kXattrInodeCodeCache)) {
return -1;
}
}
if (flags & FLAG_STORAGE_DE) {
auto path = create_data_user_de_package_path(uuid, userid, pkgname);
if (prepare_app_dir(path, target_mode, uid)) {
// TODO: include result once 25796509 is fixed
return 0;
}
// Consider restorecon over contents if label changed
if (restorecon_app_data_lazy(path, seinfo, uid)) {
return -1;
}
//对JIT Profile的支持,参见https://www.zhihu.com/question/37389356
//运行app获取profile数据,然后profile会提高jit效率,profile当然需要地方存放
if (property_get_bool("dalvik.vm.usejitprofiles")) {
const std::string profile_path = create_data_user_profile_package_path(userid, pkgname);
// read-write-execute only for the app user.
if (fs_prepare_dir_strict(profile_path.c_str(), 0700, uid, uid) != 0) {
PLOG(ERROR) << "Failed to prepare " << profile_path;
return -1;
}
std::string profile_file = create_primary_profile(profile_path);
// read-write only for the app user.
if (fs_prepare_file_strict(profile_file.c_str(), 0600, uid, uid) != 0) {
PLOG(ERROR) << "Failed to prepare " << profile_path;
return -1;
}
const std::string ref_profile_path = create_data_ref_profile_package_path(pkgname);
// dex2oat/profman runs under the shared app gid and it needs to read/write reference
// profiles.
appid_t shared_app_gid = multiuser_get_shared_app_gid(uid);
if (fs_prepare_dir_strict(
ref_profile_path.c_str(), 0700, shared_app_gid, shared_app_gid) != 0) {
PLOG(ERROR) << "Failed to prepare " << ref_profile_path;
return -1;
}
}
}
return 0;
}
创建cache和code_cache目录,设置selinux,设置JIT profile相关目录。
最后看看applyDefaultPreferredAppsLPw
void applyDefaultPreferredAppsLPw(PackageManagerService service, int userId) {
// First pull data from any pre-installed apps.
//应用程序默认值
for (PackageSetting ps : mPackages.values()) {
if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0 && ps.pkg != null
&& ps.pkg.preferredActivityFilters != null) {
ArrayList<PackageParser.ActivityIntentInfo> intents
= ps.pkg.preferredActivityFilters;
for (int i=0; i<intents.size(); i++) {
PackageParser.ActivityIntentInfo aii = intents.get(i);
applyDefaultPreferredActivityLPw(service, aii, new ComponentName(
ps.name, aii.activity.className), userId);
}
}
}
//后续从etc/preferred-apps读取xml来设置默认应用,省略
...
}
第二段
void scheduleWritePackageRestrictionsLocked(int userId) {
final int[] userIds = (userId == UserHandle.USER_ALL)
? sUserManager.getUserIds() : new int[]{userId};
for (int nextUserId : userIds) {
if (!sUserManager.exists(nextUserId)) return;
mDirtyUsers.add(nextUserId);
if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
}
}
}
WRITE_PACKAGE_RESTRICTIONS消息处理
for (int userId : mDirtyUsers) {
mSettings.writePackageRestrictionsLPr(userId);
}
mDirtyUsers.clear();
跳转到writePackageRestrictionsLPr,依据当前的PackageSetting写新user的package-restrictions.xml文件
其中所有的属性都定义与frameworks/base/services/core/java/com/android/server/pm/Settings.java。例如
linked app指能通过浏览器url调用起的app,例如
最终该设置也会写入相关文件
private static final String ATTR_HIDDEN = "hidden";
private static final String ATTR_SUSPENDED = "suspended";
private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
第三段
void scheduleWritePackageListLocked(int userId) {
if (!mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
Message msg = mHandler.obtainMessage(WRITE_PACKAGE_LIST);
msg.arg1 = userId;
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
写packages.list文件,该文件内容可参见代码注释
// pkgName - package name
// userId - application-specific user id
// debugFlag - 0 or 1 if the package is debuggable.
// dataPath - path to package's data path
// seinfo - seinfo label for the app (assigned at install time)
// gids - supplementary gids this app launches with
第四段
applyFactoryDefaultBrowserLPw设置系统资源com.android.internal.R.string.default_browser中定义的浏览器为默认浏览器第五段
primeDomainVerificationsLPw处理linked app,参见http://www.cnblogs.com/android-zcq/p/5882012.html。linked app指能通过浏览器url调用起的app,例如
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="http"
android:mimeType="application/pdf"/>
</intent-filter>
你在浏览器中输入 http://www.devdiv.com/1.pdf ,那么这个activity自动被浏览器给调起来。
最终该设置也会写入相关文件