はじめに
ある日、GooglePlayConsole上にこんな警告が出ていました。

このアプリには、android.permission.RECEIVE_SMSの権限を付与していました。
android.permission.RECEIVE_SMSの権限を使うなら、その権限をなんの目的で使うのかを明確に申請してくれというのが今回の警告の内容です。
このアプリでのandroid.permission.RECEIVE_SMSの用途は、アカウントの認証時、SMS認証をしており、認証コードを自動入力させるために、受信したSMSの内容を取得するためでした。
この用途でandroid.permission.RECEIVE_SMSを使ってしまうと、自分のアプリに必要なSMS以外のすべてのSMSをアプリから読むことができてしまいます。それはセキュリティー的にいかがなことなのか
ということで、Googleにお叱りをうけました。
そして、代替手段として、 SMS Retriever APIを使ってねと言われました。
SMS Retriever API とは
android.permission.RECEIVE_SMSを使わずに、自分のアプリに必要なSMSを受信できる仕組みが、SMS Retriever APIです。

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を追加します。
バージョンはここを参考にしてください。
dependencies {
    implementation "com.google.android.gms:play-services-auth:16.0.1"
}
実装方法
ほとんどリファレンス通りなのですが、サンプルを載せます
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が送られてきて、ハッシュが自分のアプリのハッシュと一致していた場合、
extras.get(SmsRetriever.EXTRA_SMS_MESSAGE)
で、SMSの内容を取得することができます。
ハッシュは、アプリのパッケージ名、証明書の情報で生成されます。ハッシュ作成用のサンプルコード が提供されているので、これで自分の環境にあったハッシュを生成してください。
余談?ですが、リファレンスに書いてあるコマンドでは正しいハッシュを作れませんでした…(これで数時間ハマった…)コマンドに当てはめる項目を間違えたのだと思いますが、何を間違えたのか判明したら追記します。。。
受信確認
サーバーからSMSを送ってもらうのが実際の使用用途かと思いますが、確認だけなら、SMSBroadcastReceiverが準備されている検証端末に、他の端末から<#>のプレフィックスをつけ、生成したハッシュを含んだSMSを送っても受信確認ができます![]()
まとめ
上記の通りに実装すれば、android.permission.RECEIVE_SMSの権限なしに、自分のアプリに関係するSMSの内容だけアプリ内で読み取る事ができます。
あとは煮るなり焼くなり好きにして、SMS認証コードの自動入力などに活用してください!
私が調べた時点では日本語の情報がとても少なかったので、記事を書いてみました。
参考にしていただけたら幸いです!
サンプルコード
サンプルコード全部はGitHubに載せてます![]()