5

I'm building an app where I store app data in the app-specific-folder on Google Drive. I've been able to setup everything related to file storage and retrieval. The problem I'm facing is regarding permissions. The user has an option to disconnect the app from the Google Drive settings panel.

I use the DriveScopes.DRIVE_APPDATA meaning https://www.googleapis.com/auth/drive.appdata scope to save data.

enter image description here

I'm trying to figure out how to find out if this has happened on the app side. If I try to continue using the drive related apis, with the app being disconnected, then it crashes with a UserRecoverableAuthException.

com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
    at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential$RequestHandler.intercept(GoogleAccountCredential.java:297)
    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:868)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:476)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:409)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:526)
    at abhiank.maplocs.ui.drivesync.DriveSyncService.onHandleIntent(DriveSyncService.kt:68)
    at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:78)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:214)
    at android.os.HandlerThread.run(HandlerThread.java:67)
 Caused by: com.google.android.gms.auth.UserRecoverableAuthException: NeedPermission
    at com.google.android.gms.auth.zze.zzb(Unknown Source:13)
    at com.google.android.gms.auth.zzd.zza(Unknown Source:77)
    at com.google.android.gms.auth.zzd.zzb(Unknown Source:20)
    at com.google.android.gms.auth.zzd.getToken(Unknown Source:7)
    at com.google.android.gms.auth.zzd.getToken(Unknown Source:5)
    at com.google.android.gms.auth.zzd.getToken(Unknown Source:2)
    at com.google.android.gms.auth.GoogleAuthUtil.getToken(Unknown Source:55)
    at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential.getToken(GoogleAccountCredential.java:267)
    at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential$RequestHandler.intercept(GoogleAccountCredential.java:292)

I tried the following to figure out if the app does not have the permissions or scopes.

  1. Look at data inside GoogleSignInAccount instance received from GoogleSignIn.getLastSignedInAccount(this). This had the following scopes available in account.grantedScopes() after app had been disconnected. drive.appdata is shown even though app is disconnected.
[https://www.googleapis.com/auth/drive.appdata, https://www.googleapis.com/auth/userinfo.profile, https://www.googleapis.com/auth/userinfo.email, openid, profile, email]
  1. Last thing I tried was hasPermissions method available in GoogleSignIn. I checked if the APP_DATA scope was available with this call and it returned true. So no help there either.
GoogleSignIn.hasPermissions(account, Scope(DriveScopes.DRIVE_APPDATA))

I'm really stuck now. Any help will be really appreciated. Thanks.

vepzfe
  • 4,217
  • 5
  • 26
  • 46
  • What if you `try..catch` `UserRecoverableAuthException`? Once the exception is captured, you can do you logic to let the user know or ask for permission again. – Alberto Anderick Jr Nov 15 '20 at 20:23
  • Agreed. I could do that. But there are a couple of problems in that. 1. User experience. I dont want to show signed-out after user has clicked the sync button 2. I don't know what other situation would UserRecoverableException might arise for. – vepzfe Nov 16 '20 at 07:10
  • Did you find an answer to this? I'm experiencing the same thing – Lady_ari Feb 07 '22 at 20:49
  • @Lady_ari I posted an answer to what I'm using. Its basically what @Alberto suggested - which was to add a try-catch around the code for `UserRecoverableAuthIOException`. I hope this helps. – vepzfe Feb 08 '22 at 06:47

1 Answers1

0

I ended up using a try-catch around my drive related code and catching the UserRecoverableAuthIOException. According to the documentation, this is called when -

UserRecoverableAuthExceptions signal Google authentication errors that can be recovered with user action, such as a user login.

This has worked decently well for me. Considering the fact that this question has not received any other answers in 2 years, there doesn't seem to be any method to fetch the information about whether the app is disconnected or not via an API or SDK call.

Here's the code I use

fun getGoogleDriveService(context: Context): Drive {
    val credential = GoogleAccountCredential.usingOAuth2(context, setOf(DriveScopes.DRIVE_APPDATA))
    credential.selectedAccount = GoogleSignIn.getLastSignedInAccount(context)!!.account
    return Drive.Builder(NetHttpTransport(), GsonFactory(), credential)
            .setApplicationName(DriveSyncService.APP_NAME)
            .build()
}

try {
    val driveService = getGoogleDriveService(this)
    var fileList = driveService.files().list().execute()
    //...
    //more code
} catch (e: UserRecoverableAuthIOException) {
    /*
    Doing a sign-out on the googleSignInClient so that there is no mismatch 
    in sign-in state and so that when I start sign-in process again, it 
    starts afresh
    */
    googleSignInClient.signOut()
    /*
    Then I show a pop up telling user that app was disconnected and 
    to sign in again. And then on click I start the sign-in flow again. 
    */
} catch (e: GoogleJsonResponseException) {
    //https://googleapis.dev/java/google-api-client/latest/index.html?com/google/api/client/googleapis/json/GoogleJsonResponseException.html
    //404 is file being updated/deleted was not found
    if (e.message != null && e.message!!.contains("storageQuotaExceeded")) {
        //todo handle storage exceeded error. Inform user
    }
} catch (e: IOException) {
    //todo handle network error
}

vepzfe
  • 4,217
  • 5
  • 26
  • 46