電信架構總覽

Android Telecom 架構 (也稱為「電信」) 可管理 Android 裝置上的音訊和視訊通話。這包括以 SIM 卡為基礎的呼叫,例如使用電話架構的呼叫,以及實作 ConnectionService API 的 VoIP 呼叫。

Telecom 管理的主要元件為 ConnectionServiceInCallService

ConnectionService 實作會使用 VoIP 等技術將呼叫與其他方連線。手機上最常見的 ConnectionService 實作是電話 ConnectionService。連結電信業者電話。

InCallService 實作方式提供使用者介面,可用來存取 Telecom 管理的呼叫,並可讓使用者控制這些呼叫並與其互動。最常見的 InCallService 實作是裝置隨附的手機應用程式。

電信可做為接線器。它會將 ConnectionService 實作所提供的呼叫轉送給 InCallService 實作提供的呼叫使用者介面。

您可能會基於以下原因實作 Telecom API:

建立替代電話應用程式

如要建立 Android 裝置上預設手機應用程式的替代項目,請實作 InCallService API。實作項目必須符合下列規定:

  • 它不能有任何呼叫功能,並且只能由使用者介面進行呼叫。
  • 必須處理 Telecom 架構已知的所有呼叫,而不是對呼叫性質做出假設。例如,不得假設這些通話屬於 SIM 卡電話通話,或根據任何 ConnectionService 實施通話限制,例如強制執行視訊通話的電話限制。

詳情請參閱 InCallService

整合通話解決方案

如要將呼叫解決方案整合至 Android,您可以選擇下列做法:

  • 實作自行管理的 ConnectionService API:這個選項適用於獨立呼叫應用程式的開發人員,如果使用者不希望在預設手機應用程式中顯示呼叫,或是在使用者介面中顯示其他呼叫,就很適合採用這個選項。

    使用自行管理的 ConnectionService 時,應用程式不只能與裝置上的原生電話呼叫,也要與其他實作此 API 的獨立呼叫應用程式互通。自行管理的 ConnectionService API 也會管理音訊轉送和焦點。詳情請參閱「建構呼叫應用程式」。

  • 實作 managed ConnectionService API:這個選項有助於開發呼叫解決方案,運用現有的裝置手機應用程式提供呼叫使用者介面。例如第三方實作 SIP 通話和 VoIP 通話服務。詳情請參閱 getDefaultDialerPackage()

    單獨使用 ConnectionService 才提供通話功能。沒有相關聯的使用者介面。

  • 實作 InCallService 和 ConnectionService API:如要自行建立以 ConnectionService 為基礎的呼叫解決方案,並透過其使用者介面完成,同時在同一個使用者介面中顯示所有其他 Android 呼叫,就很適合使用這個選項。使用這個方法時,實作 InCallService 時不得對顯示的呼叫來源做出任何假設。此外,如果不將預設手機應用程式設為自訂 InCallService,實作 ConnectionService 就必須繼續運作。

過濾電話

如果裝置搭載 Android 10 (API 級別 29) 以上版本,應用程式就能將不在使用者通訊錄中的號碼的來電辨識為潛在的騷擾電話。使用者可以選擇自動拒絕騷擾電話。為了讓使用者在漏接來電時更清楚瞭解來電,系統會記錄這些遭封鎖呼叫的相關資訊。使用 Android 10 API 時,無須向使用者取得 READ_CALL_LOG 權限,即可提供來電過濾和來電顯示功能。

您可以使用 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);
            }
        }
    }
}