How to use the setResultCallback method? - android

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.drive.DriveApi.ContentsResult;
import com.google.android.gms.drive.MetadataChangeSet;
public class Phototodrive 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<ContentsResult>() {
public void onResult(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 onDisconnected() {
Log.i(TAG, "GoogleApiClient connection suspended");
}
}
I`m trying to get the android google drive api quickstart application running.
I get an error like
- The method setResultCallback(new ResultCallback(){}) is undefined for the type PendingResult
- ResultCallback cannot be resolved to a type
What should I change, or why I am getting this error?

The code looks outdated (Google Play Services lib ver 14) judging by the presence of the 'onDisconnect()'. Check if you have ver 15 (Android SDK Manager) and then try quickstart, demos or this code. And your device should be on Google Play Services 4.2...

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();
}

Download database File For Google Drive API Android

I am using the drive api to create a database file in the hidden app folder on google drive. The database file is called notes.db I have been able to successfully upload the database file to google drive but I have no idea how to download it back to the user's device. This is what i'm trying to do. My app makes a folder on the user's device called School Binder. in that folder is another folder called Note backups. Here is where I backup the database. The directory is
Environment.getExternalStorageDirectory() + "/School Binder/Note Backups/Notes.db"
Google drive takes this file and uploads it to the hidden app folder. Now I want to get this notes.db file stored in that app folder on google drive and download it back to this directory on the phone.
Environment.getExternalStorageDirectory() + "/School Binder/Note Backups/Notes.db"
How do I do this. Thanks. Here is my code for uploading the database to drive this works correctly
// Define And Instantiate Variable DriveContents driveContents//
DriveContents driveContents = result.getStatus().isSuccess() ? result.getDriveContents() : null;
// Gets The Data for The File//
if (driveContents != null) try {
// Define And Instantiate Variable OutputStream outputStream//
OutputStream outputStream = driveContents.getOutputStream();
// Start Writing Data To File//
if (outputStream != null) try {
// Define And Instantiate Variable InputStream inputStream//
InputStream inputStream = new FileInputStream(dbFile);
// Define And Instantiate Variable Byte buffer//
byte[] buffer = new byte[5000];
// Define Variable Int data//
int data;
// Run Code While data Is Bigger Then Zero//
while ((data = inputStream.read(buffer, 0, buffer.length)) > 0) {
// Write To outputStream//
outputStream.write(buffer, 0, data);
// Flush outputStream//
outputStream.flush();
}
} finally {
// Close outputStream//
outputStream.close();
}
} catch (Exception e) {e.printStackTrace(); Toast.makeText(getApplicationContext(), "Failed To Upload: No Backup File Found", Toast.LENGTH_LONG).show(); return;}
How do I change this to make it work for downloading data to a file from google drive
In Lifecycle of a Drive file, Drive Android API lets your app access files even if the device is offline. To support offline cases, the API implements a sync engine, which runs in the background to upstream and downstream changes as network access is available and to resolve conflicts. Perform an initial download request if the file is not yet synced to the local context but the user wants to open the file. The API handles this automatically when a file is requested.
In downloading a file, you make an authorized HTTP GET request to the file's resource URL and include the query parameter alt=media. However, please note that downloading the file requests the user to have at least read access.
Sample HTTP Request:
GET https://www.googleapis.com/drive/v3/files/0B9jNhSvVjoIVM3dKcGRKRmVIOVU?alt=media
Authorization: Bearer ya29.AHESVbXTUv5mHMo3RYfmS1YJonjzzdTOFZwvyOAUVhrs
For the coding part, this SO post might be of help too.
I figured it out this is my code to redownload a database back to the phone
//<editor-fold desc="Create Drive Db File On Device">
// Log That The File Was Opened//
Log.d("TAG", "File contents opened");
// Define And Instantiate Variable DriveContents driveContents//
DriveContents driveContents = result.getStatus().isSuccess() ? result.getDriveContents() : null;
// Gets The Data for The File//
if (driveContents != null) try {
// Define And Instantiate Variable OutputStream outputStream//
OutputStream outputStream = new FileOutputStream(dbFile);
// Define And Instantiate Variable InputStream inputStream//
InputStream inputStream = driveContents.getInputStream();
// Define And Instantiate Variable Byte buffer//
byte[] buffer = new byte[5000];
// Define Variable Int data//
int data;
// Run Code While data Is Bigger Then Zero//
while ((data = inputStream.read(buffer, 0, buffer.length)) > 0) {
// Write To outputStream//
outputStream.write(buffer, 0, data);
// Flush outputStream//
outputStream.flush();
}
// Close outputStream//
outputStream.close();
// Discard Drive Contents//
driveContents.discard(googleApiClient);
} catch (Exception e) {e.printStackTrace(); Toast.makeText(getApplicationContext(), "File Failed To Download", Toast.LENGTH_LONG).show(); }
//</editor-fold>
here is a complete class to upload an internal database, download it and delete it from Google Drive.
Only need to call functions asynchronously and show user a progressbar.
DownloadFromGoogleDrive function saves the database in the internal database folder to the app with the name "database2"
Hope it's helpful.
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.drive.DriveApi;
import com.google.android.gms.drive.DriveApi.MetadataBufferResult;
import com.google.android.gms.drive.DriveFile.DownloadProgressListener;
import com.google.android.gms.drive.DriveId;
import com.google.android.gms.drive.DriveResource;
import com.google.android.gms.drive.Metadata;
import com.google.android.gms.drive.DriveApi.DriveContentsResult;
import com.google.android.gms.drive.DriveContents;
import com.google.android.gms.drive.DriveFile;
import com.google.android.gms.drive.DriveFolder.DriveFileResult;
import com.google.android.gms.drive.MetadataChangeSet;
import com.google.android.gms.drive.query.Filters;
import com.google.android.gms.drive.query.Query;
import com.google.android.gms.drive.query.SearchableField;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
public class BackupDatabaseActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener {
private static final String TAG = "BackupDatabaseActivity";
private GoogleApiClient api;
private boolean mResolvingError = false;
private static final int DIALOG_ERROR_CODE =100;
private static final String DATABASE_NAME = "database";
private static final String GOOGLE_DRIVE_FILE_NAME = "database_backup";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create the Drive API instance
api = new GoogleApiClient.Builder(this).addApi(Drive.API).addScope(Drive.SCOPE_FILE).
addConnectionCallbacks(this).addOnConnectionFailedListener(this).build();
}
final private ResultCallback<DriveApi.DriveContentsResult> contentsCallback = new ResultCallback<DriveApi.DriveContentsResult>() {
#Override
public void onResult(DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to create new file contents");
return;
}
CreateFileOnGoogleDrive(result);
//OR DownloadFromGoogleDrive(result);
//OR DeleteFromGoogleDrive(result);
}
};
final private ResultCallback<DriveFileResult> fileCallback = new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to create the file");
return;
}
Log.v(TAG, "File created: "+result.getDriveFile().getDriveId());
}
};
/**
* Create a file in root folder using MetadataChangeSet object.
* #param result
*/
public void CreateFileOnGoogleDrive(DriveContentsResult result){
final DriveContents driveContents = result.getDriveContents();
// Perform I/O off the UI thread.
new Thread() {
#Override
public void run() {
try {
FileInputStream is = new FileInputStream(getDbPath());
BufferedInputStream in = new BufferedInputStream(is);
byte[] buffer = new byte[8 * 1024];
BufferedOutputStream out = new BufferedOutputStream(driveContents.getOutputStream());
int n = 0;
while( ( n = in.read(buffer) ) > 0 ) {
out.write(buffer, 0, n);
}
out.flush();
out.close();
in.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String mimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType("db");
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(GOOGLE_DRIVE_FILE_NAME) // Google Drive File name
.setMimeType(mimeType)
.setStarred(true).build();
// create a file in root folder
Drive.DriveApi.getRootFolder(api)
.createFile(api, changeSet, driveContents)
.setResultCallback(fileCallback);
}
}.start();
}
/**
* Download File from Google Drive
* #param result
*/
public void DownloadFromGoogleDrive(DriveContentsResult result){
final DriveContents driveContents = result.getStatus().isSuccess() ? result.getDriveContents() : null;
if(driveContents!=null){
Query query = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, GOOGLE_DRIVE_FILE_NAME)).build();
Drive.DriveApi.query(api, query).setResultCallback(new ResultCallback<MetadataBufferResult>() {
#Override
public void onResult(MetadataBufferResult result) {
try{
DriveId driveId = result.getMetadataBuffer().get(0).getDriveId();
DriveFile driveFile = driveId.asDriveFile();
//mProgressBar.setProgress(0);
DownloadProgressListener listener = new DownloadProgressListener() {
#Override
public void onProgress(long bytesDownloaded, long bytesExpected) {
// Update progress dialog with the latest progress.
int progress = (int)(bytesDownloaded*100/bytesExpected);
Log.d(TAG, String.format("Loading progress: %d percent", progress));
// mProgressBar.setProgress(progress);
}
};
driveFile.open(api, DriveFile.MODE_READ_ONLY, listener).setResultCallback(driveContentsCallback);
}catch(Exception e){
Toast.makeText(getApplicationContext(), "File Failed To Download", Toast.LENGTH_LONG).show();
}
}
});
}else{
Toast.makeText(getApplicationContext(), "File Failed To Download", Toast.LENGTH_LONG).show();
}
}
private ResultCallback<DriveContentsResult> driveContentsCallback =
new ResultCallback<DriveContentsResult>() {
#Override
public void onResult(DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.d(TAG, "Error while opening the file contents");
return;
}
Log.d(TAG, "Downloaded");
DriveContents dc = result.getDriveContents();
try {
InputStream inputStream = dc.getInputStream();
OutputStream outputStream = new FileOutputStream(getDbPath()+"2");
byte[] buffer = new byte[8 * 1024];
//BufferedOutputStream out = new BufferedOutputStream(dc.getOutputStream());
int n = 0;
while( ( n = inputStream.read(buffer) ) > 0 ) {
outputStream.write(buffer, 0, n);
}
outputStream.flush();
outputStream .close();
//inputStream.close();
dc.discard(api);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
/**
* Delete File from Google Drive
* #param result
*/
public void DeleteFromGoogleDrive(DriveContentsResult result){
Query query = new Query.Builder()
.addFilter(Filters.eq(SearchableField.TITLE, GOOGLE_DRIVE_FILE_NAME))
.build();
Drive.DriveApi.query(api, query)
.setResultCallback(new ResultCallback<MetadataBufferResult>() {
#Override
public void onResult(MetadataBufferResult result) {
try{
Metadata metadata = result.getMetadataBuffer().get(0);
/*String a = metadata.getTitle();
String b = metadata.getDescription();
long c = metadata.getFileSize();*/
DriveResource driveResource = metadata.getDriveId().asDriveResource();
if (metadata.isTrashable()) {
if (metadata.isTrashed()) {
driveResource.untrash(api).setResultCallback(trashStatusCallback);
} else {
driveResource.trash(api).setResultCallback(trashStatusCallback);
}
} else {
Log.d(TAG, "Error trying delete");
}
}catch(Exception e){
Log.d(TAG, "Error: metadata doesn't exist");
}
}
});
}
/**
* Callback when call to trash or untrash is complete.
*/
private final ResultCallback<Status> trashStatusCallback =
new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (!status.isSuccess()) {
Log.e(TAG, "Error trying delete: " + status.getStatusMessage());
return;
}else{
Log.e(TAG, "Deleted: " + status.getStatusMessage());
}
}
};
private File getDbPath() {
return this.getDatabasePath(DATABASE_NAME);
}
#Override
public void onConnectionSuspended(int cause) {
// TODO Auto-generated method stub
Log.v(TAG, "Connection suspended");
}
#Override
public void onStart() {
super.onStart();
if(!mResolvingError) {
api.connect(); // Connect the client to Google Drive
}
}
#Override
public void onStop() {
super.onStop();
api.disconnect(); // Disconnect the client from Google Drive
}
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.v(TAG, "Connection failed");
if(mResolvingError) { // If already in resolution state, just return.
return;
} else if(result.hasResolution()) { // Error can be resolved by starting an intent with user interaction
mResolvingError = true;
try {
result.startResolutionForResult(this, DIALOG_ERROR_CODE);
} catch (SendIntentException e) {
e.printStackTrace();
}
} else { // Error cannot be resolved. Display Error Dialog stating the reason if possible.
Toast.makeText(this, "Error: Connection failed", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onConnected(Bundle connectionHint) {
Log.v(TAG, "Connected successfully");
/* Connection to Google Drive established. Now request for Contents instance, which can be used to provide file contents.
The callback is registered for the same. */
Drive.DriveApi.newDriveContents(api).setResultCallback(contentsCallback);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == DIALOG_ERROR_CODE) {
mResolvingError = false;
if(resultCode == RESULT_OK) { // Error was resolved, now connect to the client if not done so.
if(!api.isConnecting() && !api.isConnected()) {
api.connect();
}
}
}
}
}

Stuck in Infinite Google Sign-In Loop

I've been integrating Google Drive support into my app, so when I click a button, it prompts me to sign in with my Google Account. However, I get stuck in an infinite loop whenever I try that just presents me with the same sign-in screen over and over. I'm running Android 4.4.2 (KitKat) for what it's worth.
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
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.DriveId;
import com.google.android.gms.drive.OpenFileActivityBuilder;
import com.neilcpower.surveildroid.R;
import butterknife.ButterKnife;
import timber.log.Timber;
public class FilePickerActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
public static final String FILE_ID = "File ID";
private static final int REQUEST_CODE_OPENER = 48957;
private static final int REQUEST_CODE_RESOLUTION = 1;
private GoogleApiClient mGoogleApiClient;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file_picker);
ButterKnife.bind(this);
}
#Override
protected void onResume() {
super.onResume();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
}
#Override
protected void onPause() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onPause();
}
#Override
public void onConnected(Bundle connectionHint) {
Timber.i("Connected To Google API Client");
try {
IntentSender intentSender = Drive.DriveApi
.newOpenFileActivityBuilder()
.setMimeType(new String[]{"text/plain", "text/html"})
.build(mGoogleApiClient);
try {
startIntentSenderForResult(
intentSender, REQUEST_CODE_OPENER, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Timber.e(e, "Unable to send intent");
}
} catch (IllegalStateException e) {
// TODO Why is the first time onConnected called the client is not connected?
mGoogleApiClient.connect();
}
}
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Timber.i("GoogleApiClient connection failed: %s", connectionResult.toString());
if (!connectionResult.hasResolution()) {
GoogleApiAvailability.getInstance().getErrorDialog(this, connectionResult.getErrorCode(), 0).show();
return;
}
try {
connectionResult.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
} catch (IntentSender.SendIntentException e) {
Timber.e(e, "Exception while starting resolution activity");
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_OPENER:
if (resultCode == RESULT_OK) {
DriveId driveId = data.getParcelableExtra(OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
Intent returnIntent = new Intent();
returnIntent.putExtra(FILE_ID, driveId.encodeToString());
setResult(Activity.RESULT_OK, returnIntent);
}
finish();
break;
case REQUEST_CODE_RESOLUTION:
if (resultCode == RESULT_OK) {
mGoogleApiClient.connect();
}
break;
}
}
}
The error is (repeatedly) the following: ConnectionResult{statusCode=SIGN_IN_REQUIRED, resolution=PendingIntent{428a9d58: android.os.BinderProxy#428a9ce0}, message=null}
The app itself is quite large, so please let me know if any other code is required to diagnose. I've been fighting with this issue for some time now and have exhausted all Google/SO solutions for similar problems.
You are creating new Google API Client in each onResume. When you are not signed in, the client asks the user to sign in. This calls onPause in your activity which disconnects from the client so the sign-in result is never retrieved. After the user signs in, you are returned to your activity and onResume is called. That creates a new Google API Client that is not signed in so it asks the user to sign in again.
Create the client in onCreate.
Also, connect should go to onStart and disconnect to onStop.
I finally figured it out!
It turns out my partner had not authorized me using my SHA-1 key on the Google Development side, so apparently this lack of permissions manifested as a constant looping of the "Please Sign In" fragment... So of course, loading from his machine would work, but my own environment couldn't compile.
Hope this helps anyone in the future who is stuck and thinking the problem is with their mobile device/cache!

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.

Stuck in Connection Failed loop with GoogleApiClient

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.

Categories

Resources