2013年3月27日 星期三

[Android] Android Developer Note Administration

Administration(見 Android Developer Administration)
如果你是企業的系統管理員,
你可以從APIs及系統capabilities得到好處來管理Android裝置以及控管存取。

Device Administration
序言
透過Android Device Administration API,Android 2.2引入了對於企業app的支援。
API在系統層級提供了Device Administration功能。
這些APIs允許你來製作在企業設定裡有用的有安全意識的(security-aware)app,
這些設定是IT專業需求對於大量企業裝置的控管。
舉例來說,Android內建的E-mail app已經槓桿了(leveraged)新的APIs來提升Exchange的支援。
透過E-mail app,Exchange系統管理員可以強制密碼政策,
當中包括字母數字組合(alphanumeric)的密碼或者數字的PINs(橫跨裝置)。
系統管理員也可以遠端wipe(也就是回復出廠預設值)弄丟或者失竊的手持裝置。
Exchange使用者可以同步處理他們的email以及行事曆資料。

這份文件是為了要給那些在Android裝置上發展企業解決方案的開發者的。
它討論了由Device Administration API所提供的多樣化功能,
來提供對雇員的Android裝置更強的安全性。

Device Administration API Overview
下面是那些可能會用到Device Administration API的app種類。
1. Email clients。
2. 做remote wipe的安全app。
3. 裝置管理服務和app。

How does it work?
使用Device Administration API來寫入使用者安裝到他們的裝置的admin apps。
裝置的admin app強制執行所想要的政策(policies)。下面是它如何運作的模式:
1. 系統管理員寫一個device admin app用以強制遠端/本地裝置的安全性政策。
這些政策可以是在app裡被hard-coded的,或者app可以動態從第三方的server去取到。
2. app是被裝在使用者的裝置上的。Android現在並沒有一個自動化的安裝方案。
一個sysadmin可能散布app到使用者上的其中幾個方法如下:
a. Google Play。
b. 從另一個商店來打開安裝。
c. 其他方法來散布app,比如email或網站。
3. 系統提示使用者來啟動device admin app。如何及何時發生,
取決於app是如何被實作的。
4. 一旦使用者啟動了device admin app,他們就受其政策控管了。
執行這些政策在典型上授予了優勢,像是存取sensitive系統和資料。

如果使用者沒有啟動device admin app,它還是在裝置上但在不主動的模式。
使用者並不會遵循政策且任何app,相對來說也不會得到app的好處。
舉例來說,他們可能會沒辦法同步資料。
如果一個使用者沒能遵行policies(比如設定了一個違反指引的密碼),
這會有app來決定要怎麼去處理它。
儘管如此,典型的狀況的結果大概會令使用者無法同步資料。

如果裝置試著去連接一個需求不在Device Administration API裡支援的server,
連線就不會被允許。
Device Administration API現在並不允許部分的provisioning。
換句話說,如果一個裝置(比如一個legacy device)並不支援所有被敘述的policies,
那麼沒有方法可以令裝置做連結。

如果一個裝置包含了多個啟動的admin apps最嚴格的policy會被執行。
沒有任何方法去以特定的admin app來做為目標。

要解除安裝已存在在裝置上的device admin app的話,
使用者需要先以系統管理員權限來反註冊該app。

Policies
在企業的設定,狀況通常是,
員工的裝置必須依附到一組嚴格管制裝置使用的policies。
Device Administration API支援了下表所列的policies。
注意Device Administration API現在只支援password給螢幕鎖。

PolicyDescription
Password enabledRequires that devices ask for PIN or passwords.
Minimum password lengthSet the required number of characters for the password. For example, you can require PIN or passwords to have at least six characters.
Alphanumeric password requiredRequires that passwords have a combination of letters and numbers. They may include symbolic characters.
Complex password requiredRequires that passwords must contain at least a letter, a numerical digit, and a special symbol. Introduced in Android 3.0.
Minimum letters required in passwordThe minimum number of letters required in the password for all admins or a particular one. Introduced in Android 3.0.
Minimum lowercase letters required in passwordThe minimum number of lowercase letters required in the password for all admins or a particular one. Introduced in Android 3.0.
Minimum non-letter characters required in passwordThe minimum number of non-letter characters required in the password for all admins or a particular one. Introduced in Android 3.0.
Minimum numerical digits required in passwordThe minimum number of numerical digits required in the password for all admins or a particular one. Introduced in Android 3.0.
Minimum symbols required in passwordThe minimum number of symbols required in the password for all admins or a particular one. Introduced in Android 3.0.
Minimum uppercase letters required in passwordThe minimum number of uppercase letters required in the password for all admins or a particular one. Introduced in Android 3.0.
Password expiration timeoutWhen the password will expire, expressed as a delta in milliseconds from when a device admin sets the expiration timeout. Introduced in Android 3.0.
Password history restrictionThis policy prevents users from reusing the last n unique passwords. This policy is typically used in conjunction with setPasswordExpirationTimeout(), which forces users to update their passwords after a specified amount of time has elapsed. Introduced in Android 3.0.
Maximum failed password attemptsSpecifies how many times a user can enter the wrong password before the device wipes its data. The Device Administration API also allows administrators to remotely reset the device to factory defaults. This secures data in case the device is lost or stolen.
Maximum inactivity time lockSets the length of time since the user last touched the screen or pressed a button before the device locks the screen. When this happens, users need to enter their PIN or passwords again before they can use their devices and access data. The value can be between 1 and 60 minutes.
Require storage encryptionSpecifies that the storage area should be encrypted, if the device supports it. Introduced in Android 3.0.
Disable cameraSpecifies that the camera should be disabled. Note that this doesn't have to be a permanent disabling. The camera can be enabled/disabled dynamically based on context, time, and so on. Introduced in Android 4.0.

Other features:
除了支援上表列出的policies以外,
Device Administration API讓你做下面的事情:
1. 令使用者來設定新密碼。
2. 馬上鎖定裝置。
3. 清掉裝置的資料。(也就是回復裝置的原廠預設值)

Sample Application
這份文件裡使用的範例是基於在SDK samples裡的Device Administration API sample
要看下載安裝SDK samples的話,可以參照Getting the Samples

範例程式提供了device admin功能的demo。
它給使用者一個借來藉以打開device admin app。
一旦他們打開了app,他們可以使用使用者介面的按鈕來做下面的事情:
1. 設定密碼品質。
2. 指定使用者密碼的需求,比如說最小長度,必須含有最小數字的碼數。
3. 設定密碼。如果密碼並沒有符合特定的policies,
系統會回傳error。
4. 設定在裝置被wipe掉之前,容許多少次密碼嘗試失敗。
5. 設定密碼從現在起多久會過期。
6. 設定密碼歷史長度。(長度是指存在歷史紀錄的舊密碼數目)
這防止了使用者使用前n次用過的密碼。
7. 指定哪個儲存區域必須被加密。(如果裝置支援的話)
8. 設定最大的非主動的時間可以容許(其後裝置上鎖)
9. 讓裝置自動上鎖。
10. 刷掉裝置的資料。
11. 關掉相機的功能。




Developing a Device Administration Application
系統管理員可以使用Device Administration API來寫app,
用其來強制遠端/本地的裝置的安全政策執行。
這個段落總結了製作這樣子的app的步驟。

Creating the manifest:
要使用Device Administration API,
app的manifest必須包含下面的東西:
1. DeviceAdminReceiver的subclass,且包含下列:
   a. BIND_DEVICE_ADMIN的permission。
   b. 對於ACTION_DEVICE_ADMIN_ENABLED intent做回應的能力。
      (在manifest裡以intent filter來表達)
2. 在metadata裡使用,對於security policies的聲明。

下面是manifest使用範例的片段:

<activity android:name=".app.DeviceAdminSample"
            android:label="@string/activity_sample_device_admin">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.SAMPLE_CODE" />
    </intent-filter>
</activity>
<receiver android:name=".app.DeviceAdminSample$DeviceAdminSampleReceiver"
        android:label="@string/sample_device_admin"
        android:description="@string/sample_device_admin_description"
        android:permission="android.permission.BIND_DEVICE_ADMIN">
    <meta-data android:name="android.app.device_admin"
            android:resource="@xml/device_admin_sample" />
    <intent-filter>
        <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
    </intent-filter>
</receiver>

注意到:
1. 下面的attributes,
參考到給sample app在ApiDemos/res/values/strings.xml裡的string資源。
Application Resources可以看更多有關資源的部分。
   a. android:label="@string/activity_sample_device_admin"參考到
       user-readable的給activity的label。
   b. android:label="@string/sample_device_admin"參考到
       user-readable的給permission的label。
   c. android:description="@string/sample_device_admin_description"參考到
       user-readable描述(description)的permission。
       一個描述通常比較長且比起label有更多資訊。
2. android:permission="android.permission.BIND_DEVICE_ADMIN"
    是DeviceAdminReceiver的subclass必須要有的permissions,
    用來確保只有系統可以和receiver互動。(沒有app可以被允許此授權)
    這防止了其他app濫用你的device admin app。
3. android.app.action.DEVICE_ADMIN_ENABLED
    是DeviceAdminReceiver的subclass必須要處理,
    用來允許管理裝置的主要的action。
    這是當使用者開始device admin app時設定到receiver的。
    你的code基本上要在onEnabled()裡做處理。
    要被支援的話,receiver一定要要求BIND_DEVICE_ADMIN permission,
    使得其他apps不能濫用它。
4. 當一個使用者啟用device admin app時,這給了receiver許可權來執行actions,
    去向特定的系統廣播做回應。當適合的event發生時,
    app可以施予一個policy。
    舉例來說,如果使用者嘗試建立一個不符合policy的新密碼,
    app可以令使用者去選一個不同的密碼以符合要求。
5. android:resource="@xml/device_admin_sample"
    聲明了被在metadata裡使用的security policies。
    metadata提供了對裝置管理員有特別意義的附加資訊,
    它被從DeviceAdminInfo class裡解析出來。
    下面是device_admin_sample.xml的內容:

<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-policies>
    <limit-password />
    <watch-login />
    <reset-password />
    <force-lock />
    <wipe-data />
    <expire-password />
    <encrypted-storage />
    <disable-camera />
  </uses-policies>
</device-admin>

在設計你的device administration app的時候,
你不需要去包含所有的policies,
只需要做跟你的app有關的就可以了。

對於manifest file的更多討論,請見Android Developers Guide

Implementing the code
Device Administration API 包含了下面的classes:

DeviceAdminReceiver
基礎的class,用來實作一個device administration組件(component)。
這個class提供了對於被系統所傳送的raw intent actions的翻譯上的方便。
你的Device Administration app必須要include一個DeviceAdminReceiver的subclass。

DevicePolicyManager
一個用來管理在裝置上要被執行的policies的class。
大多數這個class的clients,
必需要已經發佈(published)一個使用者現在正啟用的DeviceAdminReceiver
DevicePolicyManager管理一個或多個DeviceAdminReceiver的實例的policies。

DeviceAdminInfo
這個class是用來指明一個device administrator組件的metadata。

這些classes提供了基礎給完整功能的device administration app。
剩下的段落會描述怎麼使用DeviceAdminReceiverDevicePolicyManager
兩個APIs來寫一個device admin app。

Subclassing DeviceAdminReceiver
要製作一個device admin app,你必須要開DeviceAdminReceiver的subclass。
DeviceAdminReceiver class是由一系列的callbacks所組成的,
它們在特定events發生時會被觸發。

在它的DeviceAdminReceiver subclass中,sample app簡單地顯示一個Toast提醒,
作為對於特定events的回應。
舉例來說:

public class DeviceAdminSample extends DeviceAdminReceiver {

    void showToast(Context context, String msg) {
        String status = context.getString(R.string.admin_receiver_status, msg);
        Toast.makeText(context, status, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onEnabled(Context context, Intent intent) {
        showToast(context, context.getString(R.string.admin_receiver_status_enabled));
    }

    @Override
    public CharSequence onDisableRequested(Context context, Intent intent) {
        return context.getString(R.string.admin_receiver_status_disable_warning);
    }

    @Override
    public void onDisabled(Context context, Intent intent) {
        showToast(context, context.getString(R.string.admin_receiver_status_disabled));
    }

    @Override
    public void onPasswordChanged(Context context, Intent intent) {
        showToast(context, context.getString(R.string.admin_receiver_status_pw_changed));
    }
...
}

Enabling the application
一個device admin app必須要處理的主要的events中,
其中之一是讓使用者啟用app。
使用者必須要明確地啟動app來讓policies被執行。
如果使用者選擇不去啟動app,
它仍舊會在裝置上被展示,但它的policies就不會被執行,
且使用者將不會獲得任何這個app所帶來的好處。

啟用app的程序會在使用者執行會觸發ACTION_ADD_DEVICE_ADMIN intent的action時開始。
在這個sample app哩,這會在使用者按下Enable Admin的checkbox時發生。

當使用者點下Enable Admin的checkbox時,
顯示螢幕會切換令使用者來選擇啟動device admin app,如下圖所示。


下面是那些當使用者點下Enable Admin的checkbox時,
所會執行的code。這個callback會在Preference的value被使用者改變,
且準備要被設定且/或堅持時,被喚起(invoked)。
如果使用者正在啟用app,
就會如上圖那樣切換顯示令使用者來啟用。
其他狀況的話,device admin app是被關掉的。

@Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            if (super.onPreferenceChange(preference, newValue)) {
                return true;
            }
            boolean value = (Boolean) newValue;
            if (preference == mEnableCheckbox) {
                if (value != mAdminActive) {
                    if (value) {
                        // Launch the activity to have the user enable our admin.
                        Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
                        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);
                        intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
                                mActivity.getString(R.string.add_admin_extra_app_text));
                        startActivityForResult(intent, REQUEST_CODE_ENABLE_ADMIN);
                        // return false - don't update checkbox until we're really active
                        return false;
                    } else {
                        mDPM.removeActiveAdmin(mDeviceAdminSample);
                        enableDeviceCapabilitiesArea(false);
                        mAdminActive = false;
                    }
                }
            } else if (preference == mDisableCameraCheckbox) {
                mDPM.setCameraDisabled(mDeviceAdminSample, value);
                ...
            }
            return true;
        }

intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample)這行,
敘述mDeviceAdminSample(是一個DeviceAdminReceiver組件)是目標的policy。
這行喚起了使用者的介面(如上面的圖所示),
用來指引使用者來新增device administrator到系統(或者允許它們去拒絕它)。

當app需要去執行一個operation,附隨在正在啟用狀態的device admin app,
它確認了app是啟動的(active)。為了要做到這點,
它使用了DevicePolicyManager的method isAdminActive()
注意的DevicePolicyManager的method isAdminActive()取用一個
DeviceAdminReceiver組件作為它的參數:

DevicePolicyManager mDPM;
...
private boolean isActiveAdmin() {
    return mDPM.isAdminActive(mDeviceAdminSample);
}

Managing policies
DevicePolicyManager是一個public class,管理著被執行在裝置上的policies。
DevicePolicyManager管理policies給一個或多個DeviceAdminReceiver 實例。
透過下面的程式碼取得對於DevicePolicyManager的掌控:

DevicePolicyManager mDPM =
    (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);

這個段落描述了如何使用DevicePolicyManager來進行管理員工作:
1. 設定密碼的policies
2. 設定裝置鎖
3. 執行資料wipe

Set password policies
DevicePolicyManager包含了APIs給設定和執行裝置密碼的policy。
在Device Administration API,密碼只會在螢幕鎖上面被使用。
這個段落描述了常見的密碼相關的工作。
Set a password for the device:
這個code顯示了一個使用者介面,令使用者設定一個密碼:

Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
startActivity(intent);
Set the password quality:
密碼品質可以是下面的的DevicePolicyManager的常數之一:
使用者必須輸入至少含有英文字母(或其他符號)的字元的密碼。
使用者必須輸入至少含有數字+英文(或其他符號)的字元的密碼。

使用者必須輸入至少含有數字的字元的密碼。
PASSWORD_QUALITY_COMPLEX
使用者必須輸入至少含有一個字母,一個數字,以及一個特殊符號的密碼。
這個policy需要某種類的密碼,但是不關心它到底是什麼。
PASSWORD_QUALITY_UNSPECIFIED
這個policy對於密碼沒有任何要求。

舉例來說,如果你要把密碼的policy設為需要字母+數字的密碼:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
...
mDPM.setPasswordQuality(mDeviceAdminSample, DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);

Set password content requirements:
從Android 3.0開始,DevicePolicyManager class包括了那些methods,
讓你可以微調密碼的內容,你可以設定一個policy,
讓其敘述密碼必須要含有至少n個大寫字母。
這裡是讓微調一個密碼的內容所使用的methods:


舉例來說,下面的片段描述了密碼至少要有兩個大寫字母:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
int pwMinUppercase = 2;
...
mDPM.setPasswordMinimumUpperCase(mDeviceAdminSample, pwMinUppercase);

Set the minimum password length:
你可以指定讓密碼最少要有指定的最小長度,
舉例來說:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
int pwLength;
...
mDPM.setPasswordMinimumLength(mDeviceAdminSample, pwLength);

Set maximum failed password attempts:
你可以設定在wipe掉裝置前,最大所允許失敗的密碼嘗試次數。
舉例來說:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
int maxFailedPw;
 ...
mDPM.setMaximumFailedPasswordsForWipe(mDeviceAdminSample, maxFailedPw);

Set password expiration timeout:
從Android 3.0起,
你可以使用setPasswordExpirationTimeout()的method,
來設定何時密碼會過期,
它是以一個毫秒的差值來表示的。
(從裝置管理設定了過期時間後)
舉例來說:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
long pwExpiration;
...
mDPM.setPasswordExpirationTimeout(mDeviceAdminSample, pwExpiration);

Restrict password based on history:
從Android 3.0開始,
你可以使用setPasswordHistoryLength()的method來限制使用者重新使用舊密碼。
這個method需要一個長度的參數,來指定有多少舊密碼被存起來。
當這個policy是啟用時,使用者不能輸入一個會跟前n個密碼相符的新密碼。
這防止了使用者重複使用同樣的密碼。
這個policy典型來說常和setPasswordExpirationTimeout()配合使用,
用來強迫使用者在經過指定的時間後要更新他們的密碼。

舉例來說,這個片段禁止使用者重新使用前5個用過的密碼:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
int pwHistoryLength = 5;
...
mDPM.setPasswordHistoryLength(mDeviceAdminSample, pwHistoryLength);

Set device lock:
你可以設定最大的使用者可以不主動的期間,
在那之後裝置就會鎖上。
舉例來說:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
...
long timeMs = 1000L*Long.parseLong(mTimeout.getText().toString());
mDPM.setMaximumTimeToLock(mDeviceAdminSample, timeMs);

你也可以程式化地告訴裝置現在立刻上鎖裝置:

DevicePolicyManager mDPM;
mDPM.lockNow();

Perform data wipe:
你可以使用DevicePolicyManagerwipeData() method,
來重設裝置到原廠預設值。
這在裝置不見或被偷時很有用。
通常清裝置的決定是因為碰到特定的狀況。
舉例來說,你可以使用setMaximumFailedPasswordsForWipe()來敘述
一個裝置應該在指定次數的錯誤密碼嘗試後,被清掉資料。

你可以如下進行清除資料:

DevicePolicyManager mDPM;
mDPM.wipeData(0);

wipeData()的method拿它的參數作為一個bit mask來應用在額外的狀況。
當前的值必須為0。

Disable camera:
從Android 4.0開始,你可以停用相機功能。
注意這並不需要是永久的停用。
相機可以基於context,時間,及其他東西,
來動態地開/關。

使用setCameraDisabled()的method來控管相機是否是關的。
舉例來說,這個片段將相機設定為開或關,
是基於一個checkbox的設定值:

private CheckBoxPreference mDisableCameraCheckbox;
DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
...
mDPM.setCameraDisabled(mDeviceAdminSample, mDisableCameraCheckbox.isChecked());

Storage encryption:
從Android 3.0開始,你可以使用setStorageEncryption()的method,
在來設定一個需求對儲存空間的加密的policy(當有支援的時候)。
舉例來說:

DevicePolicyManager mDPM;
ComponentName mDeviceAdminSample;
...
mDPM.setStorageEncryption(mDeviceAdminSample, true);

可以參見Device Administration API sample來看完整示範如何開啟空間加密的範例。

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)