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的Activity的onCreate() 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)

沒有留言:

張貼留言