3

I am trying to run the following code (taken largely from Stephen Wylie):

package com.googledrive.googledriveapp;
// For Google Drive / Play Services
// Version 1.1 - Added new comments & removed dead code
// Stephen Wylie - 10/20/2012
import java.io.IOException;
import java.util.ArrayList;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.common.AccountPicker;
import com.google.api.client.auth.oauth2.BearerToken;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.android2.AndroidHttp;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.json.JsonHttpRequest;
import com.google.api.client.http.json.JsonHttpRequestInitializer;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Apps.List;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.DriveRequest;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;

public class MainActivity extends Activity {
    private static final int CHOOSE_ACCOUNT=0;
    private static String accountName;
    private static int REQUEST_TOKEN=0;
    private Button btn_drive;
    private Context ctx = this;
    private Activity a = this;

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        // set up the GUI layout
        setContentView(R.layout.activity_main);
        // set the variables to access the GUI controls
        btn_drive = (Button) findViewById(R.id.btn_drive);
            btn_drive.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                chooseAccount();
            }
            });
    }

    public void chooseAccount() {
        Intent intent = AccountPicker.newChooseAccountIntent(null, null, new String[]{"com.google"}, false, null, null, null, null);
        startActivityForResult(intent, CHOOSE_ACCOUNT);
    }

    // Fetch the access token asynchronously.
    void getAndUseAuthTokenInAsyncTask(Account account) {
        AsyncTask<Account, String, String> task = new AsyncTask<Account, String, String>() {
            ProgressDialog progressDlg;
            AsyncTask<Account, String, String> me = this;

            @Override
            protected void onPreExecute() {
                progressDlg = new ProgressDialog(ctx, ProgressDialog.STYLE_SPINNER);
                progressDlg.setMax(100);
                progressDlg.setTitle("Validating...");
                progressDlg.setMessage("Verifying the login data you entered...\n\nThis action will time out after 10 seconds.");
                progressDlg.setCancelable(false);
                progressDlg.setIndeterminate(false);
                progressDlg.setOnCancelListener(new android.content.DialogInterface.OnCancelListener() {
                    public void onCancel(DialogInterface d) {
                        progressDlg.dismiss();
                        me.cancel(true);
                    }
                });
                progressDlg.show();
            }

            @Override
            protected String doInBackground(Account... params) {
                return getAccessToken(params[0]);
            }

            @Override
            protected void onPostExecute(String s) {
                if (s == null) {
                    // Wait for the extra intent
                } else {
                    accountName = s;
                    getDriveFiles();
                }
                progressDlg.dismiss();
            }
        };
        task.execute(account);
    }

    /**
     * Fetches the token from a particular Google account chosen by the user.  DO NOT RUN THIS DIRECTLY.  It must be run asynchronously inside an AsyncTask.
     * @param activity
     * @param account
     * @return
     */
    private String getAccessToken(Account account) {
        try {
            return GoogleAuthUtil.getToken(ctx, account.name, "oauth2:" + DriveScopes.DRIVE);  // IMPORTANT: DriveScopes must be changed depending on what level of access you want
        } catch (UserRecoverableAuthException e) {
            // Start the Approval Screen intent, if not run from an Activity, add the Intent.FLAG_ACTIVITY_NEW_TASK flag.
            a.startActivityForResult(e.getIntent(), REQUEST_TOKEN);
            e.printStackTrace();
            return null;
        } catch (GoogleAuthException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private Drive getDriveService() {
        HttpTransport ht = AndroidHttp.newCompatibleTransport();             // Makes a transport compatible with both Android 2.2- and 2.3+
        JacksonFactory jf = new JacksonFactory();                            // You need a JSON parser to help you out with the API response
        Credential credential = new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accountName);
        HttpRequestFactory rf = ht.createRequestFactory(credential);
        Drive.Builder b = new Drive.Builder(ht, jf, null);
        b.setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() {

            @Override
            public void initialize(JsonHttpRequest request) throws IOException {
                DriveRequest driveRequest = (DriveRequest) request;
                driveRequest.setPrettyPrint(true);
                driveRequest.setOauthToken(accountName);
            }
        });
        return b.build();
    }

    /**
     * Obtains a list of all files on the signed-in user's Google Drive account.
     */
    private void getDriveFiles() {
        Drive service = getDriveService();
        Log.d("SiteTrack", "FUNCTION getDriveFiles()");
        Files.List request;
        try {
            request = service.files().list(); // .setQ("mimeType=\"text/plain\"");
        } catch (IOException e) {


            e.printStackTrace();
            return;
        }
        do {
            FileList files;
            try {
                System.out.println("got here");
                Log.d("SiteTrack", request.toString());
                **files = request.execute();**
            } catch (IOException e) {
                e.printStackTrace();
                Log.d("SiteTrack", "Exception");
                return;
            }
            ArrayList<File> fileList = (ArrayList<File>) files.getItems();
            Log.d("SiteTrack", "Files found: " + files.getItems().size());
            for (File f : fileList) {
                String fileId = f.getId();
                String title = f.getTitle();
                Log.d("SiteTrack", "File " + fileId + ": " + title);
            }
            request.setPageToken(files.getNextPageToken());
        } while (request.getPageToken() != null && request.getPageToken().length() >= 0);
    }

    protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        if (requestCode == CHOOSE_ACCOUNT && resultCode == RESULT_OK) {
            accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
            GoogleAccountManager gam = new GoogleAccountManager(this);
            getAndUseAuthTokenInAsyncTask(gam.getAccountByName(accountName));
            Log.d("SiteTrack", "CHOOSE_ACCOUNT");
        } else if (requestCode == REQUEST_TOKEN && resultCode == RESULT_OK) {
            accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
            Log.d("SiteTrack", "REQUEST_TOKEN");
        }
    }   
}

However, I get the following exception:

11-19 16:35:27.758: W/System.err(23287): com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
11-19 16:35:27.758: W/System.err(23287): {
11-19 16:35:27.758: W/System.err(23287):   "code" : 403,
11-19 16:35:27.758: W/System.err(23287):   "errors" : [ {
11-19 16:35:27.758: W/System.err(23287):     "domain" : "usageLimits",
11-19 16:35:27.762: W/System.err(23287):     "message" : "Access Not Configured",
11-19 16:35:27.762: W/System.err(23287):     "reason" : "accessNotConfigured"
11-19 16:35:27.762: W/System.err(23287):   } ],
11-19 16:35:27.762: W/System.err(23287):   "message" : "Access Not Configured"
11-19 16:35:27.762: W/System.err(23287): }
11-19 16:35:27.762: W/System.err(23287):    at com.google.api.client.googleapis.services.GoogleClient.executeUnparsed(GoogleClient.java:237)
11-19 16:35:27.762: W/System.err(23287):    at com.google.api.client.http.json.JsonHttpRequest.executeUnparsed(JsonHttpRequest.java:207)
11-19 16:35:27.762: W/System.err(23287):    at com.google.api.services.drive.Drive$Files$List.execute(Drive.java:1071)
11-19 16:35:27.762: W/System.err(23287):    at com.googledrive.googledriveapp.MainActivity.getDriveFiles(MainActivity.java:173)
11-19 16:35:27.762: W/System.err(23287):    at com.googledrive.googledriveapp.MainActivity.access$3(MainActivity.java:156)
11-19 16:35:27.762: W/System.err(23287):    at com.googledrive.googledriveapp.MainActivity$2.onPostExecute(MainActivity.java:104)
11-19 16:35:27.765: W/System.err(23287):    at com.googledrive.googledriveapp.MainActivity$2.onPostExecute(MainActivity.java:1)
11-19 16:35:27.765: W/System.err(23287):    at android.os.AsyncTask.finish(AsyncTask.java:417)
11-19 16:35:27.765: W/System.err(23287):    at android.os.AsyncTask.access$300(AsyncTask.java:127)
11-19 16:35:27.765: W/System.err(23287):    at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
11-19 16:35:27.765: W/System.err(23287):    at android.os.Handler.dispatchMessage(Handler.java:99)
11-19 16:35:27.765: W/System.err(23287):    at android.os.Looper.loop(Looper.java:123)
11-19 16:35:27.765: W/System.err(23287):    at android.app.ActivityThread.main(ActivityThread.java:4627)
11-19 16:35:27.765: W/System.err(23287):    at java.lang.reflect.Method.invokeNative(Native Method)
11-19 16:35:27.769: W/System.err(23287):    at java.lang.reflect.Method.invoke(Method.java:521)
11-19 16:35:27.769: W/System.err(23287):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
11-19 16:35:27.769: W/System.err(23287):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
11-19 16:35:27.769: W/System.err(23287):    at dalvik.system.NativeStart.main(Native Method)

which traces to the line files = request.execute(); in my code above (I have marked it with asterisks). I have enabled both the Drive SDK and the Drive API in my Google APIs Console. Here are a couple of snapshots of my Drive SDK settings: enter image description here enter image description here For the Client ID section that is not pictured, I simply pasted the "Client ID for installed applications" from the API Access section (I also tried the "Client ID for Drive SDK"). Anyone know what the problem is?

John Roberts
  • 5,885
  • 21
  • 70
  • 124

2 Answers2

7

EDITED: Sorry I just realized that we can sign the apk in debugging mode using debug.keystore. SO, the most important thing is insert the correct SHA1 key in your Google API console. (generated using the JRE7 tool as mentioned by Stephen Wylie in his Google+ post)

Also see (developer.android.com/tools/publishing/app-signing.html#debugmode) for the keystore password

After some trial and error, I finally able to list files in my google drive Although I am not sure whether this is the most correct answer, but at least I am able to list files now

First, please refer to Stephen Wylie updated Google+ post https://plus.google.com/u/0/114042449736049687152/posts/CD3L8zcJg5Z

generate the SHA1 key with your .keystore using the JRE7 tool

I am using the .keystore file that I had been using with my google play store (If you do not have any keystore, you can go to your project, click export, then it will request you to create a .keystore file)

with the generated SHA1 key, go to your Google API console (it is best if you start from fresh, delete the current project and start a new one)

-enable Drive API

-enable Drive SDK

-go to API ACCESS

-create CLIENT ID

-IMPORTANT here, choose Installed application & Android

-ALSO IMPORTANT, key in your generated SHA1 key

-key in your CORRECT package name (com.example.xxx) that you are using in your eclipse project

-Go to Drive SDK tab -upload icon

-IMPORTANT, key in your CLIENT ID (Client ID for installed applications) from the API Access tab

-Insert 3 scopes mentioned in Stephen Wylie post, [/userinfo.email, /userinfo.profile, /auth/drive]

-Insert an URL

-Tick Multiple File Support

Again, make sure your package name in your code is same as the package name you inserted into Google API Console

Finally, in eclipse, export your project using the .keystore file you created just now

Put the exported APK file into your phone, install and try.

Check your LogCat to show the listed files from your Google Drive

I had succeeded using this way.

EDITED: If you generated the SHA1 key with the debug.keystore, you can skip the "Export" part. Just debug your application is OK. Eclipse will automatically sign the apk with debug.keystore.

EDITED: Next time your code is ready, you need to generate a new SHA1 key with your real .keystore, then input into the Google API console.

EDITED 2: Make sure your manifest include these

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />

jars that I used Imports

tcboy88
  • 1,063
  • 1
  • 9
  • 23
  • It seems to me that I've tried all these at one point or another, but I'll try again in the order you suggested. My only question so far is about your last edit - Could you please clarify what you mean by my real keystore? – John Roberts Nov 25 '12 at 00:53
  • 1
    The real .keystore that I meant, is the keystore that u are going to use to sign your apk in case u are going to publish it in Google play store. – tcboy88 Nov 25 '12 at 03:19
  • Hey man, I followed your instructions exactly, but I'm still getting the same error. Are you sure this is everything you did? Any other steps in the API console? Did you modify the original code? Any detail could really help. Perhaps you could upload your working project? – John Roberts Nov 25 '12 at 06:06
  • 1
    maybe you are using different jars from mine. I update the jars and imports in the answer. I am using mostly 1.12 which are the latest 1. Except for "google-api-services-drive-v2-rev9-1.8.0-beta.jar" which is not the latest 1. The latest 1 is rev25, but it is not compatible with Stephen code. – tcboy88 Nov 25 '12 at 13:36
  • Well, I now have all the same imports and libraries as you too, and I'm still getting the same error message. Maybe you could also upload a couple of screenshots of your API Console? I would really appreciate it man. – John Roberts Nov 25 '12 at 14:32
  • 1
    https://dl.dropbox.com/u/18371015/api1.PNG https://dl.dropbox.com/u/18371015/api2.PNG – tcboy88 Nov 25 '12 at 14:59
  • 1
    btw, can you also change in your code ,private static int REQUEST_TOKEN=1; – tcboy88 Nov 25 '12 at 15:15
  • My API console looks basically the same as yours now, and I have changed the REQUEST_TOKEN, but still the error persists. Is this line in your code the same?: return GoogleAuthUtil.getToken(ctx, account.name, "oauth2:" + DriveScopes.DRIVE); Also, which version of Google APIs are you using as your build target? – John Roberts Nov 25 '12 at 15:29
  • 1
    1) yes, exactly the same for that line of code 2) My target=android-16, and I but I change to target=android-10 and tried on my Galaxy S, and it still works Btw, you must click your button twice (with your code), the first time is grant permission, the second time is list files in LogCat – tcboy88 Nov 25 '12 at 15:59
  • I have my SDK versions set to the same as yours now, but still the same error. Pressing the button twice just gives me the same error twice. I realize that this might be a lot to ask, but could you upload a small working project of this, just so I can at least confirm that the problem isn't inside my code? I'm all out of ideas about what else to try at this point. I would really appreciate your help - I've been trying to get this working for 3 weeks now. – John Roberts Nov 25 '12 at 16:20
  • 1
    https://dl.dropbox.com/u/18371015/googledrive.zip So I added another client ID into my current API project and run this code, but it failed with 403. But my previous 1 still working fine. So I start fresh, using another gmail account, I create another API project, enter everything like my answer above. And it is a success. Printscreen here: https://dl.dropbox.com/u/18371015/drive.PNG I am still stucked in displaying it LogCat, without properly display it in ListView YET – tcboy88 Nov 25 '12 at 17:42
  • Thank you very much. So I tried your code, but I still get the same error. However, the one thing that has remained consistent for me throughout all this is my account. I am going to try to do what you did - change my account, change my project, and change my package name. I will let you know what happens. – John Roberts Nov 25 '12 at 18:19
  • Interestingly, I was just able to get the Google Tasks API to work using that new account. It's very similar in that the sample app also pulls a list of tasks after creating a profile in the API console. This Tasks API was giving me the same 403 error with the first account I was using. – John Roberts Nov 25 '12 at 19:08
  • Just got Google Tasks to work on my old account too, meaning that I cleared OAuth authentication. No idea why the same won't work for Google Drive. – John Roberts Nov 25 '12 at 19:24
  • 1
    Check the Android quickstart for Drive to learn how to use the Drive API on Android: https://developers.google.com/drive/quickstart-android – Claudio Cherubino Nov 27 '12 at 01:01
  • Thank you very much, I will check it out as soon as I get the opportunity. – John Roberts Nov 27 '12 at 03:59
  • @ClaudioCherubino The sample provided works great Claudio. Do I need to do any further setup (like enabling scopes) if I want to list files instead? – John Roberts Nov 27 '12 at 18:53
3

I encountered this issue as well and soon realized I had enabled the wrong 'service' under 'Google API Console > Services'. Try your application after enabling 'Drive API' instead of 'Drive SDK'.

Steps:
1) Go to https://code.google.com/apis/console
2) Select the correct application / project from the dropdown
3) Go to 'Services'
4) Switch 'ON' 'Drive API'

nynt
  • 31
  • 2