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

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

Telecom が管理する主なコンポーネントは、ConnectionServiceInCallService です。

ConnectionService の実装は、PSTN などのテクノロジーを基盤として、通話を他の相手に接続します。スマートフォンで最も一般的な ConnectionService 実装は、テレフォニー ConnectionService です。携帯通信会社の通話を接続します。

InCallService の実装は、Telecom によって管理される通話のユーザー インターフェースを提供し、ユーザーがこれらの通話を制御して操作できるようにします。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 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
            }
        }
    }
}

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 を正しくトリガーできるように、マニフェスト ファイルに適切なインテント フィルタと権限を指定して 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);
            }
        }
    }
}