また、3rdパーティアプリが音声発信シーケンスに介入する余地も残されています。
今回は音声発信シーケンスを見ていきます。
音声発信時の大まかな処理は下記の流れになります。
android.intent.action.CALLが音声発信を開始する直接のIntentではないことがわかります。
音声発信には通常呼以外にSIP, OTAやvoicemail、緊急呼といった様々な種類があります。
Androidは3rdパーティに緊急呼発信を許可していませんが、通常呼は許可しています。
これらの仕様を満たすために音声発信シーケンスはやや複雑化しています。
今回は音声発信シーケンスの中でも"通常呼発信"をメインに扱います。
次回は"緊急呼発信"をメインに扱います。
いずれもIntent,BroadcastIntentやHandlerを巧みに利用したテクニックです。
# ソースコードの抜粋は重要箇所のみ抜き出しています
# コードはver.4.0.3ベースです。
●音声発信の開始
ダイヤル系アプリがaction.CALLで音声発信するとOutgoingCallBroadcasterが反応します。
その名の通り音声発信ブロードキャストを送信するための透明なActivityです。
OutgoingCallBroadcasterにはアクティビティ別名が存在します。
下記定義となります(一部省略)。
・com.android.phone AndroidManifest.xml
<activity android:name="OutgoingCallBroadcaster" android:permission="android.permission.CALL_PHONE" android:theme="@android:style/Theme.NoDisplay"> <!-- CALL action intent filters, for the various ways of initiating an outgoing call. --> <intent-filter> <action android:name="android.intent.action.CALL" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="tel" /> </intent-filter> </activity> <activity-alias android:name="EmergencyOutgoingCallBroadcaster" android:targetActivity="OutgoingCallBroadcaster" android:permission="android.permission.CALL_PRIVILEGED" android:theme="@android:style/Theme.NoDisplay"> <intent-filter> <action android:name="android.intent.action.CALL_EMERGENCY" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="tel" /> </intent-filter> </activity-alias> <activity-alias android:name="PrivilegedOutgoingCallBroadcaster" android:targetActivity="OutgoingCallBroadcaster" android:permission="android.permission.CALL_PRIVILEGED"> <intent-filter> <action android:name="android.intent.action.CALL_PRIVILEGED" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="tel" /> </intent-filter> </activity-alias>
それぞれのActivityは
- OutgoingCallBroadcaster : 通常呼
- EmergencyOutgoingCallBroadcaster : 緊急呼
- PrivilegedOutgoingCallBroadcaster : 上記2つのハイブリッド
以外気にする必要はないでしょう。
OutgoingCallBroadcasterの処理を追っていきます。
・com.android.phone OutgoingCallBroadcaster.java
protected void onCreate(Bundle icicle) {
// Intentから電話番号取得
String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
// SIPや緊急呼の判定を経てのBroadcastIntent作成処理
Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
if (number != null) {
broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
}
PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);
broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);
broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString());
if (DBG) Log.v(TAG, "Broadcasting intent: " + broadcastIntent + ".");
sendOrderedBroadcast(broadcastIntent, PERMISSION, new OutgoingCallReceiver(),
null, // scheduler
Activity.RESULT_OK, // initialCode
number, // initialData: initial value for the result data
null); // initialExtras
}
送信されるブロードキャストはandroid.permission.PROCESS_OUTGOING_CALLSパーミッシ
ョン付きです。
これにより、ブロードキャスト受信側はパーミッションを宣言する必要があります。
resultDataには電話番号が格納されています。
ブロードキャスト種別はオーダーで、全てのレシーバが電話番号を処理した結果を
OutgoingCallReceiverが受け取る仕組みです。
ここで、ACTION_NEW_OUTGOING_CALLのブロードキャストIntentを投げることで、
3rdパーティアプリが音声発信をフックすることを可能にしています。
ブロードキャストの結果はOutgoingCallReceiverに届きます。
OutgoingCallReceiverの処理を追っていきます。
・com.android.phone OutgoingCallBroadcaster$OutgoingCallReceiver.java
public void doReceive(Context context, Intent intent) {
// Once the NEW_OUTGOING_CALL broadcast is finished, the resultData
// is used as the actual number to call. (If null, no call will be
// placed.)
number = getResultData();
if (number == null) {
if (DBG) Log.v(TAG, "CALL cancelled (null number), returning...");
return;
}
startSipCallOptionHandler(context, intent, uri, number);
}
ブロードキャストの結果を受信すると、ユーザが電話番号を編集している可能性がある
ため、緊急呼判定が再度行われます。
通常呼と判断されればSipCallOptionHandlerを呼び出し、処理を継続します。
・com.android.phone OutgoingCallBroadcaster.java
private void startSipCallOptionHandler(Context context, Intent intent,
Uri uri, String number) {
Intent selectPhoneIntent = new Intent(ACTION_SIP_SELECT_PHONE, uri);
selectPhoneIntent.setClass(context, SipCallOptionHandler.class);
selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent);
selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (DBG) Log.v(TAG, "startSipCallOptionHandler(): " +
"calling startActivity: " + selectPhoneIntent);
context.startActivity(selectPhoneIntent);
// ...and see SipCallOptionHandler.onCreate() for the next step of the sequence.
}
SipCallOptionHandlerはSIP通話の判定処理を行いますが、通常呼として処理すべき場合
は、そのまま通常呼処理を継続します。
#Android2.3以降よりSIP通話が標準サポートされました。
・com.android.phone SipCallOptionHandler.java
public void onCreate(Bundle savedInstanceState) {
// SIP通話判定を経て...
setResultAndFinish();
}
private void setResultAndFinish() {
runOnUiThread(new Runnable() {
public void run() {
if (mUseSipPhone && mOutgoingSipProfile == null) {
// SIP通話時処理
return;
} else {
// Woo hoo -- it's finally OK to initiate the outgoing call!
PhoneApp.getInstance().callController.placeCall(mIntent);
}
finish();
}
}
}
placeCallまでたどり着きました。これでようやく音声発信処理が開始されます。
(かなりのコードを省略しました。"Woo hoo"のコメントをみても発信処理チェックの
長いことがわかります...)
次に、placeCallの中を追っていきます。
・com.android.phone CallController.java
public void placeCall(Intent intent) {
// 様々な状態チェックや前準備を経て、RILへ発信要求
CallStatusCode status = placeCallInternal(intent);
// 音声発信画面を表示
mApp.displayCallScreen();
}
・com.android.phone PhoneApp.java
void displayCallScreen() {
startActivity(createInCallIntent());
}
/* package */ static Intent createInCallIntent() {
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
intent.setClassName("com.android.phone", getCallScreenClassName());
return intent;
}
placeCallInternal(intent)はPhoneUtils.placeCall()を呼び出し、最終的にRILへ発信
要求を送信します。
発信要求を送信した後は、音声発信中であることをユーザへ伝えるためInCallScreen
アクティビティ(getCallScreenClassNameの戻り値)を起動してUIの表示に至ります。
3rdパーティアプリが音声発信シーケンスで関与するのは
android.intent.action.CALLとandroid.intent.action.NEW_OUTGOING_CALLでしょう。
繰り返しとなりますが、
3rdパーティアプリは緊急呼を発信することができませんが、
NEW_OUTGOING_CALLのブロードキャストインテントを受信することで、音声発信シーケンス
に介入することができます。
音声発信シーケンスを解析すると、
- ブロードキャストによる他アプリ連携と拡張性の確保。
- Intentによるモジュール間の連携
コードを見るとわかりますが、3rdパーティアプリがNEW_OUTGOING_CALLを受信する場合は
細心の注意が必要です。
間違ってResultDataに意図しない値(nullや全く異なる電話番号)を代入してしまうと、
全く音声発信できない、あるいは予期しない電話番号に発信されてしまうことになります。
# Broadcastの性質上、ユーザはどのアプリが悪さをしているのか判断するのが困難です
OutgoingCallBroadcasterには、音声発信に関する様々な仕様がコードコメントとして記載
されています。
気になる方には一見の価値があります。
次回は、今回省略した"緊急呼"についてです。
緊急呼は極めて特殊な存在です。
この番号が発信されないという事態は、生命の危険に関わることなので絶対に避ける必要
があります。
そのため、緊急呼発信シーケンスは通常呼発信シーケンスとは異なるルートを通り、
あらゆる手段を使って発信を試みるように作られています。
3rdパーティアプリが緊急呼に関わることはほとんどないため、仕様に関する知識はそ
れほど役に立たないかもしれませんが、「あらゆる手段を使って発信を試みる」のロジッ
クは一見の価値があります。
以上です。
