學徒淺析Android——Role帶來的角色扮演

 

 

在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上生效。邏輯處理如下:

可以看出,這兩個action已經被內部無效化了,Role是你唯一可選的路。Role將可設置的默認應用細分成8類,每一類對應一個特定的Role類型。Role的展示頁面ReuqestRoleActivity針對調用發起者做了限制,只有同類型或者處於系統進程的應用才能調用Role服務。你用一個普通應用創建RoleManager,會發現它的isRoleAvailable()始終返回false的。

其實Role可以用一個通俗的例子來解釋,假設我們的應用是生旦淨末醜行當中的某個小角,戲臺上要從所有的旦角中選一個當默認主角,戲臺規定只有旦角行當的纔可以參與選擇。這個行當就是Role類型,戲臺就是androidQ系統,參與的方式就是調用RoleManager請求。

通過上述的比喻,就可以理解google這麼做的原因——減少服務的濫用,以往可以隨意調用的action都會被淘汰掉。爲此,針對Q我們的應用要通過配置權限或策略先成爲一個特定的短信、瀏覽器、或者桌面應用後,才能通過RoleManager切換系統同類型的默認應用。截止到Q Beta4版後,Role相關角色和權限對應表如下:

 

可以看出,作爲搭載的第一版,Role的類型並不是很成熟,還有類型存在未設置的狀態,後續肯定會有調整。我目前用到的只有ROLE_SMS和ROLE_CALL_SCREENING,下面就以這兩個角色爲例,跟大家分享下Role的使用和注意事項。

 

使用方法:

 

  1. ROLE_SMS

Manifest.xml中必須在啓動類中添加CATEGORY_DEFAULT和對應的角色API。

使用RoleManager獲得Intent對象,繼而請求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(),相關源碼如下:

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;

        }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章