学徒浅析Android——Role带来的角色扮演

时间:2024-04-08 11:18:15

 

 

在Q以前,如果我们的应用是一个短信或拨号之类的特殊应用,想要通知用户将我们的应用替换掉手机自带的预搭载应用,TelecomManager.ACTION_CHANGE_DEFAULT_DIALER和Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT就可以帮我们实现。但在Q上,google改变这种处理方案,并推出了一个新特性——Role/RoleManager。从文档介绍来看,是提供管理应用服务的,最常见的就是默认应用切换功能。

有了新的替代API,那么原先的TelecomManager.ACTION_CHANGE_DEFAULT_DIALER和Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT还能使用吗,很遗憾,虽然你可以写出来,也可以发出去,但是当你运行这两个action时,除了动作无响应外,你还会额外收到一条log:

Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT is removed for the calling package ,use RoleManager.createRequestRoleIntent() instead

即Q主动把你调用的action删除掉,再暗示你该用Role了。这个log是由于PermissionPolicyService#isActionRemovedForCallingPackage()主动规避TelecomManager.ACTION_CHANGE_DEFAULT_DIALER和Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT导致的,并且只在Q上生效。逻辑处理如下:

学徒浅析Android——Role带来的角色扮演

可以看出,这两个action已经被内部无效化了,Role是你唯一可选的路。Role将可设置的默认应用细分成8类,每一类对应一个特定的Role类型。Role的展示页面ReuqestRoleActivity针对调用发起者做了限制,只有同类型或者处于系统进程的应用才能调用Role服务。你用一个普通应用创建RoleManager,会发现它的isRoleAvailable()始终返回false的。

其实Role可以用一个通俗的例子来解释,假设我们的应用是生旦净末丑行当中的某个小角,戏台上要从所有的旦角中选一个当默认主角,戏台规定只有旦角行当的才可以参与选择。这个行当就是Role类型,戏台就是androidQ系统,参与的方式就是调用RoleManager请求。

通过上述的比喻,就可以理解google这么做的原因——减少服务的滥用,以往可以随意调用的action都会被淘汰掉。为此,针对Q我们的应用要通过配置权限或策略先成为一个特定的短信、浏览器、或者桌面应用后,才能通过RoleManager切换系统同类型的默认应用。截止到Q Beta4版后,Role相关角色和权限对应表如下:

学徒浅析Android——Role带来的角色扮演

 

可以看出,作为搭载的第一版,Role的类型并不是很成熟,还有类型存在未设置的状态,后续肯定会有调整。我目前用到的只有ROLE_SMS和ROLE_CALL_SCREENING,下面就以这两个角色为例,跟大家分享下Role的使用和注意事项。

 

使用方法:

 

  1. ROLE_SMS

Manifest.xml中必须在启动类中添加CATEGORY_DEFAULT和对应的角色API。

学徒浅析Android——Role带来的角色扮演

使用RoleManager获得Intent对象,继而请求Role处理,此时就可以弹出切换默认应用的弹窗了。具体代码如下:

学徒浅析Android——Role带来的角色扮演

 

  1. ROLE_CALL_SCREENING

Manifest.xml中需要在CallScreeningService的实现类中声明android.telecom.CallScreeningService

<service android:name="你的应用中CallScreeningService的实现类"

        android:permission="android.permission.BIND_SCREENING_SERVICE">

    <intent-filter>

        <action android:name="android.telecom.CallScreeningService"/>

    </intent-filter>

</service>

 

CallScreeningService的实现类主要重写onScreenCall(),这个方法是在受到来电时就会被触发的,你可以在这里对来电信息进行处理,将它封装到CallIdentification中,CallIdentification特意增加了对来电骚扰程度的判断,CallIdentification#setNuisanceConfidence()对应的级别共有5个,级别如下:

CONFIDENCE_NUISANCE 诈骗/骚扰电话

CONFIDENCE_LIKELY_NUISANCE 可能是诈骗/骚扰电话

CONFIDENCE_LIKELY_NOT_NUISANCE 可能不是诈骗/骚扰电话

CONFIDENCE_NOT_NUISANCE 非诈骗/骚扰电话

CONFIDENCE_UNKNOWN 未知来电

完整的一次来电信息处理代码如下:

public class CallScreeningServiceImplementation extends CallScreeningService {

     @Override

     public void onScreenCall(@NonNull Call.Details details) {

         //来电时该方法会被触发,对来电信息进行设置

         CallIdentification.Builder callIdentification = new CallIdentification.Builder();

         //来电人名称

         callIdentification.setName("Name");

         //来电其他信息

         callIdentification.setDescription("Description");

         callIdentification.setDetails("Details");

         //来电类型

         callIdentification.setNuisanceConfidence(CONFIDENCE_LIKELY_NOT_NUISANCE);

         // 将修改好的来电信息向默认的电话应用发送过去

         provideCallIdentification(details,callIdentification.build());

     }

}

对应的电话应用一侧,可以通过Call.Details.getCallIdentification()接受我们传过去的数据。

 

注意事项:

  1. 使用Role的前提是,你的应用必须是Role指定的角色应用,比如android.intent.category.HOME将你的应用指定为桌面应用,设置短信相关权限将你的应用指定为SMS应用。
  2. 对于createRequestRoleIntent()返回的Intent,不能给它添加诸如FLAG_ACTIVITY_NEW_TASK之类的flag,并且必须使用startActivityForResult(Intent, int)进行调用,其他诸如startActivity()是无效的。否则你会收到如下所示的警告日志:

package name cannot be null or empty: null

这个日志产生的原因就在Role的响应类RequestRoleActivity中。我们发起的Intent,最 终会交给PermissionController来处理。这是一个APK,它和Settings存在,Role处理页面是RequestRoleActivity。RequestRoleActivity#onCreate()中会获取两个参数,一个是mRoleName角色类型,就是我们在createRequestRoleIntent()中传入的类型。一个是mPackageName呼叫的应用包名。它的获取方式是getCallingPackage(),相关源码如下:

学徒浅析Android——Role带来的角色扮演

getCallingPackage()依赖于是否调用了startActivityForResult()、是否有明确的返回值这两个条件,只有这两个条件都满足的情况下,getCallingPackage()才能获取到当前发起请求的包名。另外如果你使用了FLAG_ACTIVITY_NEW_TASK,RequestRoleActivity会在一个新的任务栈中加载导致它没有返回值,同样会导致getCallingPackage()返回null。因此调用Role的方式只能是在不添加任何flag的情况下使用startActivityForResult来调用,即使你不需要后续onActivityForResult中的逻辑。

  1. 一旦你的应用通过Role成功设置为默认应用后,就无法再调用RoleManager进行切换了,这是因为在RequestRoleActivity#onCreate()中会判断调起的mPackageName是否已经是当前角色的持有者了。代码如下:

RoleManager roleManager = getSystemService(RoleManager.class);

        List<String> currentPackageNames = roleManager.getRoleHolders(mRoleName);

        if (currentPackageNames.contains(mPackageName)) {

            Log.i(LOG_TAG, "Application is already a role holder, role: " + mRoleName

                    + ", package: " + mPackageName);

            reportRequestResult(PermissionControllerStatsLog

                    .ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED_ALREADY_GRANTED);

            setResult(RESULT_OK);

            finish();

            return;

        }