Android 9.x 多用户机制 1 #Profile user创建过程

因为负责公司的应用多开模块,因此,对模块设计的技术进行相关的总结和备忘

0. 应用分身机制##

各个厂商的应用分身机制,大都采用原生的分身机制,实现细节上有稍微区别

OV,使用的是原生的多用户机制,跟创建一个用户的用户是一样的;这个用户空间,通过adb命令可以进入;
并且,这个空间创建的时候,会默认安装所有的系统应用;此方案的优点是被分身的应用,依赖的其他应用,在分身空间都存在,系统改动较小;缺点是,创建的分身空间占用资源较大,因为,分身空间和主空间的进程是同时运行的

xiami,也是使用的原生的多用户机制,但使用的是ProfileUser,是一个主从用户机制;并且,在创建分身用户空间的时候,并不会安装所有的系统应用到分身空间(在系统层进行了代码控制);该方案有点是,创建的分身空间占用资源较少(安装的应用少), 缺点是,需要再系统层进行大量的代码适配。

各个厂商的分身方案,不外乎这两种;

1. 多用户类型

Android中创建多用户有两种方式:

UserManager#createProfileForUser(String name, int flags, @UserIdInt int userHandle)
UserManager#createUser(String name, int flags)

我们先从profile的调用机制来分析,来分析多用户的原理

2. profile user机制创建流程

UserInfo mManagedProfileOrUserInfo;
Log.d(UserLog.TAG, "isHasExist:" + isHasExist);
mManagedProfileOrUserInfo = mUserManager.createProfileForUser("multi_user_lite",
           UserInfo.FLAG_MANAGED_PROFILE,
           Process.myUserHandle().getIdentifier());

2.1 createUserInternalUnchecked

该方法时我们要分析的主要方法,Android创建多用的主要逻辑,都在该方法中

Profile_user创建流程
  • 检查是否超出系统创建ManagedProfile数目的上限(上限为1,为常量)(canAddMoreManagedProfiles)

  • 如果创建的是非guest,非ManagedProfile ,非Demo 用户,检查是否超出系统创建User数目上线

    adb shell pm get-max-users//config_multiuserMaximumUsers
    1
    

    ManagedProfile user不受该参数控制

  • 如果创建的是Guest用户,则判断系统是否已经存在Guest用户,如果存在,则不能创建

  • 根据系统中已经存在的userid,生成本次将要创建的user的id(getNextAvailableId)(1.1.2.1 )

  • 创建/data/system/users/userid 目录(1.1.2.2)

    Environment.getUserSystemDirectory(userId).mkdirs()
    
  • 创建UserInfo,并使用UserInfo实例化UserData对象,并将UserData保存到mUsers中去;
    此时的UserInfo处于partial状态(1.1.2.3,1.1.2.4))

  • 将创建的UserData数据,写入到文件/data/system/userid.xml中去(使用XmlSerializer工具类),格式如下(1.1.2.5)

    full_k61v1_64_bsp:/data/system/users # cat 10.xml
    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <user id="10" serialNumber="10" flags="48" created="1551338624859" lastLoggedIn="1551338627706" 
        lastLoggedInFingerprint="alps//full_k61v1_64_bsp:9/PPR1.180610.011/1550903740:userdebug/dev-keys" 
        profileGroupId="0" profileBadge="0">
        <name>multi_user_lite</name>
        <restrictions />
    </user>
    
  • 根据新创建的用户,更新/data/system/users/userlist.xml,更新后的文件内容为(1.1.2.6)

    2|full_k61v1_64_bsp:/data/system/users #cat userlist.xml                                                               
    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <users nextSerialNumber="11" version="7">
        <guestRestrictions>
            <restrictions no_sms="true" no_install_unknown_sources="true" no_config_wifi="true" no_outgoing_calls="true" />
        </guestRestrictions>
        <deviceOwnerUserId id="-10000" />
        <user id="0" />
        <user id="10" />
    </users>
    
  • 如果创新的是ManagerProfile,则将主用户和被创建用户的userinfo的profileGroupId置为0

            if (parent != null) {
                    if (isManagedProfile) {
                        if (parent.info.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
                            parent.info.profileGroupId = parent.info.id;//profileGroupId置为0
                            writeUserLP(parent);
                        }
                        userInfo.profileGroupId = parent.info.profileGroupId;//profileGroupId置为0
                    } else if (isRestricted) {
                        if (parent.info.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
                            parent.info.restrictedProfileParentId = parent.info.id;
                            writeUserLP(parent);
                        }
                        userInfo.restrictedProfileParentId = parent.info.restrictedProfileParentId;
                    }
                }
  • 通过存储管理器,创建用户对应的key数据(1.1.2.7)
    storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
    
  • 新用户数据存储区域创建和数据写入(1.1.2.8)
//创建目录,分别是/data/user_de/userid/,/data/system_de/userid/;/data/user/userid/,/data/system_ce/userid/
mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
                    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
VolumeInfo{private}:
    type=PRIVATE diskId=null partGuid=null mountFlags=0 mountUserId=-1 
    state=MOUNTED 
    fsType=null fsUuid=null fsLabel=null 
    path=/data internalPath=null 
  • 真正的创建新用户(1.1.2.8)
mPm.createNewUser(userId, disallowedPackages);
  • 更新备份文件中的user信息1.1.2.10,1.1.2.11)
    上组步骤会对userData数据进行改变,例如partial状态,因此这里是更新
    /data/system/userid.xml和/data/system/userlist.xml

  • 发送创建成功广播

以下对总体流程的某些核心逻辑,进行详细解读

2.1.1 新用户数据创建存储目录创建详解

Profile User创建流程_存储目录创建

2.1.1.1 遍历可写内存

mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);

调用mUserDataPreparer辅助类的prepareUserData

    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);
            }
        }
    }

枚举所有的内部可写存储,,VolumeInfo的数据内容为,然后跳转到prepareUserDataLI

VolumeInfo{private}:
    type=PRIVATE diskId=null partGuid=null mountFlags=0 mountUserId=-1 
    state=MOUNTED 
    fsType=null fsUuid=null fsLabel=null 
    path=/data internalPath=null 

    //VolumeInfo初始化位置addInternalVolumeLocked#addInternalVolumeLocked
    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);
    }

2.1.1.2 prepareUserDataLI

主要是三个阶段:

阶段一:prepareUserStorage

主要的存储目录创建逻辑在Ext4Crypt.cpp中

bool e4crypt_prepare_user_storage(const std::string& volume_uuid, userid_t user_id, int serial,
                                  int flags) {
    LOG(DEBUG) << "e4crypt_prepare_user_storage for volume " << escape_empty(volume_uuid)
               << ", user " << user_id << ", serial " << serial << ", flags " << flags;

    if (flags & android::os::IVold::STORAGE_FLAG_DE) {//设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用
        // DE_sys key
        auto system_legacy_path = android::vold::BuildDataSystemLegacyPath(user_id);//data/system/user_id/
        auto misc_legacy_path = android::vold::BuildDataMiscLegacyPath(user_id);//data/misc/user/user_id/
        auto profiles_de_path = android::vold::BuildDataProfilesDePath(user_id);//data/misc/profiles/cur/user_id/

        // DE_n key
        auto system_de_path = android::vold::BuildDataSystemDePath(user_id);//data/system_de/user_id/
        auto misc_de_path = android::vold::BuildDataMiscDePath(user_id);//data/misc_de/userid/
        auto vendor_de_path = android::vold::BuildDataVendorDePath(user_id);//data/vendor_de/user_id
        auto user_de_path = android::vold::BuildDataUserDePath(volume_uuid, user_id);//data/user_de/user_id
        //创建上述的七个目录,并设置目录权限
        if (volume_uuid.empty()) {
            if (!prepare_dir(system_legacy_path, 0700, AID_SYSTEM, AID_SYSTEM)) return false;
            #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(profiles_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;

            if (!prepare_dir(system_de_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false;
            if (!prepare_dir(misc_de_path, 01771, AID_SYSTEM, AID_MISC)) return false;
            if (!prepare_dir(vendor_de_path, 0771, AID_ROOT, AID_ROOT)) return false;
        }
        if (!prepare_dir(user_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;
        //如果是fbe设备
        if (e4crypt_is_native()) {
            PolicyKeyRef de_ref;
            if (volume_uuid.empty()) {
                if (!lookup_key_ref(s_de_key_raw_refs, user_id, &de_ref.key_raw_ref)) return false;
                get_data_file_encryption_modes(&de_ref);
                if (!ensure_policy(de_ref, system_de_path)) return false;
                if (!ensure_policy(de_ref, misc_de_path)) return false;
                if (!ensure_policy(de_ref, vendor_de_path)) return false;
            } else {
                if (!read_or_create_volkey(misc_de_path, volume_uuid, &de_ref)) return false;
            }
            if (!ensure_policy(de_ref, user_de_path)) return false;
        }
    }

    if (flags & android::os::IVold::STORAGE_FLAG_CE) {//凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用
        // CE_n key
        auto system_ce_path = android::vold::BuildDataSystemCePath(user_id);//data/system_ce/user_id/
        auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id);//data/misc_ce/user_id/
        auto vendor_ce_path = android::vold::BuildDataVendorCePath(user_id);//data/vendor_ce/user_id/
        auto media_ce_path = android::vold::BuildDataMediaCePath(volume_uuid, user_id);//data/media/user_id/
        auto user_ce_path = android::vold::BuildDataUserCePath(volume_uuid, user_id);//data/user/user_id/

        if (volume_uuid.empty()) {
            if (!prepare_dir(system_ce_path, 0770, AID_SYSTEM, AID_SYSTEM)) return false;
            if (!prepare_dir(misc_ce_path, 01771, AID_SYSTEM, AID_MISC)) return false;
            if (!prepare_dir(vendor_ce_path, 0771, AID_ROOT, AID_ROOT)) return false;
        }
        if (!prepare_dir(media_ce_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW)) return false;
        if (!prepare_dir(user_ce_path, 0771, AID_SYSTEM, AID_SYSTEM)) return false;
        //如果支持FBE
        if (e4crypt_is_native()) {
            PolicyKeyRef ce_ref;
            if (volume_uuid.empty()) {
                if (!lookup_key_ref(s_ce_key_raw_refs, user_id, &ce_ref.key_raw_ref)) return false;
                get_data_file_encryption_modes(&ce_ref);
                if (!ensure_policy(ce_ref, system_ce_path)) return false;
                if (!ensure_policy(ce_ref, misc_ce_path)) return false;
                if (!ensure_policy(ce_ref, vendor_ce_path)) return false;

            } else {
- 
                if (!read_or_create_volkey(misc_ce_path, volume_uuid, &ce_ref)) return false;
            }
            if (!ensure_policy(ce_ref, media_ce_path)) return false;
            if (!ensure_policy(ce_ref, user_ce_path)) return false;
        }

        if (volume_uuid.empty()) {
            // Now that credentials have been installed, we can run restorecon
            // over these paths
            // NOTE: these paths need to be kept in sync with libselinux
            android::vold::RestoreconRecursive(system_ce_path);
            android::vold::RestoreconRecursive(misc_ce_path);
        }
    }
    if (!prepare_subdirs("prepare", volume_uuid, user_id, flags)) return false;

    return true;
}

在启用了 FBE 的设备上,每位用户均有两个可供应用使用的存储位置:

  • 设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用

    //DE_sys key
    
    • //data/system/user_id/
    • //data/misc/user/user_id/
    • //data/misc/profiles/cur/user_id/
      // DE_n key
    • //data/system_de/user_id/
    • //data/misc_de/userid/
    • //data/vendor_de/user_id
    • //data/user_de/user_id
  • 凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用

    • //data/system_ce/user_id/
    • //data/misc_ce/user_id/
    • //data/vendor_ce/user_id/
    • //data/media/user_id/
    • //data/user/user_id/

prepareUserDataLI主要完成以上目录的创建

阶段二:enforceSerialNumber####

        if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
            enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
            if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
                enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial);
            }
        }
        if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
            enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial);
            if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
                enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial);
            }
        }

以上四个enforceSerialNumber分别对应的目录为:

//de
/data/user_de/userid/
/data/system_de/userid/
//ce
/data/user/userid/
/data/system_ce/userid/

每个方法最后都会调用到OS的setxattr中去

    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();
        }
    }

Os.setxattr往user加密所需相应目录inode的属性中加入了key为“user.serial”,value为userinfo::serialNumber的值

阶段三:Installer#createUserData

如上调用序列图,最优的调用逻辑值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>目录并设置了新的权限,注意这段代码prepareUserStorage#e4crypt_prepare_user_storage中已经覆盖。
不过MANAGE_MISC_DIRS为0,所以e4crypt_prepare_user_storage中的相同代码其实没有走。
另一个注意点就是uid和gid是依据userid生成的,所以该目录的linux层面权限检查会更严格

2.1.2 PMS#createNewUser

    /** Called by UserManagerService */
    void createNewUser(int userId, String[] disallowedPackages) {
        synchronized (mInstallLock) {
            mSettings.createNewUserLI(this, mInstaller, userId, disallowedPackages);
        }
        synchronized (mPackages) {
            scheduleWritePackageRestrictionsLocked(userId);
            scheduleWritePackageListLocked(userId);
            applyFactoryDefaultBrowserLPw(userId);
            primeDomainVerificationsLPw(userId);
        }
    }

整体流程图如下:

PMS#createNewUser

第一步: mSettings.createNewUserLI

Profile User创建流程_createNewUser_createNewUserLI
 void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
            int userHandle, String[] disallowedPackages) {
        String[] volumeUuids;
        String[] names;
        int[] appIds;
        String[] seinfos;
        int[] targetSdkVersions;
        int packagesCount;
        synchronized (mPackages) {
            Collection<PackageSetting> packages = mPackages.values();//获取系统中的应用包名信息
            packagesCount = packages.size();
            //存储保存要安装到新用户的应用信息
            volumeUuids = new String[packagesCount];
            names = new String[packagesCount];
            appIds = new int[packagesCount];
            seinfos = new String[packagesCount];
            targetSdkVersions = new int[packagesCount];

            Iterator<PackageSetting> packagesIterator = packages.iterator();
            for (int i = 0; i < packagesCount; i++) {//遍历每个应用,只有系统应用才会被安装到新创建的用户下
                PackageSetting ps = packagesIterator.next();
                if (ps.pkg == null || ps.pkg.applicationInfo == null) {
                    continue;
                }
                final boolean shouldInstall = ps.isSystem() &&
                        !ArrayUtils.contains(disallowedPackages, ps.name);
                // Only system apps are initially installed.
                ps.setInstalled(shouldInstall, userHandle);
                if (!shouldInstall) {
                    writeKernelMappingLPr(ps);
                }
                // Need to create a data directory for all apps under this user. Accumulate all
                // required args and call the installer after mPackages lock has been released
                volumeUuids[i] = ps.volumeUuid;
                names[i] = ps.name;
                appIds[i] = ps.appId;
                seinfos[i] = ps.pkg.applicationInfo.seInfo;
                targetSdkVersions[i] = ps.pkg.applicationInfo.targetSdkVersion;
            }
        }
        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]);//在指定的目录创建app数据
            } catch (InstallerException e) {
                Slog.w(TAG, "Failed to prepare app data", e);
            }
        }
        synchronized (mPackages) {
            applyDefaultPreferredAppsLPw(service, userHandle);
        }
    }

以上代码主要做的事情

  • 获取系统中的应用包名信息,过滤应用,只有系统应用才会被安装到新的用户中
  • 对需要安装到新用户的应用执行installer.createAppData操作,其核心逻辑是在InstalldNativeService中

** InstalldNativeService#createAppData **

binder::Status InstalldNativeService::createAppData(const std::unique_ptr<std::string>& uuid,
        const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
        const std::string& seInfo, int32_t targetSdkVersion, int64_t* _aidl_return) {
    ENFORCE_UID(AID_SYSTEM);
    CHECK_ARGUMENT_UUID(uuid);
    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
    std::lock_guard<std::recursive_mutex> lock(mLock);

    const char* uuid_ = uuid ? uuid->c_str() : nullptr;
    const char* pkgname = packageName.c_str();

    // Assume invalid inode unless filled in below
    if (_aidl_return != nullptr) *_aidl_return = -1;

    int32_t uid = multiuser_get_uid(userId, appId);
    int32_t cacheGid = multiuser_get_cache_gid(userId, appId);
    mode_t targetMode = targetSdkVersion >= MIN_RESTRICTED_HOME_SDK_VERSION ? 0700 : 0751;

    // If UID doesn't have a specific cache GID, use UID value
    if (cacheGid == -1) {
        cacheGid = uid;
    }

    if (flags & FLAG_STORAGE_CE) {
        //获取要创建的app目录:data/user/user_id/pkgname/
        auto path = create_data_user_ce_package_path(uuid_, userId, pkgname);
        bool existing = (access(path.c_str(), F_OK) == 0);//检查改目录是否存在
        //创建path/cache,/path/code_cache/目录
        if (prepare_app_dir(path, targetMode, uid) ||
                prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid) ||
                prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid)) {
            return error("Failed to prepare " + path);
        }

        // Consider restorecon over contents if label changed
       //使用restorecon来恢复app目录中所有文件的SELinux配置信息
        if (restorecon_app_data_lazy(path, seInfo, uid, existing) ||
                restorecon_app_data_lazy(path, "cache", seInfo, uid, existing) ||
                restorecon_app_data_lazy(path, "code_cache", seInfo, uid, existing)) {
            return error("Failed to restorecon " + path);
        }

        // Remember inode numbers of cache directories so that we can clear
        // contents while CE storage is locked
        if (write_path_inode(path, "cache", kXattrInodeCache) ||
                write_path_inode(path, "code_cache", kXattrInodeCodeCache)) {
            return error("Failed to write_path_inode for " + path);
        }

        // And return the CE inode of the top-level data directory so we can
        // clear contents while CE storage is locked
        if ((_aidl_return != nullptr)
                && get_path_inode(path, reinterpret_cast<ino_t*>(_aidl_return)) != 0) {
            return error("Failed to get_path_inode for " + path);
        }
    }
    if (flags & FLAG_STORAGE_DE) {
       //获取要创建的app目录:data/user_de/user_id/pkgname/
        auto path = create_data_user_de_package_path(uuid_, userId, pkgname);
        bool existing = (access(path.c_str(), F_OK) == 0);
       //创建path/cache,/path/code_cache/目录
        if (prepare_app_dir(path, targetMode, uid) ||
                prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid) ||
                prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid)) {
            return error("Failed to prepare " + path);
        }

        // Consider restorecon over contents if label changed
        if (restorecon_app_data_lazy(path, seInfo, uid, existing) ||
                restorecon_app_data_lazy(path, "cache", seInfo, uid, existing) ||
                restorecon_app_data_lazy(path, "code_cache", seInfo, uid, existing)) {
            return error("Failed to restorecon " + path);
        }

        if (prepare_app_quota(uuid, findQuotaDeviceForUuid(uuid), uid)) {
            return error("Failed to set hard quota " + path);
        }

        if (!prepare_app_profile_dir(packageName, appId, userId)) {
            return error("Failed to prepare profiles for " + packageName);
        }
    }
    return ok();
}

以上代码主要完成的事情为创建以下目录:

//ce
/data/user/user_id/pkgname/
/data/user/user_id/pkgname/cache/
/data/user/user_id/pkgname/code_cache/
//de
/data/user_de/user_id/pkgname/
/data/user_de/user_id/pkgname/cache/
/data/user_de/user_id/pkgname/code_cache/
第二步:scheduleWritePackageRestrictionsLocked
scheduleWritePackageRestrictionsLocked

该函数的主要逻辑是依据系统的Settings#mPackages重新创建新用户的/data/system/users/11/package-restrictions.xml文件。

写入的内容主要如下:

  1. 系统应用的pkg信息
  2. 当前用户的preferred-activities信息
  3. 当前用户的crossProfile-intent-filters信息
  4. 当前用户的DefaultApps信息
  5. 当前用户的block-uninstall-packages信息

该文件的主要数据信息为:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<package-restrictions>
    <pkg name="com.android.fmradio" ceDataInode="579364" />
    <pkg name="com.mediatek.gba" ceDataInode="579367" />
    <pkg name="com.mediatek.ims" ceDataInode="579370" />
    <pkg name="com.android.calendar" ceDataInode="579523">
        <disabled-components>
            <item name="com.android.calendar.UpgradeReceiver" />
        </disabled-components>
    </pkg>
    <preferred-activities>
        <item name="com.android.mms/.ui.ComposeMessageActivity" match="200000" always="true" set="1">
            <set name="com.android.mms/.ui.ComposeMessageActivity" />
            <filter>
                <action name="android.intent.action.SENDTO" />
                <cat name="android.intent.category.DEFAULT" />
                <scheme name="mmsto" />
            </filter>
        </item>
        <item name="com.android.mms/.ui.ComposeMessageActivity" match="200000" always="true" set="1">
            <set name="com.android.mms/.ui.ComposeMessageActivity" />
            <filter>
                <action name="android.intent.action.SENDTO" />
                <cat name="android.intent.category.DEFAULT" />
                <scheme name="mms" />
            </filter>
        </item>
        <item name="com.android.mms/.ui.ComposeMessageActivity" match="200000" always="true" set="1">
            <set name="com.android.mms/.ui.ComposeMessageActivity" />
            <filter>
                <action name="android.intent.action.SENDTO" />
                <cat name="android.intent.category.DEFAULT" />
                <scheme name="smsto" />
            </filter>
        </item>
        <item name="com.android.mms/.ui.ComposeMessageActivity" match="200000" always="true" set="1">
            <set name="com.android.mms/.ui.ComposeMessageActivity" />
            <filter>
                <action name="android.intent.action.SENDTO" />
                <cat name="android.intent.category.DEFAULT" />
                <scheme name="sms" />
            </filter>
        </item>
    </preferred-activities>
    <persistent-preferred-activities />
    <crossProfile-intent-filters>
        <item targetUserId="0" flags="2" ownerPackage="com.android.settings">
            <filter>
                <action name="com.android.settings.DISPLAY_SETTINGS" />
                <action name="android.settings.DISPLAY_SETTINGS" />
                <cat name="android.intent.category.DEFAULT" />
            </filter>
        </item>
        ...
        <item targetUserId="0" flags="2" ownerPackage="com.android.settings">
            <filter>
                <action name="android.settings.MANAGE_DEFAULT_APPS_SETTINGS" />
                <action name="android.settings.HOME_SETTINGS" />
                <cat name="android.intent.category.DEFAULT" />
            </filter>
        </item>
        <item targetUserId="0" flags="2" ownerPackage="com.android.settings">
            <filter>
                <action name="android.settings.NFCSHARING_SETTINGS" />
                <cat name="android.intent.category.DEFAULT" />
            </filter>
        </item>
        <item targetUserId="0" flags="2" ownerPackage="com.android.settings">
            <filter>
                <action name="android.intent.action.MAIN" />
                <action name="android.settings.WIFI_CALLING_SETTINGS" />
                <cat name="android.intent.category.DEFAULT" />
                <cat name="android.intent.category.VOICE_LAUNCH" />
            </filter>
        </item>
        <item targetUserId="0" flags="2" ownerPackage="com.android.settings">
            <filter>
                <action name="android.settings.INTERNAL_STORAGE_SETTINGS" />
                <action name="android.settings.MEMORY_CARD_SETTINGS" />
                <cat name="android.intent.category.DEFAULT" />
            </filter>
        </item>
    </crossProfile-intent-filters>
    <default-apps />
</package-restrictions>
第三步:scheduleWritePackageListLocked#####
scheduleWritePackageListLocked

更新/data/system/packages.list文件

文件格式为:

com.android.fmradio 10019 0 /data/user/0/com.android.fmradio platform:privapp:targetSdkVersion=28 3002,1013
com.mediatek.gba 1001 0 /data/user/0/com.mediatek.gba platform:privapp:targetSdkVersion=26 1065,3002,1023,3003,3001,3007,1002,3010,3011,1004,2002,3006
com.mediatek.ims 1001 0 /data/user/0/com.mediatek.ims platform:privapp:targetSdkVersion=26 1065,3002,1023,3003,3001,3007,1002,3010,3011,1004,2002,3006
com.android.cts.priv.ctsshim 10022 0 /data/user/0/com.android.cts.priv.ctsshim default:privapp:targetSdkVersion=24 none
  • 第一列是app的包名,也就是AndroidManifest.xml文件中的package=”xxx.xxx.xxx”设置的内容
  • 第二列是app的使用的userid(uid), 如果没有在AndroidManifext.xml里使用android:sharedUserId属性指定UID, 在app安装的时候,系统会给这个app自动分配一uid,以后app运行时,就用这个UID运行
  • 第三列是app是否处于调试模式,由AndroidManifext.xml里android:debuggable指定
  • 第四列是app的数据存放路径,一般是”/data/data/${package_name}”这样的文件夹
  • 第五列是app的seinfo信息,这个好像和SEAndroid机制有关,具体我也不是太清楚,它的值好像有platform, default之分
  • 第六列是app所属的user group, 如果一个app不属于任何group, 这里的值是None
第四步:applyFactoryDefaultBrowserLPw#####

检查是否配置了default_browser字符串,如果配置了,并且系统安装了该应用,则设置改应用为默认的浏览器应用

    private void applyFactoryDefaultBrowserLPw(int userId) {
        // The default browser app's package name is stored in a string resource,
        // with a product-specific overlay used for vendor customization.
        String browserPkg = mContext.getResources().getString(
                com.android.internal.R.string.default_browser);
        //start, wangsenhao, for cts default Browser, 2018.10.25
        if (SystemProperties.get("ro.hct_fastpass_defaultBrowser").equals("1")) {
            browserPkg = "com.android.chrome";
        }
        //end, wangsenhao, for cts default Browser, 2018.10.25
        if (!TextUtils.isEmpty(browserPkg)) {
            // non-empty string => required to be a known package
            PackageSetting ps = mSettings.mPackages.get(browserPkg);
            if (ps == null) {
                Slog.e(TAG, "Product default browser app does not exist: " + browserPkg);
                browserPkg = null;
            } else {
                mSettings.setDefaultBrowserPackageNameLPw(browserPkg, userId);
            }
        }

        // Nothing valid explicitly set? Make the factory-installed browser the explicit
        // default.  If there's more than one, just leave everything alone.
        if (browserPkg == null) {
            calculateDefaultBrowserLPw(userId);
        }
    }
第五步:primeDomainVerificationsLPw#####

该方法主要逻辑也更新/data/system/users/user_id/package-restrictions.xml文件

第六步:scheduleWriteSettingsLocked#####

改方法主要逻辑是更新/data/system/packages.xml/文件,改文件的主要目录结构如下:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version ... />
    <version ... />

    <permissions>
        <item name="xxxS" package="xxx" protection="xx" />
        ... ...
    </permissions>

    <package xxx>
        ...
    </package>
    ...

    <shared-user xxx>
        ...
    </shared-user>
    ...

    <keyset-settings version="1">
        ...
    </keyset-settings>
</packages>

该文件主要包含以下内容

  • permission块: 里面包含了系统中所有定义的权限的信息
    这个权限包含了系统和应用定义的所有权限,系统是指framework-res.app应用
  <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
  <item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" package="com.android.providers.downloads" />
  <item name="android.permission.PROVISION_MANAGED_DEVICE_SILENTLY" package="com.android.managedprovisioning" protection="1026" />
  • keyset-settings块:里面包含了已安装app签名的public key信息
    <keyset-settings version="1">
        <keys>
            <public-key identifier="1" value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsoQJxQwimg9J8N7NhmOwD5n1jf6ZyzJ3K6bYLDFh/omXKUleH9yq5NYNs/hrNPU5ZKI9DlSKxVJ6fvnxU45ssqWoefXYnED5Uv1MLeT+vB7sxNwxqYRFqi4xMGeh/x9mK1eXR6lEwwt0mIN3HldpJgV14AsUV1Go0ho1XHeJMh/4McmCrmYaRrKIjLDIq5hMDVir5Kgj8M9F7Nw2iusg+LMPxEw1L+Y3CDxtyx6opDTkMeOKcEFJOXL2XnegWbPOHPodvN5c7JLwEmYuDV6CWOKeJudGmZ6uT6TqjyCToLo0s4UbWMH32b9xNZrovghwmfGs7gC+qRZQlRNwVpLi6QIDAQAB" />
            <public-key identifier="2" value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqivHIp7IL1ptF8NimDQQV/jxQj3UHSHoo5/qoJxAM0l22/P9fb8bP7qu1JClJuYN3H7WxdFqW9wtrrOwLM7/Xwgq/TfaaRGe+gQCk+U50kVcRPJ/mvsNHto57eZf7Qf3RT5psGKfjObVULbVsEzu/Oj94mnGNz0vWKoMLqZ62UcdDnG4hJZH1WIJ54qSkZyngG4H8WC/Gbxhlya2/XSXK1tbUcKgpqJX/3XrXwIvkHzMIEr61VhF1NsSuLuptnO4/W5CUr0/o71ebP7uHDMGLYf0hZbIGH2QyAhNaF4ag9ZI31QBMMRQNsKt0vgoysldJlVRt7Xa2R0WhwX00k6AjQIDAQAB" />
            <public-key identifier="3" value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxZCjYuLK/NcZRQSo/fptqccFj0neukgcFq8nltr16X/1LElAkR8vEyo6rLhDgztoKnt2RR9g1oMr3We6Tej2pOLB+6fWA4DxyFG7L+diP729l6B2vvlwy8gwASdqe76sOMqJJnnQiJ1Tha8RpM6L/D4ytmK22JyeZzj/+I3bj/GKjXYsVgHUrxeMaAjlwHr/gO+FVp5bX+gUBn50COlYDGjcRUngtFYww3gO4Rmmr6Of/AijS1zV0jWIJIdLa02EwlQPGPJoM159ZEJfnGNvT74q/mHdzUKMds7miod+FKpscM8OW3ic7074nwLIbzyWZR954J1Gx1cn2jjkpQ0H0wIDAQAB" />
            <public-key identifier="4" value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0aJVoM4srKopnbY0cIc5mre/6TydyZRJPXP5EVTQ20+Iuw3JlgbTbiCN10GVr/LO2u3hgOehDZsDwRWf+GX5nz9sOef8Wtew4M289IhTH8yaU6BtqDw7vWHvYvhuGiZLvI5W2OVoMp0o7hcnTlXxgq7NfKQkMiE/c9gAl8rAbvK4UOzu6IReRFvzN/XjhCEmQbyKQSae05OYySPo+Uvmflbi4dTYV2fTBVK3a0qk6dX8BbTq9ZgZYnqS9aytGYM+PII4SoWk6TINegFJLownYIv1ToFbqqzNZn0EFYsZpxV2oMOf0yrpqd7bNfM5cf5cIXrLTs8IcQHyf4l6+W8I8wIDAQAB" />
            <public-key identifier="5" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcelijlXLEs3nd/KZ2XpXTrsaf42LOYi5ilkfPRBueS3tpXlQP0pt9p7KrZHkwifK2kRLRGsV3aXPdaM/4i2cYJsEoblfHKUx2x8EYrkG/kzb/muCqkMZe19sHSf8Te4FbbTtTq6rXLXgXsLiQDK7xLuoT0SuvC4yzBUO/s0icIwIDAQAB" />
            <public-key identifier="6" value="MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA1pMZBN7GCySx7cdi4NnYJT4+zWzrHeL/Boyo6LyozWvTeG6nCqds5g67D5k1Wf/ZPnepQ+foPUtkuOT+otPmVvHiZ6gbv7IwtXjCBEO+THIYuEb1IRWG8DihTonCvjh/jr7Pj8rD2h7jMMnqk9Cnw9xK81AiDVAIBzLggJcX7moFM1nmppTsLLPyhKCkZsh6lNg7MQk6ZzcuL2QSwG5tQvFYGN/+A4HMDNRE2mzdw7gkWBlIAbMlZBNPv96YySh3SNv1Z2pUDYFUyLvKB7niR1UzEcRrmvdv3uzMjmnnyKLQjngmIJQ/mXJ9PAT+cpkdmd+brjigshd/ox1bav7pHwIBAw==" />
        </keys>
        <keysets>
            <keyset identifier="1">
                <key-id identifier="1" />
            </keyset>
            <keyset identifier="2">
                <key-id identifier="2" />
            </keyset>
            <keyset identifier="3">
                <key-id identifier="3" />
            </keyset>
            <keyset identifier="4">
                <key-id identifier="4" />
            </keyset>
            <keyset identifier="5">
                <key-id identifier="5" />
            </keyset>
            <keyset identifier="6">
                <key-id identifier="6" />
            </keyset>
        </keysets>
        <lastIssuedKeyId value="6" />
        <lastIssuedKeySetId value="6" />
    </keyset-settings>

keyset-settings块里收集了所有app签名的公钥信息,和后面介绍的package块中的信息有关联。

1. keysets块中包含了很多keyset, 每个keyset都有一个编号用identifier表示,keyset里包含的key-id里的identifier和public-key的identifier的值相对应
2. keys块中public-key里的value值就是从apk包里的签名文件里提取出来的的公钥的值
3. lastIssuedKeyId和lastIssuedKeySetId表示的是最近一次取出来的公钥所属的set编号
  • package块:里面包含了系统中所有安装的app的详细信息
    <package name="com.baidu.map.location" codePath="/system/priv-app/Baidu_Location" nativeLibraryPath="/system/priv-app/Baidu_Location/lib" primaryCpuAbi="arm64-v8a" publicFlags="940097029" privateFlags="8" ft="1691931ec40" it="1691931ec40" ut="1691931ec40" version="51" userId="10016" isOrphaned="true">
        <sigs count="1" schemeVersion="1">
            <cert index="5" key="30820253308201bca00302010202044b9dd8e7300d06092a864886f70d0101050500306d310b300906035504061302434e3111300f060355040813085368616e676861693111300f060355040713085368616e6768616931133011060355040a130a426169647520496e632e31133011060355040b130a426169647520496e632e310e300c0603550403130542616964753020170d3130303331353036353131395a180f32303634313231363036353131395a306d310b300906035504061302434e3111300f060355040813085368616e676861693111300f060355040713085368616e6768616931133011060355040a130a426169647520496e632e31133011060355040b130a426169647520496e632e310e300c06035504031305426169647530819f300d06092a864886f70d010101050003818d00308189028181009c7a58a39572c4b379ddfca6765e95d3aec69fe362ce622e629647cf441b9e4b7b695e540fd29b7da7b2ab64793089f2b69112d11ac5776973dd68cff88b671826c1286e57c7294c76c7c118ae41bf9336ff9ae0aa90c65ed7db0749ff137b815b6d3b53abaad72d7817b0b8900caef12eea13d12baf0b8cb30543bfb3489c230203010001300d06092a864886f70d01010505000381810063231fb3859d01f75cd7ed810aa5c08eb8fba5b7b7bf11f2c65ae70aa69365b7c985334a38be2c6712c77a1b8aa09d1ae84b51b0062968734700f795b08a7ff5dd73751cd63254f211cb6386fa733690d826b44c169f76c23b82f813b15c1da47a2be69369cd75bf7cdaa337d2ea38726a778583838409b482efc126f7e668b3" />
        </sigs>
        <perms>
            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
            <item name="android.permission.INSTALL_LOCATION_PROVIDER" granted="true" flags="0" />
            <item name="android.permission.INTERNET" granted="true" flags="0" />
            <item name="android.permission.WRITE_SECURE_SETTINGS" granted="true" flags="0" />
            <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
            <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
            <item name="android.permission.INTERACT_ACROSS_USERS" granted="true" flags="0" />
            <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="5" />
    </package>

package块里包含了每个app的详细信息,具体说明如下:

1. name:app的包名;codePath:表示apk的存放路径
2. nativeLibraryPath表示app使用的.so库存放的位置,primaryCpuAbi表示app以哪种abi架构运行
3. publicFlags和privateFlags应用的flag信息,privateFlags对应的是ApplicationInfo中的各种PRIVATE_FLAG
4. ft表示apk文件上次被更改的时间,it表示app第一次安装的时间,ut表示app上次被更新时间,它的值好像一直和ft相同, ota或app重装之后,这里的ft和ut可能会改变
5. version是app的版本号信息, 也就是在AndroidManifest.xml里配置的android:versioncode
6. userId是为app分配的user id, 如果有使用shareUserId, 这里出现的就是SharedUserId
7. sigs块里的count表示这个app有多少个签名信息,有的app可能会被多个证书签名。cert里的index表示这个app使用的证书的序号,当系统发现一个新的证书,这个号就会加1,key是app使用的证书内容的ascii码值;
8. PKMS在扫apk文件过程中,如果发现它和之前扫描到的apk使用的是相同的签名证书,这里就只会有个index的值,并没有key的值。拥有相同的index的package, 表明它们使用的是相同的签名
9. perms块里是这个app拥有的权限, 对于一般的app,这些权限是在AndroidManifest.xml里写明的;那些使用了相同UID的app, 这里的权限就是所有使用相同UID的app申请的权限的总和;granted表示这个权限是不是已经被允许
10. proper-signing-keyset里的identifier就是上面说的keysets里identifier的值。它是用来标明这个app使用的是哪个公钥
  • shared-user块:里面包含了所有系统定义的shareuser的信息
 <shared-user name="android.uid.mms" userId="10020">
        <sigs count="1" schemeVersion="3">
            <cert index="0" />
        </sigs>
        <perms>
            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
            <item name="android.per
             ...
            <item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
        </perms>
   </shared-user>
   <shared-user name="android.uid.system" userId="1000">
        <sigs count="1" schemeVersion="3">
            <cert index="0" />
        </sigs>
        <perms>
            <item name="android.permission.BIND_INCALL_SERVICE" granted="true" flags="0" />
            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
            ...
            <item name="android.permission.DELETE_PACKAGES" granted="true" flags="0" />
        </perms>
    </shared-user>

shared_user获取和添加的逻辑如下

    /** Gets and optionally creates a new shared user id. */
    SharedUserSetting getSharedUserLPw(String name, int pkgFlags, int pkgPrivateFlags,
            boolean create) throws PackageManagerException {
        SharedUserSetting s = mSharedUsers.get(name);
        if (s == null && create) {
            s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
            s.userId = newUserIdLPw(s);
            if (s.userId < 0) {
                // < 0 means we couldn't assign a userid; throw exception
                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                        "Creating shared user " + name + " failed");
            }
            Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId);
            mSharedUsers.put(name, s);
        }
        return s;
    }

参考文档:

android 9.x mtk源码
https://blog.csdn.net/weixin_40107510/article/details/78556427
https://www.twblogs.net/a/5b7f307a2b717767c6ae36b4/zh-cn
https://www.jianshu.com/p/d25f73805729

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容