Search code examples
androidgoogle-signinmobile-developmentsign-in-with-googleandroid-credential-manager

Google Sign In not working on device, but working fine in emulator?


I am working on an app purely for internal use, not for Play Store or anything like that. It is to be installed on our Zebra scanner which is running Android 10.

The purpose of the app is to browse Google Drive and then we select spreadsheets and scan stock.

Currently when using the emulator (Android 10) it all works as expected, Google sign in works (all be it using deprecated methods as I can't figure out how to use the new methods) and the rest of my app functions as expected.

The issue I have is when I try to install the app on the physical device. I get the 'Signing you in' bottomsheet (expected) and then I get the 'Google hasn't verified this app' screen (unexpected). I don't get this when running on the emulator as it signs in as expected.

When I click continue I get a 'Something went wrong' screen. When I click next on this screen I get a Google screen saying '400. That's an error'

Strangely in the logs I get the 'Credentials retrieved successfully' message.

I am using the same Google account on both the emulator and the physical device.

This is a snippet of my onCreate

authorizationLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val authorizationResult = Identity.getAuthorizationClient(this)
                    .getAuthorizationResultFromIntent(result.data)
                initializeDriveService(authorizationResult, initialFolderId = "1R0p0N30zZLltpRBJqIN3PGR5dMnLnx1M")
                authorizationStatus.value = "Authorization successful"
            } else {
                authorizationStatus.value = "Authorization failed"
            }
        }

        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestEmail()
            .requestIdToken(getString(WEB_CLIENT_ID))
            .requestScopes(Scope(DriveScopes.DRIVE), Scope(SheetsScopes.SPREADSHEETS))
            .build()

        googleSignInClient = GoogleSignIn.getClient(this, gso)

        signInWithGoogle()

        setContent {
            ZebraStockScannerTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    if (::driveService.isInitialized && fileListState.value.isNotEmpty()) {
                        GoogleFileExplorer(
                            driveService = driveService,
                            initialFolderId = "",
                            fileListState = fileListState,
                            onExtractedDataChanged = { newData ->
                                extractedData = newData
                            }
                        )
                    } else {
                        Text(text = "Connecting to Google Drive, please wait...")
                    }
                }
            }
        }

Rest of my sign in functions, sorry for the mess. Pretty new to Android coding.

private fun signInWithGoogle() {
        val credentialManager = CredentialManager.create(this)
        val nonce = generateNonce()

        val googleIdOption = GetGoogleIdOption.Builder()
            .setFilterByAuthorizedAccounts(true)
            .setServerClientId(getString(WEB_CLIENT_ID))
            .setAutoSelectEnabled(true)
            .setNonce(nonce)
            .build()

        val request = GetCredentialRequest.Builder()
            .addCredentialOption(googleIdOption)
            .build()

        lifecycleScope.launch {
            try {
                val result = credentialManager.getCredential(
                    request = request,
                    context = this@GoogleSignInActivity
                )
                Log.d(TAG, "Credentials retrieved successfully")
                handleSignIn(result)
            } catch (e: NoCredentialException) {
                signInWithGoogleFallback()
            } catch (e: GetCredentialException) {
                handleSignInFailure(e, this@GoogleSignInActivity)
            } catch (e: Exception) {
            }
        }
    }

    private fun signInWithGoogleFallback() {
        val signInIntent = googleSignInClient.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }

    private fun handleSignIn(result: GetCredentialResponse) {
        when (val credential = result.credential) {
            is GoogleIdTokenCredential -> {
                try {
                    val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
                    Log.d(TAG, "Google ID Token: ${googleIdTokenCredential.id}")
                    userEmail = googleIdTokenCredential.id
                    requestDriveAuthorization(initialFolderId = "")
                } catch (e: GoogleIdTokenParsingException) {
                    authorizationStatus.value = "Invalid Google ID token response"
                } catch (e: Exception) {
                    Log.e(TAG, "Unexpected error during Google ID token parsing: ${e.message}", e)
                }
            }

            else -> {
                Log.e(TAG, "Unexpected type of credential")
                authorizationStatus.value = "Unexpected type of credential"
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_AUTHORIZE && resultCode == Activity.RESULT_OK) {
            val authorizationResult =
                Identity.getAuthorizationClient(this).getAuthorizationResultFromIntent(data)
            initializeDriveService(authorizationResult, initialFolderId = "1R0p0N30zZLltpRBJqIN3PGR5dMnLnx1M")
        } else if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                handleSignInResult(task)
            } catch (e: Exception) {
                Log.e(TAG, "Error handling sign-in result: ${e.localizedMessage}")
                authorizationStatus.value = "Sign-in error: ${e.localizedMessage}"
            }
        }
    }

    private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
        try {
            val account = completedTask.getResult(ApiException::class.java)
            userEmail = account.email
            requestDriveAuthorization(initialFolderId = "1w9iWcvUtfwK-i5EC-azALMlMfuyDnHmb")
        } catch (e: ApiException) {
            authorizationStatus.value = "Google sign-in failed: ${e.statusCode}"
        } catch (e: Exception) {
            Log.e(TAG, "Unexpected error during sign-in: ${e.localizedMessage}")
        }
    }

    private fun requestDriveAuthorization(initialFolderId: String) {
        val requestedScopes = listOf(Scope(DriveScopes.DRIVE), Scope(SheetsScopes.SPREADSHEETS))
        val authorizationRequest = AuthorizationRequest.Builder()
            .setRequestedScopes(requestedScopes)
            .build()

        Identity.getAuthorizationClient(this)
            .authorize(authorizationRequest)
            .addOnSuccessListener { authorizationResult ->
                if (authorizationResult.hasResolution()) {
                    val pendingIntent = authorizationResult.pendingIntent
                    try {
                        val intentSenderRequest = pendingIntent?.let { IntentSenderRequest.Builder(it).build() }
                        authorizationLauncher.launch(intentSenderRequest)
                    } catch (e: Exception) {
                    }
                } else {
                    initializeDriveService(authorizationResult, initialFolderId)
                    authorizationStatus.value = "Authorization successful"
                }
            }
            .addOnFailureListener { e ->
                Log.e(TAG, "Failed to authorize", e)
                if (e is UserRecoverableAuthIOException) {
                    startActivityForResult(e.intent, REQUEST_AUTHORIZE)
                } else {
                    authorizationStatus.value = "Authorization failed"
                }
            }
    }

    private fun initializeDriveService(authorizationResult: AuthorizationResult, initialFolderId: String) {
        lifecycleScope.launch {
            try {
                val credential = GoogleAccountCredential.usingOAuth2(
                    this@GoogleSignInActivity, listOf(DriveScopes.DRIVE, SheetsScopes.SPREADSHEETS)
                )
                credential.selectedAccountName = userEmail

                driveService = Drive.Builder(
                    GoogleNetHttpTransport.newTrustedTransport(),
                    JacksonFactory.getDefaultInstance(),
                    credential
                ).setApplicationName(getString(R.string.app_name)).build()

                // Initialize folder name and ID caches
                val folderNameCache = mutableMapOf<String, String>()
                val folderIdCache = mutableMapOf<String, String>()

                // Fetch the initial file list from the specified folder ID
                val files = fetchFiles(driveService, initialFolderId, folderNameCache, folderIdCache)
                // Update UI or state with fetched files
                fileListState.value = files
            } catch (e: Exception) {
                Log.e("GoogleSignInActivity", "Error initializing Drive service", e)
                showDialogState.value = true
                errorMessageState.value = "Failed to initialize Drive service."
            }
        }
    }

I have tried updating the SHA-1 keys on the Firebase console with any others that I think it could be.

I have tried using a different account that works on the emulator and still the same result.

I honestly cannot think of anything else to try since I am not really getting any specific error code.

Any help or advise is greatly appreciated as I understand this might be a niche question.

EDIT

Also, just tried this on a new emulator and now I am getting the error:

java.lang.IllegalArgumentException: the name must not be empty: null

2nd EDIT

So, I figured something out. I managed to find an old Android phone (Oppo A5) and I put my app on there and it works as intended. I can sign in and do everything like I can on the emulator. So the device that I need it on (Zebra TC52) it just won't sign in with Google on the app. The device itself does have the Google account on and Play Sevices is up to date but i'm not sure what I can really do about this now.

It's a very strange issue. Is there any other way I can access the Google Drive and Sheets through the app that doesn't use GoogleSignIn as maybe that would work in my case?


Solution

  • Well, looks like an Android update from 10 to 13 solved it for me.

    I managed to find an update file for my device since it is a Zebra device but we don't have a service contract, and now it does sign in correctly.

    Still using deprecated methods but I can cross that bridge later.