Create GoogleCredential by Access Token - android

I want to design an Android file viewer for Google Drive.
At first, I implemented the app by using of the Google Android API, as follows,
private void retrieveNextPage(){
if(mHasMore == false)
return;
Query query = new Query.Builder().setPageToken(mNextPageToken).build();
com.google.android.gms.drive.Drive.DriveApi.query(getGoogleApiClient(), query).setResultCallback(metadataBufferResultResultCallback);
}
However, the Android Drive API only allows the app to view and fetch the files that created by itself. I cannot access other files on the drive through the app.
Therefore, I turned to another option, directly manipulate the Java Drive API.
According to the example on developer guide for Java,
https://developers.google.com/drive/web/quickstart/quickstart-java
The users have to manually copy and paste the "Authorization Code" between the browser and app, which is not a practical way to acquire the Access Token in Android.
To come out a new way, I used the GoogleAuthUtil in Android API to acquire the Access Token, coincided with the GoogleCredential and Drive in Java API to fetch the file list, as follows,
private static List<File> retrieveFiles(Drive service) throws IOException{
List<File> result = new ArrayList<File>();
Files.List request = service.files().list();
do {
try{
FileList fileList = request.execute();
result.addAll(fileList.getItems());
request.setPageToken(fileList.getNextPageToken());
}catch (IOException e){
Log.d(dbgT + "JavaRetrieveFiles", "Retrieved Failed");
request.setPageToken(null);
}
}while (request.getPageToken() != null && request.getPageToken().length() > 0);
return result;
}
private class RetrieveTokenTask extends AsyncTask<String, Void, String>{
#Override
protected String doInBackground(String... params){
String accountName = params[0];
String scopes = "oauth2:" + "https://www.googleapis.com/auth/drive";
String token = null;
try{
token = GoogleAuthUtil.getToken(getApplicationContext(), accountName, scopes);
}
catch (IOException e){
Log.e(excpTAG, "IO Exception: " + e.getMessage());
}
catch (UserRecoverableAuthException e){
startActivityForResult(e.getIntent(), REQ_SIGN_IN_REQUIRED);
}
catch (GoogleAuthException e)
{
Log.e(excpTAG, "GoogleAuthException: " + e.getMessage());
}
return token;
}
#Override
protected void onPostExecute(String s){
super.onPostExecute(s);
//Get Access Token
Log.d( dbgT + "Token", s);
EditText tokenText = (EditText) findViewById(R.id.tokenText);
tokenText.setText(s);
EditText fileNameText = (EditText) findViewById(R.id.editTextMeta);
GoogleCredential credential = new GoogleCredential().setAccessToken(s);
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
Drive service = new Drive.Builder(httpTransport, jsonFactory, null).setHttpRequestInitializer(credential).build();
List<File> fileList;
try{
fileList = retrieveFiles(service);
for(int i=0; i< fileList.size(); i++)
fileNameText.append(fileList.get(i).getTitle());
}catch(IOException e){
Log.d(dbgT + "RetrieveFileList", "IO Exception" );
}
}
}
Unfortunately, the app always crashes by the causing of NetworkOnMainThreadException when request.execute() in retrieveFiles is invoked.
I checked my access token s, it is usually in form of ya29.xxx...etc., and it can also be passed to my other .NET program for retrieving files from Google Drive. Therefore I can certain the access token is correct.
So my question is, how to create a correct GoogleCredential by using of access token, instead of applying authorization code in setFromTokenResponse ?
Thanks in advance.

Many thanks for Andy's tips, this problem is simply caused by the network operations occurs on the main thread, which is a very basic newbie error.
The Drive in Google Drive SDK for Java, using network libraries without any background/thread worker, and now it is functional after I put the retrieveFiles() into background.
Applying the GoogleAuthUtil in Google Play Android SDK to acquire the access token, and followed by GoogleCredential+Drive in Java SDK that use the token to do the file operation in Google Drive.
This is a right way to avoid the scope restriction in Android SDK for Google Drive, allowing the developers to acquire the full permissive of accessing Google Drive.

Related

How multiple users use google cloud speech at the same time

I'm building an app that uses Google Cloud Speech.
I have a Google Service account key in my app, and I use it to call the API.
It works well when used by one user, but does not work when multiple users use it at the same time.
For example, only one user is available or all are unavailable.
The rights of the service account key are project owner.
I think it's a service account key issue...
How do I fix it?
private class AccessTokenTask extends AsyncTask<Void, Void, AccessToken> {
#Override
protected AccessToken doInBackground(Void... voids) {
final SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
String tokenValue = prefs.getString(PREF_ACCESS_TOKEN_VALUE, null);
long expirationTime = prefs.getLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, -1);
// Check if the current token is still valid for a while
if (tokenValue != null && expirationTime > 0) {
if (expirationTime > System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TOLERANCE) {
return new AccessToken(tokenValue, new Date(expirationTime));
}
}
final InputStream stream = mContext.getResources().openRawResource(R.raw.credential);
try {
final GoogleCredentials credentials = GoogleCredentials.fromStream(stream).createScoped(SCOPE);
final AccessToken token = credentials.refreshAccessToken();
prefs.edit()
.putString(PREF_ACCESS_TOKEN_VALUE, token.getTokenValue())
.putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, token.getExpirationTime().getTime())
.apply();
return token;
} catch (IOException e) {
Log.e(TAG, "Failed to obtain access token.", e);
}
return null;
}
#Override
protected void onPostExecute(AccessToken accessToken) {
mAccessTokenTask = null;
final ManagedChannel channel = new OkHttpChannelProvider()
.builderForAddress(GOOGLE_API_HOSTNAME, GOOGLE_API_PORT)
.nameResolverFactory(new DnsNameResolverProvider())
.intercept(new GoogleCredentialsInterceptor(new GoogleCredentials(accessToken)
.createScoped(SCOPE)))
.build();
mApi = SpeechGrpc.newStub(channel);
// Schedule access token refresh before it expires
if (mHandler != null) {
mHandler.postDelayed(mFetchAccessTokenRunnable,
Math.max(accessToken.getExpirationTime().getTime() - System.currentTimeMillis() - ACCESS_TOKEN_FETCH_MARGIN, ACCESS_TOKEN_EXPIRATION_TOLERANCE));
}
}
}
This code is the code that calls 'credential.json' file on Android and gets 'Access token'.
The server for this app is python and communicates via http.
https://github.com/GoogleCloudPlatform/android-docs-samples/tree/master/speech/Speech
The description in the link above tells you to delegate the authentication to the server.
I want to write that part with python code.
What should I do?
In the link you provided in the description, they suggest you to read first the basic authentication concepts document. In your case, use a service account for the Android application.
I understand that you have already been able to provide end user credentials to a Google Cloud Platform API, as for example Cloud Speech API.
If you want to authenticate multiple users to your application you should use instead Firebase authentication. The link contains a brief explanation and a tutorial.
There are several Python client libraries for GCP that you can use, depending on what operations do you want to perform on the server. And regarding Python authentication on the server side, this documentation shows how the authentication for Google Cloud Storage works (have this example in mind as a reference).

Android expiry time of In-app subscription item in android

I am trying to get expiry time or status of subscription to ensure if user is paying regularly for my item or not . When i query using
Purchase monthlySubscription = inv.getPurchase("itemName");
or
ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
It returns following data
{
"packageName":"com.abcPackage",
"productId":"auto1week",
"purchaseTime":1453369299644,
"purchaseState":0,
"developerPayload":"PAY_LOAD",
"purchaseToken":"TOKEN",
"autoRenewing":true
}
The problem is , purchaseTime remains same after several weeks which is supposed to be change after every purchase.
I tried google Play developers API
https://developers.google.com/android-publisher/#subscriptions
but i am having a hard time implementing it on my android device .
I will be grateful if someone can guide me step by step process to get this data on android device.
https://developers.google.com/android-publisher/api-ref/purchases/subscriptions
Any help in this regard will be highly appreciated.
Not sure if this will help, but here is my server side code (in java) that connects to the developer API and returns the expiration of the subscription.
I created a Service Account in the Google Developer Console, and followed the somewhat obtuse instructions to create a key file in src/resources/keys.json. APPLICATION_NAME is the package name of my app, and PRODUCT_ID is the subscription ID from the Google PLAY developer console.
Sorry it's not really 'step by step' as you asked for, but I also am doing verification on the server side instead of on the client. I suppose on the client you could do some sort of soft-verification by checking purchaseState == 0 (1=cancelled, 2=refunded), and autoRenewing==true. You may get stuck there if they cancel though, since you are still supposed to provide service through the duration of the subscription.
public static Long doSomeWork(String token){
log.debug("Google Validation: Doing some work:" + token);
try{
// Creating new Trusted Transport
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
// Getting Auth Creds
Credential cred = getAuthCredential();
// Building Android Publisher API call
AndroidPublisher ap = new AndroidPublisher.Builder(httpTransport, JSON_FACTORY, cred)
.setApplicationName(APPLICATION_NAME).build();
// Get Subscription
AndroidPublisher.Purchases.Subscriptions.Get get = ap.purchases().subscriptions().get(
APPLICATION_NAME,
PRODUCT_ID,
token);
SubscriptionPurchase subscription = get.execute();
log.debug(subscription.toPrettyString());
log.debug("DONE (not null)");
return subscription.getExpiryTimeMillis();
} catch(IOException ex) {
ex.printStackTrace();
} catch (GeneralSecurityException ex2) {
ex2.printStackTrace();
}
log.debug("DONE (failure) (0)");
return 0L;
}
private static Credential getAuthCredential(){
log.debug("getAuthCredential");
try{
//Read the credentials from the keys file. This file is obtained from the
// Google Developer Console (not the Play Developer Console
InputStream is = GoogleReceiptValidation.class.getClassLoader().getResourceAsStream("keys.json");
String str = IOUtils.toString(is);
is.close();
JSONObject obj = new JSONObject(str);
InputStream stream = new ByteArrayInputStream(obj.toString().getBytes());
//This is apparently "beta functionality".
GoogleCredential creds = GoogleCredential.fromStream(stream);
creds = creds.createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER));
return creds;
} catch (IOException ex){
ex.printStackTrace();
} catch (JSONException ex2){
ex2.printStackTrace();
}
log.debug("No Creds found - returning null");
return null;
}

How to upload a file to GoogleDrive in android

I refer from this code
AccountManager am = AccountManager.get(activity);
am.getAuthToken(am.getAccounts())[0],
"oauth2:" + DriveScopes.DRIVE,
new Bundle(),
true,
new OnTokenAcquired(),
null);
private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
#Override
public void run(AccountManagerFuture<Bundle> result) {
try {
final String token = result.getResult().getString(AccountManager.KEY_AUTHTOKEN);
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
Drive.Builder b = new Drive.Builder(httpTransport, jsonFactory, null);
b.setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() {
#Override
public void initialize(JSonHttpRequest request) throws IOException {
DriveRequest driveRequest = (DriveRequest) request;
driveRequest.setPrettyPrint(true);
driveRequest.setKey(CLIENT ID YOU GOT WHEN SETTING UP THE CONSOLE BEFORE YOU STARTED CODING)
driveRequest.setOauthToken(token);
}
});
final Drive drive = b.build();
final com.google.api.services.drive.model.File body = new com.google.api.services.drive.model.File();
body.setTitle("My Test File");
body.setDescription("A Test File");
body.setMimeType("text/plain");
final FileContent mediaContent = new FileContent("text/plain", an ordinary java.io.File you'd like to upload. Make it using a FileWriter or something, that's really outside the scope of this answer.)
new Thread(new Runnable() {
public void run() {
try {
com.google.api.services.drive.model.File file = drive.files().insert(body, mediaContent).execute();
alreadyTriedAgain = false; // Global boolean to make sure you don't repeatedly try too many times when the server is down or your code is faulty... they'll block requests until the next day if you make 10 bad requests, I found.
} catch (IOException e) {
if (!alreadyTriedAgain) {
alreadyTriedAgain = true;
AccountManager am = AccountManager.get(activity);
am.invalidateAuthToken(am.getAccounts()[0].type, null); // Requires the permissions MANAGE_ACCOUNTS & USE_CREDENTIALS in the Manifest
am.getAuthToken (same as before...)
} else {
// Give up. Crash or log an error or whatever you want.
}
}
}
}).start();
Intent launch = (Intent)result.getResult().get(AccountManager.KEY_INTENT);
if (launch != null) {
startActivityForResult(launch, 3025);
return; // Not sure why... I wrote it here for some reason. Might not actually be necessary.
}
} catch (OperationCanceledException e) {
// Handle it...
} catch (AuthenticatorException e) {
// Handle it...
} catch (IOException e) {
// Handle it...
}
}
}
In jsonHttpRequestInitializer i get an issues. [GoogleClient$Builder cannot be resolved. It is indirectly referenced from required .class files] please suggest me what i have to do...
You have two different APIs you can use on Android, the REST and the GDAA.
REST is the 'barebones' API that gives you the full functionality of Google Drive. You also have an interactive playground to test everything (see the bottom of this page). But you have to manage the network delays, failures, etc... yourself. Ideally you would delegate that work to sync adapter service.
GDAA is built on top of REST, resides in Google Play Services and behaves as a local API with delayed promotion of objects (folders/files) to the Drive. Has only limited functionality compared to REST (forget thumbnail link, etc...). Essentially, you talk to GDAA and GDAA talks to the Drive on it's own schedule. So, you don't have to worry about on-line / off-line situations. Be careful though, this may also cause synchronization issues, since you don't have direct control over object promotion timing. The demos for GDAA can be found here and here.
I've also created a simple CRUD demo app that you can step through. The upload you're asking resides in create() method there. It is not fully up-to-date, since GDAA has implemented the 'trash' functionality already (in Google Play Services 7.00 / Rev. 23).
Good Luck

Android Google DriveFile metadata is cached and not fetched from the cloud

Burcu Dogan wrote some example code showing how to sync a local preferences file to the user's Google Drive appfolder, found here: https://github.com/googledrive/appdatapreferences-android
I've converted this example to use the current Drive SDK, now shipping with Google Play Services.
If I update the cloud Drive file with device 1, and then run the following code on device 2, I'm getting a stale "modified" timestamp from the metadata. I'm assuming this is because the results are from a local cache of the Drive file:
Step 1. Look up the preferences file by name, with a query:
/**
* Retrieves the preferences file from the appdata folder.
* #return Retrieved preferences file or {#code null}.
* #throws IOException
*/
public DriveFile getPreferencesFile() throws IOException
{
if (mDriveFile != null)
return mDriveFile;
GoogleApiClient googleApiClient = getGoogleApiClient();
if (!googleApiClient.isConnected())
LOGW(TAG, "getPreferencesFile -- Google API not connected");
else
LOGD(TAG, "getPreferencesFile -- Google API CONNECTED");
Query query = new Query.Builder()
.addFilter(Filters.contains(SearchableField.TITLE, FILE_NAME))
.build();
DriveApi.MetadataBufferResult metadataBufferResult =
Drive.DriveApi.query(getGoogleApiClient(), query).await();
if (!metadataBufferResult.getStatus().isSuccess()) {
LOGE(TAG, "Problem while retrieving files");
return null;
}
MetadataBuffer buffer = metadataBufferResult.getMetadataBuffer();
LOGD(TAG, "Preference files found on Drive: " +
buffer.getCount());
if (buffer.getCount() == 0)
{
// return null to indicate the preference file doesn't exist
mDriveFile = null;
// create a new preferences file
// mDriveFile = insertPreferencesFile("{}");
}
else
mDriveFile = Drive.DriveApi.getFile(
getGoogleApiClient(),
buffer.get(0).getDriveId());
// Release the metadata buffer
buffer.release();
return mDriveFile;
}
Step 2. Get the metadata for the file:
// Get the metadata
DriveFile file;
DriveResource.MetadataResult result = file.getMetadata(getGoogleApiClient()).await();
Metadata metadata = result.getMetadata();
// Get the modified dates
metadata.getModifiedDate();
More curiously, after running the code below (which just lists the appdatafolder files and their content) the metadata modified date, fetched above, becomes correct!! Why???
/**
*
* Simple debug activity that lists all files currently in Drive AppFolder and their contents
*
*/
public class ActivityViewFilesInAppFolder extends BaseActivity {
private static final String TAG = "ActivityViewFilesInAppFolder";
private TextView mLogArea;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add a text view to the window
ScrollView layout = new ScrollView(this);
setContentView(layout);
mLogArea = new TextView(this);
layout.addView(mLogArea);
ApiClientAsyncTask<Void, Void, String> task = new ApiClientAsyncTask<Void, Void, String>(this) {
#Override
protected String doInBackgroundConnected(Void[] params) {
StringBuffer result = new StringBuffer();
MetadataBuffer buffer = Drive.DriveApi.getAppFolder(getGoogleApiClient())
.listChildren(getGoogleApiClient()).await().getMetadataBuffer();
result.append("found " + buffer.getCount() + " files:\n");
for (Metadata m: buffer) {
DriveId id = m.getDriveId();
DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(), id);
DriveContents contents = file.open( getGoogleApiClient(),
DriveFile.MODE_READ_ONLY, null).await().getDriveContents();
FileInputStream is = new FileInputStream(contents.getParcelFileDescriptor()
.getFileDescriptor());
try {
BufferedReader bf = new BufferedReader(new InputStreamReader(is, Charsets.UTF_8));
String line=null; StringBuffer sb=new StringBuffer();
while ((line=bf.readLine()) != null ) {
sb.append(line);
}
contents.discard(getGoogleApiClient());
result.append("*** " + m.getTitle() + "/" + id + "/"
+ m.getFileSize() + "B:\n [" + sb.toString() + "]\n\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
buffer.release();
return result.toString();
}
#Override
protected void onPostExecute(String s) {
if (mLogArea != null) {
mLogArea.append(s);
Map<String, ?> values = PreferenceManager
.getDefaultSharedPreferences(ActivityViewFilesInAppFolder.this).getAll();
String localJson = new GsonBuilder().create().toJson(values);
LOGD(TAG, "Local: " + localJson);
LOGD(TAG, "File: " + s);
}
}
};
task.execute();
}
}
Is the metadata reading from a cached local copy, unless something kicks it?
Does anyone know how to force these APIs to always pull the results from the remote Drive file?
I have an answer to your question. Well 'kind of answer', and I'm sure you will not be happy with it.
I had used RESTful API in my app before switching to GDAA. And after I did, I realized that GDAA, another layer with timing delays I have no control over, is causing issues in an app that attempts to keep multiple Android devices synchronized. See SO 22980497 22382099, 22515028, 23073474 and just grep for 'requestSync'.
I was hoping that GDAA implemented some kind of GCM logic to synchronize 'behind-the-scenes'. Especially when there is the 'addChangeListener()' method that seems to be designed for that. It does not look to be the case (at least not around Sept 2014). So, I backed off to a true-and-tested scheme of using RESTful API to talk to Google Drive with DataProvider and SyncAdapter logic behind it (much like shown in the UDACITY Class here).
What I'm not happy about, is somewhat ambiguous documentation of GDAA using terms like 'synchronize' not telling us if it is 'local' of 'network' synchronization. And not answering questions like the SO 23073474 mentioned above.
It appears (and I am not a Google insider) that GDAA has been designed for apps that do not immediately synchronize between devices. Unfortunately this has not been mentioned here or here - see 1:59, costing me a lot of time and frustration.
Now the question is: Should I (we) wait until we get 'real time' synchronization from GDAA, or should we go ahead and work on home-grown GCM based sync on top of RESTful-DataProvider-SyncAdapter?
Well, I personally will start working on GCM sync and will maintain an easy-to-use miniapp that will test GDAA behavior as new versions of Google Play Services come out. I will update this answer as soon as I have the 'test miniapp' ready and up in GitHub. Sorry I did not help much with the problem itself.
Well, I just found the secret sauce to trick the new Drive API into reading metadata from the remote file instead of the local cache.
Even though reading metadata doesn't require the file to be opened, it turns out that the file needs to be opened!
So the working code to read the latest metadata from the cloud is as follows:
DriveFile file;
// Trick Google Drive into fetching the remote file
// which has the latest metadata
file.open( getGoogleApiClient(), DriveFile.MODE_READ_ONLY, null).await();
DriveResource.MetadataResult result = file.getMetadata(getGoogleApiClient()).await();
Metadata metadata = result.getMetadata();
// Get the modified date
metadata.getModifiedDate();
Question for Google -- is this working as intended? The metadata is cached unless you first open the file read_only?

Get email of an account using google plus and how to check the session while logging using OAuth

I am using webview to authenticate the user using this https://github.com/imellon/Google-Plus-Android-Sample/blob/master/src/com/imellon/android/googleplus/OAuthActivity.java to access my app and after he his succesfully authorized I need to get the user email.
I couldn't find out the way to get the email.
This is what I have now:
private void retrieveProfile() {
try {
JsonFactory jsonFactory = new JacksonFactory();
HttpTransport transport = new NetHttpTransport();
SharedPreferencesCredentialStore credentialStore = SharedPreferencesCredentialStore
.getInstance(prefs);
AccessTokenResponse accessTokenResponse = credentialStore.read();
GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(
accessTokenResponse.accessToken, transport, jsonFactory,
SharedPreferencesCredentialStore.CLIENT_ID,
SharedPreferencesCredentialStore.CLIENT_SECRET,
accessTokenResponse.refreshToken);
Builder b = Plus.builder(transport, jsonFactory)
.setApplicationName("Simple-Google-Plus/1.0");
b.setHttpRequestInitializer(accessProtectedResource);
Plus plus = b.build();
profile = plus.people().get("me").execute();
updateViews();
} catch (Exception ex) {
ex.printStackTrace();
}
}
This is how I get the Id, name and url
private void updateViews() {
Drawable image = loadImage(this, profile.getImage().getUrl());
profile_photo.setImageDrawable(image);
profile_id.setText("Id: " + profile.getId());
profile_name.setText("Name: " + profile.getDisplayName());
profile_url.setText("Url: " + profile.getUrl());
}
And finally each time when he logins to my app how do I check the session ?
Any help is greatly appreciated
Use profile.getEmails() as documented in https://developers.google.com/resources/api-libraries/documentation/plus/v1/java/latest/. The result is a list of type java.util.List<Person.Emails>.
You need to make sure your client asked for the scope plus.profile.emails.read to allow your client to retrieve that information.

Categories

Resources