Skip to content

Instantly share code, notes, and snippets.

@brainail
Created April 7, 2015 22:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brainail/d1bc6a9d58db085cbd67 to your computer and use it in GitHub Desktop.
Save brainail/d1bc6a9d58db085cbd67 to your computer and use it in GitHub Desktop.
Drive REST API, authorization flow
// Base auth task
public abstract class AbstractAuthTask extends AsyncTask<Void, Void, Void> {
protected final AuthorizationFlow mAuthFlow;
AbstractAuthTask(final AuthorizationFlow authFlow) {
mAuthFlow = authFlow;
}
}
// Base fetch task
public abstract class AbstractFetchTokenTask extends AbstractAuthTask {
protected final String mScope;
protected final String mEmail;
AbstractFetchTokenTask(final AuthorizationFlow authFlow, final String email, final String scope) {
super(authFlow);
mScope = scope;
mEmail = email;
}
// Get a authentication token if one is not available. If the error is not recoverable then
// it displays the error message on parent activity right away.
protected abstract String fetchToken() throws IOException;
@Override
protected Void doInBackground(final Void ... params) {
try {
fetchUserInfo();
} catch (IOException exception) {
onError(exception);
ToolUI.showToast(mAuthFlow.getActivity(), R.string.auth_flow_fetch_token_io_error);
}
return null;
}
private void fetchUserInfo() throws IOException {
final String authToken = fetchToken();
if (!TextUtils.isEmpty(authToken)) {
mAuthFlow.onAuthTokenFetched(authToken);
}
}
protected void onError(final Exception error) {
Log(error);
}
}
// Main flow
public class AuthorizationFlow {
public interface Callback {
public void onAuthSucceed(final AuthUserInfo userInfo);
}
private WeakReference<Activity> mActivityRef;
private String mEmail;
private AuthorizationFlow() {}
public AuthorizationFlow(final Activity activity) {
mActivityRef = new WeakReference<>(activity);
// Get stored email from preferences
mEmail = SettingsManager.getInstance().retrieveAccountEmail();
}
public AuthorizationFlow withActivity(final Activity activity) {
mActivityRef = new WeakReference<>(activity);
return this;
}
public AuthorizationFlow withEmail(final String email) {
Log("An email has set: %s", email);
mEmail = email;
return this;
}
public Activity getActivity() {
return mActivityRef.get();
}
public String getEmail() {
return mEmail;
}
// Unauthorizes our user
public void unauthorize() {
SettingsManager.getInstance().removeAccountDetails();
ToolTasks.safeExecuteAuthTask(formUnauthorizeTask());
mEmail = null;
}
// Authorizes our user
public void authorize() {
final Activity activity = getActivity();
final int statusCode = isGooglePlayServicesAvailable(activity);
if (ConnectionResult.SUCCESS == statusCode) {
Log("Success connection result to Gps");
grabUser();
} else if (GooglePlayServicesUtil.isUserRecoverableError(statusCode)) {
Log("User recoverable error connection result to Gps, statusCode: %d", statusCode);
GooglePsErrorDialog.show(activity, statusCode);
} else if (ConnectionResult.DEVELOPER_ERROR != statusCode) {
Log("Unrecoverable error: %d", statusCode);
ToolUI.showToast(activity, R.string.auth_flow_device_suitability);
}
}
private static int isGooglePlayServicesAvailable(final Activity activity) {
if (null != activity) {
return GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity);
} else {
return ConnectionResult.DEVELOPER_ERROR;
}
}
private void grabUser() {
Log("Grabbing user account ...");
if (!TextUtils.isEmpty(SettingsManager.getInstance().retrieveAccountEmail())) {
Log("User is already authorized");
ToolUI.showToast(getActivity(), R.string.auth_flow_sign_in_twice);
} else if (TextUtils.isEmpty(mEmail)) {
Log("No email address, try to pick user");
pickUser();
} else {
if (ToolNetwork.hasNetworkConnection(getActivity())) {
ToolTasks.safeExecuteAuthTask(formAuthorizeTask());
} else {
Log("No network connection available");
ToolUI.showToast(getActivity(), R.string.auth_flow_no_network);
}
}
}
private void pickUser() {
Log("Picking user account ...");
final Activity activity = getActivity();
if (null != activity) {
final Intent intent = ToolAuth.formChooseGoogleAccountsIntent();
activity.startActivityForResult(intent, AuthRequestCode.PICK_ACCOUNT);
}
}
// Provides the user a response UI when an exception occurs.
public void handleError(final Exception exception) {
Log("An exception has occurred: %s", exception);
final Activity activity = getActivity();
if (null == activity) return;
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (exception 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
final int statusCode = ((GooglePlayServicesAvailabilityException) exception).getConnectionStatusCode();
final int requestCode = AuthRequestCode.RECOVER_FROM_PLAY_SERVICES_ERROR;
GooglePsErrorDialog.show(activity, statusCode, requestCode);
} else if (exception 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.
final Intent intent = ((UserRecoverableAuthException) exception).getIntent();
activity.startActivityForResult(intent, AuthRequestCode.RECOVER_FROM_AUTH_ERROR);
}
}
});
}
public boolean handleOnActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (AuthRequestCode.PICK_ACCOUNT == requestCode) {
if (Activity.RESULT_OK == resultCode) {
Log("RESULT_OK for PICK_ACCOUNT request code");
withEmail(data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)).grabUser();
} else if (Activity.RESULT_CANCELED == resultCode) {
Log("RESULT_CANCELED for PICK_ACCOUNT request code");
ToolUI.showToast(getActivity(), R.string.auth_flow_pick_account);
}
return true;
} else if (AuthRequestCode.isRecoverableError(requestCode)) {
Log("RESULT_OK for error recover request code");
handleAuthorizeErrorResultAfterRecoverableError(resultCode, data);
return true;
}
return false;
}
private void handleAuthorizeErrorResultAfterRecoverableError(final int resultCode, final Intent data) {
if (null == data) {
Log("No data after error recover");
ToolUI.showToast(getActivity(), R.string.auth_flow_unknown_error);
} else if (Activity.RESULT_OK == resultCode) {
Log("RESULT_OK after error recover");
ToolTasks.safeExecuteAuthTask(formAuthorizeTask());
} else if (Activity.RESULT_CANCELED == resultCode) {
Log("RESULT_CANCELED after error recover. User rejected authorization");
ToolUI.showToast(getActivity(), R.string.auth_flow_rejected);
} else {
Log("Unknown error after error recover");
ToolUI.showToast(getActivity(), R.string.auth_flow_unknown_error);
}
}
void onAuthTokenFetched(final String authToken) {
final Activity activity = getActivity();
if (activity instanceof Callback) {
((Callback) activity).onAuthSucceed(new AuthUserInfo(mEmail));
}
}
private FetchTokenTask formAuthorizeTask() {
return new FetchTokenTask(this, mEmail, ToolDriveScope.formFullScope());
}
private ClearTokenTask formUnauthorizeTask() {
return new ClearTokenTask(this, mEmail);
}
}
// Codes
public final class AuthRequestCode {
private static final int BASE = 1000;
public static final int PICK_ACCOUNT = BASE;
public static final int RECOVER_FROM_AUTH_ERROR = BASE + 1;
public static final int RECOVER_FROM_PLAY_SERVICES_ERROR = BASE + 2;
public static boolean isRecoverableError(final int code) {
boolean result = RECOVER_FROM_AUTH_ERROR == code;
result |= RECOVER_FROM_PLAY_SERVICES_ERROR == code;
return result;
}
}
// Info about user
public class AuthUserInfo {
public String email;
public AuthUserInfo(final String email) {
this.email = email;
}
}
// Task to clear token/user
public class ClearTokenTask extends AbstractAuthTask {
protected final String mEmail;
ClearTokenTask(final AuthorizationFlow authFlow, final String email) {
super(authFlow);
mEmail = email;
}
@Override
protected Void doInBackground(final Void ... params) {
try {
clearToken();
} catch (Exception exception) {
Log("Something was wrong while clearing token for account");
}
return null;
}
private void clearToken() throws IOException, GoogleAuthException {
if (!TextUtils.isEmpty(mEmail)) {
final String token = GoogleAuthUtil.getToken(JApplication.appContext(), mEmail, ToolDriveScope.formFullScope());
if (!TextUtils.isEmpty(token)) {
GoogleAuthUtil.clearToken(<Application Context>, token);
}
}
}
}
// Task to fetch token/user
public class FetchTokenTask extends AbstractFetchTokenTask {
public FetchTokenTask(final AuthorizationFlow authFlow, final String email, final String scope) {
super(authFlow, email, scope);
}
@Override
protected String fetchToken() throws IOException {
try {
return GoogleAuthUtil.getToken(<Application Context>, mEmail, mScope);
} catch (UserRecoverableAuthException exception) {
// GooglePlayServices.apk is either old, disabled, or not present, which is
// recoverable, so we need to show the user some UI through the activity.
mAuthFlow.handleError(exception);
} catch (GoogleAuthException exception) {
onError(exception);
ToolUI.showToast(mAuthFlow.getActivity(), R.string.auth_flow_device_suitability);
}
return null;
}
}
// Dialog to show Gps errorsse
public class GooglePsErrorDialog extends DialogFragment {
public static final String MANAGER_TAG = "<Application Id>.tag#google.play.services.error.dialog";
public static class Args {
public static final String STATUS_CODE = "<Application Id>.arg#status.code";
public static final String REQUEST_CODE = "<Application Id>.arg#request.code";
}
private int mStatusCode;
private int mRequestCode;
public static void show(final Activity activity, final int statusCode, final int requestCode) {
if (null != activity) {
newInstance(statusCode, requestCode).show(activity.getFragmentManager(), MANAGER_TAG);
}
}
public static void show(final Activity activity, final int statusCode) {
show(activity, statusCode, 0);
}
public static GooglePsErrorDialog newInstance(final int statusCode, final int requestCode) {
final GooglePsErrorDialog dialog = new GooglePsErrorDialog();
final Bundle args = new Bundle();
args.putInt(Args.STATUS_CODE, statusCode);
args.putInt(Args.REQUEST_CODE, requestCode);
dialog.setArguments(args);
return dialog;
}
public static GooglePsErrorDialog newInstance(final int statusCode) {
return newInstance(statusCode, 0);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
mStatusCode = args.getInt(Args.STATUS_CODE, ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED);
mRequestCode = args.getInt(Args.REQUEST_CODE, 0);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Workaround for
// android.view.WindowLeaked:
// Activity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@41522b80
// that was originally added here (Dialog#show())
final Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(mStatusCode, getActivity(), mRequestCode);
return null != errorDialog ? errorDialog : super.onCreateDialog(savedInstanceState);
}
}
// Accounts chooser
public final class ToolAuth {
public final static String [] ACCOUNT_TYPES = {"com.google"};
public static Intent formChooseGoogleAccountsIntent() {
return AccountPicker.newChooseAccountIntent(null, null, ACCOUNT_TYPES, true, null, null, null, null);
}
}
// Scopes to request from Google Drive
public final class ToolDriveScope {
public static final String USER_INFO = "https://www.googleapis.com/auth/userinfo.profile";
public static final String APP_DATA = DriveScopes.DRIVE_APPDATA;
public static String formFullScope() {
final StringBuilder fullScope = new StringBuilder("oauth2:");
fullScope.append(USER_INFO).append(ToolStrings.SPACE);
fullScope.append(APP_DATA);
return fullScope.toString();
}
}
public final class ToolTasks {
// Safely executes async task.
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static void safeExecuteAuthTask(final AbstractAuthTask authTask) {
try {
if (!Sdk.isSdkSupported(Sdk.HONEYCOMB)) {
authTask.execute();
} else {
authTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} catch (Exception exception) {
Log("Smth was wrong while executing ad task.\nException: %s", exception.getLocalizedMessage());
}
}
}
public final class ToolUI {
public static void showToast(final Activity activity, final String message) {
// Show default Android toast or snackbar from the Material design ...
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment