Does anyone manage to get Google Drive Notepad sample for Android to work? I followed the instructions as given in Google's developer page but I could not get the notification to start the Google Play Service dialog for authorization.
The app starts as expected and when the method
credential.getToken();
is called, a UserRecoverableAuthException exception is raised. I expect that an authorization dialog will be shown, but nothing happens. I bring down the list of notification from the status bar, tap on the notification created by this app, and again nothing happens.
When I tap on the "Recent Apps" button, I could see "Google Play services" in the list, but it has empty (blank and black) content. Tapping it does nothing, except I was brought back to my home screen.
Any idea on this?
I tried the other sample app, "Quick Start Drive" and this one worked as expected. An authorization dialog was presented as I expected.
Further information, target device is Nexus 7 running OS 4.2.2.
Many thanks.
I had the same problem and I solved it with :
credential.setSelectedAccountName(accountName);
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
Account[] accounts = accountManager.getAccountsByType("com.google");
Account account = null;
for(Account a : accounts){
if(a.name.equals(accountName)){
account = a;
break;
}
}
if(account != null){
String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/drive";
accountManager.getAuthToken(account, AUTH_TOKEN_TYPE, null, true, null, null);
service = getDriveService(credential);
}
To authorize Google Drive API, you have to catch the UserRecoverableException and trigger the intent to have an authorization dialog.
try{
....
} catch (UserRecoverableAuthIOException e) {
Intent intent = e.getIntent();
startActivityForResult(intent, REQUEST_AUTHORIZATION_FOLDER);
}
Gabriele Mariotti, your solution seems better but can't work in that case. Indeed, the class is not an activity and the startActivityForResult method can't be called.
Related
I have implemented Google Onetap SignIn in my application. Everything is working fine, the only issue that I am observing is that on certain devices the pop-up often takes 7-10 seconds to display. Especially in case of Sign-In popup.
Since I have multiple login options available in the app it might so happen that before I can show the user his last used google account to login (via OneTap popup), he gets enough time to click on any other option (eg, Facebook) & it becomes a poor experience.
Since this pop-up is displayed by play-services, I don't see how I can optimise this time taken.
As per the code, it seems the call to
contract
.getOneTapClient()
.beginSignIn(getSignInRequest(isRegistering))
is the one taking the most time. It seems the code that queries for user's on device Google Accounts.
Using below code structure. Adding for reference
contract.getOneTapClient().beginSignIn(getSignInRequest(isRegistering))
.addOnSuccessListener { result: BeginSignInResult ->
try
{
contract.startIntentSenderForResult(
result.pendingIntent.intentSender, requestCode,
null, 0, 0, 0, null)
successCallback?.onSuccess(isRegistering, "Rendering Popup")
val timeTaken = if(isRegistering) System.currentTimeMillis() - signUpTime
else System.currentTimeMillis() - signInTime
BBLogUtils.logWithTag(TAG, "Completed in ${timeTaken/1000.0}s")
}
catch (e: IntentSender.SendIntentException)
{
failureCallback?.onFailure(isRegistering, e, ERROR_INTENT_SENDER_EXCEPTION)
}
}
.addOnFailureListener { e: Exception ->
// No saved credentials found.
// OR Temporarily blocked due to too many canceled sign-in prompts.
BBLogUtils.logWithTag(TAG, "Exception | registering=$isRegistering|rCount=$rCount | Error= ${e.message}")
failureCallback?.onFailure(isRegistering, e, ERROR_NO_CREDENTIALS_FOUND)
}
SignIn request object is the standard as prescribed by the docs
private fun getSignInRequest(isRegistering: Boolean): BeginSignInRequest
{
return BeginSignInRequest.builder()
.setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true) // So that we receive the idToken in result
.setServerClientId(contract.getGoogleAndroidClientId())
/*
* true: for Registration ie. showing all accounts
* false: for return user signIn, ie. showing only previously used accounts
**/
.setFilterByAuthorizedAccounts(!isRegistering)
.build())
.build()
}
Another related question to this feature.
On the first launch of the app on device I saw this additional pop-up
Is there someway this can be skipped ?
Answering 2nd part of my own query here.
After a lot search I still haven't bee able to find a workaround for skipping the process. Turns out that this popup is medium for play services to inform the user about the new sign-in experience.
I observed that if I installed another app using Google Onetap (eg. Reddit or PIntrest), the same pop-up appeared there as well. Also, its shown only once to a user, ie. if the pop up was shown in the Reddit app then it won't come in my app & vice-versa.
In addition, if you wan't to repro this scenario, you can clear the storage of Google Play Services. For some duration, it might show error: 16: App is not whitelisted, but after a while, you will get the How It Works pop-up again.
In Android Oreo, AccountManager.getAccountsByType("com.google"); returns null.
Its, working fine in below Android 8 versions.
Below is my code:
private static Account getAccount(AccountManager accManager) {
Account[] accounts = accManager.getAccountsByType("com.google");
Account account;
if (accounts.length > 0) {
account = accounts[0];
} else {
account = null;
}
return account;
}
Thanks in advance.
As per Android's update, from Oreo onwards we can not use AccountManager.getAccountsByType to get the list of google accounts configured on user's device, as they have updated the Google SignIn features. The new feature will prompt the user to select the account and that account will be only visible to our app.
See the documentation: https://developer.android.com/about/versions/oreo/android-8.0-changes#aaad
If you still want to continue with the old approach of showing all the account's to users, you need to get an extra consent from user by doing below procedures.
You can use GoogleAuthUtil.requestGoogleAccountsAccess to get the list of Google accounts.
A sample code is given below:
new Thread(() -> {
try {
GoogleAuthUtil.requestGoogleAccountsAccess(getApplicationContext());
} catch (Exception e) {
if (e instanceof UserRecoverableAuthException) {
startActivityForResult(((UserRecoverableAuthException) e).getIntent(),
REQ_CODE_PERMISSION_GET_GOOGLE_ACCOUNTS);
} else {
Log.e("SignIn", "Exception in getting google accounts" + e);
}
}
}).start();
This will create an activity to prompt user to accept the consent to allow Google Play Service to access the list of google accounts configured on the device.
You can then override onActivityResult() function on your activity to continue after.
Then you can use AccountManager.getAccountsByType to get the list of google accounts as you done before.
Happy Coding!
i (lets say app 'C' )am trying to get the auth token of an installed app ( say 'S' ) through Android's AccountManager's getAuthToken function.
this function is not working as expected, it doesn't return any results (the run function is never called )
AccountManagerFuture<Bundle> future1 = AccountManager.get(Main2.this).getAuthToken(account,account.type,null,false, new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> future) {
Bundle result = null;
try {
result = future.getResult();
String check = "";
}
catch (OperationCanceledException e){ }
catch (IOException e1){}
catch (AuthenticatorException e2){}
}
} , new Handler(Looper.getMainLooper()));
when i see the device ADB Logs, i see the following
java.lang.SecurityException: Activity to be started with KEY_INTENT must share Authenticator's signatures
at com.android.server.accounts.AccountManagerService$Session.onResult(AccountManagerService.java:2580)
at com.android.server.accounts.AccountManagerService$6.onResult(AccountManagerService.java:1677)
at com.android.server.accounts.AccountManagerService$6.onResult(AccountManagerService.java:1652)
at android.accounts.IAccountAuthenticatorResponse$Stub.onTransact(IAccountAuthenticatorResponse.java:59)
Apps 'C' and 'S' described above are unrelated, so they are signed with different certificates.
I am guessing the function should have worked in above scenario ( which is one of the main purpose of AccountManager - Sharing of account access tokens across apps ) as well ( with a security dialog thrown to the user on whether he should allow 'C' to access 'S' ) , whats the reason it is not working ? Am i missing anything here ?
Thanks
First go to your implementation of AbstractAuthenticator in app S. Find getAuthToken() implementation. Check, which activity you return as KEY_INTENT. It must be in same app as authenticator (yes, there are ways to launch an activity from another app).
Make sure, you run on a real device, because you must see a "grant permissions" android system screen in that case.
If you come here, than I don't know another reason except some bug. Try totally removing both apps and restarting emulator, then check if problem persists.
I'm using the google play services authentication example here
How do I reset the GoogleAuthUtil so it will ask for permission again?
It asks for permission by throwing the userRecoverableException which is fed to a dialog. But it only asks for permission one time. I need to test asking for permission again.
I've tried to uninstall the sample app and re-install the sample app and this didn't work it doesn't ask for permission seems it already knows the app.
protected String fetchToken() throws IOException {
try {
return GoogleAuthUtil.getToken(mActivity, mEmail, mScope);
} catch (UserRecoverableAuthException userRecoverableException) {
// GooglePlayServices.apk is either old, disabled, or not
// present, which is
// recoverable, so we need to show the user some UI through the
// activity.
MyGooglePlay.handleException(userRecoverableException);
} catch (GoogleAuthException fatalException) {
onError("Unrecoverable error " + fatalException.getMessage(),
fatalException);
}
return null;
}
/**
* This method is a hook for background threads and async tasks that need to provide the
* user a response UI when an exception occurs.
*/
public void handleException(final Exception e) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (e instanceof GooglePlayServicesAvailabilityException) {
// The Google Play services APK is old, disabled, or not present.
// Show a dialog created by Google Play services that allows
// the user to update the APK
int statusCode = ((GooglePlayServicesAvailabilityException)e)
.getConnectionStatusCode();
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(statusCode,
HelloActivity.this,
REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
dialog.show();
} else if (e instanceof UserRecoverableAuthException) {
// Unable to authenticate, such as when the user has not yet granted
// the app access to the account, but the user can fix this.
// Forward the user to an activity in Google Play services.
Intent intent = ((UserRecoverableAuthException)e).getIntent();
startActivityForResult(intent,
REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
}
}
});
}
You can use the Google Settings app to de-authorize connected applications, by following these steps:
Launch the Google Settings app
Choose the Connected apps option (at the top)
A list of connected apps is displayed; find the app you want to de-authorize and select it. Sorry there is no screenshot as I'm not able to remove personal info from it ATM - but it should be quite straightforward what to do here :)
Finally, click the Disconnect button (at the bottom) on the details page of the app
Note that it might take a moment before the app is de-authorized.
You can also call GoogleAuthUtil.invalidateToken or GoogleAuthUtil.clearToken, that should make it ask the permission again.
If you're a user, like what free3dom answered, you can go to Google Settings app to revoke the access.
If you want to revoke the access programmatically, you can call Google's revoke token API: https://developers.google.com/identity/protocols/OAuth2WebServer#tokenrevoke. Basically, you should first get a valid token with a set of scopes by calling GoogleAuthUtil.getToken(), and then revoke the token. After the token is revoked, you should see the permission dialog again.
On JellyBean device.
I'm following this to request an oauth2 token, e.g.
AccountManager am = AccountManager.get(getActivity());
am.invalidateAuthToken(MY_AUTH_TOKEN_TYPE, null);
am.getAuthToken(aGoogleAccount, MY_AUTH_TOKEN_TYPE, null, this,
new OnTokenAcquired(), new Handler(new OnError()));
and then make the check as per the later code sample:
private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
#Override
public void run(AccountManagerFuture<Bundle> result) {
Bundle bundle = result.getResult();
...
Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT);
if (launch != null) {
startActivityForResult(launch, 0);
return;
}
}
}
I never get a KEY_INTENT. I understand the following:
There may be many reasons for the authenticator to return an Intent. It may be the first time the user has logged in to this account. Perhaps the user's account has expired and they need to log in again, or perhaps their stored credentials are incorrect. Maybe the account requires two-factor authentication or it needs to activate the camera to do a retina scan. It doesn't really matter what the reason is. If you want a valid token, you're going to have to fire off the Intent to get it.
However, the getAuthToken always results in the permission screen, or login screen, appearing before the code hits the run method at which point the token is valid. I've tried:
Turning on 2 step authentication. Account login is requested before run so always have the token in run.
Changing the password on the server. Again account login is requested before run so always have the token in run.
Don't have the ability to try a retina scan so somewhat at a loss.
EDIT 1 The problem I have is that there may be a circumstance where I will get a KEY_INTENT and so I'd rather test this code path now rather when when it's out in the field.
Thanks in advance.
Peter.
Had a chance to do something similar on a project. This not the exactly the same as your code, and I still say that the callback docs have too many 'maybes' to be certain of how it should work, but if you use this method passing false for notifyAuthFailure, you will get an intent with the re-login screen if you change the password or enable 2FA. This is for ClientLogin, but should work similarly for OAuth 2 (not tested though). Something like:
// using Calendar ClientLogin for simplicity
Bundle authResult = am.getAuthToken(account, "cl", false, null, null).getResult();
if (authResult.containsKey(AccountManager.KEY_INTENT)) {
Intent authIntent = authResult.getParcelable(AccountManager.KEY_INTENT);
// start activity or show notification
}
I think you need to call getResult(), like this:
Intent launch = (Intent)result.getResult().get(AccountManager.KEY_INTENT);
You're using the version of getAuthToken which uses an Activity to invoke the access authorization prompt. That version of getAuthToken does not return an intent since the supplied activity is used to launch the corresponding intent. If you want to manually launch an intent, use the version of getAuthToken that was deprecated in API level 14. See the following for more information:
http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken%28android.accounts.Account,%20java.lang.String,%20boolean,%20android.accounts.AccountManagerCallback%3Candroid.os.Bundle%3E,%20android.os.Handler%29