I created my own Android account authenticator by extending AbstractAccountAuthenticator and implementing addAccount() and getAuthToken(). Some of the methods in it are called by AccountManager, but others are not.
This works great:
AccountManager#addAccount()
AccountManager accountManager = AccountManager.get(activity);
accountManager.addAccount(MyAccountAuthenticator.ACCOUNT_TYPE,
MyAccountAuthenticator.AUTHTOKEN_TYPE_FULL_ACCESS, null, null,
activity, callback, null);
The problem happens when I call AccountManager#getAuthToken() in my Activity. The AccountManager does not call the getAuthToken() method I define in my AccountAuthenticator. It calls some other default method that only checks for existence of an authToken before starting the AuthenticatorActivity.
This does not work. It does not call my getAuthToken() method:
AccountManager#getAuthToken()
AccountManager accountManager = AccountManager.get(activity);
accountManager.getAuthToken(
mAccount, MyAccountAuthenticator.AUTHTOKEN_TYPE_FULL_ACCESS, null,
activity, callback, handler);
AuthenticatorService
I created my service and defined onBind(). addAccount() should not work otherwise.
public IBinder onBind(Intent intent) {
return intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT) ? new MyAccountAuthenticator(this).getIBinder() : null;
}
EDIT: I call addAccountExplicitly in MyAuthenticatorActivity after the app gets an auth token back for the user.
Snippet from class MyAuthenticatorActivity extends AccountAuthenticatorActivity:
if (getIntent().getBooleanExtra(KEY_IS_ADDING_NEW_ACCOUNT, false)) {
// Creating the account on the device and setting the auth token we recieved
accountManager.addAccountExplicitly(account, null, null);
}
Your comment cleared things up immensely -- if you set the auth token for the account, then your getAuthToken method will not be called until the token is invalidated. You generally do this by calling invalidateAuthToken upon receiving a 401 or 403 or what have you from the web service.
From the Javadoc for the getAuthToken methods:
If a previously generated auth token is cached for this account and type, then it is returned. Otherwise, if a saved password is available, it is sent to the server to generate a new auth token. Otherwise, the user is prompted to enter a password.
Since your token is in the cache, it is returned directly and your authenticator is not consulted.
for calling AuthenticatorActivity in method AccountManager#getAuthToken, you must send intent to the activity by parcelable, method for example :
final Intent intent = new Intent(mContext, LoginActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse);
intent.putExtra(LoginActivity.ARG_ACCOUNT_TYPE, account.type);
intent.putExtra(LoginActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(LoginActivity.ARG_ACCOUNT_NAME, account.name);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
Related
In Android official Aidl document, the IPC client example declares an intent explicitly with the target "RemoteService.class". However, when server and client are not in the same package, client should not be aware of what is "RemoteService" if no dependency set. How does the example works?
ref: https://developer.android.com/guide/components/aidl.html
I searched for several working examples, and the intent is set with Action instead of the remote service class object.
In Android docs,
Intent intent = new Intent(Binding.this, RemoteService.class);
intent.setAction(IRemoteService.class.getName());
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
Currently, I expect this should be modified to:
Intent intent = new Intent("<remote-service-intent-filter-in-androidmanifest>");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
You are on the right path, but if you add the intent action at the manifest, then you should also mention the package name while binding the service.
intent.setPackage("<remote service package name>");
Caution: To ensure that your app is secure, always use an explicit
intent when starting a Service and don't declare intent filters for
your services. Using an implicit intent to start a service is a
security hazard because you cannot be certain of the service that
responds to the intent, and the user cannot see which service starts.
Beginning with Android 5.0 (API level 21), the system throws an
exception if you call bindService() with an implicit intent.
https://developer.android.com/guide/components/services
Snipplet:
Here is how I connect to a remote service on a different application with the setClassName API.
Note: This approach does not need the intent action at the manifest file.
At Client activity.
/**
* Init Service
*/
private void initService() {
if (mSampleService == null) {
Intent i = new Intent();
// set intent action
i.setAction("com.hardian.sample.aidl.ISampleService");
// mention package name with service's canaonical name
i.setClassName("com.hardian.sample", "com.hardian.sample.aidl.SampleAidlService");
// binding to a remote service
bindService(i, mSampleServiceConnection, Service.BIND_AUTO_CREATE);
}
}
At Service
/**
* {#inheritDoc}
*/
#Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind called");
if (ISampleService.class.getName().equals(intent.getAction())) {
return mSampleServiceBinder;
}
return null;
}
I am trying to get the device token once the person has logged into the app and send back the device token to our server. The login process is done in async.
According to this post , you can get the token in activities but is it possible to get in async?
I have tried to put the code in async but does not work. (I changed the MainActivity.this to (Activity)mycontext, where I have defined mycontext as a Context)
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener( (Activitiy) mycontext, new OnSuccessListener<InstanceIdResult>() {
#Override
public void onSuccess(InstanceIdResult instanceIdResult) {
String newToken = instanceIdResult.getToken();
Log.e("newToken",newToken);
}
});
Thank you very much
thanks to Shashanth , the solution is just to get the token in one activity and pass that token value to async
I want to generate and store token everytime users login. I tried for my code below, but it always generate same token when I login with another account.
mAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(LoginActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
AlertDialog.dismiss();
if (!task.isSuccessful()) {
if (password.length() < 6) {
new SweetAlertDialog(LoginActivity.this, SweetAlertDialog.ERROR_TYPE)
.setTitleText("Oops...")
.setContentText("Enter minimum 6 charachters !! ")
.show();
} else {
passwordInput.setText("");
new SweetAlertDialog(LoginActivity.this, SweetAlertDialog.ERROR_TYPE)
.setTitleText("Oops...")
.setContentText("Authentication failed !!")
.show();
}
} else {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
FirebaseUser users = FirebaseAuth.getInstance().getCurrentUser();
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference("users/"+ users.getUid());
String token = FirebaseInstanceId.getInstance().getToken();
Log.e("tokenid",""+token);
mDatabase.child("token_id").setValue(token);
finish();
}
}
});
}
});
please help, thanks..
When using the following line of code:
String token = FirebaseInstanceId.getInstance().getToken();
You are getting the Firebase Instance ID token, which is also known as FCM token, on the client side. You need to know that in Firebase there two different tokens:
A FCM Token/Instance ID token that identifies an installation of a specific application on a specific user device. It doesn't identify in any way a particular user.
An Auth ID token identifies a specific user of a specific application. It doesn't identify in any way a particular device.
The two tokens are quite different and serve different purposes. Please see the official documentation for Android regarding on how to retrieve ID tokens on clients.
The identity of the user is remembered indefinitely (or until you sign out). But their credentials (or rather, whether their token is still valid) are re-checked every hour.
FCM generates a registration token for the client app instance, hence It may happen that you'll get the same token for different users in your app. You can use forceRefresh to generate a new token every time. Register new token everytime user logins to any device and save it in DB and update with a new token on new login this way you will have a new token for each user on every login (If this fits your requirement)
Here is a good answer to understand how it works Firebase FCM force onTokenRefresh() to be called
Use UUID.randomUUID().toString()
You can read more here.
java docs - and here =)
In an android app, which uses Parse, the login flow is like this...
We have our own logic to see if the user has entered the correct credentials. Once we verify that, signUpOrLoginOnParse() is called. Parse is used just to store data and handle sessions locally. Users can not access the api without the token.
private void signUpOrLogin(final String username, final String token) {
ParseUser user = new ParseUser();
user.setUsername(username);
user.setPassword(username);
user.signUpInBackground(new SignUpCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
// sign up success. ParseUser.getCurrentUser() populated
saveTokenToCloud(token);
} else if ("condition to check if the user already exists") {
// existing user, login.
ParseUser.logInInBackground(username, username, new LogInCallback() {
#Override
public void done(ParseUser parseUser, ParseException e) {
// login was successful, ParseUser.getCurrentUser() populated
saveTokenToCloud(token);
}
});
} else {
showProgress(false);
e.printStackTrace();
}
}
});
}
private void saveTokenToCloud(String token) {
// saving token to cloud
ParseUser user = ParseUser.getCurrentUser();
user.put("token", token); // THIS IS WHERE I GET NULL POINTER EXCEPTION
user.saveEventually();
// link installation to user.
ParseInstallation parseInstallation = ParseInstallation.getCurrentInstallation();
parseInstallation.put("user", user);
parseInstallation.saveEventually();
// Starting next activity
Intent i = new Intent(this, NextActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
finish();
}
All good when I first run the app. Once, logout button is pressed (contains - Parse.logoutInBackground()), it shows the LoginActivity (current one). When trying to log in, everything succeeds but I get a NullPointerException at line 3 of saveTokenToCloud().
It says - trying to invoke virtual method .put() over a null object reference.
But, isn't Parse.currentUser() already populated since this method is called from callback of methods that do that ?
It works after restarting the app. But then the same continues if logout is pressed.
After calling logoutInBackground , future calls to getCurrentUser() will return null.
You will need to initialize the user again.
signUpInBackground will create a new ParseUser on the server, and also persist the session on disk so that you can access the user using ParseUser.getCurrentUser().
However i am not sure you should be calling it every single time you log in.
Instead of calling the getCurrentuser inside the saveToken method you can pass the user to the saveTokenMethod from the done callback parameter.
Separate you logic in distinct methods for sign up and logIn. I suggest you check before calling signUp, and not abusing it every time you want to login
The onCreate of my userDatabase that extends ContentProvider is not properly called
Here is some of my userBatabase code:
public class userDatabase extends ContentProvider {
private MainDatabaseHelper mOpenHelper;
public userDatabase(){}
public static final class MainDatabaseHelper extends SQLiteOpenHelper{...}
#Override
public boolean onCreate() {
mOpenHelper = new MainDatabaseHelper(getContext());
return true;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
long id = mOpenHelper.getWritableDatabase().insert("Users", null, values);
return Uri.withAppendedPath(CONTENT_URI, "" + id);
}
...
}
In my main activity I call:
userDatabase cpDatabase = new userDatabase();
But when I try to call cpDatabase.insert(userDatabase.CONTENT_URI, values);
Everything crashes inside insert when mOpenHelper.getWritableDatabase().insert("Users", null, values); is called.
I found out that mOpenHelper.getWritableDatabase() was the issue, as it wont run even by itself, and then I found out this was because mOpenHelper was null.
I instantiate mOpenHelper in the constructor, so I figure its not running. A few log messages confirm this, when I call userDatabase cpDatabase = new userDatabase(); my log messages showed that the userDatabase() constructor ran normally, but the onCreate never ran, so the mOpenHelper never got instantiated.
(Note: with these log messages, I noticed that the constructor and the onCreate for my userDatabase got called when my app started. I have no idea why or where. I dont understand why this was run before i tried to create an instance. and even though it was run, mOpenHelper still wasn't initialized, and when i created an instance, the constructor ran but the onCreate didnt.)
What could possibly be happening, and how can I make my onCreate run?
Since you are using content providers, according to the documentation
This method is called for all registered content providers on the
application main thread at application launch time
And the way you try to use the content provider is seems wrong and,
You don't need to manually instantiate the content provider, once you made the request via the ContentResolver by passing the URI, the system inspects the authority of the given URI and passes the request to the content provider registered with the authority.
for example
getContentResolver().delete(uri, null, null);
Where the uri is, the full URI to query.
This tutorial will guide you in right direction