電信架構總覽

Android 電信架構 (也稱為「電信」) 會管理 Android 裝置上的音訊和視訊通話。這包括以 SIM 為基礎的通話,例如使用電話服務架構的通話,以及實作 Core-Telecom Jetpack 程式庫的 VoIP 通話。

Telecom 管理的主要元件為 ConnectionServiceInCallService

ConnectionService 實作會建構 PSTN 等技術,以便將通話連線至其他使用者。手機上最常見的 ConnectionService 實作方式是電話 ConnectionService。可連線至電信業者通話。

InCallService 實作項目會提供使用者介面,讓使用者管理及互動與電信管理的通話。InCallService 最常見的實作方式,就是與裝置隨附的電話應用程式。

電信服務會做為交換機使用。它會將 ConnectionService 實作項目提供的呼叫,路由至 InCallService 實作項目提供的呼叫使用者介面。

您可能會基於下列原因導入 Telecom API:

建立替代電話應用程式

如要替換 Android 裝置上的預設電話應用程式,請實作 InCallService API。您的實作方式必須符合下列規定:

  • 不得具備任何通話功能,且只能包含用於通話的使用者介面。
  • 它必須處理 Telecom 架構所知曉的所有通話,且不得對通話的性質做出假設。舉例來說,應用程式不得假設通話是使用 SIM 卡的電話通話,也不能實作以任何 ConnectionService 為依據的通話限制,例如針對視訊通話實施電話限制。

詳情請參閱 InCallService

整合通話解決方案

如要將通話解決方案整合至 Android,您可以使用下列選項:

  • 實作自行管理的 Core-Telecom Jetpack 程式庫:這個選項非常適合獨立通話應用程式的開發人員,他們不想在預設的電話應用程式中顯示通話,也不想在使用者介面中顯示其他通話。

    整合 Core-Telecom Jetpack 程式庫後,應用程式不僅可與裝置上的系統電話通話功能互通,還能與整合 Telecom 的其他獨立通話應用程式互通。Core-Telecom 程式庫也會管理音訊路由和焦點。詳情請參閱「建構通話應用程式」。

  • 實作受管理的 ConnectionService API:這個選項可協助開發電話應用程式,讓使用者在撥打電話時,能透過該應用程式提供的使用者介面進行通話。例如第三方實作的 SIP 通話和 VoIP 通話服務。詳情請參閱 getDefaultDialerPackage()

    ConnectionService 本身只提供連線通話的功能。且沒有相關聯的使用者介面。

  • 同時實作 InCallService 和 ConnectionService API:如果您想自行建立以 ConnectionService 為基礎的通話解決方案,並在同一使用者介面中顯示所有其他 Android 通話,這個選項就非常適合。使用這種方法時,您實作 InCallService 時不得對顯示的呼叫來源做出任何假設。此外,即使預設的電話應用程式未設為自訂的 InCallServiceConnectionService 的實作也必須繼續運作。

過濾電話

搭載 Android 10 (API 級別 29) 以上版本的裝置可讓應用程式將使用者通訊錄中不存在的號碼視為潛在的垃圾來電。使用者可以選擇靜默拒接騷擾電話。為了讓使用者在錯過來電時能更清楚瞭解情況,系統會將這些遭封鎖的來電資訊記錄在通話記錄中。使用 Android 10 API 後,您就不需要再向使用者索取 READ_CALL_LOG 權限,即可提供通話篩選和來電者 ID 功能。

您可以使用 CallScreeningService 實作項目來篩選通話。當號碼不在使用者的聯絡人清單中時,請針對任何新的來電或撥出電話呼叫 onScreenCall() 函式。您可以查看 Call.Details 物件,瞭解呼叫相關資訊。具體來說,getCallerNumberVerificationStatus() 函式會納入網路供應商提供的其他號碼資訊。如果驗證狀態為失敗,表示來電號碼可能無效,或可能為垃圾來電。

Kotlin

class ScreeningService : CallScreeningService() {
    // This function is called when an ingoing or outgoing call
    // is from a number not in the user's contacts list
    override fun onScreenCall(callDetails: Call.Details) {
        // Can check the direction of the call
        val isIncoming = callDetails.callDirection == Call.Details.DIRECTION_INCOMING

        if (isIncoming) {
            // the handle (e.g. phone number) that the Call is currently connected to
            val handle: Uri = callDetails.handle

            // determine if you want to allow or reject the call
            when (callDetails.callerNumberVerificationStatus) {
                Connection.VERIFICATION_STATUS_FAILED -> {
                    // Network verification failed, likely an invalid/spam call.
                }
                Connection.VERIFICATION_STATUS_PASSED -> {
                    // Network verification passed, likely a valid call.
                }
                else -> {
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED.
                }
            }
        }
    }
}

Java

class ScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(@NonNull Call.Details callDetails) {
        boolean isIncoming = callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING;

        if (isIncoming) {
            Uri handle = callDetails.getHandle();

            switch (callDetails.getCallerNumberVerificationStatus()) {
                case Connection.VERIFICATION_STATUS_FAILED:
                    // Network verification failed, likely an invalid/spam call.
                    break;
                case Connection.VERIFICATION_STATUS_PASSED:
                    // Network verification passed, likely a valid call.
                    break;
                default:
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED
            }
        }
    }
}

onScreenCall() 函式設為呼叫 respondToCall(),以便告知系統如何回應新呼叫。這個函式會採用 CallResponse 參數,您可以使用該參數告知系統封鎖呼叫、拒絕呼叫 (就像使用者拒絕一樣),或將呼叫設為靜音。您也可以指示系統略過將這通電話加入裝置通話記錄的動作。

Kotlin

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
val response = CallResponse.Builder()
    // Sets whether the incoming call should be blocked.
    .setDisallowCall(false)
    // Sets whether the incoming call should be rejected as if the user did so manually.
    .setRejectCall(false)
    // Sets whether ringing should be silenced for the incoming call.
    .setSilenceCall(false)
    // Sets whether the incoming call should not be displayed in the call log.
    .setSkipCallLog(false)
    // Sets whether a missed call notification should not be shown for the incoming call.
    .setSkipNotification(false)
    .build()

// Call this function to provide your screening response.
respondToCall(callDetails, response)

Java

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
CallResponse.Builder response = new CallResponse.Builder();
// Sets whether the incoming call should be blocked.
response.setDisallowCall(false);
// Sets whether the incoming call should be rejected as if the user did so manually.
response.setRejectCall(false);
// Sets whether ringing should be silenced for the incoming call.
response.setSilenceCall(false);
// Sets whether the incoming call should not be displayed in the call log.
response.setSkipCallLog(false);
// Sets whether a missed call notification should not be shown for the incoming call.
response.setSkipNotification(false);

// Call this function to provide your screening response.
respondToCall(callDetails, response.build());

您必須在資訊清單檔案中註冊 CallScreeningService 實作項目,並使用適當的意圖篩選器和權限,讓系統能夠正確觸發。

<service
    android:name=".ScreeningService"
    android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallScreeningService" />
    </intent-filter>
</service>

轉接來電

搭載 Android 10 以上版本的裝置與搭載 Android 9 以下版本的裝置,在管理呼叫意圖的方式上有所不同。在 Android 10 以上版本中,ACTION_NEW_OUTGOING_CALL 廣播已淘汰,並由 CallRedirectionService API 取代。CallRedirectionService 提供介面,可用於修改 Android 平台發出的呼叫。舉例來說,第三方應用程式可能會取消通話,並透過 VoIP 重新路由。

Kotlin

class RedirectionService : CallRedirectionService() {
    override fun onPlaceCall(
        handle: Uri,
        initialPhoneAccount: PhoneAccountHandle,
        allowInteractiveResponse: Boolean
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        val callShouldProceed = true
        val callShouldRedirect = false
        when {
            callShouldProceed -> {
                placeCallUnmodified()
            }
            callShouldRedirect -> {
                // Update the URI to point to a different phone number or modify the
                // PhoneAccountHandle and redirect.
                redirectCall(handle, initialPhoneAccount, true)
            }
            else -> {
                cancelCall()
            }
        }
    }
}

Java

class RedirectionService extends CallRedirectionService {
    @Override
    public void onPlaceCall(
            @NonNull Uri handle,
            @NonNull PhoneAccountHandle initialPhoneAccount,
            boolean allowInteractiveResponse
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        // Your app should implement this logic to determine the redirection.
        boolean callShouldProceed = true;
        boolean callShouldRedirect = false;
        if (callShouldProceed) {
            placeCallUnmodified();
        } else if (callShouldRedirect) {
            // Update the URI to point to a different phone number or modify the
            // PhoneAccountHandle and redirect.
            redirectCall(handle, initialPhoneAccount, true);
        } else {
            cancelCall();
        }
    }
}

您必須在資訊清單中註冊這項服務,讓系統能夠正確啟動服務。

<service
    android:name=".RedirectionService"
    android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallRedirectionService"/>
    </intent-filter>
</service>

如要使用轉接服務,應用程式必須向 RoleManager 要求通話轉接角色。系統會詢問使用者是否允許應用程式處理通話重新導向。如果應用程式未授予此角色,系統就不會使用重新導向服務。

您應檢查應用程式在使用者啟動時是否具有此角色,以便視需要提出要求。您會啟動 RoleManager 建立的意圖,因此請務必覆寫 onActivityResult() 函式,以便處理使用者的選項。

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
            // Check if the app needs to register call redirection role.
            val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION)
            if (shouldRequestRole) {
                val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE)
            }
        }
    }

    companion object {
        private const val REDIRECT_ROLE_REQUEST_CODE = 1
    }
}

Java

class MainActivity extends AppCompatActivity {
    private static final int REDIRECT_ROLE_REQUEST_CODE = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
            // Check if the app needs to register call redirection role.
            boolean shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION);
            if (shouldRequestRole) {
                Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION);
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE);
            }
        }
    }
}