I would like to prevent user from removing the account from the Android accounts for my app. Android AbstractAccountAuthenticator provides a method getAccountRemovalAllowed which I can use to prevent from removing account. So far so good.
But when I try to remove the account from settings, the dialog is scary. Says you can only remove by doing a factory reset. I would like to customize it saying you should uninstall the app to remove the account. Is there any way to do that?
I tried providing KEY_INTENT with my own activity but no effect.
public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
Account account) throws NetworkErrorException {
// Cannot remove the account.
final Bundle result = new Bundle();
Intent i = new Intent(mContext, MyDialog.class);
result.putParcelable(AccountManager.KEY_INTENT, i);
return result;
}
Related
I'm working on an app for android phones to be used by multiple users, where they can log in with google or microsoft accounts, to connect the app info to microsoft teams and/or sharepoint if desired.
I'm coding on Android Studio, using MSAL supporting multiple accounts.
Underneath is a method I have to remove accounts from the current PublicClientApplication.MultipleAccountPublicClientApplication. It also returns the result for each removal, if they were removed or not, in a list of booleans.
When testing, all the accounts are removed successfully, but when signing in again and the microsoft sign in intent is opened, the accounts can just be clicked to sign in without password. Signing out seems kind of pointless because of this, since one can just select their user and be logged in again right away. Is it possible to require or force the Microsoft intent to log in with password?
public CompletableFuture<List<Boolean>> signOutAll() {
List<Boolean> removedList = new ArrayList<>();
CompletableFuture<List<Boolean>> future = new CompletableFuture();
for (IAccount account : accountList) {
mPCA.removeAccount(account,
new IMultipleAccountPublicClientApplication.RemoveAccountCallback() {
#Override
public void onRemoved() {
removedList.add(true);
if (accountList.size() == removedList.size()) {
future.complete(removedList);
}
}
#Override
public void onError(#NonNull MsalException exception) {
removedList.add(false);
if (accountList.size() == removedList.size()) {
future.complete(removedList);
}
}
});
}
return future;
}
--
Thank you,
Didrik
This is happening because MSAL automatically refreshes your token after expiration. When user opens your app it checks if that token is already present and valid. So you can remove the token from the Android KeyStore in onStop().
So yes you also need to remove the cache as well to remove the account from the cache, find the account that need to be removed and then call PublicClientApplication.removeAccount()
Set<IAccount> accounts = pca.getAccounts().join();
IAccount accountToBeRemoved = accounts.stream().filter(
x -> x.username().equalsIgnoreCase(
UPN_OF_USER_TO_BE_REMOVED)).findFirst().orElse(null);
pca.removeAccount(accountToBeRemoved).join();
Read more here.
On Android we basically don't have any control on the cookies because they are shared with external Chrome app and because of that it is not accessible. If you want the user to enter the password again then you should do this: AcquireTokenInteractive(scopes).WithPrompt(Prompt.ForceLogin);
When my app starts, I'd like to ask my users either to create an Account or to choose from existing ones. I've implemented an Authenticator (extended AccountAuthenticatorActivity, AbstractAccountAuthenticator, made a Service) It seems to be working, I can create new Accounts from Settings/Accounts.
When I start an AccountPicker, I get a list of already created Accounts. When I click Add acccount it shows up my Account creation Activity. But when I'm done with account creation, finishing that Activity, and going back to the AccountPicker I dont see a new option of the newly created Account. Although if I restart the app, the recently created Account is in the list.
How I start the AccountPicker:
Intent intent = accountManager.newChooseAccountIntent(null, null, new String[]{"test_namespace"}, null, null, null, null);
startActivityForResult(intent, TEST_CODE);
My questions:
Is it supposed to work like this?
Can I reload the content of the AccountPicker after I created a new
Account?
Can I just simply return an Intent with the newly created Account when I
return from my Account creation Activity?
In my authenticator activity, after the user authenticates on the server I check the existing accounts and explicitly add the account if it's not there:
boolean accountRegistered = false;
Account account = new Account(username, AccountAuthenticator.ACCOUNT_TYPE_MYAPP);
AccountManager acctMgr = AccountManager.get(this);
Account[] accounts = acctMgr.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE_MYAPP);
for (Account acct : accounts) {
if (acct.equals(account)) {
accountRegistered = true;
break;
}
}
if (accountRegistered) {
acctMgr.setPassword(account, password);
} else {
acctMgr.addAccountExplicitly(account, password, null);
}
After I do this, I see the account in the account picker.
I can't guarantee this is 100% correct; with the undocumented authentication classes, we're all flying blind.
When using Firebase invites and accessing the dynamic links at the startup of the app on Android, is there a way to know whether the user just installed the app thanks to the invite or whether it was already installed?
Many thanks,
Borja
EDIT: Thanks to Catalin Morosan for the answer
It turns out that you can find this out using method AppInviteReferral.isOpenedFromPlayStore(result.getInvitationIntent()). In the activity that runs when you click on the invitation:
// Create an auto-managed GoogleApiClient with access to App Invites.
mGoogleApiClientInvite = new GoogleApiClient.Builder(this)
.addApi(AppInvite.API)
.enableAutoManage(this, this)
.build();
// Check for App Invite invitations and launch deep-link activity if possible.
// Requires that an Activity is registered in AndroidManifest.xml to handle
// deep-link URLs.
boolean autoLaunchDeepLink = false;
AppInvite.AppInviteApi.getInvitation(mGoogleApiClientInvite, this, autoLaunchDeepLink)
.setResultCallback(
new ResultCallback<AppInviteInvitationResult>() {
#Override
public void onResult(AppInviteInvitationResult result) {
if (result.getStatus().isSuccess()) {
// Extract information from the intent
Intent intent = result.getInvitationIntent();
String invitationId = AppInviteReferral.getInvitationId(intent);
boolean alreadyUser = AppInviteReferral.isOpenedFromPlayStore(result.getInvitationIntent());
if (alreadyUser) {
// Do stuff...
} else {
// Do other stuff...
}
}
}
});
Based on this Google product form post, the Firebase Dynamic Links library will only check for incoming deep links once per app lifetime, meaning you'd need to uninstall and reinstall the app for it to check again. This feeds into the behavior of the getInvitation() method, and it appears you can imply whether the app was previously installed based on the results of this method.
To me, this seems awfully confusing. At Branch.io we do it completely differently: your link data object will always contain an is_first_session boolean, which you can programmatically handle in any way you choose.
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.
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