I'm using google-api-client-android2-1.10.3-beta.jar
.
I have some piece of code, which works half year ago. Just that recently, when I try to run them again, they no longer work. I'm getting the following exception.
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code": 403,
"errors": [
{
"domain": "global",
"location": "Authorization",
"locationType": "header",
"message": "The authentication method used is not allowed.",
"reason": "authenticationMethod"
}
],
"message": "The authentication method used is not allowed."
}
The exception is being thrown during
// request = service.files().list().setQ("title contains 'jstock-" + Utils.getJStockUUID().substring(0, 19) + "' and trashed = false");
FileList files = request.execute();
My piece of code, basically performing request
https://www.googleapis.com/drive/v2/files?q=title+contains+%27jstock-fe78440e-e0fe-4efb-%27+and+trashed+%3d+false
, by using AuthToken from com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager
I try the request in oAuth 2.0 playground. It works fine. However, I'm not sure why my code which works half year ago, doesn't work any more.
Here is the code which is used to get AuthToken
package com.jstock.cloud;
import java.io.IOException;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.jstock.engine.Subject;
import com.jstock.gui.JStockApplication;
import com.jstock.gui.Preferences;
import com.jstock.gui.R;
// The code is picked from http://code.google.com/p/google-api-java-client/source/browse/tasks-android-sample/src/main/java/com/google/api/services/samples/tasks/android/TasksSample.java?repo=samples
public class LoginManager extends Subject<LoginManager, String> {
public LoginManager(Activity activity) {
// TODO : Need to revise API key.
ClientCredentials.errorIfNotSpecified();
// Noted, the passed in activity, should override the following method.
// protected void onActivityResult(int requestCode, int resultCode, Intent data)
// In the overriden method, it should call LoginManager's onActivityResult.
this.activity = activity;
dialog = new ProgressDialog(activity);
dialog.setMessage(activity.getString(R.string.login));
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
authToken = Preferences.getAuthTOken();
accountManager = new GoogleAccountManager(JStockApplication.instance());
accountName = Preferences.getAccountName();
}
public void gotAccount() {
Account account = accountManager.getAccountByName(accountName);
if (account == null) {
chooseAccount();
return;
}
if (authToken != null) {
onAuthToken(authToken);
return;
}
dialog.show();
accountManager.getAccountManager().getAuthToken(account, AUTH_TOKEN_TYPE, true, new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle bundle = future.getResult();
if (bundle.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivityForResult(intent, REQUEST_AUTHENTICATE);
return;
} else if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {
final String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
setAuthToken(authToken);
onAuthToken(authToken);
return;
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
onAuthToken(null);
}
}, null);
}
public void chooseAccount() {
dialog.show();
accountManager.getAccountManager().getAuthTokenByFeatures(GoogleAccountManager.ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
null,
activity,
null,
null,
new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
Bundle bundle;
try {
bundle = future.getResult();
setAccountName(bundle.getString(AccountManager.KEY_ACCOUNT_NAME));
final String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
setAuthToken(authToken);
onAuthToken(authToken);
return;
} catch (OperationCanceledException e) {
// user canceled
} catch (AuthenticatorException e) {
Log.e(TAG, e.getMessage(), e);
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
}
onAuthToken(null);
}
},
null);
}
private void setAccountName(String accountName) {
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
Preferences.setAccountName(accountName);
this.accountName = accountName;
}
private void setAuthToken(String authToken) {
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
Preferences.setAuthToken(authToken);
this.authToken = authToken;
}
// To be consumed by this.activity.
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_AUTHENTICATE:
if (resultCode == Activity.RESULT_OK) {
gotAccount();
} else {
chooseAccount();
}
break;
}
}
private void onAuthToken(final String authToken) {
// Need to run on AsyncTask. This method is currently executed by main
// thread, as we are using null in the last parameter of
// getAuthTokenByFeatures and getAuthToken. I try not to notify through
// main thread, as I believe callers will most probably perform non-UI
// task.
new AsyncTask<Void, Void, Void>() {
@Override
protected void onPreExecute() {
dialog.dismiss();
}
@Override
protected Void doInBackground(Void... arg0) {
// authToken possible null, which indicates failure.
LoginManager.this.notify(LoginManager.this, authToken);
return null;
}
@Override
protected void onPostExecute(Void result) {
}
}.execute();
}
public void invalidateAuthToken() {
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
accountManager.invalidateAuthToken(authToken);
authToken = null;
Preferences.removeAuthToken();
}
private static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/drive";
private static final int REQUEST_AUTHENTICATE = 0;
private final GoogleAccountManager accountManager;
private final Activity activity;
private String accountName;
private final ProgressDialog dialog;
private String authToken;
private static final String TAG = "LoginManager";
}
Here is the code which is used to perform API request
package com.jstock.cloud;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.util.Log;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.services.GoogleKeyInitializer;
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.client.extensions.android2.AndroidHttp;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.jstock.gui.Utils;
public class CloudFile {
public final java.io.File file;
public final long checksum;
public final long date;
public final int version;
private CloudFile(java.io.File file, long checksum, long date, int version) {
this.file = file;
this.checksum = checksum;
this.date = date;
this.version = version;
}
public static CloudFile newInstance(java.io.File file, long checksum, long date, int version) {
return new CloudFile(file, checksum, date, version);
}
public static CloudFile loadFromGoogleDrive(String authToken) {
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = new GsonFactory();
GoogleCredential credential = new GoogleCredential();
Log.i("CHEOK", "authToken = " + authToken);
credential.setAccessToken(authToken);
Drive service = new Drive.Builder(transport, jsonFactory, credential)
.setApplicationName(Utils.getApplicationName())
.setJsonHttpRequestInitializer(new GoogleKeyInitializer(ClientCredentials.KEY))
.build();
List<File> files = retrieveAllJStockFiles(service);
long checksum = 0;
long date = 0;
int version = 0;
File file = null;
for (File _file : files) {
file = _file;
// Use title, not filename.
final String title = file.getTitle();
final String downloadUrl = file.getDownloadUrl();
if (title == null || downloadUrl == null) {
// Do we really need to perform null checking?
continue;
}
// Retrieve checksum, date and version information from filename.
final Matcher matcher = googleDocTitlePattern.matcher(title);
String _checksum = null;
String _date = null;
String _version = null;
if (matcher.find()){
if (matcher.groupCount() == 3) {
_checksum = matcher.group(1);
_date = matcher.group(2);
_version = matcher.group(3);
}
}
if (_checksum == null || _date == null || _version == null) {
continue;
}
try {
checksum = Long.parseLong(_checksum);
date = Long.parseLong(_date);
version = Integer.parseInt(_version);
} catch (NumberFormatException ex) {
Log.e(TAG, "", ex);
continue;
}
} // for
if (file == null) {
return null;
}
final java.io.File temp = Utils.createTempFileOnExternalCacheDir(Utils.getJStockUUID(), ".zip");
if (temp == null) {
return null;
}
// Delete temp file when program exits.
temp.deleteOnExit();
// Delete temp file when program exits.
Map<String, String> headers = new LinkedHashMap<String, String>();
headers.put("Authorization", "OAuth " + authToken);
java.io.File downloadedFile = Utils.downloadAsTempFile(file.getDownloadUrl(), headers, temp);
if (downloadedFile == null) {
return null;
}
return CloudFile.newInstance(downloadedFile, checksum, date, version);
}
/**
* Retrieve a list of File resources.
*
* @param service Drive API service instance.
* @return List of File resources.
*/
private static List<File> retrieveAllJStockFiles(Drive service) {
List<File> result = new ArrayList<File>();
Files.List request = null;
try {
// Not sure why. In oAuth 2 playground, I cannot use full JStock
// UUID. Perhaps it places restriction on length of query.
// https://www.googleapis.com/drive/v2/files?q=title+contains+%27jstock-fe78440e-e0fe-4efb-%27+and+trashed+%3d+false
//request = service.files().list();
request = service.files().list().setQ("title contains 'jstock-" + Utils.getJStockUUID().substring(0, 19) + "' and trashed = false");
} catch (IOException e) {
Log.e(TAG, "", e);
return result;
}
do {
try {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} catch (IOException e) {
Log.e(TAG, "", e);
request.setPageToken(null);
}
} while (request.getPageToken() != null && request.getPageToken().length() > 0);
return result;
}
// http://stackoverflow.com/questions/1360113/is-java-regex-thread-safe
private static final Pattern googleDocTitlePattern = Pattern.compile("jstock-" + Utils.getJStockUUID() + "-checksum=([0-9]+)-date=([0-9]+)-version=([0-9]+)\\.zip", Pattern.CASE_INSENSITIVE);
private static final String TAG = "CloudFile";
}
Here is how we perform API call
// authToken is String, which obtained from LoginManager.
CloudFile.loadFromGoogleDrive(authToken);
Any idea why the above exception is being thrown?