3

Previously, I was using the library: com.google.apis:google-api-services-photoslibrary:v1-rev1-1.23.0 But I've been informed that wasn't meant to be released, and it has some errors. So I've tried switching over the correct one: com.google.photos.library:google-photos-library-client:1.4.0 However, this has a different authentication flow.

Basically, the problem that all the documentation and examples refer to client secrets, and MY server providing an access token, required for the UserCredentials which in turn is used for the FixedCredentialsProvider and the PhotosLibraryClient.

But I don't have a server - this is a stand alone app. When setting up the Client ID on the developer console, it doesn't create a client secret when you specify app access only (it uses the signature of the app instead). Given that it lets me create that ID - I assume there must be a way of doing it without a client secret.

With the other library, the flow was:

  • Use GoogleSignInClient.getSignInIntent() and startActivityForResult() to determine the account
  • Assuming success, create a Task<GoogleSignInAccount> using the returned intent in onActivityResult
  • Get the GoogleSignInAccount from the task, and then getAccount() from that
  • Create a GoogleAccountCredential.usingOAuth2() passing the required scope for Google Photos, and then setSelectedAccount()
  • Use `PhotosLibrary.Builder() with the credential created

But with the new one, you need to use UserCredentials, which requires a client secret (not needed before, and not created when generating Android IDs) and an access token.

It seems I should be able to get the access token using GoogleAuthUtil.getToken, but then still need the client secret later. Also the getToken is not working - I'm getting an Authentication exception. It may be because my scope is wrong, but I'm not sure.

I have also looked at using https://github.com/erickogi/AndroidGooglePhotosApi/blob/master/app/src/main/java/ke/co/calista/googlephotos/Utils/AccessTokenFactory.kt to get the token, but that also requires a client secret.

This is the activity which signs in (successfully) to an account and getting the token. But I can't bridge the gap between that and getting the library client.

import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

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.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.tasks.Task;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.UserCredentials;
import com.google.photos.library.v1.PhotosLibraryClient;
import com.google.photos.library.v1.PhotosLibrarySettings;
import com.rjhartsoftware.logcatdebug.D;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    public static final String PHOTO_SCOPE = "https://www.googleapis.com/auth/photoslibrary";

    // Request codes
    private static final int RC_SIGN_IN = 9001;

    private GoogleSignInClient mGoogleSignInClient;

    private Account mAccount;
    private String mServerAuthCode;
    private AccessToken mToken;

    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        D.init(BuildConfig.VERSION_NAME, BuildConfig.DEBUG);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestScopes(new Scope(PHOTO_SCOPE))
                .requestEmail()
                .build();

        mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
    }

    @Override
    public void onStart() {
        super.onStart();

        // Check if the user is already signed in and all required scopes are granted
        GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
        if (GoogleSignIn.hasPermissions(account, new Scope(PHOTO_SCOPE))) {
            mAccount = account.getAccount();
            mServerAuthCode = account.getServerAuthCode();
            new GetToken().execute();
        } else {
            signIn();
        }
    }

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

        // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            handleSignInResult(task);
        }

    }

    private void signIn() {
        Intent signInIntent = mGoogleSignInClient.getSignInIntent();
        startActivityForResult(signInIntent, RC_SIGN_IN);
    }

    private void handleSignInResult(@NonNull Task<GoogleSignInAccount> completedTask) {
        D.log(D.GENERAL, "handleSignInResult:" + completedTask.isSuccessful()); //NON-NLS

        try {
            GoogleSignInAccount account = completedTask.getResult(ApiException.class);

            if (account != null) {
                // Store the account from the result
                mAccount = account.getAccount();
                mServerAuthCode = account.getServerAuthCode();
                new GetToken().execute();
            }
        } catch (ApiException e) {
            D.warn(D.GENERAL, "handleSignInResult:error", e); //NON-NLS

            // Clear the local account
            mAccount = null;
            mServerAuthCode = null;

        }
    }

    private class GetToken extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                mToken = null;
                String token = GoogleAuthUtil.getToken(MainActivity.this, mAccount, PHOTO_SCOPE);
                mToken = new AccessToken(token, null);
            } catch (UserRecoverableAuthException e) {
                D.warn(D.GENERAL, "Recoverable Auth Error getting token", e);
            } catch (IOException e) {
                D.error(D.GENERAL, "IO Error getting token", e);
            } catch (GoogleAuthException e) {
                D.warn(D.GENERAL, "Auth Error getting token", e);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            if (mToken != null) {
                PhotosLibraryClient client = getLibraryClient();
            }
        }
    }

    public PhotosLibraryClient getLibraryClient() {

        // At this point there is an account and server auth code, but we can't get a client yet

        try {
            UserCredentials.Builder credBuilder = UserCredentials.newBuilder()
                    .setClientId("<From Google API Dashboard>")
                    .setClientSecret("<This doesn't exist!")
                    .setAccessToken(mToken);

            UserCredentials creds = credBuilder.build();

            PhotosLibrarySettings.Builder settingsBuilder = PhotosLibrarySettings.newBuilder();
            settingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(creds));

            PhotosLibrarySettings settings = settingsBuilder.build();

            return PhotosLibraryClient.initialize(settings);
        } catch (IOException e) {
            D.error(D.GENERAL, "Error logging in to Google Photos API", e);
        }
        return null;
    }

}

richjhart
  • 336
  • 3
  • 12

1 Answers1

0

Even though you are using an Android app, you can still create a credential of type "Web Application" from the developer console and use that. You don't need a server. You can use that credential with GoogleSignIn in your Android app to get a serverAuthCode. The tricky part is that you then need to make an http call to https://www.googleapis.com/oauth2/v4/token with the serverAuthCode from the sign in result in order to get the access token that you will need for UserCredentials.Builder. This is what the Github project you referenced is doing. This approach is described in this question with lots of code samples: How to get access token after user is signed in from Gmail in Android?

Josh
  • 1,277
  • 12
  • 21