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

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

通話機能を持たず、通話用のユーザー インターフェースのみで構成する必要があります。 Telecom フレームワークが認識しているすべての通話を処理し、通話の性質を推測してはなりません。たとえば、通話が 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 つのアプリを選択できます。特定の Google Cloud リソースへの このロールを付与すると、RoleManager を使用して、 RoleManager.ROLE_DIALER ロール。

デフォルトの電話アプリでは、デバイスが通話中にユーザー インターフェースが表示され、デバイスが 運転モードではありません(例: UiModeManager#getCurrentModeType()Configuration.UI_MODE_TYPE_CAR)。

RoleManager.ROLE_DIALER のロールを付与するには、アプリが次の要件を満たしている必要があります。 要件の数:

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

注: RoleManager.ROLE_DIALER を埋めるアプリが バインディング中に null InCallService を選択すると、Telecom フレームワークは自動的にアクセス デバイスにプリロードされた電話アプリの使用に戻ります。システムによって プリロードされた電話アプリを使用して通話を続けたことをお客様に伝えます。お客様の アプリは 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 は、着信があると着信音を鳴らします。詳しくは、 着信の表示について詳しくは、下記をご覧ください 着信音を鳴らしています。

 <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 へのアクセス

<ph type="x-smartling-placeholder">
    </ph> サードパーティのコンパニオン アプリで 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 を作成し、 着信通知チャンネルに関連付けます。1 対 1 の 全画面表示を起動する通知の 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());
```