John Roberts
John Roberts

Reputation: 5986

Google Drive SDK Verify Error

I am trying to implement Stephen Wylie's Google Drive example (here). Here is my code:

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) {

        /*
         * Etc... (Other application logic belonging in onCreate)
         */
            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_READONLY);  // 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 {
                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");
        }
    }   
}

Here is my manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.googledrive.googledriveapp"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="android.app.ActivityGroup" />
        </activity>
    </application>
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
</manifest>

And here is the LogCat error I receive. It occurs when the app is first launched:

10-27 12:01:43.872: E/AndroidRuntime(1117): FATAL EXCEPTION: main
10-27 12:01:43.872: E/AndroidRuntime(1117): java.lang.VerifyError: com/googledrive/googledriveapp/MainActivity
10-27 12:01:43.872: E/AndroidRuntime(1117):     at java.lang.Class.newInstanceImpl(Native Method)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at java.lang.Class.newInstance(Class.java:1319)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.app.Instrumentation.newActivity(Instrumentation.java:1053)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1974)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.app.ActivityThread.access$600(ActivityThread.java:130)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.os.Handler.dispatchMessage(Handler.java:99)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.os.Looper.loop(Looper.java:137)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at android.app.ActivityThread.main(ActivityThread.java:4745)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at java.lang.reflect.Method.invokeNative(Native Method)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at java.lang.reflect.Method.invoke(Method.java:511)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
10-27 12:01:43.872: E/AndroidRuntime(1117):     at dalvik.system.NativeStart.main(Native Method)

Can anyone help out?

Upvotes: 0

Views: 1147

Answers (1)

Stephen Wylie
Stephen Wylie

Reputation: 934

(So as an addendum to the tutorial, let me add that in Eclipse, you need to go to the Android SDK Manager and download Google Play Services. Then find the google-play-services.jar file and add that as an "External JAR" into your Eclipse project. I talked with the OP offline and he already did this, but I forgot to mention it.)

The code won't work by itself; it's expected that you'll add it to an existing application, and simply add btn_drive somewhere into your UI to allow the user to connect to Google Drive. Luckily it'll be fairly easy to build an Android app around this code, even though it'd probably be even easier just to create a new project and then add this in. Hopefully this covers all the things you need to add.

First, in onCreate, replace that "other application logic" section with:

    super.onCreate(savedInstanceState);
    // set up the GUI layout
    setContentView(R.layout.main);
    // set the variables to access the GUI controls
    btn_drive = (Button) findViewById(R.id.btn_drive);

Then, you need a really basic XML layout. Make a new file in res/layout called main.xml (or whatever you called your R.layout in setContentView above):

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btn_drive"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Connect to Google Drive" />
</LinearLayout>

Finally, you may need the following permission in your Manifest:

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

Best of luck!

Upvotes: 3

Related Questions