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

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

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

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

要件と制限事項

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

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

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.hardware.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.hardware.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.Listener。これは null でもかまいませんが、上記のように、通話が確立されるとリスナーを使用して設定が行われます。
  • タイムアウト値(秒)。

次に例を示します。

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)が起動されます。インテント フィルタは、アプリケーションのマニフェスト ファイルで、または SipDemo サンプル アプリケーションの ActivityonCreate() メソッドのようなコードで指定できます。

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 を使用している場合は、Event Log コンソールを開きます([View] > [Tool Windows] > [Event Log])。
  5. 実行時に Logcat が自動的に起動するようにアプリケーションが設定されていることを確認します。
    1. [Run] > [Edit Configurations] の順に選択します。
    2. [Run/Debug Configurations] ウィンドウで [Miscellaneous] タブを選択します。
    3. [Logcat] で [Show logcat automatically] を選択し、[OK] を選択します。