2013年3月25日 星期一

[Android] Android Developer Note Connectivity 8

Session Initiation Protocol (見Android Developer SIP)

序言:
Android提供了API來支援Session Initiation Protocol(SIP)。
這個API讓你可以將SIP-based網路電話功能(internet telephony features)加到app上。
Android包含了一整套的SIP protocol stack以及整合的呼叫管理服務,
讓apps可以輕易的設定好收發語音通話的功能,
而不需去管理sessions,transport層的溝通,
或者音訊錄製或直接播放等問題。

下面是幾個可能會用到SIP API的幾種apps。
1. 視訊會議(Video conferencing)
2. 即時訊息(Instant messaging)

Requirements and Limitations
開發一個SIP app有幾點需求的條件:
1. 要有一台手機且其使用Android 2.3或更新版本的系統。
2. SIP是透過無線的資料連結,所以你的裝置必須要有一個資料連結。
(透過手機資料服務或者Wi-Fi)。這表示你不能在AVD上測試,
只能在實體裝置上進行測試。
3. 每一個在app溝通階段(communication session)的參加者必須都有一個SIP帳號。
有需多不同的SIP供應者能提供SIP帳號。

SIP API Classes and Interfaces
下表是對於Android SIP API裡classes和一個interface(SipRegistrationListener)的總結。

Class/InterfaceDescription
SipAudioCall處理一個透過SIP的網路語音通話。
SipAudioCall.Listener監聽和SIP通話有關的events,比如說像接收中通話("on ringing"),
或是外播中的通話("on calling")。
SipErrorCode定義了SIP actions裡的錯誤碼(error codes)。
SipManager提供APIs給SIP tasks,像是開始SIP連線,
以及提供相對應於SIP服務的存取。
SipProfile定義一個SIP的profile,當中包含一個SIP帳號,
domain及server的資訊。
SipProfile.Builder製作SipProfile的Helper class。
SipSession代表跟SIP dialog或一個不含dialog獨自(standalong)交換(transaction)
相關的SIP session。
SipSession.Listener監聽跟SIP session有關的events,
像是當一個session被註冊("on registering"),
或者一個通話外撥("on calling")。
SipSession.State定義SIP session狀態,像是"registering","outgoing call",以及"in call"。
SipRegistrationListener作為SIP registration events的監聽者的介面。

Creating the Manifest
如果你在開發一個使用SIP API的app,
記得這個功能只在Android 2.3(API level 9)及更新的版本平台。
並且在所有跑Android 2.3或更新的版本的裝置並不是全都會提供SIP支援。

要使用SIP,將下列的許可授權加到你的app的manifest:
1. android.permission.USE_SIP
2. android.permission.INTERNET
要確保你的app只能被裝到支援SIP的裝置,
將下面列出來的加到manifest:
1. <uses-sdk android:minSdkVersion="9" />
這表示了你的app需求Android 2.3或更新的版本。
2. <uses-feature android:name="android.hardware.sip.voip" />
這敘述了你的app使用SIP API。
聲明中必須要包含android:required的屬性,
以指示你要不要讓app被不支援SIP的裝置給篩到。
其他的<uses-feature>聲明可能也會需要(視你的實作而定)。
如果你的app是被設計來接受通話的話,那麼你也必須要定義一個receiver。
(BroadcastReceiver subclass)
在app的manifest裡:
<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver"/>
下面是從SipDemo的manifest取來的片段(excerpts):

<?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>


Creating a SipManager
要使用SIP API,你的app必須要作一個SipManager物件。
SipManager在你的app中做這些事情:
1. 開始SIP sessions。
2. 開始並接收calls。
3. 向SIP provider註冊及反註冊。
4. 確認session的連線性。
你可以如下建立一個SipManager的實例。

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

Registering with a SIP Server
一個典型的Android SIP app包含了一個或多個使用者,
每一個都有一個SIP帳號。
在一個Android SIP app裡,每一個SIP帳號被一個SipProfile物件所代表。

一個SipProfile定義了一個SIP profile,當中包含了:
SIP帳號,domain,以及server information。
關聯著在裝置上跑的app的SIP帳號的那個profile,
被稱作local profile。
session被連結到的那個profile稱作peer profile。
當你的SIP app將local的SipProfile記錄到SIP server時。
這有效率地註冊裝置作為發送SIP calls的地點到你的SIP address。
這段展示了如何去製作SipProfile,
使用SIP server來註冊,以及追蹤註冊的events。

你可以如下作一個SipProfile的物件:

public SipProfile mSipProfile = null;
...
SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);
mSipProfile = builder.build();

下面的code節錄了打開local profile來打且/或接收通用的SIP通話。
呼叫者可以透過來打一連串的通話,透過mSipManager.makeAudioCall
這個節錄也設定了action android.SipDemo.INCOMING_CALL
當裝置接受到一個通話時,其將會被一個intent filter來使用。
下面是註冊步驟:

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

最後,這個code在SipManager上設定了一個SipRegistrationListener
這追蹤了是否能成功被SIP service供應商給註冊。

mSipManager.setRegistrationListener(mSipProfile.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.");
}

當你的app使用profile完畢的時候,它應該要被關閉,
以釋放相關的物件回到記憶體,以及從server那反註冊裝置。
舉例來說:

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

Making an Audio Call
要打一通語音電話,你必須有下面的東西:
1. 一個SipProfile用以打電話(the "local profile"),
以及一個有效的SIP address來接收呼叫(the "peer profile")。
2. 一個SipManager 物件。
要打一通語音電話,你應該要設定一個SipAudioCall.Listener
大部分client和SIP stack間的互動交流是透過監聽器發生的。
在下面的片段裡,你可以看到SipAudioCall.Listener是如何設定的。
(在通話建立後)。

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的method makeAudioCall取得下面的參數:
1. 一個local SIP profile(呼叫者)。
2. 一個peer SIP profile(被呼叫者)。
3. 一個從SipAudioCall監聽call events的SipAudioCall.Listener
這可以是null,但就如上面所示,監聽器在通話建立後馬上就設定了。
4. 以秒計算的timeout值。
舉例來說:

call = mSipManager.makeAudioCall(mSipProfile.getUriString(), sipAddress, listener, 30);

Receiving Calls
要接收通話,一個SIP app必須要包括一個BroadcastReceiver的subclass,
其要有能力來對於一個指示有打進來的通話作回應。
那麼你必需在你的app裡做這些事情:
1. 在AndroidManifest.xml裡,定義一個<receiver>
在SipDemo裡,
這是<receiver android:name=".IncomingCallReceiver" 
android:label="Call Receiver"/>
2. 實作接收器,它是BroadcastReceiver的subclass。在SipDemo裡,
這是IncomingCallReceiver
3. 新增local file(SipProfile)偕同一個當有人呼叫local file時,
會找你的監聽器的pending intent。
4. 設定一個intent filter來篩代表撥進來的通話的action。
這個action是android.SipDemo.INCOMING_CALL

Subclassing Broadcast Receiver:
要接收呼叫,你的SIP app必須要有class繼承BroadcastReceiver
Android系統處理了打進來的SIP calls,
並當接收一個通話時,廣播一個"incoming call" intent(如同程式定義)。
這裡是從SipDemo節錄出來的繼承了BroadcastReceiver的code。

/*** 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.mSipManager.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();
            }
        }
    }
}

Setting up an intent filter to receive calls:
當SIP服務接收了一個新的call,它由app來提供,傳出一個帶有action string。
在SipDemo裡,這個action string是android.SipDemo.INCOMING_CALL
這個從SipDemo來的code片段展示了SipProfile物件,
如何從一個基於action string android.SipDemo.INCOMING_CALL
pending intent來建立起來。
SipProfile接收到call時,PendingIntent物件將會執行廣播。

public SipManager mSipManager = null;
public SipProfile mSipProfile = null;
...
Intent intent = new Intent(); 
intent.setAction("android.SipDemo.INCOMING_CALL"); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA); 
mSipManager.open(mSipProfile, pendingIntent, null);

廣播將會被intent filter給截下來,並在稍後將receiver(IncomingCallReceiver)發射。
你可以在你的app的manifest file裡,指定一個intent filter,
或者在code裡這麼做,
就如在SipDemo的sample app的ActivityonCreate() method裡。

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);
       ...
    }
    ...
}

Testing SIP Application
要測試SIP apps,你需要下面的條件:
1. Android 2.3以上的手機裝置。SIP透過無線來執行,必須要在實體機器上跑,
使用AVD來測試並不會有效。
2. 一個SIP account。有很多供應者可以提供SIP帳號。
3. 如果你進行了呼叫,必須要是一個有效的SIP帳號。

要測試SIP app:
1. 在裝置上連接到無線。(Settings > Wireless & networks > Wi-Fi > Wi-Fi settings)
2. 設定你的手機裝置以進行測試。
3. 在你的手機裝置上跑app。
4. 如果是使用Eclipse可以透過LogCat來檢視app log的輸出。
(Window > Show View > Other > Android > LogCat)

沒有留言:

張貼留言