默认应用是项目中常见的一项设置,比如默认桌面应用、浏览器应用等。Android Q之前版本设置方式通过 PackageManager 的 addPreferredActivity 接口实现。这种设置方式叫它为设置默认首选项比较恰当。设置好后,有首选项的时候就不弹出选择框。例如,设置桌面首选项应用:
private void setDefaultLauncher(PackageManager pm, String pkgName, String clsName) {
// 第1步:查询设备所有Home类型应用
Intent queryIntent = new Intent();
queryIntent.addCategory(Intent.CATEGORY_HOME);
queryIntent.setAction(Intent.ACTION_MAIN);
List homeActivities = pm.queryIntentActivities(queryIntent, 0);
if(homeActivities == null) {
return;
}
// 第2步:通过设置的包名类名,查找系统是否存在此Home类型应用
ComponentName defaultLauncher = new ComponentName(pkgName, clsName);
int activityNum = homeActivities.size();
ComponentName[] set = new ComponentName[activityNum];
int defaultMatch = -1;
for(int i = 0; i < activityNum; i++){
ResolveInfo info = homeActivities.get(i);
Log.i(TAG, "info " + info);
set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
if(clsName.equals(info.activityInfo.name)
&& pkgName.equals(info.activityInfo.packageName)){
defaultMatch = info.match;
}
}
// 如果没找到匹配的应用,就不继续设置
if(defaultMatch == -1){
return;
}
// 第3步:通过系统接口添加默认首选项应用
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);
pm.addPreferredActivity(filter, defaultMatch, set, defaultLauncher);
}
Android Q 之后这种方法逐步废弃,使用 RoleManager 替代。虽然目前还可以使用,但需要考虑兼容问题。目前已知问题:
1、Android R 上,设置-应用-默认应用 中显示的默认应用,都是通过 RoleManager 获取以及监听变化,通过 PackageManager 这种设置方式后,设置中检测不到变化,导致设置中默认应用显示存在问题(Home 类型应用是个例外,原因见下面代码分析)。
PackageManager (PM)是一个抽象类,ApplicationPackageManager (APM)继承实现部分抽象方法。addPreferredActivity 方法即在 APM 中实现,继而调用 PackageManagerService(PMS)中 addPreferredActivityInternal
private void addPreferredActivityInternal(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity, boolean always, int userId,
String opname) {
/* 检测权限代码 省略... */
synchronized (mLock) {
final PreferredIntentResolver pir = mSettings.editPreferredActivitiesLPw(userId);
// 添加默认应用到选择器,作为默认选项
pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
scheduleWritePackageRestrictionsLocked(userId);
}
// Home类型应用修改
if (!updateDefaultHomeNotLocked(userId)) {
postPreferredActivityChangedBroadcast(userId);
}
}
private boolean updateDefaultHomeNotLocked(int userId) {
if (Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ " is holding mLock", new Throwable());
}
// 第1步:获取默认首选项Home应用和当前保存的Home应用。注意这两个是不同的,保存位置不一样
if (!mSystemReady) {
// We might get called before system is ready because of package changes etc, but
// finding preferred activity depends on settings provider, so we ignore the update
// before that.
return false;
}
final Intent intent = getHomeIntent();
final List resolveInfos = queryIntentActivitiesInternal(intent, null,
PackageManager.GET_META_DATA, userId);
final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
intent, null, 0, resolveInfos, 0, true, false, false, userId);
final String packageName = preferredResolveInfo != null
&& preferredResolveInfo.activityInfo != null
? preferredResolveInfo.activityInfo.packageName : null;
final String currentPackageName = mPermissionManager.getDefaultHome(userId);
// 第2步:currentPackageName 为当前默认Home类型应用,如果设置Home应用首选项包名相同就不继续设置
if (TextUtils.equals(currentPackageName, packageName)) {
return false;
}
final String[] callingPackages = getPackagesForUid(Binder.getCallingUid());
if (callingPackages != null && ArrayUtils.contains(callingPackages,
mRequiredPermissionControllerPackage)) {
// PermissionController manages default home directly.
return false;
}
// 第3步:设置默认Home应用
mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
if (successful) {
postPreferredActivityChangedBroadcast(userId);
}
});
return true;
}
上面源码可以看出,通过 addPreferredActivity 设置首选项,如果设置的是 Home 应用,首选项改变后,包名改变,继续通过
mPermissionManager.setDefaultHome
进行设置。PermissionManagerServiceInternal 实现类是在:
PermissionManagerService.PermissionManagerServiceInternalImpl
下面是 PermissionManagerService 中 setDefaultHome 代码实现:
@Override
public void setDefaultHome(String packageName, int userId, Consumer callback) {
if (userId == UserHandle.USER_ALL) {
return;
}
// 第1步:获取默认Home应用Provider
DefaultHomeProvider provider;
synchronized (mLock) {
provider = mDefaultHomeProvider;
}
if (provider == null) {
return;
}
// 第2步:进行异步设置,设置成功后通过 callback 回调通知
provider.setDefaultHomeAsync(packageName, userId, callback);
}
DefaultHomeProvider 是 PermissionManagerServiceInternal 中一个接口类,子实现类为 RoleManagerService.DefaultHomeProvider 。实现 setDefaultHomeAsync 源码如下。
public void setDefaultHomeAsync(@Nullable String packageName, @UserIdInt int userId,
@NonNull Consumer callback) {
// 第1步:创建回调对象
RemoteCallback remoteCallback = new RemoteCallback(result -> {
boolean successful = result != null;
if (!successful) {
Slog.e(LOG_TAG, "Failed to set default home: " + packageName);
}
callback.accept(successful);
});
// 第2步:添加RoleHolder,如果包名为空,则清空RoleHolder
if (packageName != null) {
getOrCreateController(userId).onAddRoleHolder(RoleManager.ROLE_HOME,
packageName, 0, remoteCallback);
} else {
getOrCreateController(userId).onClearRoleHolders(RoleManager.ROLE_HOME, 0,
remoteCallback);
}
}
以上是完整的 addPreferredActivity 流程,通过源码可见,除了Home类型应用在设置首选项后,还继续执行,最新添加到 RoleHolder ,保证首选项和默认项都一致之外,其他应用都是设置一个首选项。(onAddRoleHolder 部分具体流程等下一节详细说明,这里暂时不作展开说明)
这解释了上面存在的问题一,其他类型应用,即使设置默认首选项后,由于Role未改变,而 设置-应用-默认应用 中是获取/监听的Role变化,导致设置中显示未变化。所以推荐使用 RoleManager 方式设置默认应用,来替代设置默认首选项。
Android Q开始,新增 RoleManager、RoleManagerService 等类,通过 RoleManager 设置默认应用取代之前的方式。例如,设置默认桌面:
private void setRoleHolderAsUser(String roleName, String packageName,
int flags, UserHandle user, Context context) {
RoleManager roleManager = (RoleManager)context.getSystemService(Context.ROLE_SERVICE);
Executor executor = context.getMainExecutor();
Consumer callback = successful -> {
if (successful) {
Log.d(TAG, "Package added as role holder, role: " + roleName + ", package: " + packageName);
} else {
Log.d(TAG, "Failed to add package as role holder, role: " + roleName + ", package: "
+ packageName);
}
};
roleManager.addRoleHolderAsUser(roleName, packageName, flags, user, executor, callback);
}
对比代码看,RoleManager 设置方式更加简洁,增加了 Consumer
1、RoleManager 设置是异步的,所以一般执行 addRoleHolderAsUser 后需要等一段时间才能得到回调结果,这是由于回调是基于上面代码中 Executor 进行,在添加流程完成后,对应的 Executor 线程中适当时候执行回调,视线程工作情况,时间不可控(实际中10ms-200ms延迟都遇到过),对于某些对时效性要求的场景不适用。
addRoleHolderAsUser 方法继续下去,是在 RoleManagerService 中调用如下方法
getOrCreateController(userId).onAddRoleHolder(roleName, packageName, flags, callback);
这个方法和上一节中最后一段,设置Home的方法是一致的,接着详细看实现源码逻辑。中间一段代码简单,跳过,直接看最后调用的 RoleControllerService 中方法。
@Override
public void onAddRoleHolder(String roleName, String packageName, int flags,
RemoteCallback callback) {
// 第1步:判断非空等条件
enforceCallerSystemUid("onAddRoleHolder");
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
Objects.requireNonNull(callback, "callback cannot be null");
// 第2步:通过创建的 Handler 进行顺序执行
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::onAddRoleHolder, RoleControllerService.this,
roleName, packageName, flags, callback));
}
RCS 是一个抽象类,它还有一个子类,位于packages/apps/PermissionController/src/com/android/permissioncontroller/role/service/RoleControllerServiceImpl.java (设置中默认应用显示的部分,也是启动 PermissionController 应用中activity)。这里 onAddRoleHolder 继续下去,调用到 RoleControllerServiceImpl 中实现。
@Override
@WorkerThread
public boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
int flags) {
if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
return false;
}
// 第1步: 通过roleName获取Role对象
Role role = Roles.get(this).get(roleName);
if (role == null) {
Log.e(LOG_TAG, "Unknown role: " + roleName);
return false;
}
if (!role.isAvailable(this)) {
Log.e(LOG_TAG, "Role is unavailable: " + roleName);
return false;
}
if (!role.isPackageQualified(packageName, this)) {
Log.e(LOG_TAG, "Package does not qualify for the role, package: " + packageName
+ ", role: " + roleName);
return false;
}
// 第2步: 通过roleName获取设备中当前此类型应用(一般只有一个),判断是否已经被设置过
boolean added = false;
if (role.isExclusive()) {
List currentPackageNames = mRoleManager.getRoleHolders(roleName);
int currentPackageNamesSize = currentPackageNames.size();
for (int i = 0; i < currentPackageNamesSize; i++) {
String currentPackageName = currentPackageNames.get(i);
if (Objects.equals(currentPackageName, packageName)) {
Log.i(LOG_TAG, "Package is already a role holder, package: " + packageName
+ ", role: " + roleName);
added = true;
continue;
}
boolean removed = removeRoleHolderInternal(role, currentPackageName, false);
if (!removed) {
// TODO: Clean up?
return false;
}
}
}
// 第3步: 调用系统添加保存,重点
boolean dontKillApp = hasFlag(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP);
added = addRoleHolderInternal(role, packageName, dontKillApp, true, added);
if (!added) {
return false;
}
// 第4步: 进行本地应用 SharedPreferences 添加保存
role.onHolderAddedAsUser(packageName, Process.myUserHandle(), this);
role.onHolderChangedAsUser(Process.myUserHandle(), this);
return true;
}
addRoleHolderInternal 调用最终又回到系统 RoleManager 中,继而调用到 RoleManagerService.addRoleHolderFromController,最终到 RoleUserState.addRoleHolder
@CheckResult
public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
boolean changed;
synchronized (mLock) {
ArraySet roleHolders = mRoles.get(roleName);
if (roleHolders == null) {
Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
+ ", package: " + packageName);
return false;
}
// 第1步:保存到列表中
changed = roleHolders.add(packageName);
if (changed) {
// 第2步:保存到本地持久化文件中
scheduleWriteFileLocked();
}
}
// 第3步:通知添加OK
if (changed) {
mCallback.onRoleHoldersChanged(roleName, mUserId, null, packageName);
}
return true;
}
@GuardedBy("mLock")
private void scheduleWriteFileLocked() {
if (mDestroyed) {
return;
}
if (!mWriteScheduled) {
// 延迟 WRITE_DELAY_MILLIS(200ms) 后写入
mWriteHandler.sendMessageDelayed(PooledLambda.obtainMessage(RoleUserState::writeFile,
this), WRITE_DELAY_MILLIS);
mWriteScheduled = true;
}
}
最终写入到本地文件的部分在 RolesPersistenceImpl.writeForUser 完成,写入文件路径为:
/data/misc_de/0/apexdata/com.android.permission/roles.xml
这里由于写入的时候有一个 WRITE_DELAY_MILLIS (默认200ms)延迟,所以在添加默认应用后需要过200ms 后,才能通过 RolesPersistenceImpl.readForUser 获取到正确的值,对于使用此方法的代码逻辑需要注意延迟问题。