Last active
December 10, 2015 13:18
-
-
Save alexjlockwood/4440089 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.accounts.Account; | |
import android.accounts.AccountManager; | |
import android.annotation.TargetApi; | |
import android.content.Context; | |
import android.content.SharedPreferences; | |
import android.content.SharedPreferences.Editor; | |
import android.os.Build; | |
import android.preference.PreferenceManager; | |
import com.google.android.gms.auth.GoogleAuthUtil; | |
public class AccountUtils { | |
private static final String KEY_ACCOUNT_NAME = "account_name"; | |
public static Account getGoogleAccountByName(Context ctx, String accountName) { | |
if (accountName != null) { | |
AccountManager am = AccountManager.get(ctx); | |
Account[] accounts = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); | |
for (Account account : accounts) { | |
if (accountName.equals(account.name)) { | |
return account; | |
} | |
} | |
} | |
return null; | |
} | |
public static String getAccountName(Context ctx) { | |
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); | |
return prefs.getString(KEY_ACCOUNT_NAME, null); | |
} | |
@TargetApi(Build.VERSION_CODES.GINGERBREAD) | |
public static void setAccountName(Context ctx, String accountName) { | |
Editor editor = PreferenceManager.getDefaultSharedPreferences(ctx).edit(); | |
editor.putString(KEY_ACCOUNT_NAME, accountName); | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { | |
editor.apply(); | |
} else { | |
editor.commit(); | |
} | |
} | |
@TargetApi(Build.VERSION_CODES.GINGERBREAD) | |
public static void removeAccount(Context ctx) { | |
Editor editor = PreferenceManager.getDefaultSharedPreferences(ctx).edit(); | |
editor.remove(KEY_ACCOUNT_NAME); | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { | |
editor.apply(); | |
} else { | |
editor.commit(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.accounts.Account; | |
import android.accounts.AccountManager; | |
import android.app.Activity; | |
import android.content.Intent; | |
import android.os.Bundle; | |
import android.widget.Toast; | |
import com.google.android.gms.auth.GoogleAuthUtil; | |
import com.google.android.gms.common.AccountPicker; | |
import com.google.android.gms.common.ConnectionResult; | |
import com.google.android.gms.common.GooglePlayServicesUtil; | |
public class AuthActivity extends Activity { | |
static final int REQUEST_CODE_RECOVER_PLAY_SERVICES = 1001; | |
static final int REQUEST_CODE_PICK_ACCOUNT = 1002; | |
static final int REQUEST_CODE_RECOVER_AUTH = 1003; | |
private AuthTask mAuthTask; | |
private Request mLastRequest; | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.main); | |
} | |
@Override | |
protected void onResume() { | |
super.onResume(); | |
if (checkPlayServices() && checkUserAccount()) { | |
// Then we're good to go! | |
} | |
} | |
private boolean checkPlayServices() { | |
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); | |
if (status != ConnectionResult.SUCCESS) { | |
if (GooglePlayServicesUtil.isUserRecoverableError(status)) { | |
showErrorDialog(status); | |
} else { | |
Toast.makeText(this, "This device is not supported.", Toast.LENGTH_LONG).show(); | |
finish(); | |
} | |
return false; | |
} | |
return true; | |
} | |
void showErrorDialog(int code) { | |
GooglePlayServicesUtil.getErrorDialog(code, this, REQUEST_CODE_RECOVER_PLAY_SERVICES).show(); | |
} | |
private boolean checkUserAccount() { | |
String accountName = AccountUtils.getAccountName(this); | |
if (accountName == null) { | |
// Then the user was not found in the SharedPreferences. Either the | |
// application deliberately removed the account, or the application's | |
// data has been forcefully erased. | |
showAccountPicker(); | |
return false; | |
} | |
Account account = AccountUtils.getGoogleAccountByName(this, accountName); | |
if (account == null) { | |
// Then the account has since been removed. | |
AccountUtils.removeAccount(this); | |
showAccountPicker(); | |
return false; | |
} | |
return true; | |
} | |
private void showAccountPicker() { | |
Intent pickAccountIntent = AccountPicker.newChooseAccountIntent(null, null, | |
new String[] { GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE }, true, null, null, null, null); | |
startActivityForResult(pickAccountIntent, REQUEST_CODE_PICK_ACCOUNT); | |
} | |
@Override | |
protected void onActivityResult(int requestCode, int resultCode, Intent data) { | |
switch (requestCode) { | |
case REQUEST_CODE_RECOVER_PLAY_SERVICES: | |
if (resultCode == RESULT_CANCELED) { | |
Toast.makeText(this, "Google Play Services must be installed and up-to-date.", | |
Toast.LENGTH_SHORT).show(); | |
finish(); | |
} | |
return; | |
case REQUEST_CODE_PICK_ACCOUNT: | |
if (resultCode == RESULT_OK) { | |
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); | |
AccountUtils.setAccountName(this, accountName); | |
} else if (resultCode == RESULT_CANCELED) { | |
Toast.makeText(this, "This application requires a Google account.", Toast.LENGTH_SHORT) | |
.show(); | |
finish(); | |
} | |
return; | |
case REQUEST_CODE_RECOVER_AUTH: | |
if (resultCode == RESULT_OK) { | |
if (mLastRequest != null) { | |
// We have recovered, so retry the old request. | |
mAuthTask = new AuthTask(this, mLastRequest); | |
mAuthTask.execute(); | |
} | |
} else if (resultCode == RESULT_CANCELED) { | |
// TODO: Choose how your application should react in the case | |
// that the user has decided not to attempt auth recovery. | |
} | |
return; | |
} | |
super.onActivityResult(requestCode, resultCode, data); | |
} | |
@Override | |
protected void onDestroy() { | |
if (mAuthTask != null) { | |
mAuthTask.cancel(true); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.alexjlockwood.gcmdemo; | |
import java.io.IOException; | |
import android.os.AsyncTask; | |
import android.util.Log; | |
import com.alexjlockwood.gcmdemo.http.Request; | |
import com.alexjlockwood.gcmdemo.http.Response; | |
import com.google.android.gms.auth.GoogleAuthException; | |
import com.google.android.gms.auth.GoogleAuthUtil; | |
import com.google.android.gms.auth.GooglePlayServicesAvailabilityException; | |
import com.google.android.gms.auth.UserRecoverableAuthException; | |
public class AuthTask extends AsyncTask<Void, Void, Response> { | |
private static final String TAG = "AuthTask"; | |
private AuthActivity mActivity; | |
private Request mRequest; | |
public AuthTask(AuthActivity activity, Request request) { | |
mActivity = activity; | |
mRequest = request; | |
} | |
@Override | |
protected Response doInBackground(Void... params) { | |
try { | |
String token = GoogleAuthUtil.getToken(mActivity, mRequest.getAccountName(), | |
mRequest.getScope()); | |
return NetworkUtils.doPost(mActivity, mRequest, token); | |
} catch (GooglePlayServicesAvailabilityException e) { | |
// GooglePlayServices.apk is either old, disabled, or not present. | |
showErrorDialog(e.getConnectionStatusCode()); | |
} catch (UserRecoverableAuthException e) { | |
// The application hasn't been authorized by the user for access to the | |
// scope. Launch an Activity for a result. | |
mActivity.startActivityForResult(e.getIntent(), AuthActivity.REQUEST_CODE_RECOVER_AUTH); | |
// TODO: ensure that this is only done once per authenticated request! | |
} catch (GoogleAuthException e) { | |
// Failure. The call is not expected to ever succeed so it should not be | |
// retried. | |
Log.e(TAG, "Unrecoverable error: " + e.getMessage()); | |
} catch (IOException e) { | |
Log.e(TAG, "IOException: " + e.getMessage()); | |
// Network or server error, the call is expected to succeed if you try | |
// again later. | |
// TODO: Implement a simple loop here that will retry the request | |
// using exponential backoff. | |
} | |
return null; | |
} | |
@Override | |
protected void onPostExecute(Response response) { | |
// TODO: Choose how your Activity should respond to response | |
// successes and response failures. | |
} | |
private void showErrorDialog(final int errorCode) { | |
mActivity.runOnUiThread(new Runnable() { | |
@Override | |
public void run() { | |
mActivity.showErrorDialog(errorCode); | |
} | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.net.HttpURLConnection; | |
import java.net.URL; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import android.content.Context; | |
import com.google.android.gms.auth.GoogleAuthUtil; | |
public final class NetworkUtils { | |
public static Response doPost(Context ctx, Request request, String token) throws IOException { | |
URL url = request.getUrl(); | |
Map<String, List<String>> headers = request.getHeaders(); | |
Map<String, String> bodyParams = request.getBodyParams(); | |
byte[] body = constructBody(bodyParams); | |
HttpURLConnection conn = null; | |
try { | |
conn = (HttpURLConnection) url.openConnection(); | |
addHeaders(conn, token, headers); | |
conn.addRequestProperty("Authorization", "OAuth " + token); | |
conn.setDoOutput(true); | |
conn.setUseCaches(false); | |
conn.setFixedLengthStreamingMode(body.length); | |
conn.setRequestMethod("POST"); | |
// post the request | |
OutputStream out = conn.getOutputStream(); | |
out.write(body); | |
out.close(); | |
// handle the response | |
BufferedInputStream in = new BufferedInputStream(conn.getInputStream()); | |
int status = conn.getResponseCode(); | |
if (status == HttpURLConnection.HTTP_OK) { | |
return new Response(status, conn.getHeaderFields(), readStream(in)); | |
} else if (status == HttpURLConnection.HTTP_UNAUTHORIZED) { | |
// The token has expired. Invalidate and get a new one. | |
GoogleAuthUtil.invalidateToken(ctx, token); | |
return doPost(ctx, request, token); | |
} else { | |
BufferedInputStream err = new BufferedInputStream(conn.getErrorStream()); | |
return new Response(status, conn.getHeaderFields(), readStream(err)); | |
} | |
} finally { | |
if (conn != null) { | |
conn.disconnect(); | |
} | |
} | |
} | |
public static void addHeaders(HttpURLConnection conn, String token, | |
Map<String, List<String>> headers) { | |
if (headers != null) { | |
for (String header : headers.keySet()) { | |
for (String value : headers.get(header)) { | |
conn.addRequestProperty(header, value); | |
} | |
} | |
} | |
} | |
public static byte[] constructBody(Map<String, String> bodyParams) { | |
// Constructs the POST body using the parameters | |
StringBuilder bodyBuilder = new StringBuilder(); | |
Iterator<Entry<String, String>> iterator = bodyParams.entrySet().iterator(); | |
while (iterator.hasNext()) { | |
Entry<String, String> param = iterator.next(); | |
bodyBuilder.append(param.getKey()).append('=').append(param.getValue()); | |
if (iterator.hasNext()) { | |
bodyBuilder.append('&'); | |
} | |
} | |
return bodyBuilder.toString().getBytes(); | |
} | |
public static byte[] readStream(InputStream in) throws IOException { | |
byte[] buf = new byte[1024]; | |
int count = 0; | |
ByteArrayOutputStream out = new ByteArrayOutputStream(1024); | |
while ((count = in.read(buf)) != -1) { | |
out.write(buf, 0, count); | |
} | |
in.close(); | |
return out.toByteArray(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.net.URL; | |
import java.util.List; | |
import java.util.Map; | |
public class Request { | |
private URL mUrl; | |
private Map<String, List<String>> mHeaders; | |
private String mAccountName; | |
private Map<String, String> mBodyParams; // non-null if POST | |
private String mScope; | |
// private ResponseHandler handler; | |
public URL getUrl() { | |
return mUrl; | |
} | |
public Map<String, List<String>> getHeaders() { | |
return mHeaders; | |
} | |
public String getAccountName() { | |
return mAccountName; | |
} | |
public Map<String, String> getBodyParams() { | |
return mBodyParams; | |
} | |
public String getScope() { | |
return mScope; | |
} | |
private Request(URL url, Map<String, List<String>> headers, Map<String, String> bodyParams, | |
String accountName, String scope) { | |
mUrl = url; | |
mHeaders = headers; | |
mAccountName = accountName; | |
mBodyParams = bodyParams; | |
mScope = scope; | |
} | |
public static Request makeGet(URL url, Map<String, List<String>> headers, String accountName, | |
String scope) { | |
return new Request(url, headers, null, accountName, scope); | |
} | |
public static Request makePost(URL url, Map<String, List<String>> headers, String accountName, | |
Map<String, String> body, String scope) { | |
return new Request(url, headers, body, accountName, scope); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
import java.util.Map; | |
public class Response { | |
private int mStatusCode; | |
private Map<String, List<String>> mHeaders; | |
private byte[] mBody; | |
public int getStatusCode() { | |
return mStatusCode; | |
} | |
public Map<String, List<String>> getHeaders() { | |
return mHeaders; | |
} | |
public byte[] getBody() { | |
return mBody; | |
} | |
public Response(int statusCode, Map<String, List<String>> headers, byte[] body) { | |
mStatusCode = statusCode; | |
mHeaders = headers; | |
mBody = body; | |
} | |
public Response(int statusCode, Map<String, List<String>> headers, String body) { | |
mStatusCode = statusCode; | |
mHeaders = headers; | |
mBody = body.getBytes(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Why do you send the same invalid token after indicating its expiration in https://gist.github.com/alexjlockwood/4440089#file-networkutils-java-L49 ?