電話着信への応答と拒否


#概要
電話機能の不正利用を防ぐためか、Androidでは一般のアプリから発着信制御を簡単に実施できないようになっています。
この制約はOSのバージョンアップを経るごとに少しずつ強まっており、完全に使えなくなる日がいつか訪れるでしょう。

ただ、現時点ではまだ回避策が残されています。
本記事ではその回避策を利用して「電話着信への応答及び拒否」を実現するプログラムを提示します。

開発プラットフォームは「Xamarin for Android」です。

★注意★
ここに掲載したプログラムコードは全てサンプルです。
そのまま利用した場合は誤動作が発生する恐れもありますのでご注意願います。

#電話着信への応答
電話機能へは TelephonyManagerクラスを介してアクセスしますが、着信操作機能は当然ながら非公開で、一般のアプリから直接それを呼び出すことはできません。
調べてみると「ヘッドセットの着信応答操作」をシミュレートするという回避策が見つかりました。

```C#
/// <summary>
/// 電話の着信に応答します。（受話します）
/// </summary>
/// <param name="ctx">コンテキスト。</param>
public void InComingAccept(Context ctx)
{
    // 【注意】
    // Headsethook の KeyDown -> KeyUp を連続して行うと、
    // Xperia Z3 Compact ではミュージックアプリが起動してしまった。
    // 電話着信への応答を行うサンプルコードの中には、DownとUpを
    // 実施しているものもある。
    //
    //    Intent btnDown = new Intent(Intent.ActionMediaButton);
    //    btnDown.PutExtra(Intent.ExtraKeyEvent, 
    //        new KeyEvent(KeyEventActions.Down, Keycode.Headsethook));
    //
    //    ctx.SendOrderedBroadcast(btnDown, "android.permission.CALL_PRIVILEGED");

    // Headsethook KeyUp をシュミレートして、電話着信への応答を行う。
    // （機種によっては機能しない可能性がある）
    Intent btnUp = new Intent(Intent.ActionMediaButton);
    btnUp.PutExtra(Intent.ExtraKeyEvent, 
        new KeyEvent(KeyEventActions.Up, Keycode.Headsethook));

    ctx.SendOrderedBroadcast(btnUp, "android.permission.CALL_PRIVILEGED");
}
```

上のサンプルでは、『ヘッドセットの（着信）ボタンを押す』という「操作」を実施しています。

しかしヘッドセットのボタン操作を自由にカスタマイズできるスマートフォンもあり、その場合は上記のプログラムが誤動作する恐れもあります。
機種依存の代替措置として捉えておいた方が良さそうです。


#電話着信の拒否
TelephonyManagerクラスの非公開API「endCall」メソッドを呼び出せば着信を切断することができます。
機種依存の話はあまり目にしませんが、非公開APIなので今後出荷される新しい Android OS では使えなくなる可能性があります。

```Java
TelephonyManager manager = 
    (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
Class c = Class.forName(manager.getClass().getName());
Method m = c.getDeclaredMethod("getITelephony");
m.setAccessible(true);
ITelephony telephony = (ITelephony)m.invoke(manager);
telephony.endCall();
```

上記は endCallメソッド呼び出しのよく見かけるサンプルです。
リフレクション機能で特定した TelephonyManagerクラスのendCallメソッド を呼び出しています。

Xamarin for Android からも同じことを実施できれば良いのですが、Xamarin側の TelephonyManagerクラスに非公開APIは実装されていないので「.NETでのリフレクション機能」を使っても endCallメソッドを呼び出すことはできません。

そこで、「JNI (Java Native Interface)」を介してJava側の TelephonyManagerクラスへ直接リフレクションを実施します。

```C#
/// <summary>
/// 電話の着信を拒否します。（切断します）
/// </summary>
/// <param name="ctx">コンテキスト。</param>
public void InComingReject(Context ctx)
{
    TelephonyManager manager = 
        (TelephonyManager)(ctx.GetSystemService(Context.TelephonyService));

    // TelephonyManager.getITelephony() のメソッドIDを取得。
    IntPtr getITelephonyId = JNIEnv.GetMethodID(
        manager.Class.Handle, 
        "getITelephony", 
        "()Lcom/android/internal/telephony/ITelephony;");

    // TelephonyManager.getITelephony() を呼び出して ITelephony インタフェースを取得し、
    // そのインタフェースを持つクラスオブジェクトを特定する。
    //   注）これらのオブジェクトへの「参照」は最後に解放すること。
    IntPtr objITelephony = JNIEnv.CallObjectMethod(manager.Handle, getITelephonyId);
    IntPtr objITelephonyClass = JNIEnv.GetObjectClass(objITelephony);

    // ITelephonyを実現しているクラス.endCall() のメソッドIDを取得し、
    // ITelephony.endCall() メソッドを呼び出す。
    IntPtr endCallId = JNIEnv.GetMethodID(objITelephonyClass, "endCall", "()Z");
    JNIEnv.CallBooleanMethod(objITelephony, endCallId);

    // 不要になった「参照」を解放。
    JNIEnv.DeleteLocalRef(objITelephony);
    JNIEnv.DeleteLocalRef(objITelephonyClass);
}
```

JNIEnv経由なので煩雑なコードになっていますが、その内容は Javaのサンプルコードと変わりません。
ただし次の点には注意が必要です。

 * インタフェースやクラスといったオブジェクトへの「参照」は、使用後に必ず「解放」しなければならない。<br/>（JNI経由で「参照」できるオブジェクトの個数には限りがあります）
 * IntPtr型を使用しているが、メソッドやプロパティのID値は「オブジェクトの参照」ではないので「解放」の必要はない。

#参考サイト
https://forums.xamarin.com/discussion/comment/155055