默认应用是项目中常见的一项设置,比如默认桌面应用、浏览器应用等。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<ResolveInfo> 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<Boolean> 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<Boolean> 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<String> 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 获取到正确的值,对于使用此方法的代码逻辑需要注意延迟问题。