DEV Community

Murillo Alves da Silva
Murillo Alves da Silva

Posted on

Lendo SMS em dispositivos android

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 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'
Enter fullscreen mode Exit fullscreen mode
  • Declarando as váriaveis
    private lateinit var smsClient: SmsRetrieverClient
    private val broadcastReceiver = SmsBroadcastReceiver()
    private lateinit var resultLauncher: ActivityResultLauncher<Intent>
Enter fullscreen mode Exit fullscreen mode
  • 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()
    }
Enter fullscreen mode Exit fullscreen mode
  • 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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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()
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • 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()
            }
        }
    }
}


Enter fullscreen mode Exit fullscreen mode
  • 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)
            }
        })
    }
Enter fullscreen mode Exit fullscreen mode
  • 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()
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • 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)
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

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

SMS em forma de Toast

Caso queira ver o código todo, ele se encontra neste repositório do github.

Contate-me
Linkedin

Top comments (0)