Error receiving broadcast Intent when using SmsRetriever - android

When authenticating using Firebase Auth, I want to auto input the code that is received via SMS. I am able to receive SMS and go through auth process manually, but when I use SmsRetriever, the app crashes and then the bottom sheet dialog shows up. This is everything that that appears in Logcat:
java.lang.RuntimeException: Error receiving broadcast Intent { act=com.google.android.gms.auth.api.phone.SMS_RETRIEVED flg=0x200010 pkg=com.finca.bank (has extras) } in com.google.android.gms.internal.firebase-auth-api.zzvb#45fb8c5
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1566)
at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7562)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int java.lang.CharSequence.length()' on a null object reference
at java.util.regex.Matcher.reset(Matcher.java:280)
at java.util.regex.Matcher.<init>(Matcher.java:186)
at java.util.regex.Pattern.matcher(Pattern.java:1034)
at com.google.android.gms.internal.firebase-auth-api.zzvd.zzf(com.google.firebase:firebase-auth##20.0.1:1)
at com.google.android.gms.internal.firebase-auth-api.zzvb.onReceive(com.google.firebase:firebase-auth##20.0.1:8)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1556)
at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2) 
This is code inside my Fragment that receives SMS:
private val SMS_CONSENT_REQUEST = 2 // Set to an unused request code
private val smsVerificationReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.get(SmsRetriever.EXTRA_STATUS) as Status
when (smsRetrieverStatus.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get consent intent
val consentIntent = extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
try {
// Start activity to show consent dialog to user, activity must be started in
// 5 minutes, otherwise you'll receive another TIMEOUT intent
startActivityForResult(consentIntent, SMS_CONSENT_REQUEST)
} catch (e: ActivityNotFoundException) {
// Handle the exception ...
}
}
CommonStatusCodes.TIMEOUT -> {
// Time out occurred, handle the error.
}
}
}
} catch (e: Exception) {
Timber.e(e, "onReceive: ")
}
}
}
override fun onResume() {
super.onResume()
val task = SmsRetriever.getClient(requireActivity()).startSmsUserConsent(null)
val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
requireActivity().registerReceiver(smsVerificationReceiver, intentFilter)
}
override fun onPause() {
super.onPause()
requireActivity().unregisterReceiver(smsVerificationReceiver)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
// ...
SMS_CONSENT_REQUEST ->
// Obtain the phone number from the result
if (resultCode == Activity.RESULT_OK && data != null) {
// Get SMS message content
val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_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.
message?.let { presenter.parseSms(it) }
// send one time code to the server
} else {
// Consent denied. User can type OTC manually.
}
}
}
Interesting thing is, the progress goes successfully in rare cases and I don't know what it depends on. Also, everything goes well in debug mode if breakpoints are set in onReceive

After several investigations, the root cause of this crash seems to be related to a conflict between Firebase Auth Instant Verification feature and SMS consent API.
In order to fix it you have two options:
Remove SMS consent API and only rely on Instant Verification
Use SMS Consent API and disable Instant Verification by setting timeout as 0. https://firebase.google.com/docs/reference/android/com/google/firebase/auth/PhoneAuthOptions.Builder#setTimeout(java.lang.Long,%20java.util.concurrent.TimeUnit))
Hope this provides some clarity around this weird issue.

Got the same error while Integrating the SMS consent API. After Searching and modification got a solution. In the below-attached image, you can see the setTimeout method consists of a parameter called timeout, change that parameter value from 60L to 0L. For more info about the method click here!

To automatically verify phone numbers, you must implement both the client and server portions of the verification flow. You just implemented the client portion. To listen to SMS and auto input the OTP code you have to also implement the server side. A complete guide is found here Perform SMS Verification on a Server

Related

do I need to use try and catch in Firebase authentication codes in Kotlin - Android

Do I need to use try and catch in Firebase authentication codes in Kotlin? i.e. in here or similar places you know as experts the goal is to prevent the crashes:
fun handleFirebaseAuthWithGoogle(idToken: String) {
val credential = GoogleAuthProvider.getCredential(idToken, null)
FirebaseAuthRepository().auth.signInWithCredential(credential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
_isNewUser.value = task.result.additionalUserInfo?.isNewUser
// Sign in success, update UI with the signed-in user's information
Log.d(LoginFragment.TAG, "signInWithCredential:success")
FirebaseAuthRepository().getCurrentUser {
_authWithGoogle.value = it
}
} else {
// If sign in fails, display a message to the user.
Log.w(LoginFragment.TAG, "signInWithCredential:failure", task.exception)
}
}
}
also here:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
// Google Sign In was successful, authenticate with Firebase
val account = task.getResult(ApiException::class.java)!!
Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
viewModel.handleFirebaseAuthWithGoogle(account.idToken!!)
UiUtils.showSnackBar(requireView(), "Google sign in Succeed", 0)
} catch (e: ApiException) {
// Google Sign In failed, update UI appropriately
Log.w(TAG, "Google sign in failed", e)
hideProgress()
UiUtils.showErrorSnackBar(requireView(), "Google sign in failed", 0)
}
}
}
When you are attempting to implement an authentication mechanism, there are multiple operations that can go wrong. In such cases, we always need to handle the Exceptions.
In your second snippet code, it makes sense to use a try-catch to handle the ApiException, while in the first it doesn't. When you deal with Firebase services, you can get a successful operation or an Exception, never both. It's one or the other. So if the task is successful, then you are successfully authenticated, otherwise, you get an Exception. You can get the corresponding Exception by calling getException() method on the Task object. So there is no need to use a try-catch here because your app won't crash in case of an Exception.
You can use .addOnSuccessListener and .addOnFailureListener to handle the different FirebaseExceptions which can be thrown while authentication.

SMS verification code request failed when authenticating using Firebase Auth

When authenticating using Firebase Auth, I want to auto input the code that is received via SMS. I am able to receive SMS and go through auth process manually, but when I use SmsRetriever, the app crashes and then the bottom sheet dialog shows up.
This is everything that that appears in Logcat:
E/FirebaseAuth: [SmsRetrieverHelper] SMS verification code request failed: unknown status code: 17010 null
Code in Fragment where user inputs their phone number:
private val SMS_CONSENT_REQUEST = 2 // Set to an unused request code
private val smsVerificationReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.get(SmsRetriever.EXTRA_STATUS) as Status
when (smsRetrieverStatus.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get consent intent
val consentIntent = extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
try {
// Start activity to show consent dialog to user, activity must be started in
// 5 minutes, otherwise you'll receive another TIMEOUT intent
startActivityForResult(consentIntent, SMS_CONSENT_REQUEST)
} catch (e: ActivityNotFoundException) {
// Handle the exception ...
}
}
CommonStatusCodes.TIMEOUT -> {
// Time out occurred, handle the error.
}
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val task = SmsRetriever.getClient(requireActivity()).startSmsUserConsent(null)
val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
requireActivity().registerReceiver(smsVerificationReceiver, intentFilter)
}
override fun sendSms() {
showProgressBar(true)
SmsRetriever.getClient(requireActivity()).startSmsUserConsent(presenter.getNumber())
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(presenter.getNumber())
.setTimeout(58L, TimeUnit.SECONDS)
.setActivity(requireActivity())
.setCallbacks(callbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
override fun onDestroy() {
super.onDestroy()
requireContext().unregisterReceiver(smsVerificationReceiver)
}
This is the code in Fragment where user has to input the code:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
// ...
SMS_CONSENT_REQUEST ->
// Obtain the phone number from the result
if (resultCode == Activity.RESULT_OK && data != null) {
// Get SMS message content
val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_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.
message?.let { presenter.parseSms(it) }
// send one time code to the server
} else {
// Consent denied. User can type OTC manually.
}
}
}
Print your FirebaseAuthException error to see what's going on. If you're using a real phone number for development and using it again and again, Firebase might block the device for a time being.
SOLUTION: Add a test phone number with a password and use it.
try to print exception in onFailure like --> {p0.message} print this line logcat and it will definately show --> E/exception in firebase: We have blocked all requests from this device due to unusual activity. Try again later. this is why because we are using this phone number many times for login
It is a too-many-request error screenshot
The solution is to either wait for few hours or -if this user is a test user- , just add the number in test users to fix the code and do not send too many SMSes.

Firebase OTP verification onVerificationCompleted not called

I'm trying to set up OTP verification so when the user enters their phone number, I send them a pin code, the onCodeSent() is called and I receive the code pin, but the problem is when onVerificationCompleted() is called, I would like to move to another activity where the user can enter the code pin to verify but it is not called at all and I don't understand why. Any help would be appreciated guys, thank you.
val auth = PhoneAuthOptions
.newBuilder(FirebaseAuth.getInstance())
.setPhoneNumber(phoneNumber)
.setTimeout(60L,TimeUnit.MILLISECONDS)
.setActivity(this)
.setCallbacks(object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(p0: PhoneAuthCredential) {
// here i want to get the smscode and send it over the next activity to verify
// but this method is not called at all
Intent(this,ChangePasswordActivity::class.java).apply {
putExtra("code",p0.smsCode)
startActivity(this)
}
}
override fun onVerificationFailed(p0: FirebaseException) {
Timber.d("Firebase Exception ${p0.message}")
}
override fun onCodeSent(code: String, p1: PhoneAuthProvider.ForceResendingToken) {
super.onCodeSent(code, p1)
}
override fun onCodeAutoRetrievalTimeOut(p0: String) {
super.onCodeAutoRetrievalTimeOut(p0)
}
})
.build()
PhoneAuthProvider.verifyPhoneNumber(auth)
onVerificationCompleted() will only be called when the phone number has been verified without any input from the user. To do what you are trying, you should be sending your intent inside onCodeSent() instead.
Here is a rough flow of events (that are covered in detail in the documentation):
Obtain phone number from user
Call PhoneAuthProvider.verifyPhoneNumber(auth) (as you are already) to send the pin to the user
onCodeSent() is called, with the verification ID and a resending token.
Inside of onCodeSent(), create an intent to launch the "pin input screen" with the verification ID.
Get a pin from the user and then combine it with the verification ID by calling PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, userInput)
Use that credential to sign in the user using signInWithCredential(credential).
val auth = PhoneAuthOptions
.newBuilder(FirebaseAuth.getInstance())
.setPhoneNumber(phoneNumber)
.setTimeout(60L,TimeUnit.MILLISECONDS)
.setActivity(this)
.setCallbacks(object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
// if here, phone number was verified automatically
mAuth.signInWithCredential(credential)
.addOnCompleteListener(/* ... */)
}
override fun onVerificationFailed(p0: FirebaseException) {
Timber.d("Firebase Exception ${p0.message}")
}
override fun onCodeSent(verificationId: String, resendToken: PhoneAuthProvider.ForceResendingToken) {
// if here, code was sent to phone number
// open pin input screen
Intent(this,ChangePasswordActivity::class.java).apply {
putExtra("verificationId",verificationId)
startActivity(this)
}
}
// we aren't using onCodeAutoRetrievalTimeOut, so it's omitted.
})
.build()
PhoneAuthProvider.verifyPhoneNumber(auth)
I was also facing this problem but when I added my project to google cloud console, enable api services(google cloud console) and enable phone verification(google cloud console)
then onVerificationCompleted is working fine.
https://console.cloud.google.com/home

How to automatically read SMS in Android?

From Jan 9th, 2019 Google will remove apps from Playstore with permissions READ SMS AND CALL LOG, if they don’t explain the necessity.
Google introduced SMS Retriever API to automatically fetch a verification code sent via SMS within the app.
But those APIs are not clearly expressed and is very confusing. I don't know if it's me who thinks it is confusing. Anyhow, here is what I have looked into to read SMS but I could understand nothing.
I am not sure if this is the correct link to read SMS automatically.
https://developers.google.com/identity/sms-retriever/request
I used these dependencies
implementation 'com.google.android.gms:play-services-auth:17.0.0'
implementation 'com.google.android.gms:play-services-auth-api-phone:17.0.0'
There is one good tutorial to implement auto-read SMS but some of the APIs are deprecated so I'm trying to find any simple explanation to implement auto-read SMS in Android.
Here is the link to that tutorial
https://androidwave.com/automatic-sms-verification-android/
You should use sms retriever api for reading otp messages. Here is how you can do that.
You need below 2 dependencies for sms retrieval code
implementation 'com.google.android.gms:play-services-auth:17.0.0'
implementation 'com.google.android.gms:play-services-auth-api-phone:17.1.0'
Define few variables like this in your activity/fragment
private val SMS_CONSENT_REQUEST = 2
private lateinit var smsVerificationReceiver: BroadcastReceiver
In your onCreate() method start SMS retriever
SmsRetriever.getClient(this).startSmsUserConsent(null)
smsReceiver()
val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
registerReceiver(smsVerificationReceiver, intentFilter)
Below is the method for broadcast receiver
private fun smsReceiver() {
smsVerificationReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.get(SmsRetriever.EXTRA_STATUS) as Status
when (smsRetrieverStatus.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get consent intent
val consentIntent =
extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
try {
// Start activity to show consent dialog to user, activity must be started in
// 5 minutes, otherwise you'll receive another TIMEOUT intent
startActivityForResult(consentIntent, SMS_CONSENT_REQUEST)
} catch (e: ActivityNotFoundException) {
// Handle the exception ...
}
}
CommonStatusCodes.TIMEOUT -> {
// Time out occurred, handle the error.
}
}
}
}
}
}
And then in onActivityResult() you can get the verification code
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
// ...
SMS_CONSENT_REQUEST ->
// Obtain the phone number from the result
if (resultCode == Activity.RESULT_OK && data != null) {
// Get SMS message content
val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_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.
val oneTimeCode = parseOneTimeCode(message) // define this function
et_otp.setText(oneTimeCode.toString())
// send one time code to the server
} else {
// Consent denied. User can type OTC manually.
}
}
}
Also don't forget to unregister receiver in onDestroy() method
unregisterReceiver(smsVerificationReceiver)

GoogleSignInClient return 8 (internal error)

I'm trying to connect my game to Google Play Games Services, but when I try to login, it always returns me an error code 8 (internal error).
The code is copy pasted from Google example:
lateinit var signInClient: GoogleSignInClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_settings)
settings_login.setOnClickListener { login() }
signInClient = GoogleSignIn.getClient(this,
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build()
)
}
private fun login() {
startActivityForResult(signInClient.signInIntent, 9001)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != 9001) {
return
}
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
try {
val account = task.getResult(ApiException::class.java)
onConnected(account)
} catch (apiException: ApiException) {
var message: String? = apiException.message
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error)
}
onDisconnected()
AlertDialog.Builder(this)
.setMessage(message)
.setNeutralButton(android.R.string.ok, null)
.show()
}
}
In Google Play Console I've linked my game with debug keystore SHA-1.
I've checked everythin mentioned in Troubleshooting guide, but I still get this message again and again.
Does someone faced this issue? Any ideas how to debug it?
EDIT:
I found that it actually logs me in - if I restart game, method signInSilently() will be successful. However, it still shows this error 8 when I logout and try to log in manually. Could it be the problem with login activity overlay?
Oh, and I checked api access in Google Play Api Console - it shows that api actually receives my calls and it doesn't mention any errors.
EDIT 2: I've added requestEmail() to GoogleSignInOptions.Builder, and it shows me overlay with access request. However, it still fails in GoogleSignIn.getSignedInAccountFromIntent(intent).getResult(ApiException::class.java) with same error (8 - internal error).
It looks like this bug in Google Play Services 12.2.21:
https://github.com/googlesamples/google-services/issues/358
Google is supposed to be working on a fix for release over the air soon..
it's maybe late but I found the reason. It fixed in my case and I see your code has same problem.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
...
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
...
}
The intent you passed to the method getSignedInAccountFromIntent() is not the intent that returned by onActivityResult. The intent you passed come from activity, so you need to change it to
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
"data" is the intent returned by onActivityResult
I didn't found the reason of this error, but I found an (ugly) workaround. I noticed, that when I restart game after manual login, even if there was this error, signInSilently() method works fine, which means that API actually authenticate me and fails later. So in catch block I'm checking for status code of error, and, if it's (8 - internal error), I'm requesting last signed in account. If account is present, I assume user to be logged in.
It's really dirty but I'm out of ideas.
//onActivityResult
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
try {
val account = task.getResult(ApiException::class.java)
onSuccess(account)
} catch (apiException: ApiException) {
val acc = GoogleSignIn.getLastSignedInAccount(context)
if (apiException.statusCode == 8 && acc != null && acc.email != null) {
onSuccess(account)
} else {
onFail(apiException)
}
}
I have a client with that error. Only ONE! With a Galaxy S9. Nothing happens when clicking on the Sign In button (startActivityForResult -> GoogleSignIn.getClient.getSignInIntent)

Categories

Resources