SMS Retriever API を使ってSMSの内容を取得する

# はじめに
ある日、GooglePlayConsole上にこんな警告が出ていました。
![caution](https://qiita-image-store.s3.amazonaws.com/0/93899/1387fdbf-ca5f-d08a-cca9-4c486cc45db5.png)

このアプリには、`android.permission.RECEIVE_SMS`の権限を付与していました。
`android.permission.RECEIVE_SMS`の権限を使うなら、その権限をなんの目的で使うのかを明確に申請してくれというのが今回の警告の内容です。

このアプリでの`android.permission.RECEIVE_SMS`の用途は、アカウントの認証時、SMS認証をしており、認証コードを自動入力させるために、受信したSMSの内容を取得するためでした。
この用途で`android.permission.RECEIVE_SMS`を使ってしまうと、自分のアプリに必要なSMS以外のすべてのSMSをアプリから読むことができてしまいます。それはセキュリティー的にいかがなことなのか:thinking:ということで、Googleにお叱りをうけました。
そして、代替手段として、 **SMS Retriever API**を使ってねと言われました。

# SMS Retriever API とは
`android.permission.RECEIVE_SMS`を使わずに、自分のアプリに必要なSMSを受信できる仕組みが、**SMS Retriever API**です。
![ｓｍｓ](https://developers.google.com/identity/sms-retriever/flow-overview.png)
https://developers.google.com/identity/sms-retriever/overview

サーバーから送られてくるSMSのメッセージに、プレフィックスと、自分のアプリであることを識別するハッシュを記載します。
https://developers.google.com/identity/sms-retriever/verify

アプリは、SMS Retriever APIをつかって、自分のアプリのハッシュが含まれているSMSの内容だけ取得できます。
https://developers.google.com/identity/sms-retriever/request


今回は上の画像の③⑤の部分の実装について書いています。

# 事前準備
app配下の`build.gradle`に `com.google.android.gms:play-services-auth`を追加します。
バージョンは[ここ](https://developers.google.com/android/guides/setup)を参考にしてください。

```gradle
dependencies {
    implementation "com.google.android.gms:play-services-auth:16.0.1"
}
```

# 実装方法
ほとんど[リファレンス](https://developers.google.com/identity/sms-retriever/request)通りなのですが、サンプルを載せます

```MainActivity.kt
class MainActivity : AppCompatActivity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        createSmsRetrieverClient()

        //Hashの作成
//        val helper = AppSignatureHelper(applicationContext)
//        Log.d("hash = ", helper.appSignatures.toString())//このハッシュをSMSに含める
    }

    private fun createSmsRetrieverClient() {
        val receiver = SMSBroadcastReceiver()
        val intentFilter = IntentFilter()
        intentFilter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION)
        registerReceiver(receiver, intentFilter)

        val client = SmsRetriever.getClient(this)
        val task = client.startSmsRetriever()

        task.addOnSuccessListener {
            //SMSRetriever追加成功時リスナー
            Log.d("Listener", "SUCCESS")
        }

        task.addOnFailureListener {
            //SMSRetriever追加失敗時リスナー
            Log.d("Listener", "FAILURE")
        }
    }

    inner class SMSBroadcastReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
                val extras = intent.extras
                val status = extras!!.get(SmsRetriever.EXTRA_STATUS) as Status
                var message = ""

                when (status.statusCode) {
                    CommonStatusCodes.SUCCESS -> {
                        //自分のアプリ用のハッシュ値が含まれているSMSを受信したときにここに入ってくる
                        //対象のSMSの内容のみ取得可能
                        message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String
                        Log.d("SMSMessage = ", message)
                    }

                    CommonStatusCodes.TIMEOUT -> {
                        //レシーバーを開始して5分間SMSを受信しないとタイムアウトになる
                        Log.d("SMSMessage = ", "TIMEOUT")
                    }
                }
            }
        }
    }

    override fun onConnectionFailed(p0: ConnectionResult) {
    }

    override fun onConnectionSuspended(p0: Int) {
    }

    override fun onConnected(p0: Bundle?) {
    }
}
```

SMSを受信したいタイミングでSmsRetrieverClientのインスタンスを作成し、SMSを受信する準備をします。
サーバーから、

```
<#> あなたの認証コードは: 123456　です
ハッシュ
```
というような内容のSMSが送られてきて、ハッシュが自分のアプリのハッシュと一致していた場合、

```kotlin
extras.get(SmsRetriever.EXTRA_SMS_MESSAGE)
```

で、SMSの内容を取得することができます。

ハッシュは、アプリのパッケージ名、証明書の情報で生成されます。ハッシュ作成用の[サンプルコード](https://github.com/googlesamples/android-credentials/blob/master/sms-verification/android/app/src/main/java/com/google/samples/smartlock/sms_verify/AppSignatureHelper.java) が提供されているので、これで自分の環境にあったハッシュを生成してください。

余談？ですが、[リファレンス](https://developers.google.com/identity/sms-retriever/verify)に書いてあるコマンドでは正しいハッシュを作れませんでした…(これで数時間ハマった…)コマンドに当てはめる項目を間違えたのだと思いますが、何を間違えたのか判明したら追記します。。。

##受信確認
サーバーからSMSを送ってもらうのが実際の使用用途かと思いますが、確認だけなら、SMSBroadcastReceiverが準備されている検証端末に、他の端末から`<#>`のプレフィックスをつけ、生成したハッシュを含んだSMSを送っても受信確認ができます:ok_woman:


#まとめ
上記の通りに実装すれば、`android.permission.RECEIVE_SMS`の権限なしに、自分のアプリに関係するSMSの内容だけアプリ内で読み取る事ができます。
あとは煮るなり焼くなり好きにして、SMS認証コードの自動入力などに活用してください！

私が調べた時点では日本語の情報がとても少なかったので、記事を書いてみました。
参考にしていただけたら幸いです！

## サンプルコード
サンプルコード全部は[GitHub](https://github.com/mii-chang/SMSRetrieverSample)に載せてます:muscle: