通信フレームワークの概要

Android Telecom フレームワーク(単に「通信」ともいいます)は、Android 搭載デバイスの音声通話とビデオ通話を管理します。これには、テレフォニー フレームワークを使用する通話や、ConnectionService API を実装する VoIP 呼び出しなどの SIM ベースの通話が含まれます。

通信会社が管理する主要コンポーネントは、ConnectionServiceInCallService です。

ConnectionService の実装では、VoIP などの技術を使用して通話を相手に接続します。スマートフォンで最も一般的な ConnectionService の実装は、テレフォニーの ConnectionService です。キャリア通話を接続する。

InCallService の実装により、通信会社が管理する通話へのユーザー インターフェースが提供され、ユーザーがこれらの通話を制御および操作できるようになります。InCallService の最も一般的な実装は、デバイスにバンドルされたスマートフォン アプリです。

通信は交換機として機能します。ConnectionService 実装が提供する呼び出しを、InCallService 実装が提供する呼び出しユーザー インターフェースに転送します。

Telecom API の実装をおすすめする理由は次のとおりです。

代替のスマートフォン アプリを作成する

Android デバイスでデフォルトのスマートフォン アプリに代わるものを作成するには、InCallService API を実装します。実装は次の要件を満たす必要があります。

  • 通話機能を備えてはならず、通話用のユーザー インターフェースのみで構成する必要があります。
  • 通信フレームワークが認識しているすべての通話を処理する必要があり、通話の性質を前提としません。たとえば、通話が SIM ベースのテレフォニー通話であると想定したり、ビデオ通話に対するテレフォニー制限の適用など、1 つの ConnectionService に基づく通話制限を実装したりしてはなりません。

詳細については、InCallService をご覧ください。

通話ソリューションを統合する

通話ソリューションを Android に統合するには、次の方法があります。

  • セルフマネージド ConnectionService API を実装する: このオプションは、スタンドアロンの通話アプリのデベロッパーで、デフォルトの電話アプリ内で通話を表示したくない場合や、ユーザー インターフェースに他の通話を表示したくない場合に最適です。

    セルフマネージド ConnectionService を使用すると、デバイス上のネイティブ テレフォニー呼び出しだけでなく、この API を実装する他のスタンドアロン通話アプリとの相互運用も行えるようになります。セルフマネージド ConnectionService API は、オーディオ ルーティングとフォーカスも管理します。詳細については、通話アプリを作成するをご覧ください。

  • マネージド ConnectionService API を実装する: このオプションにより、既存のデバイスの電話アプリを使用して通話のユーザー インターフェースを提供する通話ソリューションを簡単に開発できます。たとえば、SIP 通話サービスや VoIP 通話サービスのサードパーティ実装などです。詳しくは getDefaultDialerPackage() をご覧ください。

    ConnectionService のみは、呼び出しを接続する手段のみを提供します。ユーザー インターフェースは関連付けられていません。

  • InCallService API と ConnectionService API の両方を実装する: このオプションは、独自のユーザー インターフェースを備えた独自の ConnectionService ベースの通話ソリューションを作成し、同じユーザー インターフェースで他のすべての Android 呼び出しを表示する場合に最適です。 この方法を使用する場合、InCallService の実装では、表示する呼び出しのソースについて何も想定しないでください。また、ConnectionService の実装は、デフォルトのスマートフォン アプリをカスタム InCallService に設定しなくても引き続き機能する必要があります。

コール スクリーニング

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
            }
        }
    }
}

respondToCall() を呼び出すように onScreenCall() 関数を設定し、新しい呼び出しへの応答方法をシステムに指示します。この関数は 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);
            }
        }
    }
}