Skip to content

Instantly share code, notes, and snippets.

@esabook
Last active May 19, 2022 06:58
Show Gist options
  • Save esabook/6ca064acc595656f4435759f4e680114 to your computer and use it in GitHub Desktop.
Save esabook/6ca064acc595656f4435759f4e680114 to your computer and use it in GitHub Desktop.
/*
* Copyright (c) 2022 esabook.
* Author: egit
*/
package com.esabook.app.services
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.auth.api.phone.SmsRetriever.*
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import timber.log.Timber
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* register action
* call async smsRetriver
* parse smsOTP
* invoke callback
* unregister action
*/
class OtpAutoCompleteService(
private val activity: AppCompatActivity,
private val onOTPResult: (String) -> Unit
) {
companion object {
private val senderPhoneNumber = listOf("egit") //known esabook's numbers
private val otpPattern = listOf("(\\d{6})")
}
private val smsBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Timber.d("onReceive otp broadcast")
if (SMS_RETRIEVED_ACTION == intent?.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.get(EXTRA_STATUS) as Status
when (smsRetrieverStatus.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get consent intent
val consentIntent = extras.getParcelable<Intent>(EXTRA_CONSENT_INTENT)
try {
Timber.d("Get otp intent from broadcast with startActivityForResult")
// Start activity to show consent dialog to user, activity must be started in
// 5 minutes, otherwise you'll receive another TIMEOUT intent
val activityResultTask = activity.activityResultRegistry.register(
"otp",
ActivityResultContracts.StartActivityForResult(),
this@OtpAutoCompleteService::onOtpActivityResult
)
activityResultTask.launch(consentIntent)
} catch (e: Exception) {
Timber.e(e)
}
}
CommonStatusCodes.TIMEOUT -> {
Timber.d("Receive otp broadcast [TIMEOUT]")
}
}
}
}
}
fun startListener() {
registerReceiver()
senderPhoneNumber.forEach {
getClient(activity).startSmsUserConsent(it)
}
}
fun unregisterListener() {
activity.unregisterReceiver(smsBroadcastReceiver)
}
private fun registerReceiver() {
val intentFilter = IntentFilter(SMS_RETRIEVED_ACTION)
activity.registerReceiver(
smsBroadcastReceiver,
intentFilter,
SmsRetriever.SEND_PERMISSION,
null
)
}
private fun onOtpActivityResult(activityResult: ActivityResult) {
// Obtain the phone number from the result
if (activityResult.data != null) {
// Get SMS message content
val message = activityResult.data?.getStringExtra(EXTRA_SMS_MESSAGE)
Timber.d("Parsing otp from message: %s", message)
// Extract one-time code from the message and complete verification
// `message` contains the entire text of the SMS message, so you will need
// to parse the string.
if (message != null) {
otpPattern.forEach {
val otp = parseOneTimeCode(message, it)
if (otp.isNotBlank()) {
Timber.d("Result otp: %s", otp)
onOTPResult.invoke(otp)
return@forEach
}
}
}
} else {
Timber.d("Skip parsing otp")
// Consent denied. User can type OTC manually.
}
}
private fun parseOneTimeCode(message: String, regexPattern: String): String {
// \d is for a digit
// {} is the number of digits here 6.
val pattern: Pattern = Pattern.compile(regexPattern)
val matcher: Matcher = pattern.matcher(message)
var result = ""
if (matcher.find()) {
result = matcher.group(0) ?: result // 6 digit number
}
return result
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment