之前在项目过程中,遇到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;
}
主要处理策略如下:
- 获取当前用户支持的Role,配置在 packages/apps/PermissionController/res/xml/roles.xml
- ASSISTANT和BROWSER是没有设置的,把这2个添加到需要获取的列表里面
- 对于用户已经设置过的,用已经有的值再设置一次,不会修改用户数据
- 对于没有配置的,则getDefaultHolders,里面的实现会获取支持对应能力的设置,然后设置为默认RoleHolder
- 当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个问题修复后,这一块的逻辑就不会影响了