Skip to content

Instantly share code, notes, and snippets.

@mbarcia
Last active August 29, 2015 13:56
Show Gist options
  • Save mbarcia/9095996 to your computer and use it in GitHub Desktop.
Save mbarcia/9095996 to your computer and use it in GitHub Desktop.
package net.colaborativa.exampleapp;
// check your imports, this list may not be complete
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.OnAccountsUpdateListener;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
// This activity class (not included) would handle your login form.
// See onActivityResult() and createNewAccount(). An example can be seen at
// http://goo.gl/78CF69
import net.colaborativa.exampleapp.account.AuthenticatorActivity;
public class OfflineOnlineActivity extends Activity {
//
// request codes for activity result callbacks
//
private static final int AUTH_CONFIRM_CREDENTIALS = 1;
private static final int AUTH_CREATE_ACCOUNT = 2;
private boolean mStartedRetrievingToken;
/**
* Gets an auth token
*
* @param forceLogin true to have the app call the server's login endpoint
* @return String token
* @throws OfflineException
*/
public String retrieveToken(boolean forceLogin) throws OfflineException {
MyApiSession gs = MyApiSession.getInstance();
String token;
if (gs == null) {
// no session found, start processing, and returning empty token at the end
AccountManager am = AccountManager.get(ExampleApplication.getInstance());
Account[] accounts = am.getAccountsByType(AuthenticatorActivity.ACCOUNT_TYPE);
if (accounts.length > 0) {
// An account IS present in the system:
// Create the MyApiSession from the first android account
gs = MyApiSession.newSessionFor(accounts[0], "");
if (!isRetrievingToken()) {
Log.d(ExampleApplication.TAG, "Trying to establish a session " +
"upon initialization...");
// launch the token process if it wasn't already launched
token = triggerTokenRetrieval(gs, forceLogin);
// there is a chance that, if forceLogin is false, the token in
// AM's cache is still valid, ie. the app was stopped & started quickly.
// So return it!
if (!TextUtils.isEmpty(token)) {
Log.d(ExampleApplication.TAG, "Oh wow, the token " +
"in AM\'s cache is still valid, returning it...");
return token;
}
// otherwise, continue...
}
// Not throwing the offline exception,
// only returning an empty token for now
return "";
}
else {
// no account present in the system
Log.d(ExampleApplication.TAG, "No account found, empty token returned, " +
"creating new account...");
// launch the authenticator activity to create a new account
createNewAccount();
// and continue...
// return empty token for now
return "";
}
}
// gs is NOT null at this point, continuing with a non-null gs variable...
if ((gs.isOffline()) && !forceLogin) {
Log.d(ExampleApplication.TAG, "Offline, exception thrown.");
// Allright! If offline and we're not being told
// to re-login, then there's no alternative other than to...
throw new OfflineException();
}
else {
if (gs.isValid() && !forceLogin) {
// the session is valid, and we're not being told to re-login
// so everything's ok but we still have to "touch" it to update
// the last login time, to be + or - in sync with the service behavior
gs.touch();
// and finally, return the session token
return gs.getToken();
}
else {
// session is invalid, probably expired, or we are being told to re-login
// Note that, in neither case, the token in the AM's cache could be of help
if (!isRetrievingToken()) {
// if there's not any login in process
// invalidate the session prior than anything, as it is not valid
gs.invalidate();
Log.d(ExampleApplication.TAG, "Session invalidated, " +
"probably expired, empty token returned.");
Log.d(ExampleApplication.TAG, "Trying to re-establish " +
"the session automatically...");
// launch the token retrieval process, ignoring any
// token string possibly coming from AM's cache
triggerTokenRetrieval(gs, forceLogin);
// and, continue...
}
// Now, not throwing the offline exception,
// just returning an empty token
return "";
}
}
}
/**
* If forceLogin is false, peaks into AM's account cache for an existing token.
* If a token is found, and it passes MyApiSession validation, returns it. If it doesn't
* pass validation, makes sure to invalidate that token before going on to
* call getAuthToken(), which will then make an effective call to the service.
*
* @param gs The recipient session to retrieve the token for
*/
public String triggerTokenRetrieval(MyApiSession gs, boolean forceLogin) {
String token = null;
AccountManager am = AccountManager.get(ExampleApplication.getInstance());
token = am.peekAuthToken(gs.getAccount(), AuthenticatorActivity.ACCOUNT_TYPE);
if (!TextUtils.isEmpty(token)) {
// forceLogin as TRUE acts as a shortcut in the AND condition
// to always invalidate the cached token
if (gs.isValid() && !forceLogin) {
// can touch this
gs.revalidate(token);
return token;
}
else {
// this makes sure getAuthToken() doesn't return a bogus token later
am.invalidateAuthToken(AuthenticatorActivity.ACCOUNT_TYPE, token);
Log.i(ExampleApplication.TAG, "Invalidated AM token for account " + gs.getAccount().name);
}
}
//
// make an effective call to the login service
//
// first set the semaphore
setStartedRetrievingToken(true);
// then give some feedback to the user, ie.
// progress.setMessage("Logging you in...").show();
// use the Account Manager to fetch a token
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_TYPE, AuthenticatorActivity.ACCOUNT_TYPE);
am.getAuthToken(
gs.getAccount(),
AuthenticatorActivity.AUTHTOKEN_TYPE,
result,
this,
new GetAuthTokenCallback(),
null);
// returning empty string, freeing the main thread flow
return "";
}
class GetAuthTokenCallback implements AccountManagerCallback<Bundle> {
public void run(AccountManagerFuture<Bundle> result) {
Bundle bundle;
try {
// result comes always non-null
bundle = result.getResult();
Intent intent = (Intent) bundle.get(AccountManager.KEY_INTENT);
if (intent != null) {
// User input required
startActivityForResult(intent, AUTH_CONFIRM_CREDENTIALS);
} else {
// turn semaphore light to green
setStartedRetrievingToken(false);
// YAY! token!
String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
// WHOA! Next time retrieveToken() is called, it will have a valid
// token at hand.
MyApiSession gs = MyApiSession.getInstance();
// calling revalidate
gs.revalidate(token);
// give some feedback to the user here
}
} catch (Exception e) {
// make sure you dismiss here any progress dialog you have open
// then log the event
Log.e(ExampleApplication.TAG, e.getMessage());
// turn semaphore light to green
setStartedRetrievingToken(false);
// update the session object
gs.setOfflineStatus(true);
gs.invalidate();
// here you should provide some further feedback to the user before returning
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// this calls any fragment's code first
super.onActivityResult(requestCode, resultCode, data);
// Check which request we're responding to
if (requestCode == AUTH_CONFIRM_CREDENTIALS) {
// set semaphore light to green
setStartedRetrievingToken(false);
// Make sure the request was successful
if (resultCode != RESULT_OK) {
// go offline here
}
else {
// you're online now, do what you have to
}
}
else if (requestCode == AUTH_CREATE_ACCOUNT) {
// set semaphore light to green
setStartedRetrievingToken(false);
// Make sure the request was successful
if (resultCode != RESULT_OK) {
// go offline here
}
else {
// you're online now, do what you have to
}
}
}
/**
* Create a new android account by means of the authenticator activity
*/
private void createNewAccount() {
Intent intent = new Intent();
Bundle options = new Bundle();
options.putString(AuthenticatorActivity.ARG_ACCOUNT_TYPE,
AuthenticatorActivity.ACCOUNT_TYPE);
options.putBoolean(AuthenticatorActivity.ARG_IS_NEW_ACCOUNT,
true);
intent.putExtras(options);
ComponentName component = new ComponentName(
AuthenticatorActivity.class.getPackage().getName(),
AuthenticatorActivity.class.getName());
intent.setComponent(component);
startActivityForResult(intent, AUTH_CREATE_ACCOUNT);
}
private void setStartedRetrievingToken(boolean value) {
mStartedRetrievingToken = value;
}
private boolean isRetrievingToken() {
return (mStartedRetrievingToken == true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment