具有完全驱动器访问权限的Android应用+ Drive API ...程序是什么?

时间:2017-01-16 11:42:31

标签: android google-drive-api

我正在寻找与使用Android应用访问Google云端硬盘相关的一些指导。

1)我需要能够读取用户在我的应用之外上传的文件。这是否意味着我需要全驱动访问? (如果应用程序可以创建一个文件夹,然后查看该文件夹中存在的用户上传的所有文件,那就太棒了,但我认为它不会这样。)

2)如果我需要全驱动访问权限,Googles“Drive API for Android”似乎不支持此功能,我需要使用REST api。我认为这是真的。

3)我需要来自Google的Auth 2.0客户端ID。如果我使用其余的API,这是否意味着我需要使用“Web应用程序”ID?我想我需要这个,因为我想要一个“授权代码”。我无法使用“Android”类型ID。

4)我目前正在使用Android的“Google登录”来处理登录并提供身份验证代码。然后我可以将它转换为令牌+刷新令牌,并保存这些,这样我可以在一小时后以某种方式获得新的令牌。是否需要手动处理刷新令牌?

它变得很难看,但我认为既然我需要(?)全驱动访问,那么这就是程序。

感谢任何指导。

编辑:该问题已被确定为重复。提供的链接为问题#2提供了答案,但未解决其他问题。

我同意这个问题很混乱......

2 个答案:

答案 0 :(得分:6)

我正在回答我自己的问题。

我为此苦苦挣扎,因为A)Google的REST示例使用过时的登录过程,B)“登录”示例使用的代码不适用于“完全访问”范围,而C)有太多尝试将它们放在一起时,代码示例大不相同。

快速回答我现在看到的问题: 1)是的,读取在我的应用程序外部上传的文件需要完全驱动器访问权限。 2)是的,我需要使用REST api。 3)是的,我需要一个“Web应用程序”客户端ID。 4)Google登录似乎是目前登录的最佳方式,只要您保留刷新令牌,使用GoogleCredential对象和Drive api abject就会自动处理令牌刷新。

如果其他人正在努力使用最新的“登录”程序和REST v3从Android访问完全访问权限,下面是我的示例代码。

除了“Web应用程序”OAuth客户端ID之外,您还需要创建具有匹配包名称和证书指纹的“Android”类型ID,以便登录工作。另请注意,您的开发和生产版本将拥有不同的证书。这些Android客户端的ID /代码无需输入到应用程序中。

build.gradle:app

// Google Sign In
compile 'com.google.android.gms:play-services-auth:10.0.1'

// Drive REST API
compile('com.google.apis:google-api-services-drive:v3-rev54-1.22.0') {
    exclude group: 'org.apache.httpcomponents'
}

活动

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // Callback from Signin (Auth.GoogleSignInApi.getSignInIntent)
    if (requestCode == 1) {
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        _googleApi.handleSignInResult(result);
    }
}

开展工作的“GoogleApi”课程

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.common.api.Status;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;


public class GoogleApi implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    private Context         _context;
    private Handler         _handler;
    private GoogleCredential _credential;
    private Drive           _drive;

    private GoogleApiClient _googleApiClient;       // only set during login process
    private Activity        _activity;              // launch intent for login (UI)

    // Saved to data store
    private boolean         _loggedIn;
    private String          _refreshToken;          // store, even if user is logged out as we may need to reuse


    private static final String ClientID = "xxxxxx.apps.googleusercontent.com"; // web client
    private static final String ClientSecret = "xxxxx"; // web client

    private class FileAndErrorMsg {
        public File file;
        public String errorMsg;
        public FileAndErrorMsg (File file_, String errorMsg_) { file = file_; errorMsg = errorMsg_; }
    }
    private class FileListAndErrorMsg {
        public List<File> fileList;
        public String errorMsg;
        public FileListAndErrorMsg (List<File> fileList_, String errorMsg_) { fileList = fileList_; errorMsg = errorMsg_; }
    }

    // -------------------
    // Constructor
    // -------------------


    public GoogleApi (Context context) {

        _context = context;
        _handler = new Handler();
        loadFromPrefs();        //  loggedIn, refreshToken

        // create credential; will refresh itself automatically (in Drive calls) as long as valid refresh token exists
        HttpTransport transport = new NetHttpTransport();
        JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
        _credential = new GoogleCredential.Builder()
                .setTransport(transport)
                .setJsonFactory(jsonFactory)
                .setClientSecrets(ClientID, ClientSecret)       // .addRefreshListener
                .build();
        _credential.setRefreshToken(_refreshToken);

        // Get app name from Manifest (for Drive builder)
        ApplicationInfo appInfo = context.getApplicationInfo();
        String appName = appInfo.labelRes == 0 ? appInfo.nonLocalizedLabel.toString() : context.getString(appInfo.labelRes);

        _drive = new Drive.Builder(transport, jsonFactory, _credential).setApplicationName(appName).build();
    }

    // -------------------
    // Auth
    // -------------------

    // https://developers.google.com/identity/sign-in/android/offline-access#before_you_begin
    // https://developers.google.com/identity/sign-in/android/offline-access#enable_server-side_api_access_for_your_app
    // https://android-developers.googleblog.com/2016/02/using-credentials-between-your-server.html
    // https://android-developers.googleblog.com/2016/05/improving-security-and-user-experience.html


    public boolean isLoggedIn () {
        return _loggedIn;
    }

    public void startAuth(Activity activity) {
        startAuth(activity, false);
    }

    public void startAuth(Activity activity, boolean forceRefreshToken) {

        _activity = activity;
        _loggedIn = false;
        saveToPrefs();

        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestScopes(new Scope("https://www.googleapis.com/auth/drive"))
                .requestServerAuthCode(ClientID, forceRefreshToken)     // if force, guaranteed to get back refresh token, but will show "offline access?" if Google already issued refresh token
                .build();

        _googleApiClient = new GoogleApiClient.Builder(activity)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

        _googleApiClient.connect();
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        // Called soon after .connect()
        // This is only called when starting our Login process.  Sign Out first so select-account screen shown.  (OK if not already signed in)
        Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(Status status) {
                // Start sign in
                Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(_googleApiClient);
                _activity.startActivityForResult(signInIntent, 1);    // Activity's onActivityResult will use the same code: 1
            }
        });
    }

    @Override
    public void onConnectionSuspended(int cause) {
        authDone("Connection suspended.");
    }
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) { authDone("Connection failed."); }

    public void handleSignInResult(GoogleSignInResult result) {

        // Callback from Activity > onActivityResult
        if (result.isSuccess()) {
            GoogleSignInAccount acct = result.getSignInAccount();
            String authCode = acct.getServerAuthCode();
            new Thread(new ContinueAuthWithAuthCode_Background(authCode)).start();
        }
        else authDone("Login canceled or unable to connect to Google.");    // can we get better error message?
    }

    private class ContinueAuthWithAuthCode_Background implements Runnable {

        String _authCode;
        public ContinueAuthWithAuthCode_Background (String authCode) {
            _authCode = authCode;
        }
        public void run() {

            // Convert authCode to tokens
            GoogleTokenResponse tokenResponse = null;
            String errorMsg = null;
            try {
                tokenResponse = new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), "https://www.googleapis.com/oauth2/v4/token", ClientID, ClientSecret, _authCode, "").execute();
            }
            catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
            final GoogleTokenResponse tokenResponseFinal = tokenResponse;
            final String errorMsgFinal = errorMsg;

            _handler.post(new Runnable() { public void run() {
                // Main thread
                GoogleTokenResponse tokenResponse = tokenResponseFinal;
                String errorMsg = errorMsgFinal;
                if (tokenResponse != null && errorMsg == null) {
                    _credential.setFromTokenResponse(tokenResponse);    // this will keep old refresh token if no new one sent
                    _refreshToken = _credential.getRefreshToken();
                    _loggedIn = true;
                    saveToPrefs();
                    // FIXME: if our refresh token is bad and we're not getting a new one, how do we deal with this?
                    Log("New refresh token: " + tokenResponse.getRefreshToken());
                }
                else if (errorMsg == null) errorMsg = "Get token error.";   // shouldn't get here
                authDone(errorMsg);
            } });
        }
    }

    private void authDone(String errorMsg) {
        // Disconnect (we only need googleApiClient for login process)
        if (_googleApiClient != null && _googleApiClient.isConnected()) _googleApiClient.disconnect();
        _googleApiClient = null;
    }

    /*
    public void signOut() {
        Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(Status status) {
            }
        });
    }

    public void revokeAccess() {
        // FIXME: I don't know yet, but this may revoke access for all android devices
        Auth.GoogleSignInApi.revokeAccess(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(Status status) {
            }
        });
    }
    */

    public void LogOut() {
        _loggedIn = false;
        saveToPrefs();      // don't clear refresh token as we may need again
    }


    // -------------------
    // API Calls
    // -------------------


    public void makeApiCall() {
        new Thread(new TestApiCall_Background()).start();
    }

    private class TestApiCall_Background implements Runnable {
        public void run() {

            FileAndErrorMsg fileAndErr = getFolderFromName_b("Many Files", null);
            if (fileAndErr.errorMsg != null) Log("getFolderFromName_b error: " + fileAndErr.errorMsg);
            else {
                FileListAndErrorMsg fileListAndErr = getFileListInFolder_b(fileAndErr.file);
                if (fileListAndErr.errorMsg != null)
                    Log("getFileListInFolder_b error: " + fileListAndErr.errorMsg);
                else {
                    Log("file count: " + fileListAndErr.fileList.size());
                    for (File file : fileListAndErr.fileList) {
                        //Log(file.getName());
                    }
                }
            }

            _handler.post(new Runnable() { public void run() {
                // Main thread
            } });
        }
    }

    private FileAndErrorMsg getFolderFromName_b (String folderName, File parent) {

        // parent can be null for top level
        // Working with folders: https://developers.google.com/drive/v3/web/folder

        File folder = null;
        folderName = folderName.replace("'", "\\'");    // escape '
        String q = String.format(Locale.US, "mimeType='application/vnd.google-apps.folder' and '%s' in parents and name='%s' and trashed=false", parent == null ? "root" : parent.getId(), folderName);
        String errorMsg = null;
        try {
            FileList result = _drive.files().list().setQ(q).setPageSize(1000).execute();
            int foundCount = 0;
            for (File file : result.getFiles()) {
                foundCount++;
                folder = file;
            }
            if (foundCount == 0) errorMsg = "Folder not found: " + folderName;
            else if (foundCount > 1) errorMsg = "More than one folder found with name (" + foundCount + "): " + folderName;
        }
        catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
        if (errorMsg != null) folder = null;
        return new FileAndErrorMsg(folder, errorMsg);
    }

    private FileListAndErrorMsg getFileListInFolder_b (File folder) {

        // folder can be null for top level; does not return subfolder names
        List<File> fileList = new ArrayList<File>();
        String q = String.format(Locale.US, "mimeType != 'application/vnd.google-apps.folder' and '%s' in parents and trashed=false", folder == null ? "root" : folder.getId());
        String errorMsg = null;
        try {
            String pageToken = null;
            do {
                FileList result = _drive.files().list().setQ(q).setPageSize(1000).setPageToken(pageToken).execute();
                fileList.addAll(result.getFiles());
                pageToken = result.getNextPageToken();
            } while (pageToken != null);
        }
        catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
        if (errorMsg != null) fileList = null;
        return new FileListAndErrorMsg(fileList, errorMsg);
    }


    // -------------------
    // Misc
    // -------------------

    private void Log(String msg) {
        Log.v("ept", msg);
    }


    // -------------------
    // Load/Save Tokens
    // -------------------


    private void loadFromPrefs() {
        SharedPreferences pref = _context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
        _loggedIn = pref.getBoolean("GoogleLoggedIn", false);
        _refreshToken = pref.getString("GoogleRefreshToken", null);
    }
    private void saveToPrefs() {
        SharedPreferences.Editor editor =  _context.getSharedPreferences("prefs", Context.MODE_PRIVATE).edit();
        editor.putBoolean("GoogleLoggedIn", _loggedIn);
        editor.putString("GoogleRefreshToken", _refreshToken);
        editor.apply();     // async

    }

}

答案 1 :(得分:0)

https://developers.google.com/drive/v3/web/quickstart/android中的最新示例开箱即用。

请执行以下操作:

1 - 转到Google API控制台,使用您的软件包名称和调试/发布密钥创建OAuth2客户端ID作为签名证书指纹。

2 - 启用Google云端硬盘API

3 - 应用以下代码

build.gradle:app

 compile 'com.google.android.gms:play-services-auth:10.0.1'
 compile 'pub.devrel:easypermissions:0.2.1'
 compile('com.google.api-client:google-api-client-android:1.22.0') {
        exclude group: 'org.apache.httpcomponents'
 }
 compile('com.google.apis:google-api-services-drive:v3-rev57-1.22.0') {
        exclude group: 'org.apache.httpcomponents'
 }

活动

在此代码中,只需将范围更改为DriveScopes.DRIVE即可获得完整的驱动器访问权限

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;

import com.google.api.services.drive.DriveScopes;

import com.google.api.services.drive.model.*;

import android.Manifest;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;

public class MainActivity extends Activity
    implements EasyPermissions.PermissionCallbacks {
    GoogleAccountCredential mCredential;
    private TextView mOutputText;
    private Button mCallApiButton;
    ProgressDialog mProgress;

    static final int REQUEST_ACCOUNT_PICKER = 1000;
    static final int REQUEST_AUTHORIZATION = 1001;
    static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002;
    static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003;

    private static final String BUTTON_TEXT = "Call Drive API";
    private static final String PREF_ACCOUNT_NAME = "accountName";
    private static final String[] SCOPES = { DriveScopes.DRIVE };

    /**
     * Create the main activity.
     * @param savedInstanceState previously saved instance data.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout activityLayout = new LinearLayout(this);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.MATCH_PARENT);
        activityLayout.setLayoutParams(lp);
        activityLayout.setOrientation(LinearLayout.VERTICAL);
        activityLayout.setPadding(16, 16, 16, 16);

        ViewGroup.LayoutParams tlp = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);

        mCallApiButton = new Button(this);
        mCallApiButton.setText(BUTTON_TEXT);
        mCallApiButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCallApiButton.setEnabled(false);
                mOutputText.setText("");
                getResultsFromApi();
                mCallApiButton.setEnabled(true);
            }
        });
        activityLayout.addView(mCallApiButton);

        mOutputText = new TextView(this);
        mOutputText.setLayoutParams(tlp);
        mOutputText.setPadding(16, 16, 16, 16);
        mOutputText.setVerticalScrollBarEnabled(true);
        mOutputText.setMovementMethod(new ScrollingMovementMethod());
        mOutputText.setText(
                "Click the \'" + BUTTON_TEXT +"\' button to test the API.");
        activityLayout.addView(mOutputText);

        mProgress = new ProgressDialog(this);
        mProgress.setMessage("Calling Drive API ...");

        setContentView(activityLayout);

        // Initialize credentials and service object.
        mCredential = GoogleAccountCredential.usingOAuth2(
                getApplicationContext(), Arrays.asList(SCOPES))
                .setBackOff(new ExponentialBackOff());
    }



    /**
     * Attempt to call the API, after verifying that all the preconditions are
     * satisfied. The preconditions are: Google Play Services installed, an
     * account was selected and the device currently has online access. If any
     * of the preconditions are not satisfied, the app will prompt the user as
     * appropriate.
     */
    private void getResultsFromApi() {
        if (! isGooglePlayServicesAvailable()) {
            acquireGooglePlayServices();
        } else if (mCredential.getSelectedAccountName() == null) {
            chooseAccount();
        } else if (! isDeviceOnline()) {
            mOutputText.setText("No network connection available.");
        } else {
            new MakeRequestTask(mCredential).execute();
        }
    }

    /**
     * Attempts to set the account used with the API credentials. If an account
     * name was previously saved it will use that one; otherwise an account
     * picker dialog will be shown to the user. Note that the setting the
     * account to use with the credentials object requires the app to have the
     * GET_ACCOUNTS permission, which is requested here if it is not already
     * present. The AfterPermissionGranted annotation indicates that this
     * function will be rerun automatically whenever the GET_ACCOUNTS permission
     * is granted.
     */
    @AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS)
    private void chooseAccount() {
        if (EasyPermissions.hasPermissions(
                this, Manifest.permission.GET_ACCOUNTS)) {
            String accountName = getPreferences(Context.MODE_PRIVATE)
                    .getString(PREF_ACCOUNT_NAME, null);
            if (accountName != null) {
                mCredential.setSelectedAccountName(accountName);
                getResultsFromApi();
            } else {
                // Start a dialog from which the user can choose an account
                startActivityForResult(
                        mCredential.newChooseAccountIntent(),
                        REQUEST_ACCOUNT_PICKER);
            }
        } else {
            // Request the GET_ACCOUNTS permission via a user dialog
            EasyPermissions.requestPermissions(
                    this,
                    "This app needs to access your Google account (via Contacts).",
                    REQUEST_PERMISSION_GET_ACCOUNTS,
                    Manifest.permission.GET_ACCOUNTS);
        }
    }

    /**
     * Called when an activity launched here (specifically, AccountPicker
     * and authorization) exits, giving you the requestCode you started it with,
     * the resultCode it returned, and any additional data from it.
     * @param requestCode code indicating which activity result is incoming.
     * @param resultCode code indicating the result of the incoming
     *     activity result.
     * @param data Intent (containing result data) returned by incoming
     *     activity result.
     */
    @Override
    protected void onActivityResult(
            int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch(requestCode) {
            case REQUEST_GOOGLE_PLAY_SERVICES:
                if (resultCode != RESULT_OK) {
                    mOutputText.setText(
                            "This app requires Google Play Services. Please install " +
                            "Google Play Services on your device and relaunch this app.");
                } else {
                    getResultsFromApi();
                }
                break;
            case REQUEST_ACCOUNT_PICKER:
                if (resultCode == RESULT_OK && data != null &&
                        data.getExtras() != null) {
                    String accountName =
                            data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                    if (accountName != null) {
                        SharedPreferences settings =
                                getPreferences(Context.MODE_PRIVATE);
                        SharedPreferences.Editor editor = settings.edit();
                        editor.putString(PREF_ACCOUNT_NAME, accountName);
                        editor.apply();
                        mCredential.setSelectedAccountName(accountName);
                        getResultsFromApi();
                    }
                }
                break;
            case REQUEST_AUTHORIZATION:
                if (resultCode == RESULT_OK) {
                    getResultsFromApi();
                }
                break;
        }
    }

    /**
     * Respond to requests for permissions at runtime for API 23 and above.
     * @param requestCode The request code passed in
     *     requestPermissions(android.app.Activity, String, int, String[])
     * @param permissions The requested permissions. Never null.
     * @param grantResults The grant results for the corresponding permissions
     *     which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(
                requestCode, permissions, grantResults, this);
    }

    /**
     * Callback for when a permission is granted using the EasyPermissions
     * library.
     * @param requestCode The request code associated with the requested
     *         permission
     * @param list The requested permission list. Never null.
     */
    @Override
    public void onPermissionsGranted(int requestCode, List<String> list) {
        // Do nothing.
    }

    /**
     * Callback for when a permission is denied using the EasyPermissions
     * library.
     * @param requestCode The request code associated with the requested
     *         permission
     * @param list The requested permission list. Never null.
     */
    @Override
    public void onPermissionsDenied(int requestCode, List<String> list) {
        // Do nothing.
    }

    /**
     * Checks whether the device currently has a network connection.
     * @return true if the device has a network connection, false otherwise.
     */
    private boolean isDeviceOnline() {
        ConnectivityManager connMgr =
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        return (networkInfo != null && networkInfo.isConnected());
    }

    /**
     * Check that Google Play services APK is installed and up to date.
     * @return true if Google Play Services is available and up to
     *     date on this device; false otherwise.
     */
    private boolean isGooglePlayServicesAvailable() {
        GoogleApiAvailability apiAvailability =
                GoogleApiAvailability.getInstance();
        final int connectionStatusCode =
                apiAvailability.isGooglePlayServicesAvailable(this);
        return connectionStatusCode == ConnectionResult.SUCCESS;
    }

    /**
     * Attempt to resolve a missing, out-of-date, invalid or disabled Google
     * Play Services installation via a user dialog, if possible.
     */
    private void acquireGooglePlayServices() {
        GoogleApiAvailability apiAvailability =
                GoogleApiAvailability.getInstance();
        final int connectionStatusCode =
                apiAvailability.isGooglePlayServicesAvailable(this);
        if (apiAvailability.isUserResolvableError(connectionStatusCode)) {
            showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
        }
    }


    /**
     * Display an error dialog showing that Google Play Services is missing
     * or out of date.
     * @param connectionStatusCode code describing the presence (or lack of)
     *     Google Play Services on this device.
     */
    void showGooglePlayServicesAvailabilityErrorDialog(
            final int connectionStatusCode) {
        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
        Dialog dialog = apiAvailability.getErrorDialog(
                MainActivity.this,
                connectionStatusCode,
                REQUEST_GOOGLE_PLAY_SERVICES);
        dialog.show();
    }

    /**
     * An asynchronous task that handles the Drive API call.
     * Placing the API calls in their own task ensures the UI stays responsive.
     */
    private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
        private com.google.api.services.drive.Drive mService = null;
        private Exception mLastError = null;

        MakeRequestTask(GoogleAccountCredential credential) {
            HttpTransport transport = AndroidHttp.newCompatibleTransport();
            JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
            mService = new com.google.api.services.drive.Drive.Builder(
                    transport, jsonFactory, credential)
                    .setApplicationName("Drive API Android Quickstart")
                    .build();
        }

        /**
         * Background task to call Drive API.
         * @param params no parameters needed for this task.
         */
        @Override
        protected List<String> doInBackground(Void... params) {
            try {
                return getDataFromApi();
            } catch (Exception e) {
                mLastError = e;
                cancel(true);
                return null;
            }
        }

        /**
         * Fetch a list of up to 10 file names and IDs.
         * @return List of Strings describing files, or an empty list if no files
         *         found.
         * @throws IOException
         */
        private List<String> getDataFromApi() throws IOException {
            // Get a list of up to 10 files.
            List<String> fileInfo = new ArrayList<String>();
            FileList result = mService.files().list()
                 .setPageSize(10)
                 .setFields("nextPageToken, files(id, name)")
                 .execute();
            List<File> files = result.getFiles();
            if (files != null) {
                for (File file : files) {
                    fileInfo.add(String.format("%s (%s)\n",
                            file.getName(), file.getId()));
                }
            }
            return fileInfo;
        }


        @Override
        protected void onPreExecute() {
            mOutputText.setText("");
            mProgress.show();
        }

        @Override
        protected void onPostExecute(List<String> output) {
            mProgress.hide();
            if (output == null || output.size() == 0) {
                mOutputText.setText("No results returned.");
            } else {
                output.add(0, "Data retrieved using the Drive API:");
                mOutputText.setText(TextUtils.join("\n", output));
            }
        }

        @Override
        protected void onCancelled() {
            mProgress.hide();
            if (mLastError != null) {
                if (mLastError instanceof GooglePlayServicesAvailabilityIOException) {
                    showGooglePlayServicesAvailabilityErrorDialog(
                            ((GooglePlayServicesAvailabilityIOException) mLastError)
                                    .getConnectionStatusCode());
                } else if (mLastError instanceof UserRecoverableAuthIOException) {
                    startActivityForResult(
                            ((UserRecoverableAuthIOException) mLastError).getIntent(),
                            MainActivity.REQUEST_AUTHORIZATION);
                } else {
                    mOutputText.setText("The following error occurred:\n"
                            + mLastError.getMessage());
                }
            } else {
                mOutputText.setText("Request cancelled.");
            }
        }
    }
}