また、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パーティアプリが緊急呼に関わることはほとんどないため、仕様に関する知識はそ
れほど役に立たないかもしれませんが、「あらゆる手段を使って発信を試みる」のロジッ
クは一見の価値があります。
以上です。