デフォルトの電話アプリを作成する

デフォルトの電話アプリを使用すると、Android Telecom フレームワークは、ロール マネージャーと通話中サービスを使用してアプリに通話状態を通知し、Android デバイス上のデフォルトの電話アプリの代替アプリを作成し、InCallService API を実装できます。実装は次の要件を満たす必要があります。

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

通話アプリでは、ユーザーがデバイスで音声通話やビデオ通話を発信または着信できます。次のスクリーンショットに示すように、通話アプリでは、デフォルトの電話アプリのインターフェースではなく、アプリ独自のユーザー インターフェースを通話に使用します。

通話アプリの例
独自のインターフェースを使用する通話アプリの例

Android フレームワークに含まれている android.telecom パッケージには、Telecom フレームワークに沿って通話アプリを作成するためのクラスが用意されています。Telecom フレームワークに沿ってアプリを作成することには、次のような利点があります。

  • アプリが、デバイス内のネイティブの Telecom サブシステムと適切に相互運用できます。
  • アプリが、このフレームワークに準拠している他の通話アプリと適切に相互運用できます。
  • アプリでこのフレームワークを使用して、音声や動画のルーティングを管理できます。
  • アプリでこのフレームワークを使用して、自身の通話にフォーカスがあるかどうかを判断できます。

マニフェストの宣言と権限

次の例に示すように、アプリ マニフェストで、アプリが MANAGE_OWN_CALLS 権限を使用することを宣言します。

<manifest … >
    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
</manifest>

アプリの権限の宣言について詳しくは、権限をご覧ください。

アプリの ConnectionService クラスを実装するクラスを指定しているサービスを宣言する必要があります。Telecom サブシステムでは、サービスをバインドできるようにするために、サービスが BIND_TELECOM_CONNECTION_SERVICE 権限を宣言する必要があります。アプリ マニフェスト内でサービスを宣言する方法を、次の例に示します。

<service android:name="com.example.MyConnectionService"
    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.ConnectionService" />
    </intent-filter>
</service>

サービスを含むアプリ コンポーネントの宣言について詳しくは、アプリ コンポーネントをご覧ください。

接続サービスを実装する

通話アプリは、Telecom サブシステムがバインドできる ConnectionService クラスの実装を提供する必要があります。ConnectionService の実装では次のメソッドをオーバーライドする必要があります。

onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)

アプリで placeCall(Uri, Bundle) を呼び出すと、それを受けて Telecom サブシステムがこのメソッドを呼び出して新しい発信を作成します。アプリは Connection クラスの実装(詳細は接続の実装を参照)の新しいインスタンスを返して、その新しい発信を表します。以下の処理を行うことで、発信接続をさらにカスタマイズできます。

onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

このメソッドは、アプリが placeCall(Uri, Bundle) メソッドを呼び出したものの通話を発信できなかった場合に、Telecom サブシステムによって呼び出されます。こうした状況が発生した場合、アプリはユーザーに対して、発信ができなかったことを通知(たとえば、アラート ボックスやトーストを使用)する必要があります。進行中の緊急通報がある場合や、通話の発信前に保留にできない進行中の通話が別のアプリにある場合は通常、アプリから通話を発信できません。

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

アプリが addNewIncomingCall(PhoneAccountHandle, Bundle) メソッドを呼び出して、アプリ内での新しい着信をシステムに通知すると、Telecom サブシステムがこのメソッドを呼び出します。アプリは Connection の実装(詳細は接続の実装を参照)の新しいインスタンスを返して、新しい着信を表します。以下の処理を行うことで、着信接続をさらにカスタマイズできます。

onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

アプリが addNewIncomingCall(PhoneAccountHandle, Bundle) メソッドを呼び出して、新しい着信があったものの着信が許可されていないことを Telecom サブシステムに通知すると、Telecom サブシステムがこのメソッドを呼び出します(詳しくは、通話の制約をご覧ください)。アプリは着信を拒否する必要があります。また、オプションで、不在着信があったことをユーザーに伝える通知を表示できます。

接続を実装する

アプリは Connection のサブクラスを作成して、アプリ内で通話を表す必要があります。実装において、次のメソッドをオーバーライドする必要があります。

onShowIncomingCallUi()

このメソッドは、新しい着信が追加されて、アプリが自身の着信 UI を表示する必要がある場合、Telecom サブシステムによって呼び出されます。

onCallAudioStateChanged(CallAudioState)

このメソッドは、アプリに現在の音声経路またはモードが変更されたことを通知するために、Telecom サブシステムによって呼び出されます。これは、アプリが setAudioRoute(int) メソッドを使用して音声モードを変更したことを受けて呼び出されます。また、システムによって音声経路が変更された場合(Bluetooth ヘッドセットが切断された場合など)にも、このメソッドが呼び出されることがあります。

onHold()

このメソッドは、Telecom サブシステムが通話の保留をリクエストする場合に、Telecom サブシステムによって呼び出されます。このリクエストを受けて、アプリは通話を保留にしたうえで setOnHold() メソッドを呼び出して、通話が保留になっていることをシステムに通知する必要があります。このメソッドは、通話を表示している通話サービス(Android Auto など)が、通話を保留にするというユーザーのリクエストの中継を求めた場合に、Telecom サブシステムによって呼び出されることがあります。また、ユーザーが別のアプリで通話をアクティブにした場合にも呼び出されます。通話サービスについて詳しくは、InCallService をご覧ください。

onUnhold()

このメソッドは、Telecom サブシステムが保留になっている通話の再開をリクエストする場合に、Telecom サブシステムによって呼び出されます。アプリは通話を再開した後で setActive() メソッドを呼び出して、通話が保留状態ではなくなったことをシステムに通知する必要があります。このメソッドは、通話を表示している通話サービス(Android Auto など)が通話再開のリクエストの中継を求めた場合に、Telecom サブシステムによって呼び出されることがあります。通話サービスについて詳しくは、InCallService をご覧ください。

onAnswer()

このメソッドは、着信への応答が必要であることをアプリに通知するために、Telecom サブシステムによって呼び出されます。アプリは通話に応答した後で setActive() メソッドを呼び出して、通話に応答済みであることをシステムに通知する必要があります。このメソッドは、アプリが新しい着信を追加したときに、すでに別のアプリに保留にできない進行中の通話があると、Telecom サブシステムによって呼び出されることがあります。このような場合、アプリに代わって Telecom により着信 UI が表示されます。フレームワークには、応答する通話の動画の状態の指定をサポートするオーバーロード メソッドが用意されています。詳しくは onAnswer(int) をご覧ください。

onReject()

このメソッドは、Telecom サブシステムが着信の拒否を求める場合に、Telecom サブシステムによって呼び出されます。アプリは通話を拒否した後、setDisconnected(DisconnectCause) を呼び出して REJECTED をパラメータとして指定する必要があります。そのうえで、アプリは destroy() メソッドを呼び出して、アプリが通話を処理済みであることをシステムに通知する必要があります。Telecom サブシステムは、ユーザーがアプリで着信を拒否した場合にこのメソッドを呼び出します。

onDisconnect()

このメソッドは、Telecom サブシステムが通話の切断を求める場合に、Telecom サブシステムによって呼び出されます。通話が終了したら、アプリで setDisconnected(DisconnectCause) メソッドを呼び出し、パラメータとして LOCAL を指定して、ユーザー リクエストによって通話が切断されたことを示す必要があります。そのうえで、アプリは destroy() メソッドを呼び出して、アプリが通話を処理済みであることを Telecom サブシステムに通知する必要があります。このメソッドは、ユーザーが Android Auto などの別の通話サービスを介して通話を切断した場合に、システムによって呼び出されることがあります。また、現在の通話を切断して他の通話を発信できるようにする必要がある場合(ユーザーが緊急通報を発信する必要がある場合など)にも、システムによってこのメソッドが呼び出されることがあります。通話サービスについて詳しくは、InCallService をご覧ください。

一般的な通話シナリオを処理する

通話フローでの ConnectionService API の使用には、android.telecom パッケージ内の他のクラスとのやり取りが含まれます。以降のセクションでは、一般的な通話シナリオと、アプリで同 API を使用してそうした通話を処理する方法について説明します。

着信に応答する

着信の処理フローは、他のアプリに進行中の通話があるかどうかによって変わります。処理フローが変わる理由は、デバイス上のすべての通話アプリに安定した環境を確実に提供することを目的として、他のアプリにアクティブな通話がある場合に Telecom フレームワークがいくつかの制約を設ける必要があるためです。詳しくは、通話の制約をご覧ください。

他のアプリにアクティブな通話がない場合

他のアプリでアクティブな通話がない場合は、次の手順に沿って着信に応答します。

  1. アプリが通常のメカニズムを使用して新しい着信を受け取ります。
  2. addNewIncomingCall(PhoneAccountHandle, Bundle) メソッドを使用して、新しい着信を Telecom サブシステムに通知します。
  3. Telecom サブシステムがアプリの ConnectionService の実装にバインドし、onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) メソッドを使用して、新しい着信を表す Connection クラスの新しいインスタンスをリクエストします。
  4. Telecom サブシステムが onShowIncomingCallUi() メソッドを使用して、アプリに対し、自身の着信ユーザー インターフェースを表示する必要があることを通知します。
  5. アプリが、関連する全画面インテントを持つ通知を使用して着信 UI を表示します。詳しくは onShowIncomingCallUi() をご覧ください。
  6. ユーザーが着信に応答した場合は setActive() メソッドを呼び出します。ユーザーが着信を拒否した場合は、パラメータに REJECTED を指定した setDisconnected(DisconnectCause) メソッドを呼び出した後、destroy() メソッドを呼び出します。

他のアプリに、保留にできないアクティブな通話がある場合

他のアプリに保留にできないアクティブな通話がある場合は、次の手順に沿って着信に応答します。

  1. アプリが通常のメカニズムを使用して新しい着信を受け取ります。
  2. addNewIncomingCall(PhoneAccountHandle, Bundle) メソッドを使用して、新しい着信を Telecom サブシステムに通知します。
  3. Telecom サブシステムがアプリの ConnectionService の実装にバインドし、onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) メソッドを使用して、新しい着信を表す Connection オブジェクトの新しいインスタンスをリクエストします。
  4. Telecom サブシステムが、アプリへの着信を示す着信 UI を表示します。
  5. ユーザーが着信に応答した場合、Telecom サブシステムは onAnswer() メソッドを呼び出します。アプリ側では setActive() メソッドを呼び出して、通話に接続したことを Telecom サブシステムに通知する必要があります。
  6. ユーザーが通話を拒否した場合、Telecom サブシステムは onReject() メソッドを呼び出します。アプリ側ではパラメータに REJECTED を指定して setDisconnected(DisconnectCause) メソッドを呼び出した後、destroy() メソッドを呼び出す必要があります。

通話を発信する

通話の発信フローには、Telecom フレームワークが設けた制約により通話を発信できない場合の処理が含まれます。詳しくは、通話の制約をご覧ください。

通話を発信するには、次の手順に従います。

  1. ユーザーがアプリ内で発信を開始します。
  2. placeCall(Uri, Bundle) メソッドを使用して、新しい発信を Telecom サブシステムに通知します。メソッドのパラメータについての考慮事項を以下に示します。
    • Uri パラメータは、通話を発信している先のアドレスを表します。通常の電話番号の場合は、tel: URI スキームを使用します。
    • Bundle パラメータを使用してアプリの PhoneAccountHandle オブジェクトを EXTRA_PHONE_ACCOUNT_HANDLE エクストラに追加することで、通話アプリについての情報を提供できます。アプリはすべての発信に PhoneAccountHandle オブジェクトを提供する必要があります。
    • また、Bundle パラメータを使用して、EXTRA_START_CALL_WITH_VIDEO_STATE エクストラに STATE_BIDIRECTIONAL の値を指定することで、発信が動画を含んでいるかどうかを指定できます。デフォルトでは、Telecom サブシステムはビデオ通話をスピーカーフォンにルーティングします。
  3. Telecom サブシステムがアプリの ConnectionService の実装にバインドします。
  4. アプリからの発信ができない場合、Telecom サブシステムは onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) メソッドを呼び出して、現在通話を開始できないことをアプリに通知します。アプリ側では、通話を開始できないことをユーザーに通知する必要があります。
  5. アプリからの発信が可能な場合、Telecom サブシステムは onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) メソッドを呼び出します。アプリ側では、新しい発信を表す Connection クラスのインスタンスを返す必要があります。接続で設定する必要のあるプロパティについて詳しくは、接続サービスを実装するをご覧ください。
  6. 発信が接続されたら setActive() メソッドを呼び出して、通話がアクティブであることを Telecom サブシステムに通知します。

通話を終了する

通話を終了するには、次の手順に従います。

  1. setDisconnected(DisconnectCause) を呼び出します。その際、ユーザーが通話を終了した場合はパラメータで LOCAL を送信し、相手が通話を終了した場合はパラメータで REMOTE を送信します。
  2. destroy() メソッドを呼び出します。

通話の制約

一貫性のあるシンプルな通話環境をユーザーに確実に提供するために、Telecom フレームワークはデバイス上の通話の管理にいくつかの制約を課しています。たとえば、ユーザーが FooTalk と BarTalk という 2 つの通話アプリをインストールしていて、それぞれのアプリが自己管理 ConnectionService API を実装しているとします。この場合、次の制約が適用されます。

  • API レベル 27 以前を搭載したデバイスでは、一度に 1 つのアプリのみが進行中の通話を保持できます。この制約により、ユーザーが FooTalk アプリを使用して通話中の場合、BarTalk アプリでは新しい通話を発着信することができません。

    API レベル 28 以降を搭載したデバイスでは、FooTalk と BarTalk の両方が CAPABILITY_SUPPORT_HOLD 権限と CAPABILITY_HOLD 権限を宣言していれば、ユーザーはアプリを切り替えて別の通話を開始または通話に応答することで、進行中の通話を複数保持できます。

  • ユーザーが(内蔵の電話アプリを使用するなどして)通常管理の通話を進行中である場合、ユーザーは通話アプリを介した通話を行うことはできません。つまり、ユーザーが携帯通信会社を使用して通常の通話をしている場合、そのユーザーは FooTalk の通話も BarTalk の通話も同時にすることはできません。

  • ユーザーが緊急通報を発信すると、Telecom サブシステムはアプリの通話を切断します。

  • ユーザーが緊急通報をしている間、アプリでの電話の発着信はできません。

  • アプリが着信を受けたときに別の通話アプリで進行中の通話がある場合、着信に応答すると、他のアプリで進行中の通話は終了します。アプリは自身の通常の着信ユーザー インターフェースを表示してはなりません。Telecom フレームワークが着信ユーザー インターフェースを表示し、ユーザーに対して、新しい通話に応答すると進行中の通話が終了することを伝えます。つまり、ユーザーが FooTalk での通話中に BarTalk アプリで通話が着信した場合、Telecom フレームワークはユーザーに対して、BarTalk に新しい通話が着信していること、BarTalk の通話に応答すると FooTalk の通話が終了することを伝えます。

デフォルトの電話アプリになる

デフォルトの電話アプリ/電話アプリとは、デバイスの通話中に通話中ユーザー インターフェースを提供するアプリです。また、ユーザーが通話を開始する手段や、デバイスで通話の履歴を確認する手段もユーザーに提供されます。デバイスには、システムが提供するデフォルトの電話アプリまたは電話アプリがバンドルされています。ユーザーは、システムアプリからこのロールを引き継ぐアプリを 1 つ選択できます。このロールを担うアプリは、RoleManager を使用して、RoleManager.ROLE_DIALER ロールを付与するようリクエストします。

デバイスが通話中で、デバイスが運転モードでない場合(つまり、UiModeManager#getCurrentModeType()Configuration.UI_MODE_TYPE_CAR ではない)、デフォルトの電話アプリがユーザー インターフェースを提供します。

RoleManager.ROLE_DIALER ロールを付与するには、アプリがいくつかの要件を満たす必要があります。

  • Intent#ACTION_DIAL インテントを処理する必要があります。つまり、ユーザーが発信を開始するためのダイヤルパッド UI をアプリで用意する必要があります。
  • InCallService API を完全に実装し、着信 UI と通話中 UI の両方を提供する必要があります。

注: RoleManager.ROLE_DIALER を処理するアプリがバインディング中に null InCallService を返す場合、通信フレームワークは自動的にデバイスにプリロードされている電話アプリを使用するようフォールバックします。システムは、プリロードされた電話アプリを使用して通話が続行されたことをユーザーに知らせる通知を表示します。アプリは null バインディングを返さないでください。返すと、RoleManager.ROLE_DIALER の要件を満たさなくなります。

注: アプリが RoleManager.ROLE_DIALER を満たし、実行時に変更を加えると、このロールの要件を満たさなくなると、RoleManager はアプリをロールから自動的に削除し、アプリを終了します。たとえば、PackageManager.setComponentEnabledSetting(ComponentName, int, int) を使用して、アプリがマニフェストで宣言している InCallService をプログラムで無効にすると、アプリは RoleManager.ROLE_DIALER に期待される要件を満たさなくなります。

プリロードされた電話アプリは、アプリが RoleManager.ROLE_DIALER ロールを満たしている場合でも、ユーザーが緊急通報を発信するときに常に使用されます。緊急通報の発信時に最適なエクスペリエンスを提供するために、デフォルトの電話アプリでは、緊急通報を含め、常に TelecomManager.placeCall(Uri, Bundle) を使用する必要があります。これにより、プラットフォームはリクエストがデフォルトの電話アプリから送信されたものであることを確認できます。プリロードされていない電話アプリが Intent#ACTION_CALL を使用して緊急通報を発信した場合、確認のために Intent#ACTION_DIAL を使用してプリロードされている電話アプリに送られますが、これは最適とは言えません。

以下は、InCallService のマニフェスト登録の例です。メタデータ TelecomManager#METADATA_IN_CALL_SERVICE_UI は、この特定の InCallService 実装が組み込みのコール UI を置き換えることを意図していることを示します。メタデータ TelecomManager#METADATA_IN_CALL_SERVICE_RINGING は、InCallService が着信時に着信音を再生することを示します。着信 UI を表示し、アプリで着信音を再生する方法について詳しくは、下記をご覧ください。

 <service android:name="your.package.YourInCallServiceImplementation"
          android:permission="android.permission.BIND_INCALL_SERVICE"
          android:exported="true">
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING"
          android:value="true" />
      <intent-filter>
          <action android:name="android.telecom.InCallService"/>
      </intent-filter>
 </service>

注: InCallService に属性 android:exported="false" をマークしないでください。この属性をマークすると、呼び出し中に実装へのバインドが失敗する可能性があります。

InCallService API を実装するだけでなく、Intent#ACTION_DIAL インテントを処理するアクティビティもマニフェストで宣言する必要があります。以下の例は、その方法を示しています。

 <activity android:name="your.package.YourDialerActivity"
           android:label="@string/yourDialerActivityLabel">
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
           <data android:scheme="tel" />
      </intent-filter>
 </activity>

ユーザーがアプリをインストールして初めて実行する際に、RoleManager を使用して、アプリを新しいデフォルトのスマートフォン アプリにするかどうかユーザーに求めるメッセージを表示する必要があります。

以下のコードは、アプリがデフォルトの電話/電話アプリになることをリクエストする方法を示しています。

 private static final int REQUEST_ID = 1;

 public void requestRole() {
     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
     startActivityForResult(intent, REQUEST_ID);
 }

 public void onActivityResult(int requestCode, int resultCode, Intent data) {
     if (requestCode == REQUEST_ID) {
         if (resultCode == android.app.Activity.RESULT_OK) {
             // Your app is now the default dialer app
         } else {
             // Your app is not the default dialer app
         }
     }
 }

ウェアラブル デバイス用の InCallService へのアクセス

    サードパーティのコンパニオン アプリであるアプリが InCallService API にアクセスする場合、アプリでできることは次のとおりです。

    1. マニフェストで MANAGE_ONGOING_CALLS 権限を宣言する
    2. CompanionDeviceManager API を介してコンパニオン アプリとして物理ウェアラブル デバイスに関連付けます。詳しくは、https://developer.android.com/guide/topics/connectivity/companion-device-pairing をご覧ください。
    3. BIND_INCALL_SERVICE 権限を使用してこの InCallService を実装する

着信通知の表示

アプリは、InCallService#onCallAdded(Call) 経由で新しい着信を受信すると、着信用の着信 UI を表示します。これを行うには、NotificationManager API を使用して新しい着信通知を送信します。

アプリがメタデータ TelecomManager#METADATA_IN_CALL_SERVICE_RINGING を宣言する場合、アプリは着信に対して着信音を再生します。アプリは、目的の着信音を指定する NotificationChannel を作成する必要があります。次に例を示します。

 NotificationChannel channel = new NotificationChannel(YOUR_CHANNEL_ID, "Incoming Calls",
          NotificationManager.IMPORTANCE_MAX);
 // other channel setup stuff goes here.

 // We'll use the default system ringtone for our incoming call notification channel.  You can
 // use your own audio resource here.
 Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
 channel.setSound(ringtoneUri, new AudioAttributes.Builder()
          // Setting the AudioAttributes is important as it identifies the purpose of your
          // notification sound.
          .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
          .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
      .build());

 NotificationManager mgr = getSystemService(NotificationManager.class);
 mgr.createNotificationChannel(channel);

アプリは新しい着信を受信すると、着信用の Notification を作成し、着信通知チャンネルに関連付けます。通知に PendingIntent を指定すると、着信 UI が全画面表示されます。ユーザーがスマートフォンを頻繁に使用している場合、通知マネージャー フレームワークは通知をヘッドアップ通知として表示します。ユーザーがスマートフォンを使用していないときは、代わりに全画面表示の着信 UI が使用されます。 次に例を示します。

 // Create an intent which triggers your fullscreen incoming call user interface.
 Intent intent = new Intent(Intent.ACTION_MAIN, null);
 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
 intent.setClass(context, YourIncomingCallActivity.class);
 PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 // Build the notification as an ongoing high priority item; this ensures it will show as
 // a heads up notification which slides down over top of the current content.
 final Notification.Builder builder = new Notification.Builder(context);
 builder.setOngoing(true);
 builder.setPriority(Notification.PRIORITY_HIGH);
 // Set notification content intent to take user to the fullscreen UI if user taps on the
 // notification body.
 builder.setContentIntent(pendingIntent);
 // Set full screen intent to trigger display of the fullscreen UI when the notification
 // manager deems it appropriate.
 builder.setFullScreenIntent(pendingIntent, true);
 // Setup notification content.
 builder.setSmallIcon( yourIconResourceId );
 builder.setContentTitle("Your notification title");
 builder.setContentText("Your notification content.");
 // Use builder.addAction(..) to add buttons to answer or reject the call.
 NotificationManager notificationManager = mContext.getSystemService(
     NotificationManager.class);
 notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
```