• Android 设置默认应用


    一、通过 PackageManager 设置

            默认应用是项目中常见的一项设置,比如默认桌面应用、浏览器应用等。Android Q之前版本设置方式通过 PackageManager 的 addPreferredActivity 接口实现。这种设置方式叫它为设置默认首选项比较恰当。设置好后,有首选项的时候就不弹出选择框。例如,设置桌面首选项应用:

    1. private void setDefaultLauncher(PackageManager pm, String pkgName, String clsName) {
    2. // 第1步:查询设备所有Home类型应用
    3. Intent queryIntent = new Intent();
    4. queryIntent.addCategory(Intent.CATEGORY_HOME);
    5. queryIntent.setAction(Intent.ACTION_MAIN);
    6. List homeActivities = pm.queryIntentActivities(queryIntent, 0);
    7. if(homeActivities == null) {
    8. return;
    9. }
    10. // 第2步:通过设置的包名类名,查找系统是否存在此Home类型应用
    11. ComponentName defaultLauncher = new ComponentName(pkgName, clsName);
    12. int activityNum = homeActivities.size();
    13. ComponentName[] set = new ComponentName[activityNum];
    14. int defaultMatch = -1;
    15. for(int i = 0; i < activityNum; i++){
    16. ResolveInfo info = homeActivities.get(i);
    17. Log.i(TAG, "info " + info);
    18. set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
    19. if(clsName.equals(info.activityInfo.name)
    20. && pkgName.equals(info.activityInfo.packageName)){
    21. defaultMatch = info.match;
    22. }
    23. }
    24. // 如果没找到匹配的应用,就不继续设置
    25. if(defaultMatch == -1){
    26. return;
    27. }
    28. // 第3步:通过系统接口添加默认首选项应用
    29. IntentFilter filter = new IntentFilter();
    30. filter.addAction(Intent.ACTION_MAIN);
    31. filter.addCategory(Intent.CATEGORY_HOME);
    32. filter.addCategory(Intent.CATEGORY_DEFAULT);
    33. pm.addPreferredActivity(filter, defaultMatch, set, defaultLauncher);
    34. }

            Android Q 之后这种方法逐步废弃,使用 RoleManager 替代。虽然目前还可以使用,但需要考虑兼容问题。目前已知问题:

    1、Android R 上,设置-应用-默认应用 中显示的默认应用,都是通过 RoleManager 获取以及监听变化,通过 PackageManager 这种设置方式后,设置中检测不到变化,导致设置中默认应用显示存在问题(Home 类型应用是个例外,原因见下面代码分析)。

            PackageManager (PM)是一个抽象类,ApplicationPackageManager (APM)继承实现部分抽象方法。addPreferredActivity 方法即在 APM 中实现,继而调用 PackageManagerService(PMS)中 addPreferredActivityInternal

    1. private void addPreferredActivityInternal(IntentFilter filter, int match,
    2. ComponentName[] set, ComponentName activity, boolean always, int userId,
    3. String opname) {
    4. /* 检测权限代码 省略... */
    5. synchronized (mLock) {
    6. final PreferredIntentResolver pir = mSettings.editPreferredActivitiesLPw(userId);
    7. // 添加默认应用到选择器,作为默认选项
    8. pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
    9. scheduleWritePackageRestrictionsLocked(userId);
    10. }
    11. // Home类型应用修改
    12. if (!updateDefaultHomeNotLocked(userId)) {
    13. postPreferredActivityChangedBroadcast(userId);
    14. }
    15. }
    1. private boolean updateDefaultHomeNotLocked(int userId) {
    2. if (Thread.holdsLock(mLock)) {
    3. Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
    4. + " is holding mLock", new Throwable());
    5. }
    6. //1步:获取默认首选项Home应用和当前保存的Home应用。注意这两个是不同的,保存位置不一样
    7. if (!mSystemReady) {
    8. // We might get called before system is ready because of package changes etc, but
    9. // finding preferred activity depends on settings provider, so we ignore the update
    10. // before that.
    11. return false;
    12. }
    13. final Intent intent = getHomeIntent();
    14. final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
    15. PackageManager.GET_META_DATA, userId);
    16. final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
    17. intent, null, 0, resolveInfos, 0, true, false, false, userId);
    18. final String packageName = preferredResolveInfo != null
    19. && preferredResolveInfo.activityInfo != null
    20. ? preferredResolveInfo.activityInfo.packageName : null;
    21. final String currentPackageName = mPermissionManager.getDefaultHome(userId);
    22. //2步:currentPackageName 为当前默认Home类型应用,如果设置Home应用首选项包名相同就不继续设置
    23. if (TextUtils.equals(currentPackageName, packageName)) {
    24. return false;
    25. }
    26. final String[] callingPackages = getPackagesForUid(Binder.getCallingUid());
    27. if (callingPackages != null && ArrayUtils.contains(callingPackages,
    28. mRequiredPermissionControllerPackage)) {
    29. // PermissionController manages default home directly.
    30. return false;
    31. }
    32. //3步:设置默认Home应用
    33. mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
    34. if (successful) {
    35. postPreferredActivityChangedBroadcast(userId);
    36. }
    37. });
    38. return true;
    39. }

            上面源码可以看出,通过 addPreferredActivity 设置首选项,如果设置的是 Home 应用,首选项改变后,包名改变,继续通过 

    mPermissionManager.setDefaultHome

            进行设置。PermissionManagerServiceInternal 实现类是在:

    PermissionManagerService.PermissionManagerServiceInternalImpl
    

            下面是 PermissionManagerService 中 setDefaultHome 代码实现:

    1. @Override
    2. public void setDefaultHome(String packageName, int userId, Consumer<Boolean> callback) {
    3. if (userId == UserHandle.USER_ALL) {
    4. return;
    5. }
    6. //1步:获取默认Home应用Provider
    7. DefaultHomeProvider provider;
    8. synchronized (mLock) {
    9. provider = mDefaultHomeProvider;
    10. }
    11. if (provider == null) {
    12. return;
    13. }
    14. //2步:进行异步设置,设置成功后通过 callback 回调通知
    15. provider.setDefaultHomeAsync(packageName, userId, callback);
    16. }

            DefaultHomeProvider 是 PermissionManagerServiceInternal 中一个接口类,子实现类为 RoleManagerService.DefaultHomeProvider 。实现 setDefaultHomeAsync 源码如下。

    1. public void setDefaultHomeAsync(@Nullable String packageName, @UserIdInt int userId,
    2. @NonNull Consumer<Boolean> callback) {
    3. //1步:创建回调对象
    4. RemoteCallback remoteCallback = new RemoteCallback(result -> {
    5. boolean successful = result != null;
    6. if (!successful) {
    7. Slog.e(LOG_TAG, "Failed to set default home: " + packageName);
    8. }
    9. callback.accept(successful);
    10. });
    11. //2步:添加RoleHolder,如果包名为空,则清空RoleHolder
    12. if (packageName != null) {
    13. getOrCreateController(userId).onAddRoleHolder(RoleManager.ROLE_HOME,
    14. packageName, 0, remoteCallback);
    15. } else {
    16. getOrCreateController(userId).onClearRoleHolders(RoleManager.ROLE_HOME, 0,
    17. remoteCallback);
    18. }
    19. }

           以上是完整的 addPreferredActivity 流程,通过源码可见,除了Home类型应用在设置首选项后,还继续执行,最新添加到 RoleHolder ,保证首选项和默认项都一致之外,其他应用都是设置一个首选项。(onAddRoleHolder 部分具体流程等下一节详细说明,这里暂时不作展开说明)

            这解释了上面存在的问题一,其他类型应用,即使设置默认首选项后,由于Role未改变,而 设置-应用-默认应用 中是获取/监听的Role变化,导致设置中显示未变化。所以推荐使用 RoleManager 方式设置默认应用,来替代设置默认首选项。

    二、通过 RoleManager 设置

            Android Q开始,新增 RoleManager、RoleManagerService 等类,通过 RoleManager 设置默认应用取代之前的方式。例如,设置默认桌面:

    1. private void setRoleHolderAsUser(String roleName, String packageName,
    2. int flags, UserHandle user, Context context) {
    3. RoleManager roleManager = (RoleManager)context.getSystemService(Context.ROLE_SERVICE);
    4. Executor executor = context.getMainExecutor();
    5. Consumer callback = successful -> {
    6. if (successful) {
    7. Log.d(TAG, "Package added as role holder, role: " + roleName + ", package: " + packageName);
    8. } else {
    9. Log.d(TAG, "Failed to add package as role holder, role: " + roleName + ", package: "
    10. + packageName);
    11. }
    12. };
    13. roleManager.addRoleHolderAsUser(roleName, packageName, flags, user, executor, callback);
    14. }

            对比代码看,RoleManager 设置方式更加简洁,增加了 Consumer callback 的回调,根据回调结果确认设置是否成功。但是 RoleManager 设置存在以下问题:

    1、RoleManager 设置是异步的,所以一般执行 addRoleHolderAsUser 后需要等一段时间才能得到回调结果,这是由于回调是基于上面代码中 Executor 进行,在添加流程完成后,对应的 Executor 线程中适当时候执行回调,视线程工作情况,时间不可控(实际中10ms-200ms延迟都遇到过),对于某些对时效性要求的场景不适用。

            addRoleHolderAsUser 方法继续下去,是在 RoleManagerService 中调用如下方法

    getOrCreateController(userId).onAddRoleHolder(roleName, packageName, flags,
            callback);

            这个方法和上一节中最后一段,设置Home的方法是一致的,接着详细看实现源码逻辑。中间一段代码简单,跳过,直接看最后调用的 RoleControllerService 中方法。

    1. @Override
    2. public void onAddRoleHolder(String roleName, String packageName, int flags,
    3. RemoteCallback callback) {
    4. // 第1步:判断非空等条件
    5. enforceCallerSystemUid("onAddRoleHolder");
    6. Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
    7. Preconditions.checkStringNotEmpty(packageName,
    8. "packageName cannot be null or empty");
    9. Objects.requireNonNull(callback, "callback cannot be null");
    10. // 第2步:通过创建的 Handler 进行顺序执行
    11. mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
    12. RoleControllerService::onAddRoleHolder, RoleControllerService.this,
    13. roleName, packageName, flags, callback));
    14. }

        RCS 是一个抽象类,它还有一个子类,位于packages/apps/PermissionController/src/com/android/permissioncontroller/role/service/RoleControllerServiceImpl.java (设置中默认应用显示的部分,也是启动 PermissionController 应用中activity)。这里 onAddRoleHolder 继续下去,调用到 RoleControllerServiceImpl 中实现。

    1. @Override
    2. @WorkerThread
    3. public boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
    4. int flags) {
    5. if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
    6. return false;
    7. }
    8. // 第1步: 通过roleName获取Role对象
    9. Role role = Roles.get(this).get(roleName);
    10. if (role == null) {
    11. Log.e(LOG_TAG, "Unknown role: " + roleName);
    12. return false;
    13. }
    14. if (!role.isAvailable(this)) {
    15. Log.e(LOG_TAG, "Role is unavailable: " + roleName);
    16. return false;
    17. }
    18. if (!role.isPackageQualified(packageName, this)) {
    19. Log.e(LOG_TAG, "Package does not qualify for the role, package: " + packageName
    20. + ", role: " + roleName);
    21. return false;
    22. }
    23. // 第2步: 通过roleName获取设备中当前此类型应用(一般只有一个),判断是否已经被设置过
    24. boolean added = false;
    25. if (role.isExclusive()) {
    26. List currentPackageNames = mRoleManager.getRoleHolders(roleName);
    27. int currentPackageNamesSize = currentPackageNames.size();
    28. for (int i = 0; i < currentPackageNamesSize; i++) {
    29. String currentPackageName = currentPackageNames.get(i);
    30. if (Objects.equals(currentPackageName, packageName)) {
    31. Log.i(LOG_TAG, "Package is already a role holder, package: " + packageName
    32. + ", role: " + roleName);
    33. added = true;
    34. continue;
    35. }
    36. boolean removed = removeRoleHolderInternal(role, currentPackageName, false);
    37. if (!removed) {
    38. // TODO: Clean up?
    39. return false;
    40. }
    41. }
    42. }
    43. // 第3步: 调用系统添加保存,重点
    44. boolean dontKillApp = hasFlag(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP);
    45. added = addRoleHolderInternal(role, packageName, dontKillApp, true, added);
    46. if (!added) {
    47. return false;
    48. }
    49. // 第4步: 进行本地应用 SharedPreferences 添加保存
    50. role.onHolderAddedAsUser(packageName, Process.myUserHandle(), this);
    51. role.onHolderChangedAsUser(Process.myUserHandle(), this);
    52. return true;
    53. }

            addRoleHolderInternal 调用最终又回到系统 RoleManager 中,继而调用到 RoleManagerService.addRoleHolderFromController,最终到 RoleUserState.addRoleHolder

    1. @CheckResult
    2. public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
    3. boolean changed;
    4. synchronized (mLock) {
    5. ArraySet<String> roleHolders = mRoles.get(roleName);
    6. if (roleHolders == null) {
    7. Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
    8. + ", package: " + packageName);
    9. return false;
    10. }
    11. //1步:保存到列表中
    12. changed = roleHolders.add(packageName);
    13. if (changed) {
    14. //2步:保存到本地持久化文件中
    15. scheduleWriteFileLocked();
    16. }
    17. }
    18. //3步:通知添加OK
    19. if (changed) {
    20. mCallback.onRoleHoldersChanged(roleName, mUserId, null, packageName);
    21. }
    22. return true;
    23. }
    24. @GuardedBy("mLock")
    25. private void scheduleWriteFileLocked() {
    26. if (mDestroyed) {
    27. return;
    28. }
    29. if (!mWriteScheduled) {
    30. // 延迟 WRITE_DELAY_MILLIS(200ms) 后写入
    31. mWriteHandler.sendMessageDelayed(PooledLambda.obtainMessage(RoleUserState::writeFile,
    32. this), WRITE_DELAY_MILLIS);
    33. mWriteScheduled = true;
    34. }
    35. }

            最终写入到本地文件的部分在 RolesPersistenceImpl.writeForUser 完成,写入文件路径为:

    /data/misc_de/0/apexdata/com.android.permission/roles.xml

            这里由于写入的时候有一个 WRITE_DELAY_MILLIS (默认200ms)延迟,所以在添加默认应用后需要过200ms 后,才能通过 RolesPersistenceImpl.readForUser 获取到正确的值,对于使用此方法的代码逻辑需要注意延迟问题。

  • 相关阅读:
    【安装库】使用win10自带健康工具生成电池报告及相关问题解决
    软件供应链的漏洞及攻击类型
    Vue中实现放大镜效果
    怎么用Vite创建一个Vue3的项目
    计算机丢失mfc140u.dll怎么办,mfc140u.dll丢失的解决方法分享
    centos7 探测某个tcp端口是否在监听
    jaotc
    【python】defaultdict设置初始值
    底层驱动day2作业
    级联选择器的二维数组键值问题
  • 原文地址:https://blog.csdn.net/tq501501/article/details/126462114