セッション開始プロトコルの概要

eSIM と SIM カードを検出する

カードの検出

SIM カードと eSIM を組み込んだ Android デバイスは、[`TelephonyManager](/reference/android/telephony/TelephonyManager) や [``SubscriptionManager`](/reference/android/telephony/SubscriptionManager) などの Telephony API で以下の ID を使用します。 * サブスクリプション ID: モバイル サブスクリプションの一意の ID。 * 論理スロット インデックス(または ID): 論理 SIM スロットを参照する一意のインデックス。 論理スロット ID は 0 から始まり、デバイスでサポートされるアクティブ スロットの数に応じて増えます。たとえば、デュアル SIM デバイスには通常、スロット 0 とスロット 1 があります。デバイスに複数の物理スロットがあり、1 つのアクティブ スロットしかサポートしていない場合、論理スロット ID は 0 のみになります。 * 物理スロット インデックス(または ID): 物理 SIM スロットを指す一意のインデックス。 物理スロット ID は 0 から始まり、デバイスの物理スロット数に応じて増加します。これは、デバイスにある論理スロットの数とは異なります。つまり、デバイスが使用できるアクティブなスロットの数に相当します。たとえば、デュアル SIM モードとシングル SIM モードを切り替えるデバイスは、物理スロットが常に 2 つある場合でも、シングル SIM モードでは論理スロットが 1 つだけになります。 * カード ID: UiccCard の識別に使用される一意の ID。 {5}2 つの論理スロットと 3 つの物理スロットがある場合の ID の使用方法](/images/guide/topics/connectivity/tel-ids.png) 上の図の説明: * デバイスには 2 つの論理スロットがあります。 * 物理スロット 0 には、アクティブなプロファイルを持つ物理 UICC カードがある。 * 物理スロット 2 はアクティブなプロファイルを持つ eUICC です。 * 物理スロット 1 は現在使用中ではない。 {5}3 つの論理スロットと 2 つの物理スロットがある場合の ID の使用方法](/images/guide/topics/connectivity/tel-ids-2.png) 上図の説明: * デバイスには 3 つの論理スロットがあります。 * 物理スロット 0 には、アクティブなプロファイルを持つ物理 UICC カードがある。 * 物理スロット 1 の eUICC にはダウンロードされた 2 つのプロファイルがあり、どちらも MEP(複数有効化プロファイル)を使用してアクティブになっています。

セッション開始プロトコルの概要

Android は、セッション開始プロトコル(SIP)をサポートする API を提供しています。これにより、SIP ベースのインターネット テレフォニー機能をアプリケーションに追加できます。Android には、完全な SIP プロトコル スタックと統合通話管理サービスが含まれており、セッション、トランスポート レベルの通信、音声録音または再生を直接管理することなく、アプリから音声通話の発信と着信を簡単に設定できます。

SIP API を使用するアプリケーションの種類の例を次に示します。

  • ビデオ会議
  • インスタント メッセージ

要件と制限事項

SIP アプリケーションを開発するための要件は次のとおりです。

  • Android 2.3 以降を搭載しているモバイル デバイスが必要です。
  • SIP はワイヤレス データ接続で動作するため、デバイスでデータ接続(モバイルデータ サービスまたは Wi-Fi)が必要です。つまり、AVD ではテストできず、実機でのみテストできます。詳細については、SIP アプリケーションのテストをご覧ください。
  • アプリケーションの通信セッションの各参加者には、SIP アカウントが必要です。さまざまな SIP プロバイダが SIP アカウントを提供しています。

注: android.net.sip ライブラリはビデオ通話をサポートしていません。android.net.sip などの SIP スタックを使用して VoIP 通話を実装する場合は、VoIP 通話実装の基礎として、最新の多くのオープンソース方法の一つを検討してください。または、ConnectionService API を実装して、これらの呼び出しをデバイスの電話アプリに緊密に統合することもできます。

SIP API のクラスとインターフェース

Android SIP API に含まれるクラスとインターフェース(SipRegistrationListener)の概要を以下に示します。

クラス / インターフェース 説明
SipAudioCall SIP を介したインターネット音声通話を処理します。
SipAudioCall.Listener SIP 通話に関連するイベントのリスナー。たとえば、通話が着信中(「呼び出し中」)、発信中(「通話中」)など。
SipErrorCode SIP アクション中に返されるエラーコードを定義します。
SipManager SIP 接続の開始など、SIP タスク用の API を提供し、関連する SIP サービスへのアクセスを提供します。
SipProfile SIP アカウント、ドメイン、サーバー情報を含む SIP プロファイルを定義します。
SipProfile.Builder SipProfile を作成するためのヘルパークラス。
SipSession SIP ダイアログまたはダイアログ内ではないスタンドアロン トランザクションに関連付けられている SIP セッションを表します。
SipSession.Listener セッションの登録(「登録中」)や通話の発信(「通話中」)など、SIP セッションに関連するイベントのリスナー。
SipSession.State 「登録」、「発信」、「着信」など、SIP セッションの状態を定義します。
SipRegistrationListener SIP 登録イベントのリスナーであるインターフェース。

マニフェストの作成

SIP API を使用するアプリを開発する場合、この機能がサポートされているのは Android 2.3(API レベル 9)以降のバージョンのプラットフォームのみであることにご注意ください。また、Android 2.3(API レベル 9)以降を搭載しているデバイスについては、すべてのデバイスが SIP に対応しているわけではありません。

SIP を使用するには、アプリケーションのマニフェストに次の権限を追加します。

  • android.permission.USE_SIP
  • android.permission.INTERNET

SIP をサポートするデバイスにのみアプリをインストールできるようにするには、アプリのマニフェストに次の行を追加します。

<uses-sdk android:minSdkVersion="9" />

これは、アプリケーションに Android 2.3 以降が必要であることを示しています。詳細については、API レベル<uses-sdk> 要素のドキュメントをご覧ください。

SIP をサポートしていないデバイス(Google Play など)からアプリをフィルタする方法を制御するには、アプリのマニフェストに次の行を追加します。

<uses-feature android:name="android.software.sip.voip" />

これは、アプリケーションが SIP API を使用していることを示しています。宣言には、SIP をサポートしていないデバイスからアプリを除外するかどうかを示す android:required 属性を含める必要があります。実装によっては、他の <uses-feature> 宣言も必要になる場合があります。詳細については、<uses-feature> 要素のドキュメントをご覧ください。

アプリケーションが通話を受信するように設計されている場合は、アプリケーションのマニフェストで受信側(BroadcastReceiver サブクラス)を定義する必要もあります。

<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />

SipDemo マニフェストからの抜粋を次に示します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.android.sip">
  ...
     <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
  ...
  <uses-sdk android:minSdkVersion="9" />
  <uses-permission android:name="android.permission.USE_SIP" />
  <uses-permission android:name="android.permission.INTERNET" />
  ...
  <uses-feature android:name="android.software.sip.voip" android:required="true" />
  <uses-feature android:name="android.hardware.wifi" android:required="true" />
  <uses-feature android:name="android.hardware.microphone" android:required="true" />
</manifest>

SipManager の作成

SIP API を使用するには、アプリケーションで SipManager オブジェクトを作成する必要があります。SipManager は、アプリで次の処理を行います。

  • SIP セッションの開始。
  • 通話の開始と受信。
  • SIP プロバイダへの登録と登録解除。
  • セッション接続の確認。

新しい SipManager を次のようにインスタンス化します。

Kotlin

val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
    SipManager.newInstance(this)
}

Java

public SipManager sipManager = null;
...
if (sipManager == null) {
    sipManager = SipManager.newInstance(this);
}

SIP サーバーへの登録

一般的な Android SIP アプリには、それぞれ SIP アカウントを持つ 1 人以上のユーザーが関与します。Android SIP アプリでは、各 SIP アカウントは SipProfile オブジェクトで表されます。

SipProfile は、SIP アカウント、ドメイン、サーバー情報を含む SIP プロファイルを定義します。アプリケーションを実行しているデバイス上の SIP アカウントに関連付けられたプロファイルは、ローカル プロファイルと呼ばれます。セッションが接続されるプロファイルは、ピア プロファイルと呼ばれます。SIP アプリケーションがローカルの SipProfile を使用して SIP サーバーにログインすると、SIP アドレスの SIP 呼び出しの送信先としてデバイスが実質的に登録されます。

このセクションでは、SipProfile を作成して SIP サーバーに登録し、登録イベントを追跡する方法について説明します。

SipProfile オブジェクトは、次のように作成します。

Kotlin

private var sipProfile: SipProfile? = null
...

val builder = SipProfile.Builder(username, domain)
        .setPassword(password)
sipProfile = builder.build()

Java

public SipProfile sipProfile = null;
...

SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);
sipProfile = builder.build();

次のコードの抜粋では、汎用 SIP 通話の発信と受信のローカル プロファイルを開きます。呼び出し元は、mSipManager.makeAudioCall を介して後続の呼び出しを行うことができます。この抜粋で、アクション android.SipDemo.INCOMING_CALL も設定されます。これは、デバイスが通話を受信したときにインテント フィルタで使用されます(通話を受信するためのインテント フィルタのセットアップを参照)。これは登録手順です。

Kotlin

val intent = Intent("android.SipDemo.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
sipManager?.open(sipProfile, pendingIntent, null)

Java

Intent intent = new Intent();
intent.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
sipManager.open(sipProfile, pendingIntent, null);

最後に、このコードは SipManagerSipRegistrationListener を設定します。これにより、SipProfile が SIP サービス プロバイダに正常に登録されたかどうかが追跡されます。

Kotlin

sipManager?.setRegistrationListener(sipProfile?.uriString, object : SipRegistrationListener {

    override fun onRegistering(localProfileUri: String) {
        updateStatus("Registering with SIP Server...")
    }

    override fun onRegistrationDone(localProfileUri: String, expiryTime: Long) {
        updateStatus("Ready")
    }

    override fun onRegistrationFailed(
            localProfileUri: String,
            errorCode: Int,
            errorMessage: String
    ) {
        updateStatus("Registration failed. Please check settings.")
    }
})

Java

sipManager.setRegistrationListener(sipProfile.getUriString(), new SipRegistrationListener() {

    public void onRegistering(String localProfileUri) {
        updateStatus("Registering with SIP Server...");
    }

    public void onRegistrationDone(String localProfileUri, long expiryTime) {
        updateStatus("Ready");
    }

    public void onRegistrationFailed(String localProfileUri, int errorCode,
        String errorMessage) {
        updateStatus("Registration failed.  Please check settings.");
    }
}

プロファイルの使用を完了したら、アプリはプロファイルを閉じて関連オブジェクトをメモリに解放し、デバイスからデバイスの登録を解除する必要があります。たとえば、以下の場合です。

Kotlin

fun closeLocalProfile() {
    try {
        sipManager?.close(sipProfile?.uriString)
    } catch (ee: Exception) {
        Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee)
    }
}

Java

public void closeLocalProfile() {
    if (sipManager == null) {
       return;
    }
    try {
       if (sipProfile != null) {
          sipManager.close(sipProfile.getUriString());
       }
     } catch (Exception ee) {
       Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee);
     }
}

音声通話を発信する

音声通話を発信するには、次のものが必要です。

  • 通話を発信している SipProfile(ローカル プロファイル)と、通話を受信するための有効な SIP アドレス(ピア プロファイル)。
  • SipManager オブジェクト。

音声通話を発信するには、SipAudioCall.Listener を設定する必要があります。クライアントと SIP スタックとのやり取りの多くは、リスナーを介して行われます。このスニペットでは、呼び出しの確立後に SipAudioCall.Listener がどのように設定を行うかを示しています。

Kotlin

var listener: SipAudioCall.Listener = object : SipAudioCall.Listener() {

    override fun onCallEstablished(call: SipAudioCall) {
        call.apply {
            startAudio()
            setSpeakerMode(true)
            toggleMute()
        }
    }

    override fun onCallEnded(call: SipAudioCall) {
        // Do something.
    }
}

Java

SipAudioCall.Listener listener = new SipAudioCall.Listener() {

   @Override
   public void onCallEstablished(SipAudioCall call) {
      call.startAudio();
      call.setSpeakerMode(true);
      call.toggleMute();
         ...
   }

   @Override

   public void onCallEnded(SipAudioCall call) {
      // Do something.
   }
};

SipAudioCall.Listener のセットアップが完了すると、呼び出しを行うことができます。SipManager メソッド makeAudioCall は、次のパラメータを受け取ります。

  • ローカル SIP プロファイル(発信側)。
  • ピア SIP プロファイル(通話を受けるユーザー)。
  • SipAudioCall からの通話イベントをリッスンする SipAudioCall.Listenernull にすることもできますが、前述のように、呼び出しが確立されるとリスナーを使用して設定が行われます。
  • タイムアウト値(秒)。

次に例を示します。

Kotlin

val call: SipAudioCall? = sipManager?.makeAudioCall(
        sipProfile?.uriString,
        sipAddress,
        listener,
        30
)

Java

call = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, listener, 30);

通話を受信する

通話を受信するには、着信があることを示すインテントに応答できる BroadcastReceiver のサブクラスが SIP アプリに含まれている必要があります。したがって、アプリケーションで次のことを行う必要があります。

  • AndroidManifest.xml<receiver> を宣言します。SipDemo では、<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" /> です。
  • 受信側(BroadcastReceiver のサブクラス)を実装する。SipDemo では、IncomingCallReceiver です。
  • ローカル プロファイルが呼び出されたときにレシーバを起動するペンディング インテントを使用して、ローカル プロファイル(SipProfile)を初期化します。
  • 着信を表すアクションでフィルタリングするインテント フィルタをセットアップします。SipDemo では、このアクションは android.SipDemo.INCOMING_CALL です。

BroadcastReceiver のサブクラス化

通話を受信するには、SIP アプリケーションで BroadcastReceiver をサブクラス化する必要があります。Android システムは着信 SIP 通話を処理し、着信があると「着信通話」インテント(アプリケーションで定義)をブロードキャストします。SipDemo サンプルのサブクラス化された BroadcastReceiver コードを次に示します。

Kotlin

/**
 * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
 */
class IncomingCallReceiver : BroadcastReceiver() {

    /**
     * Processes the incoming call, answers it, and hands it over to the
     * WalkieTalkieActivity.
     * @param context The context under which the receiver is running.
     * @param intent The intent being received.
     */
    override fun onReceive(context: Context, intent: Intent) {
        val wtActivity = context as WalkieTalkieActivity

        var incomingCall: SipAudioCall? = null
        try {
            incomingCall = wtActivity.sipManager?.takeAudioCall(intent, listener)
            incomingCall?.apply {
                answerCall(30)
                startAudio()
                setSpeakerMode(true)
                if (isMuted) {
                    toggleMute()
                }
                wtActivity.call = this
                wtActivity.updateStatus(this)
            }
        } catch (e: Exception) {
            incomingCall?.close()
        }
    }

    private val listener = object : SipAudioCall.Listener() {

        override fun onRinging(call: SipAudioCall, caller: SipProfile) {
            try {
                call.answerCall(30)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

Java

/**
 * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
 */
public class IncomingCallReceiver extends BroadcastReceiver {
    /**
     * Processes the incoming call, answers it, and hands it over to the
     * WalkieTalkieActivity.
     * @param context The context under which the receiver is running.
     * @param intent The intent being received.
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        SipAudioCall incomingCall = null;
        try {
            SipAudioCall.Listener listener = new SipAudioCall.Listener() {
                @Override
                public void onRinging(SipAudioCall call, SipProfile caller) {
                    try {
                        call.answerCall(30);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;
            incomingCall = wtActivity.sipManager.takeAudioCall(intent, listener);
            incomingCall.answerCall(30);
            incomingCall.startAudio();
            incomingCall.setSpeakerMode(true);
            if(incomingCall.isMuted()) {
                incomingCall.toggleMute();
            }
            wtActivity.call = incomingCall;
            wtActivity.updateStatus(incomingCall);
        } catch (Exception e) {
            if (incomingCall != null) {
                incomingCall.close();
            }
        }
    }
}

通話を受信するためのインテント フィルタの設定

SIP サービスは、新しい通話を受信すると、アプリから提供されたアクション文字列を使用してインテントを送信します。SipDemo では、このアクション文字列は android.SipDemo.INCOMING_CALL です。

SipDemo からのこのコードの抜粋は、アクション文字列 android.SipDemo.INCOMING_CALL に基づいて、ペンディング インテントで SipProfile オブジェクトが作成される方法を示しています。PendingIntent オブジェクトは、SipProfile が呼び出しを受信するとブロードキャストを実行します。

Kotlin

val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
    SipManager.newInstance(this)
}

var sipProfile: SipProfile? = null
...

val intent = Intent("android.SipDemo.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
sipManager?.open (sipProfile, pendingIntent, null)

Java

public SipManager sipManager = null;
public SipProfile sipProfile = null;
...

Intent intent = new Intent();
intent.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
sipManager.open(sipProfile, pendingIntent, null);

ブロードキャストはインテント フィルタによってインターセプトされ、レシーバ(IncomingCallReceiver)が起動されます。インテント フィルタは、アプリのマニフェスト ファイルで指定するか、アプリの Activity にある SipDemo サンプルアプリの onCreate() メソッドのようにコードで行うことができます。

Kotlin

class WalkieTalkieActivity : Activity(), View.OnTouchListener {
    ...
    lateinit var callReceiver: IncomingCallReceiver
    ...

    override fun onCreate(savedInstanceState: Bundle) {
        val filter = IntentFilter().apply {
            addAction("android.SipDemo.INCOMING_CALL")
        }
        callReceiver = IncomingCallReceiver()
        this.registerReceiver(callReceiver, filter)
        ...
    }
    ...
}

Java

public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {
...
    public IncomingCallReceiver callReceiver;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {

       IntentFilter filter = new IntentFilter();
       filter.addAction("android.SipDemo.INCOMING_CALL");
       callReceiver = new IncomingCallReceiver();
       this.registerReceiver(callReceiver, filter);
       ...
    }
    ...
}

SIP アプリケーションのテスト

SIP アプリケーションをテストするには、次のものが必要です。

  • Android 2.3 以降を搭載しているモバイル デバイス。SIP はワイヤレスで動作するため、実際のデバイスでテストする必要があります。AVD でのテストは機能しません。
  • SIP アカウント。さまざまな SIP プロバイダが SIP アカウントを提供しています。
  • 通話を発信する場合は、有効な SIP アカウントにも発信する必要があります。

SIP アプリケーションをテストする手順は次のとおりです。

  1. デバイスでワイヤレスに接続します([設定] > [無線とネットワーク] > [Wi-Fi] > [Wi-Fi 設定])。
  2. デバイスでの開発の説明に沿って、モバイル デバイスをテスト用に設定します。
  3. デバイスでの開発の説明に従って、モバイル デバイスでアプリケーションを実行します。
  4. Android Studio を使用している場合は、イベントログ コンソールを開いてアプリケーション ログの出力を表示できます([View] > [Tool Windows] > [Event Log])。
  5. 実行時に Logcat を自動的に起動するようにアプリが構成されていることを確認します。
    1. [Run] > [Edit Configurations] を選択します。
    2. [Run/Debug Configurations] ウィンドウで [Miscellaneous] タブを選択します。
    3. [Logcat] で [Show logcat automatically] を選択し、[OK] を選択します。