Lendo SMS em dispositivos android
Published: 2023-04-10 19:37:01

**Introdução**
A verificação em dois fatores é amplamente utilizada, visto que garante maior segurança tanto para a empresa quanto para o usuário, pois confirma que o recurso está sendo acessado pelo titular da conta. Com isso, foi desenvolvida uma API que permite a leitura de SMS recebidos em tempo real. Neste artigo, abordaremos essa forma de verificação e como ela é suportada usando conceitos de broadcast receiver.

**Prós e contras da API**
Como todo recurso de software, existe prós e contras e deve ser resguardado para se utilizar em uma ocasião que realmente traz mais benefício do que malefício. 

Os pontos fortes da API SMS Retriever são: 
* Sem permissões no manifest
* API mantida pela criadora do android, a Google;
* Fácil implementação.

Os pontos contras:
* Permissão de consentimento único. Enquanto o aplicativo estiver em execução, só consegue ler o SMS única vez;
* Documentação da API desatualizada com alguns métodos;
* É necessário fazer manipulações para pegar o SMS e implementar um regex para pegar o código de verificação.

**O que de fato é essa API e por que deve usar ela?**
A API SMS Retriever, é o principal e único recurso de aplicativos que não tem como objetivo principal o tratamento de mensagens e ligações. Com isso, em um aplicativo que se adequam a esses requisitos, como Whatsapp, Telegram e entre outros conseguem fazer a verificação por SMS através de apenas um broadcast receiver e permissões, seja em tempo de execução (programáticamente) quanto no manifest. Antes, era bem comum os aplicativos pegarem o SMS do usuário com essas permissões, mas em 2018, a Google tirou essa flexibilização devido o uso [indevido ](https://9to5google.com/2018/10/08/play-store-policy-prevent-leaks/) do recurso. 

**Implementação**

* Dependências:
  Adicione no build.gradle do app essas dependências: 

```
    implementation 'com.google.android.gms:play-services-auth:17.0.0'
    implementation 'com.google.android.gms:play-services-auth-api-phone:17.4.0'
```

* Declarando as váriaveis

```
    private lateinit var smsClient: SmsRetrieverClient
    private val broadcastReceiver = SmsBroadcastReceiver()
    private lateinit var resultLauncher: ActivityResultLauncher<Intent>
```
* Instanciando e registrando o SmsClient

```
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        resultLauncher()
        smsClient = SmsRetriever.getClient(this@MainActivity)
        val actionId = IntentFilter()
        val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
        registerReceiver(broadcastReceiver, intentFilter)
        configureFieldsWithSMS()
        initSmsClient()
    }
```

* Criando listeners de sucesso e erro para o usuário:
```
interface States {

    fun onSuccess(consentIntent: Intent)
    fun error(typeError: TypeError)

    companion object {
        var statesResult: States? = null
        fun instanceStates(states: States) {
            statesResult = states
        }
    }
}
```

* Colocando para detectar o primeiro SMS a ser recebido

Caso dê erro, entra no onFailure e se der sucesso para poder detectar os SMS a ser recebido, entra no onSuccess. Caso não receba um SMS a 5 minutos, ele entra em Timeout. 

```
 fun initSmsClient() {
        smsClient.startSmsUserConsent(null).addOnSuccessListener {
            Toast.makeText(this, "Esperando pelo SMS", Toast.LENGTH_LONG).show()
        }.addOnFailureListener {
            Toast.makeText(this, it.message.toString(), Toast.LENGTH_LONG).show()
            it.printStackTrace()
        }
    }
```

* Configurando o broadcast receiver

Para processar as transmissões e eventos, é necessário utilizar um broadcast receiver, registrar ele na activity e passar um IntentFilter (assim como está no onCreate)

```
class SmsBroadcastReceiver() : BroadcastReceiver() {

    private fun <T>configureParcelable(parcelable: String, intent: Intent, classParcelable: Class<T>) : T? {
        return if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            intent.extras?.getParcelable(parcelable, classParcelable)
        }else{
            intent.extras?.getParcelable(parcelable)
        }
    }

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == SmsRetriever.SMS_RETRIEVED_ACTION) {
            try {
                val smsRetrieverStatus = configureParcelable(SmsRetriever.EXTRA_STATUS, intent, Status::class.java)

                when (smsRetrieverStatus?.statusCode) {
                    CommonStatusCodes.SUCCESS -> {
                        val consentIntent = configureParcelable(SmsRetriever.EXTRA_CONSENT_INTENT, intent, Intent::class.java)
                        consentIntent?.let { States.statesResult?.onSuccess(it)}
                    }

                    CommonStatusCodes.ERROR -> {
                        States.statesResult?.error(TypeError.ERROR)
                    }

                    CommonStatusCodes.TIMEOUT -> {
                        States.statesResult?.error(TypeError.TIMEOUT)
                    }
                }

            } catch (exception: java.lang.Exception) {
                States.statesResult?.error(TypeError.EXCEPTION)
                exception.printStackTrace()
            }
        }
    }
}


```
* Apresentando o SMS para o usuário

```
 private fun configureFieldsWithSMS() {
        States.instanceStates(object : States {
            override fun onSuccess(consentIntent: Intent) {
                resultLauncher.launch(consentIntent)
            }

            override fun error(typeError: TypeError) {
                Log.d("ERROR", typeError.name)
            }
        })
    }
```

* Processando resposta do usuário: 
Caso o usuário deu OK ao consentimento de ler SMS, conseguiremos ler a mensagem. Com isso, é necessário resgatar esse código no activityResult

```
private fun resultLauncher() {
         resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result->
            if(result.resultCode == Activity.RESULT_OK) {
                val sms = result.data?.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE).toString()
                Toast.makeText(this@MainActivity, sms, Toast.LENGTH_LONG).show()
            }
        }
    }
```
* Criando a mensagem no emulador:
Para simular, é necessário seguir um padrão de mensagem especificada pela Google. 

![Imagem retirada da [documentação](https://developers.google.com/identity/sms-retriever/verify?hl=pt-br)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9d7nth1s8sfc5n1bzeul.png)
Com isso, já que não temos um server side para mandar o SMS com códigos, iremos configurar manualmente no emulador e mandar por lá mesmo. 
Clique nas configurações do emulador (3 pontos): 
Vá na opção _Phone_ do lado esquerdo e depois insira um texto com os padrões especificados acima. Exemplo: **teste 1234**.

Com isso, será apresentando o bottomSheet pedindo o consentimento: 

![BottomSheet requisitando consentimento do usuário](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ujbuekne9gx8b4a5op3.png)

E caso o usuário dê o aceite, o SMS será apresentando na tela em forma de Toast:

![SMS em forma de Toast](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43ppx5z1u5nhccbzfiws.png)

Caso queira ver o código todo, ele se encontra neste [repositório ](https://github.com/tumusx/tools-android/tree/master/tasks/src/main/java/com/github/tumusx/tasks) do github.

Contate-me
[Linkedin](https://www.linkedin.com/in/tumusx/)