SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

Xamarin.Android SMS(Cメール)受信

【 Xamarin 記事一覧 】

1 SMSメッセージの受信

SMSの受信時には Android が SMS_RECEIVED (android.provider.Telephony.SMS_RECEIVED) というブロードキャストを送信しています。
SMSを受信したい場合は、このブロードキャストを受け取るレシーバを実装します。

(1) パーミッション

SMS_RECEIVEDを受信するにはRECEIVE_SMSのパーミッション、SMSを読み取るにはREAD_SMSのパーミッションが必要です。
パーミッションを忘れても、受信しないだけでエラー(例外)が発生するわけではないので、うっかりすると嵌るかも知れません。


(2) 受信処理


受信データは、SMS_RECEIVEDインテントのExtra(キー名:"pdus")に格納されており、1通ごとのメッセージは、Java.Lang.Object形式で取得できます。
メッセージ単体はPDU(protocol description unit)形式のbyte
文字列ですのでSmsMessageクラスのCreateFromPdu()で、byte[]形式のPDUデータをSmsMessageインスタンスへデコードして取得します。
実装したブロードキャストレシーバでSMS受信処理をしても、最初からインストールされているSMSメッセンジャーにもメールは到着します。

[BroadcastReceiver]
[IntentFilter(new string[] { "android.provider.Telephony.SMS_RECEIVED" })]
public class SmsReceiver :BroadcastReceiver {
    public override void OnReceive(Context context, Intent intent) {
        var bundle = intent.Extras;
        if (bundle != null) {
            var pdus = (Java.Lang.Object[])bundle.Get("pdus");
            var msgs = new SmsMessage[pdus.Length];
            for (var i = 0; i < msgs.Length; i++){
                msgs[i] = SmsMessage.CreateFromPdu((byte[]) pdus[i]);
                Toast.MakeText(context,string.Format("From:{0}\r\nData:{1}", msgs[i].OriginatingAddress, msgs[i].MessageBody),ToastLength.Short).Show();
            }
        } 
    }
}

なお、ブロードキャストレシーバは、属性指定の他、RegisterReceiver()でも設置可能です。

var intentFilter = new IntentFilter();
intentFilter.AddAction("android.provider.Telephony.SMS_RECEIVED");
RegisterReceiver(new SmsReceiver(), intentFilter);

2 優先受信

特定のプログラムでこのSMSの機能を利用すれば、簡単にデータの送受が可能です。
しかし、使用したメッセージがそのまま既存のSMSメッセンジャーの受信ボックスに残るのは避けたいはずです。
そこで、SMS_RECEIVEDのブロードキャストを高い優先度で受信し、特定のメッセージを処理して、そのブロードキャストを止める処理を実装します。

(1) 優先受信

ブロードキャスト受信の優先度は、マニュフェストのintent-filterに記述します。
高い優先度の属性指定は次のとおりです。

[BroadcastReceiver]
    [IntentFilter(new string[] { "android.provider.Telephony.SMS_RECEIVED" }, Priority = (int)IntentFilterPriority.HighPriority)]
    class SmsReceiver :BroadcastReceiver {

上記の結果、マージされたマニュフェストには、「android:priority="1000"」が追加されています。

<receiver android:name="androidapplication1.SmsReceiver">
      <intent-filter android:priority="1000">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
      </intent-filter>
    </receiver>

リファレンスによれば、優先度は -1000 から 1000 の間で設定され、高い数字の方が高い優先度となっています。

(1) ブロードキャストの停止

送信元が090-1234-5678のメッセ−ジだけを表示して、ブロードキャストを停止する処理です。
これによって、当該メールはSMSメッセンジャーの受信ボックスには届かなくなります。
OnReceiveに入った時点で、取りあえずブロードキャストを停止し、ターゲットのメールでなかった場合に再開しています。

[BroadcastReceiver]
[IntentFilter(new string[] { "android.provider.Telephony.SMS_RECEIVED" }, Priority = (int)IntentFilterPriority.HighPriority)]
class SmsReceiver :BroadcastReceiver {
    public override void OnReceive(Context context, Intent intent) {
        //取りあえずブロードキャストを止める
        InvokeAbortBroadcast();
        var bundle = intent.Extras;
        if (bundle != null) {
            var pdus = (Java.Lang.Object[])bundle.Get("pdus");
            var msgs = new SmsMessage[pdus.Length];
            for (var i = 0; i < msgs.Length; i++){
                msgs[i] = SmsMessage.CreateFromPdu((byte[]) pdus[i]);
                //送信元が090-1234-5678のメールだけが対象となる
                if (msgs[i].OriginatingAddress == "09012345678") {
                    //メッセージを表示
                    Toast.MakeText(context,
                        string.Format("From:{0}\r\nData:{1}", msgs[i].OriginatingAddress, msgs[i].MessageBody),
                        ToastLength.Short).Show();
                } else {
                    //該当メールでない場合は、ブロードキャストの停止をキャンセルする
                    ClearAbortBroadcast();
                }
            }
        }
    }
}

送信元が090-1234-5678のメールは、表示されてSMSメッセンジャでは受信されていない



送信元が090-1234-5678以外のメールは、表示されずにSMSメッセンジャで受信される


3 エミュレータへのSMS送信

デバッグは、telnetでメッセージをエミュレータに送信して行いました。

C:\>telnet localhost 5554

Android Console: type 'help' for a list of commands
OK
sms send 09012345678 TEST
OK
exit

ホストとの接続が切断されました。
C:\>

【 Xamarin 記事一覧 】