android RoleManagerService

之前在项目过程中,遇到P版本升级到Q版本,有2个默认应用发生变化的问题,浏览器和语音助手,都变成Google的应用,记录一下分析的过程

1.RoleManagert和RoleManagerService相关的介绍

RoleManager就android Q版本新增加的特性,主要用来管理默认应用设置的,目前支持的Role有8种

public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
public static final String ROLE_BROWSER = "android.app.role.BROWSER";
public static final String ROLE_DIALER = "android.app.role.DIALER";
public static final String ROLE_SMS = "android.app.role.SMS";
public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
public static final String ROLE_HOME = "android.app.role.HOME";
public static final String ROLE_CALL_REDIRECTION = "android.app.role.CALL_REDIRECTION";
public static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING";

从对Role的描述来看,Role是系统中与某些特权相关联的唯一名称,系统支持的Role列表可能会随系统应用的更新而变化,对于非系统App不可见的,三方App应该使用isRoleAvailable接口来判断某个Role是否可用

对应用提供OnRoleHoldersChangedListener,用来监听这个Role发生变化。一般用法

// 实现接口
class RoleObserver implements OnRoleHoldersChangedListener {

    @Override
    public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
         // 如果关注的Role发生变化 ,可以在这个里面进行处理
    }
}

// 注册Listen
private RoleManager mRm = mContext.getSystemService(RoleManager.class);
mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL);

分2个步骤

  • 实现接口,当关注的接口发生变化的时候,进行相应的处理
  • 注册监听,将Listen注册到RoleManager里面,当系统的Role发生变化的时候,会回调onRoleHoldersChanged

针对系统应用,提供权限管理,需要想要改变Role,则需要在AndroidManifest.xml添加权限申请,注意这些权限是signature|installer的,三方应用无法使用

    <!-- @SystemApi Allows an application to manage the holders of a role.
         @hide -->
    <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
                android:protectionLevel="signature|installer" />

    <!-- @SystemApi Allows an application to observe role holder changes.
         @hide -->
    <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
                android:protectionLevel="signature|installer" />
2.默认浏览器和语音助手变化的原因分析

这2个应用的原因不一样,对比Pixel的现象也一致,确认是Google Q版本升级引入的,在后面的版本也都修改了

2.1默认浏览器变化的原因分析

默认浏览器变化的原因是:

androidP升级到Q版本后,没有对升级前的数据进行处理,ROLE_BROWSER的holder是空的,所以在设置里面选择默认应用的时候,会弹出多个浏览器让用户自己选择

RoleManagerService是一个系统服务,在随系统进程一起启动

08-22 18:41:08.513  1247  1247 I SystemServer: StartRoleManagerService
08-22 18:41:10.076  1247  1247 I SystemServiceManager: Calling onStartUser u0

在SystemServiceManager的startUser方法里面,会调用所有SystemService的onStartUser方法,

    public void startUser(final int userHandle) {
        Slog.i(TAG, "Calling onStartUser u" + userHandle);
        final int serviceLen = mServices.size();
        for (int i = 0; i < serviceLen; i++) {
            final SystemService service = mServices.get(i);
            try {
                service.onStartUser(userHandle);
            } catch (Exception ex) {
            }
        }
    }

然后看RoleManagerService.onStartUser,最终会调用到performInitialGrantsIfNecessaryAsync方法。

    public void onStartUser(@UserIdInt int userId) {
        performInitialGrantsIfNecessary(userId);
    }
    
    @MainThread
    private void performInitialGrantsIfNecessary(@UserIdInt int userId) {
        CompletableFuture<Void> result = performInitialGrantsIfNecessaryAsync(userId);
        try {
            result.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e);
        }
    }
    
    private CompletableFuture<Void> performInitialGrantsIfNecessaryAsync(@UserIdInt int userId) {
        RoleUserState userState;
        userState = getOrCreateUserState(userId);

        String packagesHash = computeComponentStateHash(userId);
        String oldPackagesHash = userState.getPackagesHash();
        boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash);
        // 由于大版本升级,签名信息发生了变化,Needgrant为true
        if (needGrant) {
            //TODO gradually add more role migrations statements here for remaining roles
            // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
            // for a given role before adding a migration statement for it here
            // 出问题的地方,没有对ROLE_BROWSER进行处理
            migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId);
            migrateRoleIfNecessary(RoleManager.ROLE_DIALER, userId);
            migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId);
            migrateRoleIfNecessary(RoleManager.ROLE_EMERGENCY, userId);

            // Some vital packages state has changed since last role grant
            // Run grants again
            Slog.i(LOG_TAG, "Granting default permissions...");
            // 在一个新的线程里面执行写入任务
            CompletableFuture<Void> result = new CompletableFuture<>();
            getOrCreateController(userId).grantDefaultRoles(FgThread.getExecutor(),
                    successful -> {
                        if (successful) {
                            userState.setPackagesHash(packagesHash);
                            result.complete(null);
                        } else {
                            result.completeExceptionally(new RuntimeException());
                        }
                    });
            return result;
        } else if (DEBUG) {
            Slog.i(LOG_TAG, "Already ran grants for package state " + packagesHash);
        }
        return CompletableFuture.completedFuture(null);
    }

注意performInitialGrantsIfNecessaryAsync方法里面,只处理了4个Role的,其中没有不包含ROLE_BROWSER,10.0.0_r6源代码里面是这样的

最新的代码10.0.0_r36里面这个问题已经修改了,修改记录如下
Fix migration for default browser
在这里插入图片描述

2.2默认语音助手变化的原因分析

根因是在VoiceInteractionManagerService启动的时候,会调用一次onRoleHoldersChanged对应的Log打印如下(自己添加的Log)

            @Override
            public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
                Slog.w(TAG,"onRoleHoldersChanged roleName = " + roleName + " ; roleHolders.isEmpty() = " + roleHolders.isEmpty());
                if (roleHolders.isEmpty()) {
                    Slog.w(TAG,"Settings.Secure.ASSISTANT " + " set empty ");
                    Settings.Secure.putStringForUser(getContext().getContentResolver(),
                            Settings.Secure.ASSISTANT, "", userId);
                } else {
                }

对应的Log打印如下

08-22 18:41:08.513  1247  1247 I SystemServer: StartRoleManagerService
08-22 18:41:08.523  1247  1247 I SystemServiceManager: Starting com.android.server.voiceinteraction.VoiceInteractionManagerService
08-22 18:41:08.536  1247  1247 W VoiceInteractionManagerService: onRoleHoldersChanged roleName = android.app.role.ASSISTANT ; roleHolders.isEmpty() = true
08-22 18:41:08.536  1247  1247 W VoiceInteractionManagerService: Settings.Secure.ASSISTANT  set empty 

08-22 18:41:10.118  1247  1247 W RoleManagerService:  performInitialGrantsIfNecessaryAsync needgrant = true

直接将Settings.Secure.ASSISTANT设置为空了,然后在上面的那一节的时候可以看到,将升级前的数据转换成Role的逻辑是在另外一个线程里面的,在这个设置之后,所以migrateRoleIfNecessary里面的代码也没有效果

没有Migrating相关的打印,roleHolders.isEmpty()为true,直接返回了

migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId);


    private void migrateRoleIfNecessary(String role, @UserIdInt int userId) {
        // Any role for which we have a record are already migrated
        RoleUserState userState = getOrCreateUserState(userId);
        if (!userState.isRoleAvailable(role)) {
            List<String> roleHolders = mLegacyRoleResolver.getRoleHolders(role, userId);
            if (roleHolders.isEmpty()) {
                return;
            }
            Slog.i(LOG_TAG, "Migrating " + role + ", legacy holders: " + roleHolders);
            userState.addRoleName(role);
            int size = roleHolders.size();
            for (int i = 0; i < size; i++) {
                userState.addRoleHolder(role, roleHolders.get(i));
            }
        }
    }

其中getRoleHolders方法的实现代码如下,也是通过读取Settings.Secure.ASSISTANT配置来的,在VoiceInteractionManagerService设置为空字符串后,返回的是一个空列表

    @Override
    public List<String> getRoleHolders(@NonNull String roleName, @UserIdInt int userId) {
        switch (roleName) {
            case RoleManager.ROLE_ASSISTANT: {
                String packageName;
                String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                        Settings.Secure.ASSISTANT, userId);
                // AssistUtils was using the default assistant app if Settings.Secure.ASSISTANT is
                // null, while only an empty string means user selected "None".
                // 在VoiceInteractionManagerService设置为空之后 ,走到了最后的那个else里面,packageName = null
                if (setting != null) {

                } else if (mContext.getPackageManager().isDeviceUpgrading()) {
                    String defaultAssistant = mContext.getString(R.string.config_defaultAssistant);
                    packageName = !TextUtils.isEmpty(defaultAssistant) ? defaultAssistant : null;
                } else {
                    packageName = null;
                }
                return CollectionUtils.singletonOrEmpty(packageName);
            }

综合上面的代码分析,得出的结论是:

在VoiceInteractionManagerService里面,第一次调用onRoleHoldersChanged的时机不对,因为从启动的流程来,这个方法会先于RoleManagerService里面的migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId);导致升级前的结果没有保存

android原生的修改记录
Fix legacy role holder resolution for assistant
在这里插入图片描述

3.默认浏览器和语音助手变成Google应用的原因

在packages/apps/PermissionController/src/com/android/packageinstaller/role/service/RoleControllerServiceImpl.java文件里面,有如下打印

Log.i(LOG_TAG, "Adding package as default/fallback role holder, package: "+ packageName + ", role: " + roleName);

从实际测试的结果来看,RoleControllerServiceImpl.onGrantDefaultRoles里面的打印会走到

   public boolean onGrantDefaultRoles() {
         
        // 1.获取当前用户支持的Role,配置在 packages/apps/PermissionController/res/xml/roles.xml
        ArrayMap<String, Role> roleMap = Roles.get(this);
        List<Role> roles = new ArrayList<>();
        List<String> roleNames = new ArrayList<>();
        ArraySet<String> addedRoleNames = new ArraySet<>();
        int roleMapSize = roleMap.size();
        for (int i = 0; i < roleMapSize; i++) {
            // 2. 从上面2节的分析,ASSISTANT和BROWSER是没有设置的
            if (!mRoleManager.isRoleAvailable(roleName)) {
                addedRoleNames.add(roleName);
            }
        }

        // Go through the holders of all roles.
        int rolesSize = roles.size();
        for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
            Role role = roles.get(rolesIndex);

            String roleName = role.getName();

            // For each of the current holders, check if it is still qualified, redo grant if so, or
            // remove it otherwise.
            List<String> currentPackageNames = mRoleManager.getRoleHolders(roleName);
            int currentPackageNamesSize = currentPackageNames.size();
            for (int currentPackageNamesIndex = 0;
                    currentPackageNamesIndex < currentPackageNamesSize;
                    currentPackageNamesIndex++) {
                String packageName = currentPackageNames.get(currentPackageNamesIndex);
                // 3. 对于用户已经设置过的,用已经有的值再设置一次,不会修改用户数据
                if (role.isPackageQualified(packageName, this)) {
                    // We should not override user set or fixed permissions because we are only
                    // redoing the grant here. Otherwise, user won't be able to revoke permissions
                    // granted by role.
                    addRoleHolderInternal(role, packageName, false, false, true);
                } else {
                    Log.i(LOG_TAG, "Removing package that no longer qualifies for the role,"
                            + " package: " + packageName + ", role: " + roleName);
                    removeRoleHolderInternal(role, packageName, false);
                }
            }

            // 4.对于没有配置的,则getDefaultHolders,里面的实现会获取支持对应能力的设置,然后设置为默认RoleHolder
            currentPackageNames = mRoleManager.getRoleHolders(roleName);
            currentPackageNamesSize = currentPackageNames.size();
            if (currentPackageNamesSize == 0) {
                List<String> packageNamesToAdd = null;
                if (addedRoleNames.contains(roleName)) {
                    packageNamesToAdd = role.getDefaultHolders(this);
                }
                if (packageNamesToAdd == null || packageNamesToAdd.isEmpty()) {
                    packageNamesToAdd = CollectionUtils.singletonOrEmpty(role.getFallbackHolder(
                            this));
                }

                int packageNamesToAddSize = packageNamesToAdd.size();
                for (int packageNamesToAddIndex = 0; packageNamesToAddIndex < packageNamesToAddSize;
                        packageNamesToAddIndex++) {
                    String packageName = packageNamesToAdd.get(packageNamesToAddIndex);

                    if (!role.isPackageQualified(packageName, this)) {
                        Log.e(LOG_TAG, "Default/fallback role holder package doesn't qualify for"
                                + " the role, package: " + packageName + ", role: " + roleName);
                        continue;
                    }
                    Log.i(LOG_TAG, "Adding package as default/fallback role holder, package: "
                            + packageName + ", role: " + roleName);
                    // TODO: If we don't override user here, user might end up missing incoming
                    // phone calls or SMS, so we just keep the old behavior. But overriding user
                    // choice about permission without explicit user action is bad, so maybe we
                    // should at least show a notification?
                    addRoleHolderInternal(role, packageName, true);
                }
            }

            // 5.当Role是独占的且有多个应用支持的时候,保留第一个
            currentPackageNames = mRoleManager.getRoleHolders(roleName);
            currentPackageNamesSize = currentPackageNames.size();
            if (role.isExclusive() && currentPackageNamesSize > 1) {
                Log.w(LOG_TAG, "Multiple packages holding an exclusive role, role: "
                        + roleName);
                // No good way to determine who should be the only one, just keep the first one.
                for (int currentPackageNamesIndex = 1;
                        currentPackageNamesIndex < currentPackageNamesSize;
                        currentPackageNamesIndex++) {
                    String packageName = currentPackageNames.get(currentPackageNamesIndex);

                    Log.i(LOG_TAG, "Removing extraneous package for an exclusive role, package: "
                            + packageName + ", role: " + roleName);
                    removeRoleHolderInternal(role, packageName, false);
                }
            }
        }

        AsyncTask.execute(
                () -> Utils.updateUserSensitive(getApplication(), Process.myUserHandle()));

        return true;
    }

主要处理策略如下:

  1. 获取当前用户支持的Role,配置在 packages/apps/PermissionController/res/xml/roles.xml
  2. ASSISTANT和BROWSER是没有设置的,把这2个添加到需要获取的列表里面
  3. 对于用户已经设置过的,用已经有的值再设置一次,不会修改用户数据
  4. 对于没有配置的,则getDefaultHolders,里面的实现会获取支持对应能力的设置,然后设置为默认RoleHolder
  5. 当Role是独占的且有多个应用支持的时候,保留第一个

重点看一下AssistantRoleBehavior和BrowserRoleBehavior,2个的逻辑类似
packages/apps/PermissionController/src/com/android/packageinstaller/role/model/AssistantRoleBehavior.java
packages/apps/PermissionController/src/com/android/packageinstaller/role/model/BrowserRoleBehavior.java

以AssistantRoleBehavior为例来说明

    @Override
    public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user,
            @NonNull Context context) {
        Context userContext = UserUtils.getUserContext(context, user);
        ActivityManager userActivityManager = userContext.getSystemService(ActivityManager.class);
        PackageManager userPackageManager = userContext.getPackageManager();
        Set<String> availableAssistants = new ArraySet<>();

        if (!userActivityManager.isLowRamDevice()) {
            // google voice配置了metadata,所以先添加进去的,对应上面的第5条,直接设置了google voice
            List<ResolveInfo> services = userPackageManager.queryIntentServices(
                    ASSIST_SERVICE_PROBE, PackageManager.GET_META_DATA
                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
            int numServices = services.size();
            for (int i = 0; i < numServices; i++) {
                ResolveInfo service = services.get(i);

                if (isAssistantVoiceInteractionService(userPackageManager, service.serviceInfo)) {
                    availableAssistants.add(service.serviceInfo.packageName);
                }
            }
        }

        List<ResolveInfo> activities = userPackageManager.queryIntentActivities(
                ASSIST_ACTIVITY_PROBE, PackageManager.MATCH_DEFAULT_ONLY
                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
        int numActivities = activities.size();
        for (int i = 0; i < numActivities; i++) {
            availableAssistants.add(activities.get(i).activityInfo.packageName);
        }

        return new ArrayList<>(availableAssistants);
    }

在PermissionController里面设置了后,又会触发VoiceInteractionManagerService.onRoleHoldersChanged,重新设置Settings.Secure.ASSISTANT的值

当然在android把那2个问题修复后,这一块的逻辑就不会影响了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值