Tuesday, January 28, 2014

Google Authentication APIs and some ideas on how to use them


Google provides OAuth2 service that lets users authorize an Application to access their data. So, in Android, the user opens an App (or does some other operation in an already open App), the App requests access to the user's data from one of the Google's Services. In the web, the usual next step is for the browser to show a Google Authorization page where the user authorizes. But in Android there are two flavours for an App to get this authentication. Irrespective of the way the App gets authorization, once it happens, the App will be able to access data. This post deals with the two different ways Android attempts to get the user to authorize.


  • The first way in which Google APIs let user to authorize is - just show a popup. I browsed the internet and this is the image of the popup that resembles what you get to see.

http://blog.doityourselfandroid.com/wp-content/uploads/2011/08/authorize_screen2.png

  • The second way in which Google APIs let user to authorize is - to show Notification.


It definitely makes sense to have these two methods. The first one is used when the authorization needs to happen from an Activity. The second one is used when the authorization needs to happen from a background services (Service, Sync Adapter). In the first case, the Android application has a UI component to actually show the popup. In the second case, it does not. So, it tries to show a notification hoping to catch the attention of the user.

Here is where my problem is: I mostly ignore these notifications if I see more than 3. If an user (like me) happens to ignore the notification, our Service never executes. To make matters worse, imagine this - A user taps on Save button. The data gets saved in local SQLite database. Our Sync Adapter notices that data needs to be synchronized. It needs to get user's authentication to access, say, Google Drive to store data. User does not see the notification but thinks he has saved the data. Too bad.

Workarounds..?
Each service gets invoked using startService() call. It accepts an Intent. We can put in data in an Intent. Even in case of Sync Adapters, we start it using ContentResolver.requestSync(android.accounts.Account, java.lang.String, android.os.Bundle) with extras Bundle. So, my first idea is -
  1. When user performs an action that requests background service to start, request them to authorize and then pass on the token to the background service. 
  2. Background service uses this token instead of showing a notification to the user.

If ours is a simple app and it is the only app in the phone, it should work great.

Except for these two situations that I could think of:
  1. The background service does not happen to use the token immediately. May be, it does a few other long-running operations (like accessing Twitter, for example) before running the Google service request. This will probably lead to our token expiring every time we run the background service.
  2. Sync Adapters are not guaranteed to run immediately. All you do is to request Sync Manager to run the Sync Adapter whenever it deems the phone is ready for that. May be, some other application is running Sync Adapter to sync its data. This will probably be an intermittent issue. It might go away if the user retried.

After digging around a bit, this is what I found - GoogleAuthUtil.getToken() and its variants remember that our App has been authorized before.

This is very important. This allows us to use getToekn() and its variants multiple times in an Application with a guarantee that user will be prompted (popup / notification) for authorization only once.

So, these steps will solve our problem completely:

  1. In an Activity, when the user performs an operation that calls a background service (which is going to use Google Service), use getToken() in the Activity to show a popup to the user immediately.
  2. In the Service, call getTokenWithNotification(). Since the behaviour so far has guaranteed that no notification will be sent to the user from the background Service, we can get the most user-friendly experience.
This is the technique I am using in my latest Android App. Hope it helps someone.

Monday, January 20, 2014

Content Provider vs Content Resolver in Android

I see many people who start learning Android programming confuse between ContentProvider and ContentResolver. I hope I can clear some confusion (atleast not aggravate their confusions).

Content Provider is a class that extends ContentProvider. But conceptually, Content Provider is a class that provides data to other components of an Android application. But data can be sourced from many different places - web service, local SQLite database, or just an Array, or a combination of these. Every application developer designs their own data source based on their needs. Which means, each application developer should design and implement their own Content Provider that supports these four operations - insert(), query(), update(), delete(). If you are familiar with Design Patterns, this would fall under Strategy pattern.

So, someone wants to insert data, Content Provider executes insert() code which can probably call a HTTP POST in REST-based service. But, there is a caveat - Content Provider can be exposed across applications. For example, Contacts content provider is available to other Android application - you can get all contacts stored in the phone.



Imagine you are a developer of the components of an Android application other than the Content Provider. Since Content Provider is a class, we will somehow need to access the class for all CURD operations. If you are using a Content Provider from an different application (eg; Contacts) this is going to limit our development. Even more difficult would be to use Content Provider from third party.

Content Resolver helps solve these problems faced by the developers of Android components that use Content Provider. Each Content Provider is registered to a URL and exposed (to the same Android application or to other applications as well) through the Android Manifest. In Content Resolver, we pass the URI of the Content Provider along with the data (or query). Content Resolver gets the URI and resolves it to a specific Content Provider and invokes appropriate method (query(), insert(), update(), delete()).


So, in short, Content Resolver resolves the URI to a particular Content Provider.

Content Provider actually performs the operation - in local SQLite db, some REST service, etc. - depending on the design.

Tuesday, January 7, 2014

Delete all rows from Content Provider

A quick tip:

To delete all rows from content provider, call 

provider.delete(uri, "1", null);

URI - the authorities string of the provider
"1" - Returns the count
null - there are no selection arguments. All rows needs to be deleted. So, null.

Friday, January 3, 2014

"com.google.android.gms.auth.GoogleAuthException: Unknown" in GoogleAuthUtils.getToken() call

I was recently developing a simple Android application that would access Google Spreadsheets. To access the spreadsheet, the users would have to authorize the app to access their data. To get that authorization, I will have to use this command:

mToken = GoogleAuthUtil.geToken(MyActivity.this, accountName, "oauth2:https://spreadsheets.google.com/feeds https://docs.google.com/feeds");


The problem was that, I was getting this error when getToken() executed:


Exception in getToken() - GoogleAuthException
com.google.android.gms.auth.GoogleAuthException: Unknown
at com.google.android.gms.auth.GoogleAuthUtil.getToken(Unknown Source)
  at com.google.android.gms.auth.GoogleAuthUtil.getToken(Unknown Source)
  at somepackage.LoginHomeActivity$2.doInBackground(LoginHomeActivity.java:80)
  at somepackage.LoginHomeActivity$2.doInBackground(LoginHomeActivity.java:1)
  at android.os.AsyncTask$2.call(AsyncTask.java:264)
  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
  at java.util.concurrent.FutureTask.run(FutureTask.java:137)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
  at java.lang.Thread.run(Thread.java:856)

After a bit of digging around, I realize that the reason for the error is the scope string. Here, I am using this:

oath2:https://spreadsheets.google.com/feeds https://docs.google.com/feeds

It should be this: https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/

That is, I don't have an option of choosing just a bunch of scopes. I will have to use all of the urls as scope.

Just FYI, I got the scope value for Google Spreadsheet from here 

Thursday, January 2, 2014

Google Spreadsheet API - Java - com.google.gdata.util.ParseException: Unrecognized content type:application/binary

I was developing an application using Google Spreadsheet API.  I was trying to load a particular spreadsheet in my application. This was the code:

URL spreadSheetUrl = new URL("https://spreadsheets.google.com/feeds/spreadsheets/private/full");
SpreadsheetQuery query = new SpreadsheetQuery(spreadSheetUrl);
query.setTitleQuery("xyz");
query.setTitleExact(true);
SpreadsheetFeed spreadSheetFeed = service.getFeed(query, SpreadsheetFeed.class);

I was getting this error:

com.google.gdata.util.ParseException: Unrecognized content type:application/binary
com.google.gdata.client.Service.parseResponseData(Service.java:2136)
com.google.gdata.client.Service.parseResponseData(Service.java:2098)
com.google.gdata.client.Service.getFeed(Service.java:1136)
com.google.gdata.client.Service.getFeed(Service.java:1077)
com.google.gdata.client.GoogleService.getFeed(GoogleService.java:676)
com.google.gdata.client.Service.getFeed(Service.java:1034)

Apparently, this is because I did not give user credentials to access the spreadsheet. For some reason, Google does not return a valid response if the user is not logged in. It could return - NotAuthorizedException or something like that. But it keeps returning ParseException.