Stuck in Connection Failed loop with GoogleApiClient - android

I am trying to implement the com.google.android.gms.common.api.GoogleApiClient in my project.
The problem is that every time I try to connect, I get the call back to the onConnectionFailed listener with a pending intent that I execute. On a clean install, the very first pending intent will launch an account select screen. This is expected. Every subsequent restart of the app will bypass the account selection, unless the app's data is cleared in the Application Manager.
After the account-select screen, the signing-in screen will appear breifly. It never signs in though. The onActivityResult will be called after the signing-in screen flashes, which tries to connect the client. It doesn't connect, and calls the onConnectionFailed listener again.
If I keep trying to execute the intents, I get stuck in loop with the signing in screen breifly appearing, then disappearing, but never connecting or getting signed in. The ConnectionResult.toString indicates "Sign_In_Required", and returns an error code of 4 (the same as the Sign_In_Required constant.
On the API console, I've implemented an Ouath 2.0 client ID, and a public API access key for android applications. Notably, my app works using the older com.google.api.services.drive.Drive client.
As for my code:
I've tried using two different implementations here and here. I tried to implement the second example making as few changes as possible. It is reproduced below:
public class MainActivity extends Activity implements ConnectionCallbacks,
OnConnectionFailedListener {
private static final String TAG = "android-drive-quickstart";
private static final int REQUEST_CODE_CAPTURE_IMAGE = 1;
private static final int REQUEST_CODE_CREATOR = 2;
private static final int REQUEST_CODE_RESOLUTION = 3;
private GoogleApiClient mGoogleApiClient;
private Bitmap mBitmapToSave;
/**
* Create a new file and save it to Drive.
*/
private void saveFileToDrive() {
// Start by creating a new contents, and setting a callback.
Log.i(TAG, "Creating new contents.");
final Bitmap image = mBitmapToSave;
Drive.DriveApi.newContents(mGoogleApiClient).setResultCallback(new ResultCallback<DriveApi.ContentsResult>() {
#Override
public void onResult(DriveApi.ContentsResult result) {
// If the operation was not successful, we cannot do anything
// and must
// fail.
if (!result.getStatus().isSuccess()) {
Log.i(TAG, "Failed to create new contents.");
return;
}
// Otherwise, we can write our data to the new contents.
Log.i(TAG, "New contents created.");
// Get an output stream for the contents.
OutputStream outputStream = result.getContents().getOutputStream();
// Write the bitmap data from it.
ByteArrayOutputStream bitmapStream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.PNG, 100, bitmapStream);
try {
outputStream.write(bitmapStream.toByteArray());
} catch (IOException e1) {
Log.i(TAG, "Unable to write file contents.");
}
// Create the initial metadata - MIME type and title.
// Note that the user will be able to change the title later.
MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
.setMimeType("image/jpeg").setTitle("Android Photo.png").build();
// Create an intent for the file chooser, and start it.
IntentSender intentSender = Drive.DriveApi
.newCreateFileActivityBuilder()
.setInitialMetadata(metadataChangeSet)
.setInitialContents(result.getContents())
.build(mGoogleApiClient);
try {
startIntentSenderForResult(
intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0);
} catch (SendIntentException e) {
Log.i(TAG, "Failed to launch file chooser.");
}
}
});
}
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
// Create the API client and bind it to an instance variable.
// We use this instance as the callback for connection and connection
// failures.
// Since no account name is passed, the user is prompted to choose.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
// Connect the client. Once connected, the camera is launched.
mGoogleApiClient.connect();
}
#Override
protected void onPause() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onPause();
}
#Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_CODE_CAPTURE_IMAGE:
// Called after a photo has been taken.
if (resultCode == Activity.RESULT_OK) {
// Store the image data as a bitmap for writing later.
mBitmapToSave = (Bitmap) data.getExtras().get("data");
}
break;
case REQUEST_CODE_CREATOR:
// Called after a file is saved to Drive.
if (resultCode == RESULT_OK) {
Log.i(TAG, "Image successfully saved.");
mBitmapToSave = null;
// Just start the camera again for another photo.
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
REQUEST_CODE_CAPTURE_IMAGE);
}
break;
}
}
#Override
public void onConnectionFailed(ConnectionResult result) {
// Called whenever the API client fails to connect.
Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
if (!result.hasResolution()) {
// show the localized error dialog.
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization
// dialog is displayed to the user.
try {
result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
} catch (SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected.");
if (mBitmapToSave == null) {
// This activity has no UI of its own. Just start the camera.
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
REQUEST_CODE_CAPTURE_IMAGE);
return;
}
saveFileToDrive();
}
#Override
public void onConnectionSuspended(int cause) {
Log.i(TAG, "GoogleApiClient connection suspended");
}
}

This happens because after the first login/authorization android keeps using the same default account parameters. If you want to avoid the loop and make sure the picker shows again you must clear completely the default account by calling Plus.AccountApi.clearDefaultAccount(mGoogleApiClient) before reconnecting again.
To achieve this, you must add the Plus.API scope to the GoogleApiClient builder:
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addApi(Plus.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
And then you can clear the default account before rebuilding the api client and connecting to a different account (rebuilding the api client when changing accounts avoids problems):
// if the api client existed, we terminate it
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
mGoogleApiClient.disconnect();
}
// build new api client to avoid problems reusing it
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addApi(Plus.API)
.addScope(Drive.SCOPE_FILE)
.setAccountName(account)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
No additional permissions or api activations are needed for using the Plus.API scope this way. I hope this helps with your problem.

It is a tough one, since I don't have time to completely re-run and analyze your code. And without running it, I don't see anything obvious.
But, since I have this stuff up and running in my app, I'd like to help. Unfortunately the Google Play services connection and authorization code is scattered all over my app's fragments and activities. So, I made an attempt to create a dummy activity and pull all the stuff in it. By 'all the stuff' I mean the account manager wrapper (GA) and associated account picker code.
The result is some 300 lines of gibberish that may work, but I don't make any claims it will. Take a look and good luck.
package com.......;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
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;
import com.google.android.gms.common.api.GoogleApiClient;
public class GooApiClient extends Activity implements
GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
private static final String DIALOG_ERROR = "dialog_error";
private static final String REQUEST_CODE = "request_code";
private static final int REQ_ACCPICK = 1;
private static final int REQ_AUTH = 2;
private static final int REQ_RECOVER = 3;
private GoogleApiClient mGooApiClient;
private boolean mIsInAuth; //block re-entrancy
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (checkPlayServices() && checkUserAccount()) {
gooInit();
gooConnect(true);
}
}
#Override
public void onConnected(Bundle bundle) {
Log.d("_", "connected");
}
#Override
public void onConnectionSuspended(int i) { }
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.d("_", "failed " + result.hasResolution());
if (!mIsInAuth) {
if (result.hasResolution()) {
try {
mIsInAuth = true;
result.startResolutionForResult(this, REQ_AUTH);
} catch (IntentSender.SendIntentException e) {
suicide("authorization fail");
}
} else {
suicide("authorization fail");
}
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent it) {
Log.d("_", "activity result " + requestCode + " " + resultCode);
switch (requestCode) {
case REQ_AUTH: case REQ_RECOVER: {
mIsInAuth = false;
if (resultCode == Activity.RESULT_OK) {
gooConnect(true);
} else if (resultCode == RESULT_CANCELED) {
suicide("authorization fail");
}
return;
}
case REQ_ACCPICK: { // return from account picker
if (resultCode == Activity.RESULT_OK && it != null) {
String emil = it.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (GA.setEmil(this, emil) == GA.CHANGED) {
gooInit();
gooConnect(true);
}
} else if (GA.getActiveEmil(this) == null) {
suicide("selection failed");
}
return;
}
}
super.onActivityResult(requestCode, resultCode, it); // DO NOT REMOVE
}
private boolean checkPlayServices() {
Log.d("_", "check PS");
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (status != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(status)) {
mIsInAuth = true;
errorDialog(status, LstActivity.REQ_RECOVER);
} else {
suicide("play services failed");
}
return false;
}
return true;
}
private boolean checkUserAccount() {
String emil = GA.getActiveEmil(this);
Account accnt = GA.getPrimaryAccnt(this, true);
Log.d("_", "check user account " + emil + " " + accnt);
if (emil == null) { // no emil (after install)
if (accnt == null) { // multiple or no accounts available, go pick one
accnt = GA.getPrimaryAccnt(this, false);
Intent it = AccountPicker.newChooseAccountIntent(accnt, null,
new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null
);
this.startActivityForResult(it, LstActivity.REQ_ACCPICK);
return false; //--------------------->>>
} else { // there's only one goo account registered with the device, skip the picker
GA.setEmil(this, accnt.name);
}
// UNLIKELY BUT POSSIBLE, emil's OK, but the account have been removed since (through settings)
} else {
accnt = GA.getActiveAccnt(this);
if (accnt == null) {
accnt = GA.getPrimaryAccnt(this, false);
Intent it = AccountPicker.newChooseAccountIntent(accnt, null,
new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null
);
this.startActivityForResult(it, LstActivity.REQ_ACCPICK);
return false; //------------------>>>
}
}
return true;
}
private void gooInit(){
String emil = GA.getActiveEmil(this);
Log.d("_", "goo init " + emil);
if (emil != null){
mGooApiClient = new GoogleApiClient.Builder(this)
.setAccountName(emil).addApi(com.google.android.gms.drive.Drive.API)
.addScope(com.google.android.gms.drive.Drive.SCOPE_FILE)
.addConnectionCallbacks(this).addOnConnectionFailedListener(this)
.build();
}
}
private void gooConnect(boolean bConnect) {
Log.d("_", "goo connect " + bConnect);
if (mGooApiClient != null) {
if (!bConnect) {
mGooApiClient.disconnect();
} else if (! (mGooApiClient.isConnecting() || mGooApiClient.isConnected())){
mGooApiClient.connect();
}
}
}
private void suicide(String msg) {
GA.removeActiveAccnt(this);
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
finish();
}
private void errorDialog(int errorCode, int requestCode) {
Bundle args = new Bundle();
args.putInt(DIALOG_ERROR, errorCode);
args.putInt(REQUEST_CODE, requestCode);
ErrorDialogFragment dialogFragment = new ErrorDialogFragment();
dialogFragment.setArguments(args);
dialogFragment.show(getFragmentManager(), "errordialog");
}
public static class ErrorDialogFragment extends DialogFragment {
public ErrorDialogFragment() { }
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
int errorCode = getArguments().getInt(DIALOG_ERROR);
int requestCode = getArguments().getInt(DIALOG_ERROR);
return GooglePlayServicesUtil.getErrorDialog(errorCode, getActivity(), requestCode);
}
#Override
public void onDismiss(DialogInterface dialog) {
getActivity().finish();
}
}
private static class GA {
private static final String ACC_NAME = "account_name";
public static final int FAIL = -1;
public static final int UNCHANGED = 0;
public static final int CHANGED = +1;
private static String mCurrEmil = null; // cache locally
private static String mPrevEmil = null; // cache locally
public static Account[] getAllAccnts(Context ctx) {
return AccountManager.get(acx(ctx)).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
}
public static Account getPrimaryAccnt(Context ctx, boolean bOneOnly) {
Account[] accts = getAllAccnts(ctx);
if (bOneOnly)
return accts == null || accts.length != 1 ? null : accts[0];
return accts == null || accts.length == 0 ? null : accts[0];
}
public static Account getActiveAccnt(Context ctx) {
return emil2Accnt(ctx, getActiveEmil(ctx));
}
public static String getActiveEmil(Context ctx) {
if (mCurrEmil != null) {
return mCurrEmil;
}
mCurrEmil = ctx == null ? null : pfs(ctx).getString(ACC_NAME, null);
return mCurrEmil;
}
public static Account getPrevEmil(Context ctx) {
return emil2Accnt(ctx, mPrevEmil);
}
public static Account emil2Accnt(Context ctx, String emil) {
if (emil != null) {
Account[] accounts =
AccountManager.get(acx(ctx)).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
for (Account account : accounts) {
if (emil.equalsIgnoreCase(account.name)) {
return account;
}
}
}
return null;
}
/**
* Stores a new email in persistent app storage, reporting result
* #param newEmil new email, optionally null
* #param ctx activity context
* #return FAIL, CHANGED or UNCHANGED (based on the following table)
* OLD NEW SAVED RESULT
* ERROR FAIL
* null null null FAIL
* null new new CHANGED
* old null old UNCHANGED
* old != new new CHANGED
* old == new new UNCHANGED
*/
public static int setEmil(Context ctx, String newEmil) {
int result = FAIL; // 0 0
mPrevEmil = getActiveEmil(ctx);
if ((mPrevEmil == null) && (newEmil != null)) {
result = CHANGED;
} else if ((mPrevEmil != null) && (newEmil == null)) {
result = UNCHANGED;
} else if ((mPrevEmil != null) && (newEmil != null)) {
result = mPrevEmil.equalsIgnoreCase(newEmil) ? UNCHANGED : CHANGED;
}
if (result == CHANGED) {
mCurrEmil = newEmil;
pfs(ctx).edit().putString(ACC_NAME, newEmil).apply();
}
return result;
}
public static void removeActiveAccnt(Context ctx) {
mCurrEmil = null;
pfs(ctx).edit().remove(ACC_NAME).apply();
}
private static Context acx(Context ctx) {
return ctx == null ? null : ctx.getApplicationContext();
}
private static SharedPreferences pfs(Context ctx) {
return ctx == null ? null : PreferenceManager.getDefaultSharedPreferences(acx(ctx));
}
}
}
BTW, I know how to spell 'email', 'Emil' just happened to be my uncle's name and I couldn't resist :-)
UPDATE (2015-Apr-11):
I've recently re-visited the code that handles Google Drive Authorization and Account switching. The result can be found here and it supports both REST and GDAA apis.

Related

How to download and save in local directory from Google drive selected file in Android programmatically?

I am using Google Drive API in my application, I am able to pick(.doc/docx/.pdf in my case) file from google drive, till now everything is fine. But I want to download the selected file and need to send that file to our server by using Multipart. I tried multiple ways, I am getting DriveId and DriveFile but unfortunately I am unable to download download selected file.
I have gone through the Android developer documentation
I am using below code
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.drive.DriveFile;
import com.google.android.gms.drive.DriveId;
import com.google.android.gms.drive.DriveResource;
import com.google.android.gms.drive.OpenFileActivityBuilder;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.drive.DriveScopes;
public class DriveActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
/**
* DriveId of an existing folder to be used as a parent folder in
* folder operations samples.
*/
public static final String EXISTING_FOLDER_ID = "0B2EEtIjPUdX6MERsWlYxN3J6RU0";
/**
* DriveId of an existing file to be used in file operation samples..
*/
public static final String EXISTING_FILE_ID = "0ByfSjdPVs9MZTHBmMVdSeWxaNTg";
/**
* Extra for account name.
*/
protected static final String EXTRA_ACCOUNT_NAME = "account_name";
/**
* Request code for auto Google Play Services error resolution.
*/
protected static final int REQUEST_CODE_RESOLUTION = 1;
/**
* Next available request code.
*/
protected static final int NEXT_AVAILABLE_REQUEST_CODE = 2;
private static final String TAG = "===GoogleDriveActivity";
private static final int REQUEST_CODE_OPENER = 2;
/**
* Google API client.
*/
private GoogleApiClient mGoogleApiClient;
private static final String[] SCOPES = {DriveScopes.DRIVE_FILE};
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
private String accountName;
DriveResource.MetadataResult metadataResult;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
connect();
}
#Override
protected void onStop() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onStop();
}
#Override
protected void onRestart() {
super.onRestart();
connect();
}
private void connect() {
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addScope(Drive.SCOPE_APPFOLDER) // required for App Folder sample
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
mGoogleApiClient.connect();
}
/**
* Called when {#code mGoogleApiClient} is disconnected.
*/
#Override
public void onConnectionSuspended(int cause) {
Log.i(TAG, "GoogleApiClient connection suspended");
}
/**
* Called when {#code mGoogleApiClient} is trying to connect but failed.
* Handle {#code result.getResolution()} if there is a resolution is
* available.
*/
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
if (!result.hasResolution()) {
// show the localized error dialog.
GoogleApiAvailability.getInstance().getErrorDialog(this, result.getErrorCode(), 0).show();
return;
}
try {
result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
/**
* Getter for the {#code GoogleApiClient}.
*/
public GoogleApiClient getGoogleApiClient() {
return mGoogleApiClient;
}
#Override
public void onConnected(Bundle connectionHint) {
IntentSender intentSender = Drive.DriveApi
.newOpenFileActivityBuilder()
.setMimeType(new String[]{"application/msword", " application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.google-apps.document", "application/pdf"})
.build(getGoogleApiClient());
AccountManager manager = (AccountManager) getSystemService(ACCOUNT_SERVICE);
Account[] list = manager.getAccountsByType("com.google");
//Getting the first account because that is the primary account for that user
accountName = list[0].name;
try {
startIntentSenderForResult(
intentSender, REQUEST_CODE_OPENER, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.w(TAG, "Unable to send intent", e);
}
}
/**
* Handles resolution callbacks.
*/
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case REQUEST_CODE_OPENER:
Intent intent = null;
if (resultCode == RESULT_OK) {
DriveId driveId = data.getParcelableExtra(
OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
String resourceId = driveId.getResourceId();
DriveFile file = driveId.asDriveFile();
}
break;
case REQUEST_CODE_RESOLUTION:
mGoogleApiClient.connect();
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
}
}
Please can someone help me to download selected file from google drive programmatically?
Thanks in advance.
There are two ways you can download it, via REST or using GDAA. You can use either depending on where you code comfortably.
When using REST:
/*************************************************************************
* get file contents
* #param resId file driveId
* #return file's content / null on fail
*/
static InputStream read(String resId) {
if (mGOOSvc != null && mConnected && resId != null) try {
File gFl = mGOOSvc.files().get(resId).setFields("downloadUrl").execute();
if (gFl != null){
String strUrl = gFl.getDownloadUrl();
return mGOOSvc.getRequestFactory()
.buildGetRequest(new GenericUrl(strUrl)).execute().getContent();
}
} catch (Exception e) { /* error handling */ }
return null;
}
WHEN using GDAA:
/************************************************************************************************
* get file contents
* #param id file driveId
* #return file's content / null on fail
*/
static byte[] read(String id) {
byte[] buf = null;
if (mGAC != null && mGAC.isConnected() && id != null) try {
DriveFile df = Drive.DriveApi.getFile(mGAC, DriveId.decodeFromString(id));
DriveContentsResult rslt = df.open(mGAC, DriveFile.MODE_READ_ONLY, null).await();
if ((rslt != null) && rslt.getStatus().isSuccess()) {
DriveContents cont = rslt.getDriveContents();
buf = UT.is2Bytes(cont.getInputStream());
cont.discard(mGAC); // or cont.commit(); they are equiv if READONLY
}
} catch (Exception e) { UT.le(e); }
return buf;
}
Check this SO post for the differentiation of Drive Rest in GDAA.
1 The GDAA's main identifier, the DriveId lives in GDAA (GooPlaySvcs) only and does not exist in the REST Api. You must retrieve 'ResourceId' which is the main identifier in the REST Api (see SO 29030110).
2 ResourceId can be obtained from the DriveId only after GDAA committed (uploaded) the file/folder (see SO 22874657)
3 You will run into a lot of timing issues caused by the fact that GDAA 'buffers' network requests on it's own schedule (system optimized), whereas the REST Api let your app control the waiting for the response. In general, if you scan these SO questions, you'll find a lot of chatter about these issues (it's a mess, though).
Hope this helps.
Just Pass DriveID of Upload File And Filename you want to give while download
public void DownloadFile(final DriveId driveId, final File filename, final GoogleApiClient mGoogleApiClient) {
if (!filename.exists()) {
try {
filename.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
final DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, driveId);
file.getMetadata(mGoogleApiClient).setResultCallback(metadataRetrievedCallback);
DriveFolder folder = Drive.DriveApi.getFolder(mGoogleApiClient, driveId);
folder.getMetadata(mGoogleApiClient).setResultCallback(metadataRetrievedCallback1);
new Thread(new Runnable()
{
#Override
public void run()
{
DriveApi.DriveContentsResult driveContentsResult = file.open(mGoogleApiClient,DriveFile.MODE_READ_ONLY, null).await();
DriveContents driveContents = driveContentsResult.getDriveContents();
InputStream inputstream = driveContents.getInputStream();
try
{
FileOutputStream fileOutput = new FileOutputStream(filename);
byte[] buffer = new byte[1024];
int bufferLength = 0;
while ((bufferLength = inputstream.read(buffer)) > 0)
{
fileOutput.write(buffer, 0, bufferLength);
}
fileOutput.close();
inputstream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}

Android app + Drive API with full-drive access ... what's the procedure?

I'm looking for some guidance related to accessing Google Drive with an Android app.
1) I need to be able to read files uploaded by users outside of my app. Does this mean I need full-drive access? (If the app could create a folder and then see all files uploaded by the user that exist in this folder, that would be great, but I don't think it works this way.)
2) If I need full-drive access, it seems that Googles "Drive API for Android" doesn't support this, and I need to use the REST api. I think this is true.
3) I need an Auth 2.0 client ID from Google. If I use the rest API, does this mean I need to use a "Web Application" ID? I think I need this because I want an "auth code". I wasn't able to get it working with an "Android" type ID.
4) I'm currently using "Google Sign-In" for Android to handle the login and provide an auth code. I can then convert this into a Token + Refresh Token, and save these so I can get new tokens after an hour in some fashion. Is this manually handling of refresh tokens required?
It's getting ugly, but I think that since I need (?) full-drive access then this is the procedure.
Thanks for any guidance.
Edit: The question has been identified as a duplicate. The link provided gives an answer for question #2, but doesn't address the other questions.
I agree the question is messy...
I'm answering my own question.
I struggled with this because A) Google's REST example uses an outdated login process, B) The "Sign In" example uses code that doesn't work with "full-access" scope, and C) the there were too many vastly different code examples when trying to put it all together.
To quickly answer my questions as I see it now:
1) Yes, full-drive access is required to read files uploaded outside my app.
2) Yes, I need to use REST api.
3) Yes, I need a "Web Application" client ID.
4) Google Sign-In seems the best way currently to sign in, and using a GoogleCredential object along with the Drive api abject will handle the token refreshes automatically, as long as you keep around a refresh token.
In case anyone else is struggling with accessing Drive with full-access from Android using the latest "Sign-In" procedure and REST v3, below is my sample code.
In addition to the "Web application" OAuth client ID, you also need to create an "Android" type ID with a matching package name and certificate fingerprint in order for the Sign-In to work. Also note that you'll have different certificates for your dev and production versions. The IDs/codes from these Android clients do not need to be entered into the app.
build.gradle : app
// Google Sign In
compile 'com.google.android.gms:play-services-auth:10.0.1'
// Drive REST API
compile('com.google.apis:google-api-services-drive:v3-rev54-1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
Activity
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Callback from Signin (Auth.GoogleSignInApi.getSignInIntent)
if (requestCode == 1) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
_googleApi.handleSignInResult(result);
}
}
A "GoogleApi" class to do the work
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.common.api.Status;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class GoogleApi implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private Context _context;
private Handler _handler;
private GoogleCredential _credential;
private Drive _drive;
private GoogleApiClient _googleApiClient; // only set during login process
private Activity _activity; // launch intent for login (UI)
// Saved to data store
private boolean _loggedIn;
private String _refreshToken; // store, even if user is logged out as we may need to reuse
private static final String ClientID = "xxxxxx.apps.googleusercontent.com"; // web client
private static final String ClientSecret = "xxxxx"; // web client
private class FileAndErrorMsg {
public File file;
public String errorMsg;
public FileAndErrorMsg (File file_, String errorMsg_) { file = file_; errorMsg = errorMsg_; }
}
private class FileListAndErrorMsg {
public List<File> fileList;
public String errorMsg;
public FileListAndErrorMsg (List<File> fileList_, String errorMsg_) { fileList = fileList_; errorMsg = errorMsg_; }
}
// -------------------
// Constructor
// -------------------
public GoogleApi (Context context) {
_context = context;
_handler = new Handler();
loadFromPrefs(); // loggedIn, refreshToken
// create credential; will refresh itself automatically (in Drive calls) as long as valid refresh token exists
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
_credential = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(ClientID, ClientSecret) // .addRefreshListener
.build();
_credential.setRefreshToken(_refreshToken);
// Get app name from Manifest (for Drive builder)
ApplicationInfo appInfo = context.getApplicationInfo();
String appName = appInfo.labelRes == 0 ? appInfo.nonLocalizedLabel.toString() : context.getString(appInfo.labelRes);
_drive = new Drive.Builder(transport, jsonFactory, _credential).setApplicationName(appName).build();
}
// -------------------
// Auth
// -------------------
// https://developers.google.com/identity/sign-in/android/offline-access#before_you_begin
// https://developers.google.com/identity/sign-in/android/offline-access#enable_server-side_api_access_for_your_app
// https://android-developers.googleblog.com/2016/02/using-credentials-between-your-server.html
// https://android-developers.googleblog.com/2016/05/improving-security-and-user-experience.html
public boolean isLoggedIn () {
return _loggedIn;
}
public void startAuth(Activity activity) {
startAuth(activity, false);
}
public void startAuth(Activity activity, boolean forceRefreshToken) {
_activity = activity;
_loggedIn = false;
saveToPrefs();
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(new Scope("https://www.googleapis.com/auth/drive"))
.requestServerAuthCode(ClientID, forceRefreshToken) // if force, guaranteed to get back refresh token, but will show "offline access?" if Google already issued refresh token
.build();
_googleApiClient = new GoogleApiClient.Builder(activity)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
_googleApiClient.connect();
}
#Override
public void onConnected(Bundle connectionHint) {
// Called soon after .connect()
// This is only called when starting our Login process. Sign Out first so select-account screen shown. (OK if not already signed in)
Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
// Start sign in
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(_googleApiClient);
_activity.startActivityForResult(signInIntent, 1); // Activity's onActivityResult will use the same code: 1
}
});
}
#Override
public void onConnectionSuspended(int cause) {
authDone("Connection suspended.");
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) { authDone("Connection failed."); }
public void handleSignInResult(GoogleSignInResult result) {
// Callback from Activity > onActivityResult
if (result.isSuccess()) {
GoogleSignInAccount acct = result.getSignInAccount();
String authCode = acct.getServerAuthCode();
new Thread(new ContinueAuthWithAuthCode_Background(authCode)).start();
}
else authDone("Login canceled or unable to connect to Google."); // can we get better error message?
}
private class ContinueAuthWithAuthCode_Background implements Runnable {
String _authCode;
public ContinueAuthWithAuthCode_Background (String authCode) {
_authCode = authCode;
}
public void run() {
// Convert authCode to tokens
GoogleTokenResponse tokenResponse = null;
String errorMsg = null;
try {
tokenResponse = new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), "https://www.googleapis.com/oauth2/v4/token", ClientID, ClientSecret, _authCode, "").execute();
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
final GoogleTokenResponse tokenResponseFinal = tokenResponse;
final String errorMsgFinal = errorMsg;
_handler.post(new Runnable() { public void run() {
// Main thread
GoogleTokenResponse tokenResponse = tokenResponseFinal;
String errorMsg = errorMsgFinal;
if (tokenResponse != null && errorMsg == null) {
_credential.setFromTokenResponse(tokenResponse); // this will keep old refresh token if no new one sent
_refreshToken = _credential.getRefreshToken();
_loggedIn = true;
saveToPrefs();
// FIXME: if our refresh token is bad and we're not getting a new one, how do we deal with this?
Log("New refresh token: " + tokenResponse.getRefreshToken());
}
else if (errorMsg == null) errorMsg = "Get token error."; // shouldn't get here
authDone(errorMsg);
} });
}
}
private void authDone(String errorMsg) {
// Disconnect (we only need googleApiClient for login process)
if (_googleApiClient != null && _googleApiClient.isConnected()) _googleApiClient.disconnect();
_googleApiClient = null;
}
/*
public void signOut() {
Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
}
});
}
public void revokeAccess() {
// FIXME: I don't know yet, but this may revoke access for all android devices
Auth.GoogleSignInApi.revokeAccess(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
}
});
}
*/
public void LogOut() {
_loggedIn = false;
saveToPrefs(); // don't clear refresh token as we may need again
}
// -------------------
// API Calls
// -------------------
public void makeApiCall() {
new Thread(new TestApiCall_Background()).start();
}
private class TestApiCall_Background implements Runnable {
public void run() {
FileAndErrorMsg fileAndErr = getFolderFromName_b("Many Files", null);
if (fileAndErr.errorMsg != null) Log("getFolderFromName_b error: " + fileAndErr.errorMsg);
else {
FileListAndErrorMsg fileListAndErr = getFileListInFolder_b(fileAndErr.file);
if (fileListAndErr.errorMsg != null)
Log("getFileListInFolder_b error: " + fileListAndErr.errorMsg);
else {
Log("file count: " + fileListAndErr.fileList.size());
for (File file : fileListAndErr.fileList) {
//Log(file.getName());
}
}
}
_handler.post(new Runnable() { public void run() {
// Main thread
} });
}
}
private FileAndErrorMsg getFolderFromName_b (String folderName, File parent) {
// parent can be null for top level
// Working with folders: https://developers.google.com/drive/v3/web/folder
File folder = null;
folderName = folderName.replace("'", "\\'"); // escape '
String q = String.format(Locale.US, "mimeType='application/vnd.google-apps.folder' and '%s' in parents and name='%s' and trashed=false", parent == null ? "root" : parent.getId(), folderName);
String errorMsg = null;
try {
FileList result = _drive.files().list().setQ(q).setPageSize(1000).execute();
int foundCount = 0;
for (File file : result.getFiles()) {
foundCount++;
folder = file;
}
if (foundCount == 0) errorMsg = "Folder not found: " + folderName;
else if (foundCount > 1) errorMsg = "More than one folder found with name (" + foundCount + "): " + folderName;
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
if (errorMsg != null) folder = null;
return new FileAndErrorMsg(folder, errorMsg);
}
private FileListAndErrorMsg getFileListInFolder_b (File folder) {
// folder can be null for top level; does not return subfolder names
List<File> fileList = new ArrayList<File>();
String q = String.format(Locale.US, "mimeType != 'application/vnd.google-apps.folder' and '%s' in parents and trashed=false", folder == null ? "root" : folder.getId());
String errorMsg = null;
try {
String pageToken = null;
do {
FileList result = _drive.files().list().setQ(q).setPageSize(1000).setPageToken(pageToken).execute();
fileList.addAll(result.getFiles());
pageToken = result.getNextPageToken();
} while (pageToken != null);
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
if (errorMsg != null) fileList = null;
return new FileListAndErrorMsg(fileList, errorMsg);
}
// -------------------
// Misc
// -------------------
private void Log(String msg) {
Log.v("ept", msg);
}
// -------------------
// Load/Save Tokens
// -------------------
private void loadFromPrefs() {
SharedPreferences pref = _context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
_loggedIn = pref.getBoolean("GoogleLoggedIn", false);
_refreshToken = pref.getString("GoogleRefreshToken", null);
}
private void saveToPrefs() {
SharedPreferences.Editor editor = _context.getSharedPreferences("prefs", Context.MODE_PRIVATE).edit();
editor.putBoolean("GoogleLoggedIn", _loggedIn);
editor.putString("GoogleRefreshToken", _refreshToken);
editor.apply(); // async
}
}
The latest example in https://developers.google.com/drive/v3/web/quickstart/android works out of the box.
Just do the following:
1 - Go to Google API console and create an OAuth2 Client ID using your package name and debug/release key as your Signing-certificate fingerprint.
2 - Enable Google Drive API
3 - Apply the following code
build.gradle : app
compile 'com.google.android.gms:play-services-auth:10.0.1'
compile 'pub.devrel:easypermissions:0.2.1'
compile('com.google.api-client:google-api-client-android:1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
compile('com.google.apis:google-api-services-drive:v3-rev57-1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
Activity
In this code just change the scope to DriveScopes.DRIVE for full drive access
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.*;
import android.Manifest;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
public class MainActivity extends Activity
implements EasyPermissions.PermissionCallbacks {
GoogleAccountCredential mCredential;
private TextView mOutputText;
private Button mCallApiButton;
ProgressDialog mProgress;
static final int REQUEST_ACCOUNT_PICKER = 1000;
static final int REQUEST_AUTHORIZATION = 1001;
static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002;
static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003;
private static final String BUTTON_TEXT = "Call Drive API";
private static final String PREF_ACCOUNT_NAME = "accountName";
private static final String[] SCOPES = { DriveScopes.DRIVE };
/**
* Create the main activity.
* #param savedInstanceState previously saved instance data.
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout activityLayout = new LinearLayout(this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
activityLayout.setLayoutParams(lp);
activityLayout.setOrientation(LinearLayout.VERTICAL);
activityLayout.setPadding(16, 16, 16, 16);
ViewGroup.LayoutParams tlp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mCallApiButton = new Button(this);
mCallApiButton.setText(BUTTON_TEXT);
mCallApiButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCallApiButton.setEnabled(false);
mOutputText.setText("");
getResultsFromApi();
mCallApiButton.setEnabled(true);
}
});
activityLayout.addView(mCallApiButton);
mOutputText = new TextView(this);
mOutputText.setLayoutParams(tlp);
mOutputText.setPadding(16, 16, 16, 16);
mOutputText.setVerticalScrollBarEnabled(true);
mOutputText.setMovementMethod(new ScrollingMovementMethod());
mOutputText.setText(
"Click the \'" + BUTTON_TEXT +"\' button to test the API.");
activityLayout.addView(mOutputText);
mProgress = new ProgressDialog(this);
mProgress.setMessage("Calling Drive API ...");
setContentView(activityLayout);
// Initialize credentials and service object.
mCredential = GoogleAccountCredential.usingOAuth2(
getApplicationContext(), Arrays.asList(SCOPES))
.setBackOff(new ExponentialBackOff());
}
/**
* Attempt to call the API, after verifying that all the preconditions are
* satisfied. The preconditions are: Google Play Services installed, an
* account was selected and the device currently has online access. If any
* of the preconditions are not satisfied, the app will prompt the user as
* appropriate.
*/
private void getResultsFromApi() {
if (! isGooglePlayServicesAvailable()) {
acquireGooglePlayServices();
} else if (mCredential.getSelectedAccountName() == null) {
chooseAccount();
} else if (! isDeviceOnline()) {
mOutputText.setText("No network connection available.");
} else {
new MakeRequestTask(mCredential).execute();
}
}
/**
* Attempts to set the account used with the API credentials. If an account
* name was previously saved it will use that one; otherwise an account
* picker dialog will be shown to the user. Note that the setting the
* account to use with the credentials object requires the app to have the
* GET_ACCOUNTS permission, which is requested here if it is not already
* present. The AfterPermissionGranted annotation indicates that this
* function will be rerun automatically whenever the GET_ACCOUNTS permission
* is granted.
*/
#AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS)
private void chooseAccount() {
if (EasyPermissions.hasPermissions(
this, Manifest.permission.GET_ACCOUNTS)) {
String accountName = getPreferences(Context.MODE_PRIVATE)
.getString(PREF_ACCOUNT_NAME, null);
if (accountName != null) {
mCredential.setSelectedAccountName(accountName);
getResultsFromApi();
} else {
// Start a dialog from which the user can choose an account
startActivityForResult(
mCredential.newChooseAccountIntent(),
REQUEST_ACCOUNT_PICKER);
}
} else {
// Request the GET_ACCOUNTS permission via a user dialog
EasyPermissions.requestPermissions(
this,
"This app needs to access your Google account (via Contacts).",
REQUEST_PERMISSION_GET_ACCOUNTS,
Manifest.permission.GET_ACCOUNTS);
}
}
/**
* Called when an activity launched here (specifically, AccountPicker
* and authorization) exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
* #param requestCode code indicating which activity result is incoming.
* #param resultCode code indicating the result of the incoming
* activity result.
* #param data Intent (containing result data) returned by incoming
* activity result.
*/
#Override
protected void onActivityResult(
int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case REQUEST_GOOGLE_PLAY_SERVICES:
if (resultCode != RESULT_OK) {
mOutputText.setText(
"This app requires Google Play Services. Please install " +
"Google Play Services on your device and relaunch this app.");
} else {
getResultsFromApi();
}
break;
case REQUEST_ACCOUNT_PICKER:
if (resultCode == RESULT_OK && data != null &&
data.getExtras() != null) {
String accountName =
data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
SharedPreferences settings =
getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_ACCOUNT_NAME, accountName);
editor.apply();
mCredential.setSelectedAccountName(accountName);
getResultsFromApi();
}
}
break;
case REQUEST_AUTHORIZATION:
if (resultCode == RESULT_OK) {
getResultsFromApi();
}
break;
}
}
/**
* Respond to requests for permissions at runtime for API 23 and above.
* #param requestCode The request code passed in
* requestPermissions(android.app.Activity, String, int, String[])
* #param permissions The requested permissions. Never null.
* #param grantResults The grant results for the corresponding permissions
* which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(
requestCode, permissions, grantResults, this);
}
/**
* Callback for when a permission is granted using the EasyPermissions
* library.
* #param requestCode The request code associated with the requested
* permission
* #param list The requested permission list. Never null.
*/
#Override
public void onPermissionsGranted(int requestCode, List<String> list) {
// Do nothing.
}
/**
* Callback for when a permission is denied using the EasyPermissions
* library.
* #param requestCode The request code associated with the requested
* permission
* #param list The requested permission list. Never null.
*/
#Override
public void onPermissionsDenied(int requestCode, List<String> list) {
// Do nothing.
}
/**
* Checks whether the device currently has a network connection.
* #return true if the device has a network connection, false otherwise.
*/
private boolean isDeviceOnline() {
ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
/**
* Check that Google Play services APK is installed and up to date.
* #return true if Google Play Services is available and up to
* date on this device; false otherwise.
*/
private boolean isGooglePlayServicesAvailable() {
GoogleApiAvailability apiAvailability =
GoogleApiAvailability.getInstance();
final int connectionStatusCode =
apiAvailability.isGooglePlayServicesAvailable(this);
return connectionStatusCode == ConnectionResult.SUCCESS;
}
/**
* Attempt to resolve a missing, out-of-date, invalid or disabled Google
* Play Services installation via a user dialog, if possible.
*/
private void acquireGooglePlayServices() {
GoogleApiAvailability apiAvailability =
GoogleApiAvailability.getInstance();
final int connectionStatusCode =
apiAvailability.isGooglePlayServicesAvailable(this);
if (apiAvailability.isUserResolvableError(connectionStatusCode)) {
showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
}
}
/**
* Display an error dialog showing that Google Play Services is missing
* or out of date.
* #param connectionStatusCode code describing the presence (or lack of)
* Google Play Services on this device.
*/
void showGooglePlayServicesAvailabilityErrorDialog(
final int connectionStatusCode) {
GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
Dialog dialog = apiAvailability.getErrorDialog(
MainActivity.this,
connectionStatusCode,
REQUEST_GOOGLE_PLAY_SERVICES);
dialog.show();
}
/**
* An asynchronous task that handles the Drive API call.
* Placing the API calls in their own task ensures the UI stays responsive.
*/
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private com.google.api.services.drive.Drive mService = null;
private Exception mLastError = null;
MakeRequestTask(GoogleAccountCredential credential) {
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
mService = new com.google.api.services.drive.Drive.Builder(
transport, jsonFactory, credential)
.setApplicationName("Drive API Android Quickstart")
.build();
}
/**
* Background task to call Drive API.
* #param params no parameters needed for this task.
*/
#Override
protected List<String> doInBackground(Void... params) {
try {
return getDataFromApi();
} catch (Exception e) {
mLastError = e;
cancel(true);
return null;
}
}
/**
* Fetch a list of up to 10 file names and IDs.
* #return List of Strings describing files, or an empty list if no files
* found.
* #throws IOException
*/
private List<String> getDataFromApi() throws IOException {
// Get a list of up to 10 files.
List<String> fileInfo = new ArrayList<String>();
FileList result = mService.files().list()
.setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
List<File> files = result.getFiles();
if (files != null) {
for (File file : files) {
fileInfo.add(String.format("%s (%s)\n",
file.getName(), file.getId()));
}
}
return fileInfo;
}
#Override
protected void onPreExecute() {
mOutputText.setText("");
mProgress.show();
}
#Override
protected void onPostExecute(List<String> output) {
mProgress.hide();
if (output == null || output.size() == 0) {
mOutputText.setText("No results returned.");
} else {
output.add(0, "Data retrieved using the Drive API:");
mOutputText.setText(TextUtils.join("\n", output));
}
}
#Override
protected void onCancelled() {
mProgress.hide();
if (mLastError != null) {
if (mLastError instanceof GooglePlayServicesAvailabilityIOException) {
showGooglePlayServicesAvailabilityErrorDialog(
((GooglePlayServicesAvailabilityIOException) mLastError)
.getConnectionStatusCode());
} else if (mLastError instanceof UserRecoverableAuthIOException) {
startActivityForResult(
((UserRecoverableAuthIOException) mLastError).getIntent(),
MainActivity.REQUEST_AUTHORIZATION);
} else {
mOutputText.setText("The following error occurred:\n"
+ mLastError.getMessage());
}
} else {
mOutputText.setText("Request cancelled.");
}
}
}
}

Connection Failed loop with GoogleApiClient

I been getting an error trying to execute this Android Drive API example googledrive/android-quickstart, the app run fine then shows a windows dialog to select the google account, I select one and in logcat I get this:
I/drive-quickstart﹕ GoogleApiClient connection failed: ConnectionResult{statusCode=SIGN_IN_REQUIRED, resolution=PendingIntent{44c22a28: android.os.BinderProxy#44c1b1c0}}
Then shows the dialog again seems like a infinite loop
I have been already configured the OAuth and other parameters in google developer console.
Here is my code Thanks in advance..
public class CloudPaintActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private static final String TAG = "drive-quickstart";
private static final int REQUEST_CODE_CAPTURE_IMAGE = 1;
private static final int REQUEST_CODE_CREATOR = 2;
private static final int REQUEST_CODE_RESOLUTION = 3;
private GoogleApiClient mGoogleApiClient;
private Bitmap mBitmapToSave;
/**
* Create a new file and save it to Drive.
*/
private void saveFileToDrive() {
// Start by creating a new contents, and setting a callback.
Log.i(TAG, "Creating new contents.");
final Bitmap image = mBitmapToSave;
Drive.DriveApi.newDriveContents(mGoogleApiClient)
.setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() {
#Override
public void onResult(DriveApi.DriveContentsResult result) {
// If the operation was not successful, we cannot do anything
// and must
// fail.
if (!result.getStatus().isSuccess()) {
Log.i(TAG, "Failed to create new contents.");
return;
}
// Otherwise, we can write our data to the new contents.
Log.i(TAG, "New contents created.");
// Get an output stream for the contents.
OutputStream outputStream = result.getDriveContents().getOutputStream();
// Write the bitmap data from it.
ByteArrayOutputStream bitmapStream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.PNG, 100, bitmapStream);
try {
outputStream.write(bitmapStream.toByteArray());
} catch (IOException e1) {
Log.i(TAG, "Unable to write file contents.");
}
// Create the initial metadata - MIME type and title.
// Note that the user will be able to change the title later.
MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
.setMimeType("image/jpeg").setTitle("Android Photo.png").build();
// Create an intent for the file chooser, and start it.
IntentSender intentSender = Drive.DriveApi
.newCreateFileActivityBuilder()
.setInitialMetadata(metadataChangeSet)
.setInitialDriveContents(result.getDriveContents())
.build(mGoogleApiClient);
try {
startIntentSenderForResult(
intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.i(TAG, "Failed to launch file chooser.");
}
}
});
}
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
// Create the API client and bind it to an instance variable.
// We use this instance as the callback for connection and connection
// failures.
// Since no account name is passed, the user is prompted to choose.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addApi(Plus.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
// Connect the client. Once connected, the camera is launched.
mGoogleApiClient.connect();
// if the api client existed, we terminate it
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
mGoogleApiClient.disconnect();
}
}
#Override
protected void onPause() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onPause();
}
#Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_CODE_CAPTURE_IMAGE:
// Called after a photo has been taken.
if (resultCode == Activity.RESULT_OK) {
// Store the image data as a bitmap for writing later.
mBitmapToSave = (Bitmap) data.getExtras().get("data");
}
break;
case REQUEST_CODE_CREATOR:
// Called after a file is saved to Drive.
if (resultCode == RESULT_OK) {
Log.i(TAG, "Image successfully saved.");
mBitmapToSave = null;
// Just start the camera again for another photo.
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
REQUEST_CODE_CAPTURE_IMAGE);
}
break;
}
}
#Override
public void onConnectionFailed(ConnectionResult result) {
// Called whenever the API client fails to connect.
Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
if (!result.hasResolution()) {
// show the localized error dialog.
GoogleApiAvailability.getInstance().getErrorDialog(this, result.getErrorCode(), 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization
// dialog is displayed to the user.
try {
result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected.");
if (mBitmapToSave == null) {
// This activity has no UI of its own. Just start the camera.
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
REQUEST_CODE_CAPTURE_IMAGE);
return;
}
saveFileToDrive();
}
#Override
public void onConnectionSuspended(int cause) {
Log.i(TAG, "GoogleApiClient connection suspended");
}
}
Try this if you don't need multiple accounts:
public class MainActivity extends AppCompatActivity {
private static final int REQ_CONNECT = 1;
private Activity mAct;
private static GoogleApiClient mGAC;
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
mAct = this;
setContentView(R.layout.activity_main);
if (bundle == null) try {
mGAC = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override public void onConnectionSuspended(int i) { }
#Override
public void onConnected(Bundle bundle) {
Toast.makeText(mAct, "bingo", Toast.LENGTH_LONG).show(); // connected
saveFileToDrive();
}
})
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult connResult) {
if (connResult != null) {
if (!connResult.hasResolution()) {
int errCode = connResult.getErrorCode();
GooglePlayServicesUtil.getErrorDialog(errCode, mAct, 0).show();
return;
} else try {
connResult.startResolutionForResult(mAct, REQ_CONNECT);
return;
} catch (Exception e) { e.printStackTrace(); }
}
finish(); //--- FAIL - no resolution ---------->>>
}
})
.build();
} catch (Exception e) { e.printStackTrace(); }
}
#Override
protected void onResume() { super.onResume();
mGAC.connect();
}
#Override
protected void onPause() { super.onPause();
mGAC.disconnect();
}
#Override
protected void onActivityResult(int request, int result, Intent data) {
switch (request) {
case REQ_CONNECT:
if (result == RESULT_OK)
mGAC.connect();
else
finish(); //--- FAIL, user cancelled ------------->>>
break;
}
super.onActivityResult(request, result, data);
}
}
If you need to switch multiple accounts, add:
.addApi(Plus.API)
to your builder and call:
Plus.AccountApi.clearDefaultAccount(mGAC);
from wherever (menu for instance). Then create a new instance of mGAC and connect. It will pop up the account selection dialog again. But your app will not know which account the user selected (or created).
If you need to know your current user, you can drop the the Plus.API and manage the GooDrive accounts yourself with Account Picker, but you need to implement an Account Manager and instantiate mGAC with
.setAccountName([ACCOUNT EMAIL])
as seen here (follow the REQ_ACCPICK and see the Account Manager UT.AM).
Good Luck
Based off your error, it seems for whatever reason it fails to sign in. I believe in this case you should check if hasResolution returns true. If so, you can call [startResolutionForResult](https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult.html#startResolutionForResult(android.app.Activity, int)) which will prompt the user to sign in.
As far why it fails to sign in, it's a bit difficult to tell. It sounds like you're using the AccountPicker? Perhaps you can try with only a single account signed into the device.
I have observed the behavior you describe when the OAuth 2.0 client ID set up on the Google Cloud console did not match the apk I was trying to run, either by Signing-certificate fingerprint or by Package name.

List files from Google Drive and get downloadUrl for selected file in Android

I am trying to show a list of files the user has on their Google Drive from my Android app and once the user has selected a file, I want to get the downloadUrl and the Bearer token for that account to give it to my application server to download it.
I have been looking around and it seems very confusing. There are 2 SDKs, Android SDK and Java SDK (REST based) for Google Drive.
I successfully was able to get the list of files and display it using the Android SDK (I did not have to build any UI), and when the user selects a file, I got all the metadata about the file but the downloadUrl. I did get some links like the webContentLink and the alternateLink, but it turns out that because the file was not shared, I cannot pass these links to my server to download it.
Upon some more research I found out that the downloadUrl is accessible by using the Java SDK. My question is, do I have to build my own UI to display a list of files I obtain? How do I handle the folder hierarchy if I have to build my UI to show these files?
Below is the code which prints the data about the File. I have implemented this code based on the tutorial.
public class GoogleDriveActivity extends Activity {
private GoogleApiClient mGoogleApiClient;
public com.google.api.services.drive.Drive mService;
public GoogleAccountCredential credential;
public static final int REQUEST_AUTHORIZATION = 3;
public static final int REQUEST_ACCOUNT_PICKER = 4;
private static final String PREF_ACCOUNT_NAME = "accountName";
private static final String[] SCOPES = {DriveScopes.DRIVE_METADATA_READONLY};
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_google_drive);
SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
credential = GoogleAccountCredential.usingOAuth2(
getApplicationContext(), Arrays.asList(SCOPES))
.setBackOff(new ExponentialBackOff())
.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, "abc.test#gmail.com"));
mService = new com.google.api.services.drive.Drive.Builder(
transport, jsonFactory, credential)
.setApplicationName("My Application")
.build();
}
#Override
public void onResume() {
super.onResume();
refreshResults();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_AUTHORIZATION:
if (resultCode != RESULT_OK) {
chooseAccount();
}
break;
case REQUEST_ACCOUNT_PICKER:
Log.w("gd", "in account picker");
if (resultCode == RESULT_OK && data != null &&
data.getExtras() != null) {
String accountName =
data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
credential.setSelectedAccountName(accountName);
SharedPreferences settings =
getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_ACCOUNT_NAME, accountName);
editor.commit();
}
} else if (resultCode == RESULT_CANCELED) {
Log.W("gd", "in cancelled");
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
break;
}
}
private void chooseAccount() {
startActivityForResult(
credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}
private void refreshResults() {
new GoogleDriveAsync(this).execute();
}
public class GoogleDriveAsync extends AsyncTask<Void, Void, Void> {
private GoogleDriveActivity activity;
#Override
protected Void doInBackground(Void... voids) {
try {
getDataFromApi();
} catch (final GooglePlayServicesAvailabilityIOException availabilityException) {
Log.w("gd", "GPS unavailable");
} catch (UserRecoverableAuthIOException userRecoverableException) {
Log.w("gd", "user recoverable");
activity.startActivityForResult(
userRecoverableException.getIntent(),
GoogleDriveActivity.REQUEST_AUTHORIZATION);
} catch (Exception e) {
Log.w("gd", "general exception " + e.getMessage());
}
return null;
}
GoogleDriveAsync(GoogleDriveActivity activity) {
this.activity = activity;
}
/**
* Fetch a list of up to 10 file names and IDs.
*
* #return List of Strings describing files, or an empty list if no files
* found.
* #throws IOException
*/
private List<String> getDataFromApi() throws IOException {
// Get a list of up to 10 files.
List<String> fileInfo = new ArrayList<String>();
FileList result = activity.mService.files().list()
.setMaxResults(10)
.execute();
List<File> files = result.getItems();
if (files != null) {
for (File file : files) {
fileInfo.add(String.format("%s (%s) (%s)\n",
file.getTitle(), file.getId(), file.getDownloadUrl()));
}
}
Log.w("gd", "file info is " + fileInfo.toString());
return fileInfo;
}
}
}
EDIT: Please see my answer (not the accepted one) for a working sample. The use case is: list all the Google Drive files for the user and when selected one, get the downloadUrl and the access_token for the file.
In order to use the file/folder outside of your GDAA based app, you need so-called ResourceID. This ResourceId is a string that uniquely identifies the object of Google Drive (see here)
Turn DriveId into ResourceId:
DriveId driveId = metadata.getDriveId();
String resourceId = driveId.getResourceId();
Once you have ResourceId, you can construct 'download URL' for your server app from it. The ResourceID string is the one found if you go to drive.google.com, select a file/folder and perform rightbutton > getLink.
(looks like 'https://drive.google.com/open?id=0B1mQUW2__I_am_the_resource_id')
Just take this string '0B1mQUW2__I_am_the_resource_id' to the 'TryIt' playground here and paste it to the 'fileId' field.
So, the short answer is that you don't need RESTful Api to get the file/folder identifier you can use elsewhere.
Second part of your question, 'How do I handle the folder hierarchy if I have to build my UI to show these files?' is answered (somewhat) in the 'createTree()/testTree()' methods of MainActivity of these 2 demos (GDAADemo, RESTDemo). These are the same tasks implemented on the GDAA as well as the REST Apis and the choice depends mainly on the SCOPE your app needs (GDAA supports only the FILE scope, whereas REST supports both the FILE and the DRIVE scopes)
Good Luck
The Activity:
public class GoogleDriveActivity extends Activity implements
GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
private GoogleApiClient mGoogleApiClient;
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
public static final int REQUEST_CODE_RESOLUTION = 1;
public static final int REQUEST_CODE_SELECT = 2;
private static final String[] SCOPES = { DriveScopes.DRIVE_FILE};
private static final String TAG = "GoogleDrive";
private String accountName;
#Override
protected void onResume() {
super.onResume();
setupGoogleClient();
}
#Override
protected void onPause() {
super.onPause();
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
}
#Override
public void onConnected(Bundle bundle) {
IntentSender intentSender = Drive.DriveApi
.newOpenFileActivityBuilder()
.build(mGoogleApiClient);
AccountManager manager = (AccountManager) getSystemService(ACCOUNT_SERVICE);
Account[] list = manager.getAccountsByType("com.google");
//Getting the first account because that is the primary account for that user
accountName = list[0].name;
try {
startIntentSenderForResult(intentSender, REQUEST_CODE_SELECT, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.w(TAG, "Unable to send intent to connect to Google API client " + e.getMessage());
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (!connectionResult.hasResolution()) {
return;
}
try {
connectionResult.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
} catch (IntentSender.SendIntentException e) {
Log.w(TAG, "Unable to send intent to connect to Google API client " + e.getMessage());
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_SELECT:
if (resultCode == RESULT_OK) {
DriveId driveId = data.getParcelableExtra(OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
String resourceId = driveId.getResourceId();
new GoogleDriveAsync(this).execute(resourceId);
}
finish();
break;
case REQUEST_CODE_RESOLUTION:
if (resultCode == RESULT_OK) {
mGoogleApiClient.connect();
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
break;
}
}
#Override
public void onConnectionSuspended(int i) {
Log.w(TAG, "Connection to Google API client suspended");
}
private void setupGoogleClient() {
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
mGoogleApiClient.connect();
}
class GoogleDriveAsync extends AsyncTask<String, Void, Void> {
private GoogleDriveActivity activity;
GoogleDriveAsync(GoogleDriveActivity activity) {
this.activity = activity;
}
#Override
protected Void doInBackground(String... args) {
try {
String id = args[0];
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(getApplicationContext(), Arrays.asList(SCOPES));
credential.setBackOff(new ExponentialBackOff());
credential.setSelectedAccountName(accountName);
com.google.api.services.drive.Drive service = new com.google.api.services.drive.Drive.Builder(transport, jsonFactory, credential).build();
File file = service.files().get(id).setFields("downloadUrl").execute();
if (file != null) {
String strUrl = file.getDownloadUrl();
String token = GoogleAuthUtil.getToken(activity, accountName, "oauth2: " + Drive.SCOPE_FILE);
//This is your downloadUrl and token
Log.w(TAG, "download link is " + strUrl + " and token is " + token);
}
} catch (final GooglePlayServicesAvailabilityIOException availabilityException) {
Log.w(TAG, "Google Play Services not available to get downloadUrl of selected file");
} catch (UserRecoverableAuthIOException userRecoverableException) {
Log.w(TAG, "User authorization error in getting downloadUrl " + userRecoverableException.getMessage());
} catch (Exception e) {
Log.w(TAG, "Exception in getting downloadUrl " + e.getMessage());
}
return null;
}
}
}
The AndroidManifest (relevant lines) :
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<activity
android:name="com.myapp.GoogleDriveActivity"
android:label="#string/app_name"
<meta-data android:name="com.google.android.apps.drive.APP_ID" android:value="id=<your-gogole-project-id>"/>
</activity>
The build.gradle (relevant lines):
compile 'com.google.android.gms:play-services-drive:7.8.0'
compile 'com.google.api-client:google-api-client:1.20.0'
compile 'com.google.api-client:google-api-client-android:1.20.0'
compile 'com.google.api-client:google-api-client-gson:1.20.0'
compile 'com.google.apis:google-api-services-drive:v2-rev170-1.20.0'

Android Google Play Services returns invalid token for blogger api

I've been given access to the blogger API, I've confirmed that on my developer console.
I've also written some code to perform oAuth2 with Google Play Services using some of the code below.
String SCOPE ="oauth2:https://www.googleapis.com/auth/blogger";
GoogleAuthUtil.getToken(context, "myEmail#gmail.com", mScope);
It returns a token. As it should.
However, once I try to access the api using the token i get a error.
Unexpected response code 403 for https://www.googleapis.com/blogger/v3/users/self/blogs
Here is my request:
And here is my response:
Here is my BaseActivity.java code that gets the token:
public class BaseActivity extends Activity {
static final int REQUEST_CODE_PICK_ACCOUNT = 1000;
static final int REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR = 1001;
static final int REQUEST_CODE_RECOVER_FROM_AUTH_ERROR = 1002;
private static final String SCOPE ="oauth2:https://www.googleapis.com/auth/blogger";
private String mEmail; // Received from newChooseAccountIntent(); passed to getToken()
public ProgressDialog mDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDialog = new ProgressDialog(this);
login();
}
public void login() {
pickUserAccount();
}
private void pickUserAccount() {
String[] accountTypes = new String[]{"com.google"};
Intent intent = AccountPicker.newChooseAccountIntent(null, null, accountTypes, false, null, null, null, null);
startActivityForResult(intent, REQUEST_CODE_PICK_ACCOUNT);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_PICK_ACCOUNT) {
// Receiving a result from the AccountPicker
if (resultCode == RESULT_OK) {
mEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
// With the account name acquired, go get the auth token
getToken();
} else if (resultCode == RESULT_CANCELED) {
// The account picker dialog closed without selecting an account.
// Notify users that they must pick an account to proceed.
Toast.makeText(this, "Pick Account", Toast.LENGTH_SHORT).show();
}
} else if ((requestCode == REQUEST_CODE_RECOVER_FROM_AUTH_ERROR ||
requestCode == REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR)
&& resultCode == RESULT_OK) {
// Receiving a result that follows a GoogleAuthException, try auth again
getToken();
}
}
private void getToken() {
if (mEmail == null) {
pickUserAccount();
} else {
if (isDeviceOnline()) {
new getTokenTask(BaseActivity.this, mEmail, SCOPE).execute();
} else {
Toast.makeText(this, "Not online", Toast.LENGTH_LONG).show();
}
}
}
/**
* This method is a hook for background threads and async tasks that need to
* provide the user a response UI when an exception occurs.
*/
public void handleException(final Exception e) {
// Because this call comes from the AsyncTask, we must ensure that the following
// code instead executes on the UI thread.
runOnUiThread(new Runnable() {
#Override
public void run() {
if (e 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
int statusCode = ((GooglePlayServicesAvailabilityException)e).getConnectionStatusCode();
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(statusCode, BaseActivity.this, REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
dialog.show();
} else if (e 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.
Intent intent = ((UserRecoverableAuthException)e).getIntent();
startActivityForResult(intent, REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
}
}
});
}
public boolean isDeviceOnline() {
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
return true;
} else {
return false;
}
}
public class getTokenTask extends AsyncTask{
Activity mActivity;
String mScope;
String mEmail;
getTokenTask(Activity activity, String name, String scope) {
this.mActivity = activity;
this.mScope = scope;
this.mEmail = name;
}
#Override
protected Object doInBackground(Object[] params) {
try {
String token = fetchToken();
Preferences.saveString(Constants.KEY_BLOGGER_TOKEN, token);
} catch (IOException e) {
// The fetchToken() method handles Google-specific exceptions,
// so this indicates something went wrong at a higher level.
// TIP: Check for network connectivity before starting the AsyncTask.
}
return null;
}
/**
* Gets an authentication token from Google and handles any
* GoogleAuthException that may occur.
*/
protected String fetchToken() throws IOException {
try {
return GoogleAuthUtil.getToken(mActivity, mEmail, mScope);
} catch (UserRecoverableAuthException userRecoverableException) {
// GooglePlayServices.apk is either old, disabled, or not present
// so we need to show the user some UI in the activity to recover.
((BaseActivity)mActivity).handleException(userRecoverableException);
} catch (GoogleAuthException fatalException) {
// Some other type of unrecoverable exception has occurred.
// Report and log the error as appropriate for your app.
}
return null;
}
}
}
I've been banging my head against the wall on this one. Anyone have any ideas?
Finally figured it out.
My build.gradle file somehow ended up having a different Application ID than my manifest. I changed it so they both match the manifest, and boom! it worked.

Categories

Resources