Androidでの通話状態の補足と状態遷移（設計編）


#概要
Android OS の電話機能は OSカーネル および 標準電話アプリの管理下にあり、一般のアプリ（非システム系のアプリ）からは自由に制御できないようになっています。
ただ、通話状態（発信・着信）についてはブロードキャストレシーバを用いて一般アプリからも取得可能なため、それをトリガーとするツールアプリ（発着信の強制切断 や 電話番号にプレフィックス番号を付ける など）の作成は可能です。

本記事では、それらの作成に役立つ「通話状態の補足と状態遷移」に関する仕様を考察します。

#システムから取得可能なイベント
電話の発着信に伴うイベントは、TelephonyManagerから「インテントアクション（ブロードキャストアクション）」の形で取得することができます。

* android.intent.action.NEW_OUTGOING_CALL
* android.intent.action.PHONE_STATE

#### ■ android.intent.action.NEW_OUTGOING_CALL
電話の発信を検知したときに発生するブロードキャストイベントです。
発信する電話番号を取得してその内容を自由に変更する事ができますが、誤った値を格納すると発信時の不具合（電話できない、誤った番号に発信する など）を引き起こしてしまうので、取り扱い方には注意が必要です。

#### ■ android.intent.action.PHONE_STATE
いわゆる「電話の状態」が変更されたときに発生するブロードキャストイベントです。
ここで言う「電話の状態」とは次の３種類を指します。

　1. Idle
　2. Ringing
　3. OffHook

#####【Idle】
電話の発信・着信が行われていない「無動作（アイドル）」状態であることを示しています。　　　
#####【Ringing】
電話の着信を検出し「呼び出し音 鳴動中」であることを示しています。
電話発信後の「接続相手電話機の呼び出し」のことではありません。
#####【OffHook】
受話器を持ち上げて「発信 または 着信を開始」したことを示しています。

これらはいずれも「特定のタイミングにおける自電話機の状態」を示したものです。
一つの状態だけを判定しても「着信の呼び出しがあったが、取らずに即切断した」などの『呼制御の挙動』を知ることはできません。

#電話機の状態を考える
「電話機の状態」を挙げるとすれば次の３種です。

 * 無動作（アイドル）
 * 発信呼び出し中 または 着信呼び出し中
 * 通話中

これらは電話の基本的な動作を網羅しており、各状態を正しく把握することは「電話機能を利用するアプリ」を考える上でとても重要です。
発信動作と着信動作を分けて扱うものとし、もう少しだけこれを細分化してみます。

 * 無動作（アイドル）
 * 発呼（発信）操作開始
 * 発呼（発信）・送話中
 * 着呼（着信）中
 * 受話中

着呼時は「受話状態」が独立して存在するのに対し、発呼時は「発呼・送話中」という二つの状況が混同しています。
両状態を正確に切り分けるには「相手先電話機が着信を受け付けた（通話を開始した）」ことを判断する必要がありますが、その通知機能がアプリに対して公開されていないためです。

もっとも、完全な電話制御機能を有するアプリを一般の人が作成するのは難しく、電話状態を把握するだけであれば上記の形で十分です。
ここでは上記５種を『電話機の状態』として扱う事にします。

>ソースファイルを見ると分かりますが、Android OS自身はもちろん上記の状態を正確に把握しています。
電話機能を公開しすぎるとセキュリティ上の脆弱性を招くため、あえて隠蔽しているのかもしれません。
（単に私が公開機能を見落としているだけかもしれませんが）

#状態遷移のトリガー（イベント）を考える
システムから取得可能なイベントは次の４種です。

 * NEW_OUTGOING_CALL
 * PHONE_STATE(Idle)
 * PHONE_STATE(Ringing)
 * PHONE_STATE(OffHook)

発着呼を区別して「電話の状態」を遷移させるには明らかにイベントが不足しています。
ここに挙げた個々のイベントだけでは発呼と着呼を切り分けることすら困難です。
一連の状態の移り変わりを判断するには、「どの状態で何のイベントが発生したのか」を正しく認識する必要があります。

## 発呼（発信）時のイベントの流れ
電話の発信操作を列挙すると次の順番になります。

　 1. 電話の受話器を持ち上げ、相手先の電話番号をダイアルする。
　 2. 相手先の電話機を呼び出す。（呼び出し音の鳴動）
　 3. 相手が電話の受話器を持ち上げて回線が接続される。
　 4. 通話を開始する。
　 5. 受話器を置き、回線を切断する。

これをAndroid OSから取得可能なイベントに置き換えるとこうなります。

　 1. NEW_OUTGOING_CALL
　 2. PHONE_STATE(OffHook)
　 3. --- （該当なし）
　 4. --- （該当なし）
　 5. PHONE_STATE(Idle)

「3.」と「4.」に該当するイベントはありません。
理由は前項に記述したとおりなので、ここでは不問とします。

## 着呼（着信）時のイベントの流れ
電話の着信操作を列挙すると次の順番になります。

  1. 相手先電話機からの着呼要求を受け、呼び出し音が鳴動。
  2. 電話の受話器を持ち上げ、回線を接続する。
  3. 通話を開始する。
  4. 受話器を置き、回線を切断する。

これをAndroid OSから取得可能なイベントに置き換えるとこうなります。

  1. PHONE_STATE(Ringing)
  2. PHONE_STATE(OffHook)
  3. --- （該当なし）
  4. PHONE_STATE(Idle)

## 発呼（発信）と着呼（着信）の識別方法
Android OSから取得可能なイベントに置き換えた流れを比較すれば、最初のイベントによって簡単に判別できることがわかります。

 * 最初のイベントが「NEW_OUTGOING_CALL」のときは発呼（発信）。
 * 最初のイベントが「PHONE_STATE(Ringing)」のときは着呼（着信）。

２番目以降の後続イベントは発呼（発信）または着呼（着信）専用の流れなので、たとえイベントの発生順番が同じでも両者を混同しないでください。

#呼制御の状態遷移表
「電話機の状態」と「状態遷移のイベント」が明確になったので、状態遷移を判別する条件及び処理内容を列挙してみます。

 1. アイドル状態でNEW_OUTGOING_CALLイベントが発生したら「発呼（発信）開始」と見なし、発呼操作開始状態へ移行する。
 2. 発呼操作開始状態でPHONE_STATE(OffHook)イベント発生したら「発呼・送話の開始」と見なし、発呼・送話中状態へ移行する。
 3. 発呼・送話中状態でPHONE_STATE(Idle)イベントが発生したら「送話終了」と見なし、アイドル状態へ移行する。
 4. アイドル状態でPHONE_STATE(Ringing)イベントが発生したら「着呼開始」と見なし、着呼中状態へ移行する。
 5. 着呼中状態でPHONE_STATE(OffHook)イベントが発生したら「受話中」と見なし、受話中状態へ移行する。
 6. 受話中状態でPHONE_STATE(Idle)イベントが発生したら「受話終了」と見なし、アイドル状態へ移行する。

一見すると整理された詳細仕様であり、単なる箇条書き もしくは UMLのアクティビティ図として詳細設計書に記載されることもあるでしょう。
しかしそのような仕様書を見かけたら、「仕様」書として重大な欠陥を抱えている可能性を危惧してください。

上記は「正常処理の流れ」だけを示したに過ぎず、「異常処理に陥るタイミング」や「その後のリカバリ動作」については何一つ明確になっていません。
様々なケースを事前に洗い出して検討するには、「状態遷移表」を作成してみるのが一番効果的です。

## 状態遷移表
現時点で判明している「正常処理の流れ」を「状態」と「イベント」で直行する状態遷移表で表してみます。

|　|アイドル<br/>（状態A）|発呼操作開始<br/>（状態B）|発呼・送話中<br/>（状態C）|着呼中<br/>（状態D）|受話中<br/>（状態E）|
|:-----|:-----|:-----|:-----|:-----|:-----|
|NEW_OUTGOING_CALL<br/>（イベント1）|[発呼操作開始]<br/>→ 状態Bへ| | | | |
|PHONE_STATE(OffHook)<br/>（イベント2）| |[発呼・送話開始]<br/>→ 状態Cへ| |[受話]<br/>→ 状態Eへ| |
|PHONE_STATE(Ringing)<br/>（イベント3）|[着呼開始]<br/>→ 状態Dへ| | | | |
|PHONE_STATE(Idle)<br/>（イベント4）|<center>---</center>| |[送話終了]<br/>→ 状態Aへ| |[受話終了]<br/>→ 状態Aへ|
　※ 状態A-イベント4 セルは状態が遷移しないので「無処理」の扱いです。
　　　
記入済みのセルについては特に説明する必要は無いと思います。
問題は大量に残された未記入のセルで、これが全て想定外の事象が発生し得るタイミングになります。

>箇条書きやUML図だけでこれだけのタイミングを全て網羅しきった仕様書は、メーカー系列の仕事を通じてこの３０年間で一度もお目にかかったことがありません。
逆にこのような状態遷移表は幾度も目にしています。
（最近は「動けば良いや」が蔓延して見かけることも少なくなりましたが）

## 着呼（着信）に応じず即回線切断するケース
スマートフォンの着信画面を見ると呼び出しに応じず回線を即切断するためのUIがあり、電話機能の一つとしてごく普通に提供されています。
前述の状態遷移表でも「未記入セル」の形で提示されており、「状態D-イベント4」がそれに該当します。
つまり「仕様漏れ」だったという訳です。

一般的に「仕様漏れ」と呼ばれるミスは事象の想定不足が原因である事が多々あります。
状態遷移表を用いると「未検討のタイミング」が明確化されるので、そのようなミスを防ぐ事ができます。
積極的に利用した方が良いと思います。

下表は改訂後の状態遷移表です。

|　|アイドル<br/>（状態A）|発呼操作開始<br/>（状態B）|発呼・送話中<br/>（状態C）|着呼中<br/>（状態D）|受話中<br/>（状態E）|
|:-----|:-----|:-----|:-----|:-----|:-----|
|NEW_OUTGOING_CALL<br/>（イベント1）|[発呼操作開始]<br/>→ 状態Bへ| | | | |
|PHONE_STATE(OffHook)<br/>（イベント2）| |[発呼・送話開始]<br/>→ 状態Cへ| |[受話]<br/>→ 状態Eへ| |
|PHONE_STATE(Ringing)<br/>（イベント3）|[着呼開始]<br/>→ 状態Dへ| | | | |
|PHONE_STATE(Idle)<br/>（イベント4）|<center>---</center>| |[送話終了]<br/>→ 状態Aへ|[着呼取消]<br/>→ 状態Aへ|[受話終了]<br/>→ 状態Aへ|
　※ 状態A-イベント4 セルは状態が遷移しないので「無処理」の扱いです。