對話啟動協定總覽

偵測 eSIM 卡和 SIM 卡

偵測卡片

配備 SIM 卡和 eSIM 卡的 Android 裝置會在電話 API 中使用以下 ID,包括 [`TelephonyManager`](/reference/android/telephony/TelephonyManager) 和 [`SubscriptionManager`](/reference/android/telephony/SubscriptionManager): * 訂閱 ID:行動訂閱項目的專屬 ID。* 邏輯運算單元索引或 ID:參照邏輯 SIM 卡插槽的專屬索引。 邏輯運算單元 ID 會從 0 開始,然後依據裝置上支援的有效運算單元數量而定。舉例來說,雙 SIM 卡裝置通常具備運算單元 0 和插槽 1。如果裝置有多個實體運算單元,但僅支援一個有效的運算單元,裝置使用的運算單元 ID 會是 0。* 實體運算單元索引或 ID:參照實體 SIM 卡插槽的專屬索引。 實體運算單元 ID 會從 0 開始,然後依據裝置上的實體運算單元數量增加。這與裝置擁有的邏輯運算單元數量不同,後者是對應裝置可使用的運算單元數量。舉例來說,一個在雙 SIM 卡和單一 SIM 卡模式之間切換的裝置可能會有兩個實體運算單元,但單一 SIM 卡模式只會有一個邏輯運算單元。* 卡片 ID:用來識別 UiccCard 的專屬 ID。 ![在包含兩個邏輯運算單元和三個實體運算單元的情況下,使用 ID 的圖表](/images/guide/topics/connectivity/tel-ids.png) 上圖: * 裝置有兩個邏輯版位。 * 實體運算單元 0 中有具有有效設定檔的實體 UICC 資訊卡。 * 實體運算單元 2 中是具備有效設定檔的 eUICC。 * 目前未使用實體運算單元 1。 ![在有三個邏輯版位和兩個實體運算單元的情況下,使用 ID 的圖表](/images/guide/topics/connectivity/tel-ids-2.png) 上圖: * 裝置有三個邏輯運算單元。 * 實體運算單元 0 中有具有有效設定檔的實體 UICC 資訊卡。 * 實體運算單元 1 中的 eUICC 包含兩個已下載的設定檔,且兩者均使用 MEP (多個啟用設定檔) 處於啟用狀態。

對話啟動協定總覽

Android 提供的 API 支援工作階段初始通訊協定 (SIP)。這樣一來,您就能在應用程式中新增 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 工作的 API (例如啟動 SIP 連線),並提供相關 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。宣告應包含 android:required 屬性,表明您要將應用程式從不提供 SIP 支援的裝置中篩除。視您的實作方式而定,可能還需要其他 <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 Server

一般的 Android SIP 應用程式涉及一或多位使用者,每個使用者都有 SIP 帳戶。在 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);

最後,此程式碼會在 SipManager 上設定 SipRegistrationListener。這會追蹤 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);

接聽來電

如要接聽來電,SIP 應用程式必須包含 BroadcastReceiver 子類別,並能回應意圖以表明有來電的意圖。因此,您必須在應用程式中執行下列操作:

  • 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)。您可以在應用程式的資訊清單檔案中指定意圖篩選器,也可以在應用程式 ActivitySipDemo 範例應用程式的 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 會自動顯示」,然後選取「OK」