Chapter 5

REST, Content Providers, Concurrency, Networking, and Sync Adapters

WHAT’S IN THIS CHAPTER?

  • Understanding REST basics
  • Looking at RESTful Contacts: An example REST API
  • Learning about Android networking
  • Understanding concurrency and lifecycles
  • Building an architecture for robust networking
  • Implementing RESTfulContacts: An example client
  • Understanding sync adapters
  • Understanding Spring for Android

Wrox.com Code Downloads for this Chapter

Please note that all the code examples in this chapter are available at https://github.com/wileyenterpriseandroid/Examples.git and as a part of the book’s code download at www.wrox.com on the Download Code tab.

Mobile developers — including Android developers — face common challenges when communicating with remote services. Tasks that are straightforward on a local network or even the wired Internet — using a remote service or requesting remote data — can have all sorts of subtle pitfalls in a mobile environment. Attempting to use the network efficiently while synchronizing data between mobile platforms and backend web services adds additional headaches. Among the key challenges that Android developers face are these:

  • Data synchronization between a mobile client and a web service
  • Handling large datasets
  • Using Android APIs to solve these problems in such a way that the solutions can be reused across application domains
  • Android MVC and correct handling of the UI thread during remote requests

The previous chapters built the foundation for Android applications, setting the stage by describing the Android user interface, database programming and, most important, content providers. This chapter builds on that foundation — which, so far, is entirely local — to introduce client-side network programming. Here you step off the isolated mobile device and expand your horizons to a connected mobile device.

This chapter introduces REST (Representational State Transfer) as a powerful architectural style. It demonstrates this style with two example clients that store information about contacts in a RESTful server. The next chapter illustrates the construction of the server with which the clients communicate. Together, these two chapters provide the model for a generic, functional, end-to-end mobile platform based on sound principles for robust Android programming.


NOTE Note that in order to run the client application created in this chapter it is necessary, also, to run a server like the one described in the next chapter. The material presented here follows logically from the discussion of content providers in Ch. 4 and, thus, appears here, before the description of the server on which it depends.

The Android platform creates an issue of particular concern to developers creating networked applications: How do well behaved networking and data management interact with Android’s process model — the lifecycles of activity and service components? This chapter describes three rock-solid architectural approaches that answer that question.

  • Service-centric — Based on an Android IntentService
  • ContentProvider-centric — Based on an Android ContentProvider
  • SyncAdapter-centric — Based on Android’s sync adapter framework

BASIC REST

The exploration of external data management on Android starts with a primer on what has become a standard set of architectural constraints for designing communication protocols between Internet services and their clients — Representational State Transfer or REST. Although REST has roots that go well back into the mid-1990s, the name was introduced and formalized by Roy Fielding in his doctoral dissertation in the year 2000. One of the designers of HTTP, Fielding used REST as a way of formalizing an architectural style that meets the goals of the web, among them extreme, anarchic scalability.

Why REST?

As far back as 1990, Sun Fellow Peter Deutsch codified a list of assumptions that engineers were making at the time in their distributed computing architectures that doomed the resulting products to failure. Dubbed by Deutsch as the “Fallacies of Networked Computing,” the first four are attributed to Bill Joy and Dave Lyon and the last to James Gosling. They are:

1. The network is reliable.
2. Latency is zero.
3. Bandwidth is infinite.
4. The network is secure.
5. Topology doesn’t change.
6. There is one administrator.
7. Transport cost is zero.
8. The network is homogenous.

Engineers at the time assumed — sometimes implicitly — that multiple computers connected together by a network would behave as an analog of multiple components connected in a computer. They expected, in other words, that a network would behave like a single, huge machine. As is so often the case in engineering, size matters. The early attempts to scale architecture linearly, from a single machine to a network, were largely disappointing.

Enter REST and a radical change in point of view. Instead of hiding the vagaries of the underlying network, the REST style focuses on them. The architectural constraints imposed by the RESTful style assume the contradictions of Deutsch’s fallacies and provide elegant tools for designing consistent, resilient, and highly scalable client-server systems.

RESTful style architectures are particularly relevant in the world of mobile computing. The network as perceived from a mobile device is even less reliable than that perceived by the pioneers of distributed architecture. In the course of a normal day, a mobile device might be powered down abruptly, lose its signal in a subway tunnel, switch from a 3G to a WiFi network, and so on. An architectural style that frankly acknowledges this environment and offers the developer ways to thrive in it means the difference between applications that constantly and mysteriously fail and those that work. As you will see, the constraints imposed by the Android-managed container are yet another reason to prefer the RESTful style.

The exact definition of REST and whether a particular API is RESTful can be the source of lengthy and heated discussions. This chapter makes every attempt to avoid those discussions, focusing instead on what distinguishes the REST style from others, and how REST is particularly suited for use in Android.

An API that is RESTful will tend to have the following attributes:

  • It is client/server. A RESTful API clearly distinguishes the role of a client, the entity that makes requests for services, from a server, the entity that listens for those requests and supplies the services.
  • It is stateless. In a RESTful API a client cannot expect a server to hold context between requests. Each client request must contain all of the information necessary for the server to process it completely.
  • It describes the exchange of representations of named resources, not the exchange of those resources themselves. This is a bit subtle, but fairly important — it acknowledges a layer of abstraction between internal and external representations of objects. A resource is any object that can be named, commonly with a URI. A representation is simply a transferable document that describes the current state of some resource. A trivial example of this distinction is a server that is willing to describe the single resource named by a given URI using either JSON or XML: a single resource with multiple representations. A more technical example is a protocol in which a representation includes a version number. This representation is a snapshot of the resource at some particular moment in time and, clearly, not the resource itself.
  • It has a uniform interface. This is probably the best known and most significant of the REST constraints. A RESTful API will, typically, support only the four standard CRUD methods, insert, update, delete, and query (PUT, POST, DELETE and GET, respectively, in HTTP) regardless of the application’s functional API. This design choice represents a focus on the nature of the infrastructure that supports client/server transactions: an unreliable, asynchronous network. This is dramatically different from the APIs typical of other remote technologies such as COM, CORBA, and SOAP that have a set of operations that is much richer and more tightly coupled to the behavior of the specific service.

These attributes lead to protocols that have some very nice characteristics. First of all, because the interface is uniform, it is possible to create generic proxies for RESTful APIs. That means that a proxy for one RESTful API is a proxy for any RESTful API. The client of a RESTful API cannot tell whether it is talking to the origin server or to some local cache that is ignorant of the service it is caching. RESTful APIs automatically scale with the network.

The REST constraints for idempotency, the transfer of resource representations rather than objects, and against server-side session state also facilitate the caching of RESTful protocols. Although a particular representation of a resource may be out of date, it is probably consistent forever. A service and its clients can negotiate the degree of staleness that is tolerable. Many RESTful APIs include specific metadata that describes what can be cached and for how long.

By recognizing and embracing the vagaries of the medium — a slow, unreliable network with changing, anarchic topology — REST insulates a client from the concerns of the server.

REST over HTTP

Although the REST style can be used with nearly any protocol, in the Internet, HTTP is the vehicle of choice because it is RESTful in itself. The combination of a URI to name a target resource; the four methods PUT, POST, DELETE, and GET; and the request content provide a transport that most RESTful services simply adopt. Let’s take a minute to review a few of the details of the mapping from REST to HTTP.

URIs

Almost all RESTful APIs in the Internet use URIs to name their resources. These URIs have a predictable structure that is defined in RFC 3986. Here is an abbreviated version of that structure:

http[s]://<host>[:<port>]/(<path-seg>/)*<path-seg>[?<param>=<value>(&<param>=<value>)*]

This proto-typical URI has four parts — a scheme, an authority, a path, and a query. In detail:

  • The scheme for a URI used in a RESTful protocol is very likely to be either http:// or https://.
  • The authority for a URI used by a RESTful protocol is likely to be the DNS name of the origin server for the service that is the target of the request. In addition to the hostname, the URI may contain the port number on the target host at which the server is accepting connections.
  • The path portion of a URI used by a RESTful protocol is a standard slash-separated (/), hierarchical namespace. Just as the scheme and authority sections of the URI probably identify a service, so the path portion identifies a specific resource maintained by that service.
  • A URI used in a RESTful protocol may have a query section. If the section exists, it begins with a question mark (?). Following the question mark is a list of one or more key/value pairs separated by ampersands (&). The key/value pairs are given as a key followed by an equals sign (=) and then its value.

NOTE There is more information on the full syntax of a URI at:

Although the similarity is only partial, a URI in a RESTful API might be understood as a reference to an object in much the same way that a Java variable is a reference to an object. In a well-designed Java program, it is likely that you have no idea what, actually, is at the end of a Java reference. You can ask for information about the referenced object’s state, and you may even be able to change that state. You should not take the liberty, though, of guessing at the object’s actual implementation.

In a RESTful protocol, the URI functions in much the same way. The client program can ask about the resource to which the URI refers and may, similarly, be able to ask the service to update that resource. However, it never touches the actual resource and is not free to infer the resource’s implementation.

Contents

In addition to containing a URI, an HTTP request may contain a typed data payload. For example, the payload often is structured application data. When this is the case, the data is typically represented either as XML or, more popular in recent history, JSON.

An HTTP request has a header section that contains metadata about the request. Among other things, the header for a request containing a payload will specify a Content-Type. The value for the content type header field is a MIME (Multipurpose Internet Mail Extension) type that describes how the payload should be interpreted.

MIME is an Internet standard for describing content type. If the payload data is structured application data, XML or JSON, as described previously, the Content-Type field will contain application/xml or application/json. If the content is something else entirely — perhaps an audio file — the MIME type will identify it accordingly (audio/mp3, for instance).


NOTE There is more information on MIME and HTTP at the following sites.

An Example REST API

To make this discussion of REST more concrete, let’s look at a simplistic web service API. This API is the basis for the example code — clients and a backend service — implemented in this and the next chapter. The API supports persistent operations on a collection of contact resources. Although the example is simplistic in several respects — respects that will be discussed in detail later in the chapter — the general concept is entirely realistic. Any mobile application that has a social aspect will have to track relationships between entities that are similar to the contacts used in this example. Developers familiar with the Android platform will know that it already supports a rich and extensible framework for contacts. Creating a useful social application will require, at best, understanding and integrating with that framework. At least as likely, though, it will require a custom implementation such as the one discussed here.

Contact Representation

The example API supports a set of contact resources. These resources will be represented in messages between the client and the server as JSON documents described by the following schema:

{
    "title": "RESTfulContacts",
    "type": "object",
    "properties": {
                "id": {
                        "type": "integer"
                },
                "firstName": {
                        "description": "first name",
                        "type": "string"
                },
                "lastName": {
                        "description": "last name",
                        "type": "string"
                }
                "phone": {
                        "description": "phone number",
                        "type": "string"
                }
                "email": {
                        "description": "email address",
                        "type": "string"
                }
                "version": {
                        "description": "version id",
                        "type": "integer",
                        "minimum": 0
                }
                "updateTime": {
                        "description": "time of last sync",
                        "type": "integer",
                        "minimum": 0
                }
                "deleted": {
                        "description": "contact has been deleted",
                        "type": "boolean",
                }
        }
}

NOTE This document is in a format called json-schema. JSON schema is the JSON analog for XML Schema; it is used to describe the format of a family of JSON documents. The rest of this book will use it, frequently, for that purpose. There is more documentation on JSON schema at:

Remember that since this is a RESTful API, the previous schema describes only a representation of the actual resource. The version, updateTime, and deleted attributes, for instance, are metadata that describe a resource. The client and server may use that metadata to synchronize their respective versions of the resources.

In order to exchange information about those internal representations — however they are implemented — the client and the server must be able to describe those resources in a way that fits into the JSON schema. This process — creating a transferable representation from a resource — is called marshaling. For example, here is the marshaled representation of the resource for John Smith:

{
    "firstName": "john",
    "lastName": "smith",
    "phone:": "781-123-4567",
    "email": "[email protected]"
}

Contact Methods and URIs

The JSON schema describes the payload for the HTTP requests that the client sends to the server. Defining the schema is analogous to defining parameters for API methods. In this simple example, the schema is the union of the parameters for all of the API methods. You might think of it is as if all of the arguments to all of the methods supported by the server were combined into a single parameter, passed to each of those methods. To complete the definition of the RESTful Contacts API, you also need a catalog of the small set of methods that the server supports. Here it is:

  • A request for the states of all contacts:
    GET /Contacts
  • A request for the state of a specific contact, the contact with id 1:
    GET /Contacts/1
  • A request to create a new contact. The payload describes the new contact:
    POST Contacts
payload:
{
    "firstName": "john",
    "lastName": "smith",
    "phone:": "781-123-4567",
    "email": "[email protected]"
}
  • A request to update the phone number associated with a contact, which is contact #1 again. As with the preceding example, the payload contains a description of the contact fields to be changed, along with their new values:
    PUT /Contacts/1
payload:
{
    "phone:": "781-123-4567" 
}
  • A request to delete all of the information about the contact whose phone number was changed:
    DELETE /Contacts/1
  • A request to synchronize information about multiple contacts. The payload (not shown here) is a list of one or more contacts that have been changed on the client and that must be updated on the server:
    POST /Contacts/sync

Contact Transactions

This section puts this API, as specified so far, into practice. You can try out a few HTTP transactions by issuing requests from the command line using the command-line tool curl. Exercising the server in this way will reveal its behavior.


NOTE The backward-slash () character in the command line examples below, is the line continuation character. It is used by UNIX command line interpreters (shells) to indicate that a single command spans several lines. It is used here simply for formatting purposes: It is not actually part of the curl command. This session may look slightly different in other command-line interpreters.

First, let’s create a new contact:

> curl -X POST 
    -H "Content-Type: application/json" 
    -d '{"firstName":"mike","email":"[email protected]",
         "lastName":"layton","phone":"826-9027"}' 
    http://wileycontacts.com:8080/springServiceContacts/Contacts
 
 
POST /springServiceContacts/Contacts HTTP/1.1
User-Agent: curl/7.28.0
Host: wileycontacts.com:8080
Accept: */*
Content-Type: application/json
Content-Length: 91
{
    "firstName":"mike",
    "lastName":"layton",
    "email":"[email protected]",
    "phone":"826-9027"
}
 
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Jan 2013 05:56:31 GMT
{
    "location":"http://wileycontacts.com:8080/springServiceContacts/Contacts/28"
}

That seems to have worked! The 200 return status indicates that the server successfully processed the request. The returned payload appears to be the URI for the newly created contact. Excellent! You should now be able to retrieve this newly created resource:

> curl -X GET http://wileycontacts.com:8080/springServiceContacts/Contacts/28
 
GET /springServiceContacts/Contacts/28 HTTP/1.1
User-Agent: curl/7.28.0
Host: wileycontacts.com:8080
Accept: */*
 
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Jan 2013 06:21:36 GMT
 
{
     "id":28,
     "firstName":"mike",
     "lastName":"layton",
     "phone":"(802) 826-9027",
     "email":"[email protected]",
     "version":1,
     "updateTime":1357451791659,
     "deleted":false
}

That worked too. The service is holding version “1” of the resource.

This demonstrates the basic functionality of the API. Tidy up by deleting the test resource:

> curl -X DELETE http://wileycontacts.com:8080/springServiceContacts/Contacts/28
 
 
DELETE /springServiceContacts/Contacts/28 HTTP/1.1
User-Agent: curl/7.28.0
Host: wileycontacts.com:8080
Accept: */*
 
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 3
Date: Sun, 06 Jan 2013 06:35:55 GMT

Again, this seems to have worked. The 200 status confirms that the server successfully processed the request. To verify that the test resource is gone, you can query for it again.

> curl -X GET http://wileycontacts.com:8080/springServiceContacts/Contacts/28
 
 
GET /springServiceContacts/Contacts/28 HTTP/1.1
User-Agent: curl/7.28.0
Host: wileycontacts.com:8080
Accept: */*
 
HTTP/1.1 404 Not Found
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 952
Date: Sun, 06 Jan 2013 06:50:45 GMT

The resource is gone. The 404 status indicates that it no longer exists on the server.

As this section demonstrates, a basic RESTful interface can be a very simple thing. It does not require complex clients — in this case you used a simple HTTP command-line tool — and both the queries from the client and the responses from the server are straightforward and understandable. REST can get quite complex, but it doesn’t start that way.

ANDROID NETWORKING

Before we turn to creating client code for this RESTful API, this section reviews some basic networking and how applications implement HTTP connections on the Android platform.

Network connections and their attributes can be finicky and time-consuming to debug. Trying to turn around even the simplest REST request can be maddening when a tight-lipped server returns nothing more informative than a 400 status in response to a request. The fault can be in the request headers (unexpected or multiple content types, an unsupported encoding, or a missing response type), or in the payload (bad JSON or XML syntax, or possibly something wrong in the payload semantics, such as a missing attribute). Because REST syntax is so generic, it can be difficult to determine the root cause of a failure.

As always, an excellent strategy for dealing with this sort of problem is to start with working code. There are many example RESTful HTTP client implementations to be found with simple web searches. There are also two good examples in this section. Copy them and tweak them until they meet your needs.

Better yet, if there is a client that already works with the specific server that is your target, use it! If it is in a different language, translate it. If that is impractical, use a network monitor like tcpdump or Wireshark to determine exactly what it is sending and make sure your client sends the same thing. Of course, if you have access to the server itself, you may be able to use its log to understand what is going on. Once you have something that works, it is easy to modify and refactor it, backing out changes when it breaks.

The Apache Libraries

Android contains two entirely different networking libraries, found in the packages java.net and org.apache.http, respectively. Until about 2011, the word (some of it from Google insiders) seemed to be that Apache framework was a better choice. Listing 5-1 shows the implementation of a method that does an HTTP POST using the Apache framework.

LISTING 5-1: HTTP POST implemented using the Apache libraries

public void post(Uri uri, String payload, ResponseHandler hdlr) {
    HttpPost req = new HttpPost(uri.toString());
    req.setHeader(HTTP.USER_AGENT, USER_AGENT);
 
    if (null != payload) {
        StringEntity s = new StringEntity(payload);
        s.setContentType(MIME_JSON);
        req.setEntity(s);
    }
 
    if (null != hdlr) { req.setHeader(HEADER_ACCEPT, MIME_JSON); }
 
    HttpParams httpParams = new BasicHttpParams();
    HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT);
    HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT);
    DefaultHttpClient client = new DefaultHttpClient(httpParams);
 
    HttpResponse resp = client.execute(req);
 
    if (null != hdlr) {
        Reader in = new InputStreamReader(resp.getEntity().getContent());
        try { hdlr.handleRepsonse(); }
        finally {
            try { in.close(); } catch (Exception e) { }
        }
    }
}

The java.net Libraries

Recently, though, the tune has changed. The Android development team devoted considerable effort to the java.net libraries and is now recommending them as the preferred choice. The timing of the change correlates pretty well with the release of Gingerbread. The Android Developer website says:

Apache HTTP client has fewer bugs in Android 2.2 (Froyo) and earlier releases. For Android 2.3 (Gingerbread) and later, HttpURLConnection is the best choice.

At the time of this writing, Froyo comprises less than 10 percent of the Android installed base — and even that share is shrinking. Unless there is a clear, specific reason for choosing the Apache frameworks, Android developers should choose the java.net HTTP library classes.

An implementation of a fairly general HTTP request processor using the java.net libraries looks like Listing 5-2. This is the one you want.

LISTING 5-2: HTTP POST implemented using the java.net libraries

private int sendRequest(
    HttpMethod method,
    Uri uri,
    String payload,
    ResponseHandler hdlr)
    throws IOException
{
    HttpURLConnection conn
        = (HttpURLConnection) new URL(uri.toString()).openConnection();
    int code = HttpURLConnection.HTTP_INTERNAL_ERROR;
    try {
        conn.setReadTimeout(HTTP_READ_TIMEOUT);
        conn.setConnectTimeout(HTTP_CONN_TIMEOUT);
        conn.setRequestMethod(method.toString());
        conn.setRequestProperty(HEADER_USER_AGENT, USER_AGENT);
        conn.setRequestProperty(HEADER_ENCODING, ENCODING_NONE);
 
        if (null != hdlr) {
            conn.setRequestProperty(HEADER_ACCEPT, MIME_JSON);
            conn.setDoInput(true);
        }
 
        if (null != payload) {
            conn.setRequestProperty(HEADER_CONTENT_TYPE, MIME_JSON);
            conn.setFixedLengthStreamingMode(payload.length());
            conn.setDoOutput(true);
 
            conn.connect();
            Writer out = new OutputStreamWriter(
                new BufferedOutputStream(conn.getOutputStream()),
                "UTF-8");
            out.write(payload);
            out.flush();
        }
 
        code = conn.getResponseCode();
 
        if (null != hdlr) {
            hdlr.handleRepsonse(new BufferedReader(
                new InputStreamReader(conn.getInputStream())));
        }
    }
    finally {
        if (null != conn) {
            try { conn.disconnect(); } catch (Exception e) { }
        }
    }
 
    return code;
}

NOTE The best reference material for the Android network frameworks is the following:
Should you need it, there is additional information about the Apache framework here:

Permissions

Remember that on Android platforms, network access is a restricted privilege. An application must request permissions to gain access. In order for any of this code to work, you need to include the following request in your Android manifest:

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

Chapter 12 describes permissions, security, and access control, in detail.

CONSIDERING CONCURRENCY AND LIFECYCLES

There is an old adage in some developer circles: “The novice programmer believes concurrency is hard but the journeyman programmer does not fear it. The master programmer believes that concurrency is hard.” Concurrency is a very important issue in Android and most developers embarking on an Android project can benefit from a review. While a complete discussion of Java concurrency is well outside the scope of this book, reviewing some of the key issues is well worth the time. The beginning of this chapter alluded to the fact that on the Android platform invoking a network request can be a minefield of subtle problems for which even developers with substantial mobile and Java experience may not be prepared. To illustrate these issues, this section tours a series of code snippets, each of which will highlight one or more problems that Android developers may encounter.

The Android Concurrency Architecture

To begin the discussion, we want to review a few of the basics of Android’s concurrency architecture.

An Android application is a single Linux process. It has its own address space — other applications cannot accidentally change its state — and one or more of its own threads of execution.

An Android application is also a managed container. Unlike the applications that run on a common laptop or even an iPhone, an Android application does not usually control how the application process is started or stopped. Instead, an Android app is a set of special objects, declared to the framework in the manifest file, each of which can be created and used at the framework’s whim. Server-side developers who have experience with JEE containers and managed beans will find this concept familiar.

The main thread in an Android application’s process is usually called the UI thread, sometimes the main looper or just the main thread. Unless there is a specific arrangement to execute code on some other thread, the UI thread powers all an application’s components — activities, services, content providers, and so on. All run on the UI thread.

This poses a problem because the UI thread eponymously powers the UI. Any task that occupies it for any significant period of time will cause the UI to become unresponsive. That is intolerable: Long-running tasks must run on a different thread. The Android framework will terminate your program with prejudice — with an Application Not Responding error — if it hangs the UI thread.

There is a related issue that also stems from the single threaded nature of Android’s UI. The UI code is not thread safe and verifies, during method calls, that it is running on the UI thread. If a UI object discovers that one of its methods is being called from something other than the UI thread, it immediately throws an exception, killing the application. When a long-running task produces results on a non-UI thread, it must safely communicate those results back to the UI thread for presentation in the UI.

As if it were not enough that an Android developer must constantly confront one of the most difficult things to do in Java — publishing objects between threads — there is another concern. The Android framework controls the lifecycle of component objects: activities, services, and so on. It does not, on the other hand, control the lifecycle of asynchronous threads spawned to handle long-running tasks. This leads to two more unsatisfactory consequences.

First of all, if a long-running task holds a reference — a Java variable — to a managed object, it can prevent that object from being garbage collected. Spawning a thread that holds a reference to an activity, for instance, might easily keep that activity object around long after the Android framework has no use for it. The activity has been leaked — its memory cannot be reclaimed.

The second unsatisfactory consequence of the clash between objects with managed and unmanaged lifecycles is that the object with the managed lifecycle may be in an inconsistent state when a long-running task tries to use it. In the previous example, an attempt to draw on the device screen using the reference to the destroyed activity is likely to result in unpredictable behavior.

This conundrum — getting long-running tasks off the UI thread and getting the results back onto the UI thread, while keeping object lifecycles congruent — is a key force that drives the architecture of Android applications, especially ones that participate in an enterprise system.

A Naive Request

You might think that it’s straightforward to invoke a RESTful operation from an Android application. It is as simple as adding one of the example clients in Listings 5-1 and 5-2 to your code and then calling it as necessary, right? After considering the threading problem, though, it should be clear that such an extremely naïve approach is not sufficient.

Developers with some UI experience might address the issue by spawning a worker thread to make the remote call and then notifying the UI thread when the call completes. As the previous section notes, this solves some problems but creates others. Developers with Android experience may even use the framework’s scheduling tool, AsyncTask, to manage long-running tasks without spawning lots of expensive, heavy-weight thread objects. Truly savvy disciples of the robot may use the powerful asynchronous tool introduced in the previous chapter, the Loader. Alas, none of these solutions, depicted schematically in Figure 5-1, is sufficient.

What’s wrong with these simple and seemingly reasonable approaches? Unfortunately, plenty:

  • The Android framework might shut down a long-running network process.
  • The managed object to which the result of the transaction must be reported might be destroyed before the transaction is complete. The application recovers but discards the result. This is an unacceptable waste of the battery and network bandwidth.
  • After sending the request, the device might enter a network dead spot and be unable to communicate with the server. The request has been sent but might not succeed.
  • The device might be powered down (or, worse yet, the battery suddenly removed). The request was never sent and there is no indication that it failed.
  • The Android framework might need space for a new application and might terminate the process for the application running the task. All of the threads, including the one running the long-running task, are ended.

The problem here is structural. Users see an application as a collection of contracts. When, for instance, users press a Send button, they believe that they have entered into a contract with the application to send something. If the application fails to send the data, that’s bad enough. If it fails to do so without notification, the user experience is very bad. When the user demands a contract on one side, and the network is unreliable on the other, that’s a problem.

A mobile application that represents the state of its contract with the user as an in-memory command object is doomed to failure. In order to meet the user’s expectations, it is essential to record those contracts in some persistent way.

AN ARCHITECTURE FOR ROBUST NETWORKING

How, then, can an Android application call a remote server safely and efficiently? There are three approaches that vary from one another, slightly, in their implementations. Although the three approaches differ in their implementation, they share common themes. All of the approaches are based on RESTful architecture. All depend on the fact that a query to a content provider is similar to the query to the remote service for which the content provider is a proxy. In all three, resource state, including whether the resource has changed since the last time it was synchronized with the remote service, is stored locally in a content provider (probably backed by a SQLite database). Finally, in all three, the task of synchronizing the data held locally in the content provider with the parallel data on the remote server, is implemented in an Android service, not an activity.


NOTE The discussion in this chapter — and, indeed, the rest of this book — is based in part on an architecture proposed in a presentation by Google Engineer, Virgil Dobjanschi, at the 2010 Google I/O. The presentation is available on YouTube:

The three resulting approaches, detailed in the next several sections are:

  • Service-centric
  • ContentProvider-centric
  • SyncAdapter-centric

An architectural-level examination of these three approaches may make them seem top-heavy and over-engineered. As you review this section remember that, just because a functional component appears in a schematic does not necessarily mean that it requires a substantial amount of code. Depending on the underlying complexity of the application in which these abstract architectures are used, a diagram component may represent a Java class, a method, or even just a few lines of code somewhere.

Approach 1: Service-Centric

Although this approach may be the simplest of the three, it is also the least malleable. This section looks at it quickly, as you can see in Figure 5-2.

When an activity must perform an asynchronous task, it makes what looks like straightforward method calls to a service helper. The service helper is nothing more than a proxy that translates the arguments to the method call into extra data in an intent. When the service helper fires the intent, the service catches it in its onStartService method. Listing 5-3 shows what this might look like in an implementation of a client for the simple contacts API.

LISTING 5-3: A service helper

 public void createContact(
     Activity ctxt,
     String fname,
     String lname,
     String email,
     String phone)
{
     Intent intent = new Intent(ctxt, ContactsService.class);
     intent.putExtra(ContactsService.FNAME, fname);
     intent.putExtra(ContactsService.LNAME, lname);
     intent.putExtra(ContactsService.PHONE, phone);
     intent.putExtra(ContactsService.EMAIL, email);
     ctxt.startService(intent);
}

NOTE There is a complete explanation of intents and intent services in the Android developer documentation:

When the service receives the intent, it is responsible for several things. Its first order of business is to move the process of handling the intent off the UI thread. Recall that even in an Android service, class methods are run on the UI thread, by default. The Android IntentService is a perfect tool for scheduling tasks on background threads. Each call to an intent service’s onStartService method results in a call to the service onHandleIntent method running on a daemon thread.

On the daemon thread the service implementation must:

  • Create the new resource in the database.
  • Mark the new resource as “dirty” — which means it’s not yet synchronized with the service.
  • Initiate a transaction with the remote service to update the remote resource.
  • Record the ID for the update transaction in the database.

A single call to a content provider handles three of these four requirements.

To initiate the transaction with the remote server, the service object uses another architectural abstraction that is simpler than it sounds, the REST method. A REST method is very similar to the service helper. It is simply a proxy that translates a method call, from the service, into an HTTP request to the remote server.

When the remote server returns its HTTP response, processing proceeds back up the stack. The REST method returns to the service. The service interprets the response. Response processing must always clear the ID of the outstanding request, recorded when the request was initiated (because the transaction has completed). Depending on whether the request was successful, it may also clear the “dirty” flag and record other metadata — the time of last sync or a version number — in the resource.

If the response from the remote server forces the content provider to change a resource in any way that is visible from the UI, the content provider will use the content observer protocol to notify listeners that they need to re-query.

Notice that the burden of communicating inbound data — new information sent by the server — back to the UI thread, is handled by the content provider. Instead of explicitly publishing the new data into the UI thread, in order to make it available for display in view components, updates are pushed into the content provider. The content provider notifies the view that the data has changed, and the view makes a normal, loader-driven query to refresh the display.

Even at this crude level of detail, the appeal of this multi-tier architecture is apparent. The activity does not create or manage any AsyncTasks. The state of requests to the remote server is kept in the database. The state information may even be useful to the user and, therefore, reflected in the UI. The processor component may be somewhat complex, but it is complexity that is intrinsic in the problem and ignored by less sophisticated solutions.

Approach 2: ContentProvider-Centric

This approach is very appealing because of its homogeneity. It has a fractal-like design in which the small components look very much like the larger components that they comprise (see Figure 5-3). This is the architecture used to implement the example client in the next part of this chapter.

The components in this approach are similar to those in the previous one. The significant difference is that here the activity makes RESTful calls to a content provider as if all of the data were local. Hidden behind the content provider is a service helper that forwards requests to a service. The service is responsible, as it was in the previous approach, for synchronizing local resources with their counterparts on the remote server and doing so on a thread other than the UI thread. It uses a REST method, also similar to that in the previous style, to communicate with the remote server.

When the remote server returns a response, a processor component updates the content provider, which notifies content observers as necessary.

Notice that this style does require AsyncTasks in the activity. Content provider operations may take significant time and cannot be performed on the UI thread. On the other hand, this adds a certain symmetry to the architecture. The queries that populate the cursor adapter were, even in the previous approach, performed from a loader. A loader is, essentially, an AsyncTask.

In this style, both outbound updates and inbound queries use AsyncTasks. Note, though, that these AsyncTasks perform only local operations. The lifetime of one of these tasks is only the time necessary to read or write a SQLite database.

At this point the origin of a second name for these architectural styles, “figure-eight,” should become apparent. In addition to the UI thread, there are two asynchronous processes. One of them — the lower lobe — synchronizes the content provider with the remote server. The second — the upper lobe — obtains information from the content provider and publishes it into the UI. AsyncTasks and loaders comprise the upper lobe, asynchronously synchronizing the UI with the data model in the content provider. In both this and the preceding approach the lower lobe is an intent service thread. It pushes data to the network or publishes it to the UI by storing it directly into the content provider.

You’ll revisit this architecture in more detail later in the chapter, when you build the restfulCachingProviderContacts project.

Approach 3: SyncAdapter-Centric

This final style is very similar to the previous approach. The essential difference is that it uses the Android sync adapter framework, instead of a custom service, to manage the remote synchronization process. See Figure 5-4.

Sync adapters are resilient, persistent, and very efficient. They are complex and are discussed separately, in detail, in the next section of this chapter.

REST within Android

You may have noticed the parallels between the discussion of content providers in the previous chapter and the discussion of REST in this one. The relationships between the managed components within an Android application are a microcosm of the relationships between objects outside the application on the greater network. As on the Internet, client and service components have disparate lifecycles, different owners, and scale unpredictably.

Further, as you saw in Chapter 4, Android content providers support inter-process communication (IPC). The KeyValClient demonstrated a content provider that is part of one application and used by client code that is running in a completely different Linux process as part of a separate application. The details of how this works — an Android technology called Binder — are only partially addressed in this book. Note, however, that the Android framework implements communication between a client application and a content provider running as part of a different application by handing the client application a proxy for the serving content provider. In other words, the client holds a proxy to the serving content provider, and that proxy is indistinguishable from that content provider. Sound familiar?

The fact that an Android system looks a lot like a network, internally, gives rise to the architectural choice, noted in Chapter 4, that may surprise and even disturb developers with experience in back-end server-side development. Although most Enterprise Java developers (JEE) are at home using REST as an external architectural style, they are typically used to using object-relational mapping (ORM) technologies for moving data internally.

Android is much more homogeneous — the REST style is pervasive. UI components like activities use loaders to run RESTful queries against content providers, and receive cursors in return. Most JEE developers would shudder at the server-side equivalent, running a SQL query from a servlet and pushing a cursor into the JSP. In Android, however, this is a sensible choice. Using a standardized cursor API to manage relational data is useful, because client code cannot distinguish between a simple cursor, delivered from another component of the same application, or a cursor wrapper, constructed by Binder and delivered across an IPC connection from a completely different application. A fluent Android developer is comfortable using REST not only for requests to external services, but also for requests to internal services.

The restfulCachingProviderContacts Project: An Example Client

In this section, you’ll see how to build a client for the RESTful API discussed so far. The full project containing all code and metadata for the client application in this chapter is available from https://github.com/wileyenterpriseandroid/Examples.git and as a part of the book’s code download at www.wrox.com on the Download Code tab. This section analyzes only the highlights. The project is called restfulCachingProviderContacts and it produces an application, RESTfulContacts, that demonstrates basic REST techniques and the architectural style discussed in this chapter. It is based on the second architectural style, the content provider-centric API backed by a custom service.

This application is by no means industrial strength or ready for release. Although it is a good start, it has shortcomings that will provide motivation for the more mature architectures discussed in the second half of this book.

The application presents a typical list view that displays contact information, as you can see in Figure 5-5. List cells contain the contact attributes, as described in the RESTful API: first name, last name, phone number, and e-mail address. In addition — though you can’t see the color in this black-and-white figure — there is a small bar on the left of each row that shows the synchronization status of a contact. The status bar is green if the contact is fully synchronized with the server side. It is yellow if there is a synchronization transaction outstanding. It’s red if the contact is out of sync and there is no active transaction to sync it.

Clicking on the row for an individual contact or on the New button at the bottom of the page brings up a detail view. The view is populated with contact details, in the former case, and empty, in the latter. Again, there is a narrow status line at the top of the page that reflects that contact synchronization status — green, yellow, or red.

The delete button at the bottom of the page deletes an existing contact. The submit button submits changes to an existing contact or creates a new one. See Figure 5-6.

The wrench icon in the Action Bar brings up a preferences page from which users can enter the base URI for the server with which the client will communicate. The README file in the project’s source describes how to configure an endpoint for a demo server.

Adding a Contact

Start examining the program by tracing the process of adding a new contact. This process begins when a user uses the New button to navigate to the details page, fills it out, and then presses the Submit button.

The implementation of the Submit button handler is shown in Listing 5-4. The button onClickListener calls the update method. The update method makes a sanity check (to make sure that the user hasn’t mistakenly submitted an empty form) and then creates a ContentValues object to hold the user’s input. Again, note that there is no intermediate POJO (Plain Ole Java Object) representation of the contact. Instead, the UI represents that data as an instance of the standard, pervasive ContentValues object.

When it has constructed the ContentValues object, it passes it to an AsyncTask called UpdateContact that posts the new values to the content provider. Remember that in order to pass the mutable content values object safely to the AsyncTask thread, the calling activity must guarantee that it no longer holds any references to it.

When the user is creating a new contact, the URI used to name the contact whose information should be edited is null. The task can determine whether an insert or an update is required by checking the URI.

The UpdateContact asynchronous task is quite safe. It is fire-and-forget, runs quickly, does not directly report results, and does not hold references to managed components.


NOTE It is best practice to make all AsyncTasks static. Because Java is a block-structured language, variables declared in a surrounding block are visible from within inner blocks. Java implements this for nested classes by manufacturing an implicit pointer, a reference to the outer class that is not visible to the programmer. A non-static AsyncTask declared inside an activity always has a reference to that activity. That reference will cause all of the problems described earlier in this chapter.

LISTING 5-4: Adding a contact, the UI

static class UpdateContact extends AsyncTask<Uri, Void, Void> {
    private final ContentResolver resolver;
    private final ContentValues vals;
 
    public UpdateContact(ContentResolver resolver, ContentValues vals) {
        this.resolver = resolver;
        this.vals = vals;
    }
 
    @Override
    protected Void doInBackground(Uri... args) {
        Uri uri = args[0];
        if (null != uri) { resolver.update(uri, vals, null, null); }
        else { resolver.insert(ContactsContract.URI, vals); }
        return null;
    }
}
// code elided...
void update() {
    String s = fnameView.getText().toString();
    if (TextUtils.isEmpty(s)) {
        Toast.makeText(
            this,
            R.string.name_required,
            Toast.LENGTH_SHORT)
            .show();
        return;
    }
 
    ContentValues vals = new ContentValues();
    addString(fnameView, fname, vals, ContactsContract.Columns.FNAME);
    addString(lnameView, lname, vals, ContactsContract.Columns.LNAME);
    addString(phoneView, phone, vals, ContactsContract.Columns.PHONE);
    addString(emailView, email, vals, ContactsContract.Columns.EMAIL);
 
    new UpdateContact(getContentResolver(), vals).execute(contactUri);
 
    goToContacts();
}

Upon receiving the insert request that the UI sends to create a new contact, the content provider does a couple of things, as shown in Listing 5-5.

The Contacts content provider maintains a virtual table, as described in Chapter 4. Its first job is to convert virtual columns to physical columns.

The next step, although only a couple of lines of code, is the key point of this entire chapter. The fact that this newly created record is out of sync with the backend service is not represented as a running task. Instead it is represented as state in the database.

To do this, the code first calls the sendInsert method. In this implementation, sendInsert is the embodiment of the service helper architectural component. It creates a unique transaction ID and forwards the request to the service component as an intent. Marshalling method parameters as an intent in this way was first shown in Listing 5-3. The service will process the intent asynchronously, initiating a transaction with the remote server (as will be described shortly). The sendInsert method returns immediately with the transaction ID.

When the sendInsert method returns, having scheduled the remote transaction, the insert method completes its work by calling localInsert. localInsert adds the newly created record to the database, including the “dirty” flag, marking it as unsynchronized, and the transaction ID, identifying the transaction that has been scheduled to synchronize it.

The localInsert method also notifies any observers that the dataset backing the content provider’s URI has changed. For example, if the user is now looking at the list of all contacts — quite likely, since creating a new contact causes the UI to navigate back to the list view — that list displays the newly entered, but out of sync, contact. Because it is out of sync but has a transaction scheduled to synchronize it, the status bar at the left of the row should be yellow. The notification generated in the localInsert method will cause the UI to update to show the yellow status bar.

LISTING 5-5: Adding a contact, the content provider

@Override
public Uri insert(Uri uri, ContentValues vals) {
    switch (uriMatcher.match(uri)) {
        case CONTACTS_DIR:
            break;
 
        default:
            throw new UnsupportedOperationException(
                "Unrecognized URI: " + uri);
    }
 
    vals = COL_MAP.translateCols(vals);
    vals.put(ContactsHelper.COL_DIRTY, MARK);
 
    String xact = sendInsert(vals);
    vals.put(ContactsHelper.COL_SYNC, xact);
 
    return localInsert(uri, vals);
}
 
public Uri localInsert(Uri uri, ContentValues vals) {
    long pk = helper.getWritableDatabase().insert(
        ContactsHelper.TAB_CONTACTS,
        ContactsContract.Columns.FNAME,
        vals);
 
    if (0 > pk) { uri = null; }
    else {
        uri = uri.buildUpon().appendPath(String.valueOf(pk)).build();
        getContext().getContentResolver().notifyChange(uri, null);
    }
 
    return uri;
}
 
private String sendInsert(ContentValues vals) {
    Intent intent
        = RESTService.getIntent(getContext(), RESTService.Op.CREATE);
 
    putContentValues(vals, intent);
 
    getContext().startService(intent);
 
    return intent.getStringExtra(RESTService.XACT);
}

The application’s intent service will eventually get around to processing the request that was posted from the sendInsert method. Since the service is an intent service, that processing automatically takes place on a daemon thread. Again, remember that this is not true for subclasses of a standard service.

The implementation of the request processor section of the intent service is shown in Listing 5-6. The sendRequest method used to post an HTTP request to the network is the embodiment of the HTTP method architectural component, and is the network client shown in Listing 5-2. The MessageHandler object marshals the content values object to JSON, and the resulting message is passed on as the payload for the HTTP request.

When the server returns its response, the contents are passed to the response handler. It simply unmarshals that content into another new, content values object. If the request is successful, the new contact has been synchronized and no longer dirty.

Whether or not the request was successful, there is no longer an outstanding transaction to synchronize the data. Once the service receives the transaction response, it is obliged to update the content provider when the transaction completes. This update is handled in the cleanup method, the embodiment of the processor component in this implementation.

The cleanup method calls the content provider to update the record on which the transaction is outstanding. The implementation is a little flabby here; it uses the ID of the transaction to identify the record requiring update. This requires exposing columns in the provider contract that really should be private. A more complete application might describe a second virtual table to be used only by the service and in which the transaction ID was the primary key.

The content provider receives the update, stores it in the database, and notifies the UI. The color of the status bar next to the new contact will change to green if the request was successful or to red if it was not.

LISTING 5-6: Adding a contact, the service

private void createContact(Bundle args) {
    if (args.containsKey(ID)) {
        throw new IllegalArgumentException("create must not specify id");
    }
    Uri uri = ((ContactsApplication) getApplication()).getApiUri();
 
    final ContentValues vals = new ContentValues();
    try {
        String payload = new MessageHandler().marshal(args);
 
        sendRequest(
            HttpMethod.POST,
            uri,
            payload,
            new ResponseHandler() {
                @Override
                public void handleRepsonse(BufferedReader in)
                    throws IOException
                {
                    new MessageHandler().unmarshal(in, vals);
                } });
 
        vals.putNull(ContactsContract.Columns.DIRTY);
    }
    catch (Exception e) {
        Log.w(TAG, "create failed: " + e, e);
    }
    finally {
        cleanup(args, vals);
    }
}
 
private void cleanup(Bundle args, ContentValues vals) {
    if (null == vals) { vals = new ContentValues(); }
 
    vals.putNull(ContactsContract.Columns.SYNC);
    if (BuildConfig.DEBUG) {
        Log.d(TAG, "cleanup @" + args.getString(XACT) + ": " + vals);
    }
 
    getContentResolver().update(
        ContactsContract.URI,
        vals,
        ContactsProvider.SYNC_CONSTRAINT,
        new String[] { args.getString(XACT) });
}

While still raw and unfinished, this program is a much firmer architectural grounding on which to base mobile applications. The code is not significantly more complex than a naïve implementation. Once you grasp the concept of storing synchronization state in the database, it is easy to read and understand.

This implementation is so much more robust! Unlike the naïve implementation, this application could be extended to recognize and handle records that have not been successfully synchronized: They are the records that are still marked as dirty and that do not have pending synchronizing transactions.

Extending it to notify the user when synchronization succeeds or when new data arrives is similarly straightforward. This is a solid basis for applications that delight their users.

USING SYNC ADAPTERS

The RESTfulContacts client, as described so far, is a major step toward an enterprise-enabled application. It not only uses the network, but also uses it in a safe and robust way. Its RESTful architecture, internal and external, elegantly navigates the minefield of Android concurrency and lifecycle management issues. It is, to paraphrase Albert Einstein, as simple as possible, and no simpler.

There are, however, still some significant problems. Displaying a contact’s synchronization status in the UI might be interesting, perhaps even useful, if done well. RESTfulContacts as it stands, though, is an excellent example of a horrible user experience. When a contact’s synchronization status turns red, there is no hope that it will ever turn green again by itself. Worse than that, there is very little that users can do to remedy the situation.

The bad UI experience actually reflects a deeper architectural problem. Imagine the following scenario. Suppose that the user chooses a contact for editing and mistypes the telephone number. After submitting the edit, she immediately notices the misspelling in the list view and edits the contact information a second time. If she manages to submit the second edit before the first one completes, the ID for the first transaction will be overwritten in the database. Although this might just work accidentally, it certainly seems unreliable.

There is an even broader consideration. As it stands, the server-side database is wide open. Even if you and I have different base URIs to keep our contacts separate, there is nothing to stop me from using your URI and discovering all of your contacts. This is intolerable. Users need to be able to control access to their personal information. Adding authentication to the RESTfulContacts application would be quite a chore. Fortunately, the Android framework provides a tool that addresses many of these issues, the sync adapter. This section will develop a new application, based on it. The full project containing all code and metadata for the client application in this chapter is available from https://github.com/wileyenterpriseandroid/Examples.git and as a part of the book’s code download at www.wrox.com on the Download Code tab. This section analyzes only the highlights. The project is called syncAdapterContacts, and it produces an application, SyncContacts.

The Android Synchronization service, frequently referred to by the term sync adapter is made up of two components. One of the components is responsible for synchronizing data between a local content provider and a backend data service. The other component manages the accounts that the synchronization service uses when authenticating itself with the backend service.

In the Android system, synchronization is organized around accounts. While one can imagine other, more data-oriented units of synchronization — a database, tables within a database, and so on — in Android it is an account that is synchronized.

Android Account Management

Account management in Android is a large, complex and ill-documented space. Much of it is also outside the scope of this book. Because accounts are the unit of synchronization, however, it will be necessary to create one in order to give the client application something to synchronize.

In order to create an account, an application must do several things:

  • Request the permissions necessary to manage accounts.
  • Declare an account authentication service component in its manifest.
  • Within the service declaration, create a metadata declaration that refers to a resource describing the application’s account type.
  • Create a Java implementation of the declared service that returns an instance of a subclass of Android’s account authentication template class.
  • Optionally, implement preference pages for the account.

This section examines these requirements.

Declaring an Account Authenticator

Much of the work involved in getting account creation to work takes place in the manifest. Be warned that configuring an account authenticator is meticulous work. Android discussion forums are full of descriptions of how minor mistakes in these declarations have caused the entire Android system to crash and reboot. You would be well advised to start with working code and to make small, incremental changes to it to make it meet your specific needs.

Listing 5-7 shows the essential portions of a manifest declaring an account authentication service:

LISTING 5-7: Adding an account management service to the manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.enterpriseandroid.syncadaptercontacts"
    android:versionCode="4"
    android:versionName="1.0RC5" >
 
    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="17" />
 
    <!-- Network -->
    <uses-permission android:name="android.permission.INTERNET" />
    
    <!-- Accounts -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
 
    <application
        android:name=".ContactsApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
 
<!-- ... component declarations omitted -->
 
        <service
           android:name=".sync.AccountService"
           android:exported="false" >
           <intent-filter>
               <action android:name="android.accounts.AccountAuthenticator" />
           </intent-filter>
 
            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/account" />
        </service>
    </application>
 
</manifest>

First, note that the application uses the two permissions android.permission.AUTHENTICATE_ACCOUNTS and android.permission.WRITE_SYNC_SETTINGS. The first of these permissions controls the ability of an application to create an account at all. The Android system distinguishes between the ability to create a new account and the ability to change the configuration of that account. Without the second permission, an application cannot modify an account it has just created.

There are two other permissions that may be necessary to an application that manipulates accounts (note that managing accounts is different than authenticating them). The permission android.permission.GET_ACCOUNTS allows an application to view the list of accounts known to the account service. If an application wishes to make use of an existing account — an account that it did not itself create — it may need to request this permission. The permission android.permission.MANAGE_ACCOUNTS allows an application to create and edit accounts.

As you design your account system, recall that these are fairly powerful permissions you are requesting. As anyone who has seen Spider-Man knows, “With great power comes great responsibility.” Your application is responsible, not only for using these permissions only for honorable purposes but also for making sure that any code that actually uses them is secure from attack. This is code that should be written carefully and, if at all possible, reviewed by a security professional.

In Android, accounts of a given account type are defined by the existence of a service component that meets several rigid requirements. The service declaration identifies the component that Android will use to create, configure, and authenticate one or more accounts of the declared account type. In order for Android to recognize a service as an account authenticator, its declaration must have all of the following elements:

  • It must declare a service that, on bind, returns an instance of AbstractAccountAuthenticator.
  • The declaration must contain a filter for the intent android.accounts.AccountAuthenticator.
  • The declaration must contain a reference to a metadata file.
  • The referenced metadata file must contain an account-authenticator element.
  • The account-authenticator element in the metadata file must contain an android:accountType attribute.

Unless the account authenticator (the service, the intent filter, the metadata declaration and the account type) exists, there is no account type. Without an account type, there are no accounts. Unless there is an account, there is nothing for the synchronization service to synchronize.

Recall that, by default, adding an intent filter to a service causes that service to be accessible to external applications that send the filtered intent: The service is exported. That makes sense in most cases. Providing external applications access to an application component is one of the most common reasons for declaring an intent filter. For an account authenticator, however, the fewer access patterns, the better. Perhaps surprisingly, an account authentication service does not have to be exported. The service in Listing 5-7 has its “exported” attribute explicitly set to false. It cannot be used from other applications.

This is certainly the safest way to configure an authenticator. If it is necessary to allow some external access — perhaps there are multiple applications that need to use the same authenticator — it is also possible to use permissions to control access (permissions are discussed in detail, in Chapter 12).

The next absolute requirement in the declaration of an account authentication service is a metadata reference. The metadata element must have a name attribute whose value is android.accounts.AccountAuthenticator (this is also the name of the intent accepted by the service intent filter). The metadata attribute must refer (using an android:resource attribute) to a correctly built account-authenticator resource. Listing 5-8 shows a very simple example of such a resource.

LISTING 5-8: An account authenticator metadata resource

RES/XML/ACCOUNT.XML


<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountPreferences="@xml/account_prefs"
    android:accountType="@string/account_type"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:smallIcon="@drawable/ic_launcher" />
 

RES/VALUES/STRINGS.XML


<?xml version="1.0" encoding="utf-8"?>
<resources>
 
<!-- ... string declarations omitted -->
 
    <!-- Sync -->
    <string name ="account_type"
        >com.enterpriseandroid.syncadaptercontacts.ACCOUNT</string>
 
</resources>
 

This resource appears to be fairly fault-tolerant in recent versions of Android. As mentioned previously, though, in older versions of Android, seemingly trivial errors could cause a reboot of the entire Android system. If your application targets older Androids, be sure to test thoroughly on each specific, targeted version to make sure that your implementation is sound.

The meanings of most of the attributes in the metadata file are self-evident. There are two that require additional explanation. The first of these is the android:accountType.

The account type is — as are most of the definable tokens used in the Android system: permissions, content provider authorities, and so on — simply a bag of characters. There are no semantic constraints. You could probably define a new account type for your application gov.whitehouse.zork if you chose to do so. By convention, of course, you will probably use the package name for your application or the reversed domain name of the remote service to be synchronized. In the example, the account type is a reference to a string resource whose value is com.enterpriseandroid.syncadaptercontacts.ACCOUNT.

The second attribute in the metadata file that requires some discussion, is android:accountPreferences. This attribute points to yet another resource, this time a file defining the preferences page that will be used to configure the account. It is likely that the page that you want to use here — preferences that configure the account — will also be part of your application’s standard preferences. They are probably available, from your application, through an action bar item.

If you have already built preferences pages for your application, you may be tempted to refer here to one of the preference definition resources you’ve already created. Unfortunately, that won’t work. You cannot simply use a standard permissions definition resource (preference-headers, PreferenceScreen). Instead you need a special resource, like the one shown in Listing 5-9, that uses an intent to launch your application’s preferences activity.

LISTING 5-9: Account authenticator preferences

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
 
    <Preference
        android:key="account_settings"
        android:summary="@string/prefs_sync_summary"
        android:title="@string/prefs_sync_title" >
        <intent
            android:targetClass
                ="com.enterpriseandroid.syncadaptercontacts.PrefsActivity"
            android:targetPackage
                ="com.enterpriseandroid.syncadaptercontacts" >
            <extra
                android:name=":android:no_headers"
                android:value="true" />
            <extra
                android:name=":android:show_fragment"
                android:value
            ="com.enterpriseandroid.syncadaptercontacts.PrefsActivity$SyncPrefs"
            />
        </intent>
    </Preference>
</PreferenceScreen>
 

This declaration will cause your application to be started, and your preferences activity to be run, exactly as if you’d navigated there from the application menu. The intent declaration in the example uses intent extras to navigate directly to the sync prefs page (":android:show_fragment" mimics the behavior of the PreferenceActivity.EXTRA_SHOW_FRAGMENT flag).


NOTE Re-using application preferences can be a can of worms. To begin, the documentation of XML specification of intents is not very good. There is another issue, though.
While the example code nicely navigates directly to the sync prefs page, the bad news is that, if your application is started from preferences like these, then it is running, and its top activity is the sync prefs page. If you, subsequently, launch it from the launcher, you will find yourself in preferences, not in your launch page. Consider setting the android:clearTaskOnLaunch attribute to true in your launch activity to correct this.

This completes the declaration of a very simple authentication service. While there are many possible variations and extensions on this minimal example — some documented and some not — this declaration is sufficient to allow the creation of an account that can be synchronized with the Android synchronization service.

The manifest declarations define a new account type, com.enterpriseandroid.syncadaptercontacts.ACCOUNT, in the metadata for a service component. The implementation of that service component, the class com.enterpriseandroid.syncadaptercontacts.sync.AccountService, is responsible for managing and authenticating accounts of the new type. You can now turn to the implementation of that service.

Using an Account Authenticator

In order to understand the implementation of the account authenticator it will be very useful, first, to understand what happens when an application wants to manage an account. Consider then what happens when some application unrelated to SyncContacts want to manage SyncContacts accounts. It turns out that, there is no need to simply imagine such an application! The ubiquitous Settings application, pre-installed as part of the Android’s system, does exactly that. Without any loss of generality — another application would implement the same behavior in the same way — the example will use the Settings application for demonstration.

When started, the Settings (this example is taken from Android V4.0.3, Ice Cream Sandwich) application displays a page similar to the one shown on the left in Figure 5-7.

Selecting the “Accounts & sync” item invokes a page like the one shown in the center of the figure. Finally, selecting “ADD ACCOUNT” from the bottom of the page presents a page like the one on the right of the figure.

When the SyncContacts application is installed on a device, the Android framework on that device discovers the new account type declared in its manifest as part of the installation process. It remembers it. When the Settings application requests a list of all account types present on the device, the new type is included in the list, along with its icon and label, and they are displayed, as shown on the right side of Figure 5-7. Clicking the corresponding item will cause the Settings application to initiate the process of creating a new account.

When the Settings application needs to create a new account, it obtains an instance of the Android framework’s AccountManager object. It calls the method addAccount on the object. This call initiates an inter-process communications connection that, eventually, binds the SyncContacts authenticator, AccountService. The framework knows that it should bind this particular service because one of the arguments to the addAccount method is the account type for the account to be added, the SyncContacts account type. The framework starts the SyncContacts application if it is not already running, binds the service, and forwards the request to its onBind method.


NOTE The creation of inter-process communication channels is what bound services are all about. Briefly, the process goes like this: A client running in one process calls Context.bindService with an intent identifying the service to which it wants to connect. For each such request, the service process receives a call to its onBind method and returns an IBinder object. The client back in its process receives a corresponding IBinder object that connects the two processes through Binder, Android’s IPC kernel extension.
For further information on the use of Android bound services, see:

Implementing an Account Authenticator

The AccountService, the service that supports SyncContacts’ implementation of an account authentication service, is shown in its entirety in Listing 5-10. As is frequently the case for an Android bound service, the implementation of the service itself is trivial. It is simply a named factory that returns the IBinder object that supports inter-process communication — in this case, between Android’s account manager service and the account authenticator. The object that the service returns in its onBind method, the AccountMgr, is the thing that actually manages the accounts of the type declared in the manifest.

LISTING 5-10: The account service

public class AccountService extends Service {
    private volatile AccountMgr mgr;
 
    @Override
    public void onCreate() {
        super.onCreate();
        mgr = new AccountMgr(getApplicationContext());
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        return mgr.getIBinder();
    }
}

An implementation of an account authenticator must be a subclass of the abstract class AbstractAccountAuthenticator. AbstractAccountAuthenticator wraps an IAccountAuthenticator, which is the IBinder object that forms the IPC connection to the account manager. IAccountAuthenticator, though, is a hidden type and is not exposed by the Android API. The only way to use it is to subclass AbstractAccountAuthenticator.

As a subclass of the abstract type, an authenticator implementation must define seven methods: addAccount, getAuthToken, updateCredentials, hasFeatures, confirmCredentials, editProperties, and getAuthTokenLabel. Each of these methods can be quite complex. An authenticator’s implementation of the addAccount method, might, for instance, require a user to enter data from a security token, send that data to a remote service, wait for the remote service to send a one-time passphrase via SMS to a pre-determined phone number, and then confirm the password with the remote service.

An authenticator may also be very simple. In the example explored here, most of the methods simply throw an UnsupportedOperationException. The implementation of the addAccount method here actually does no authentication at all. It creates a one account per application installation and uses that account for all communications with the upstream contacts server. In order to accommodate such a broad range of policies, most of the methods in AbstractAccountAuthenticator operate in three different modes, identified here as immediate, intent, and delayed.

Listing 5-11 demonstrates an implementation of the immediate mode.

LISTING 5-11: Simple account creation

@Override
public Bundle addAccount(
        AccountAuthenticatorResponse response,
        String accountType,
        String authTokenType,
        String[] requiredFeatures,
        Bundle options)
{
    Bundle reply = new Bundle();
 
    String at = ctxt.getString(R.string.account_type);
    reply.putString(AccountManager.KEY_ACCOUNT_TYPE, at);
 
    if (!at.equals(accountType)) {
        reply.putInt(AccountManager.KEY_ERROR_CODE, -1);
           reply.putString(
                   AccountManager.KEY_ERROR_MESSAGE,
                   "Unrecognized account type");
        return reply;
    }
 
    Account account = new Account(ctxt.getString(R.string.app_name), accountType);
    if (!AccountManager.get(ctxt).addAccountExplicitly(account, null, null)) {
        reply.putInt(AccountManager.KEY_ERROR_CODE, -1);
           reply.putString(
                   AccountManager.KEY_ERROR_MESSAGE,
                   "Unable to create account");
        return reply;
    }
    reply.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
 
    String provider = ctxt.getString(R.string.contacts_authority);
    ContentResolver.setIsSyncable(account, provider, 1);
    ContentResolver.setSyncAutomatically(account, provider, true);
 
    String token = obtainToken(authTokenType);
    if (null == token) {
        reply.putInt(AccountManager.KEY_ERROR_CODE, -1);
           reply.putString(
                   AccountManager.KEY_ERROR_MESSAGE,
                   "Unrecognized token type");
        return reply;
    }
    reply.putString(AccountManager.KEY_AUTHTOKEN, token);
 
    return reply;
}

In immediate mode, an authenticator methods return a bundle that contains any values the authenticator wants to return to its caller. The implementation of the addAccount method in Listing 5-11, for instance, has enough information so that when it is run, it can create the account on the spot (using AccountManager.addAccountExplicitly). The type and name for the single account it will create are simply resource values that it looks up.

In the bundle, the authenticator returns the account type for which the caller requested an account. If it successfully creates an account of that type, it makes the new account synchronizable, sets it up to sync automatically, and returns the account name and an authorization token for it.

Understanding the other two modes, intent and delayed, requires revisiting the client side of account management. Most of the methods on the account manager — the client-side framework that an application, Settings, in this example, uses to manage accounts — return values that are Java Future objects (AccountManagerFuture<Bundle> specifically). A method that creates an IPC connection and proxies its request to another process cannot provide its return values immediately. Instead it returns a future, a token that represent an asynchronous computation. The future can be redeemed for the value of that computation when the computation completes.

In the example, the Settings program calls the account manager’s addAccount method to manage a SyncContacts account. The method returns a Future object that represents the computations being done by the bound account authenticator at the other end of the connection. If the Settings program needs to obtain a result from that remote computation, it calls the future’s getResult method. This method blocks until the asynchronous computation is complete and then returns the bundle that is forwarded across the IPC connection from the authenticator’s addAccount method.

In immediate mode, the authenticator returned its result relatively quickly. The future’s getResult method on the client side of the connection would block only briefly (still too long to be called from the UI thread, however).

In intent mode, however, instead of returning values to be forwarded to the client application, the authenticator instead returns a bundle with string value for the key KEY_INTENT like this:

   Intent intent = new Intent(ctxt, AuthenticatorActivity.class);
   intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
   reply.putParcelable(AccountManager.KEY_INTENT, intent);

The returned bundle may contain other content, as necessary. Instead of returning this bundle directly to the client, though, the framework processes it specially. If the bundle contains a KEY_INTENT key, the framework extracts the intent that is its value and uses it to start the activity it identifies (AuthenticatorActivity.class in this case). When the activity runs, it can take as much time as it likes and is free to interact with the user, the network, biometrics sensors, goat entrails, or anything else necessary to authenticate a new account. Frequently this means collecting some data from the user and forwarding them to a backend server. The Android framework provides the handy activity subclass AccountAuthenticatorActivity as a base class for activities that support an account authenticator.

When the activity eventually completes, it (or one of its delegates) retrieves the response object, also in the bundle returned by the authenticator, and calls its onResponse method.

Remember the calling client? It is still waiting for the getResult method of the future object returned from its call to unblock and return a value! It is only when the onResponse method of the response object — it was passed to the account authenticator in the original client call — is invoked, that the client’s getResult method finally unblocks and returns the bundle that the authenticator supplied as parameter to the onResponse method. Obviously, if the authenticator is operating in intent mode, the client can wait for a very long time, to get its response. It must be designed to accommodate that eventuality.

Delayed mode is similar to intent mode, except that the account authenticator returns null instead of a bundle. When the framework observes the null response, it, again, does not unblock the caller’s getResult method. Unlike intent mode, though, it does not take any other action. In this mode, it is the authenticator’s responsibility to make sure that the response object’s onResponse method gets called. The authenticator is free to take action itself or to pass the response object along to any delegate it chooses. Eventually, though, it must make a decision and allow the client to proceed.


WARNING Beware! It is quite possible for an account authenticator to unintentionally block a client for a very, very long time by incautiously returning null from one of its methods!

Creating an Account

With the implementation of the account authenticator, as shown in Listings 5-10 and 5-11, it is now possible to create an account. Returning to the window shown on the right of Figure 5-7 and selecting the SyncContacts item from the list results in a window similar to that shown in Figure 5-8.

An account has been created! Clicking on the SyncContacts item on the left of Figure 5-8 brings up the window shown on the right. Selecting the Sync Prefs entry on the setting page (the right-hand window) navigates to the SyncContacts application preferences page.

It is also possible to initiate account creation from within an application. Listing 5-12 shows an application menu item that has the same effect as the interactions in Figures 5-7 and 5-8. In order to run this code an application must request the permission android.permission.MANAGE_ACCOUNTS.

Notice that that the code does not expect a response from the call to addAccount. An examination of the Android source reveals that the Settings application works the same way. Neither calls getResponse on the future object returned by the call to addAccount. This suggests that it is probably safe to return null from the corresponding call in the account authenticator.

LISTING 5-12: Using the account manager within the application

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.item_prefs:
            startActivity(new Intent(this, PrefsActivity.class));
            break;
 
        case R.id.item_account:
            AccountManager.get(this).addAccount(
                    getString(R.string.account_type),
                    getString(R.string.token_type),
                    null,
                    null,
                    null,
                    null,
                    null);
            break;
 
    default:
        Log.i(TAG, "Unrecognized menu item: " + item);
        return false;
    }
 
    return true;
}

Finally, notice that the sync icon in the left window of Figure 5-8 is dimmed (whether the account was created from within the application or from Setting) and that there is no information in the “DATA & SYNCHRONZATION” section of the window on the right of the figure. That is because there is no service to synchronize the account. That’s the project for the next section.

Creating a Sync Adapter

Creating a sync adapter is quite a bit simpler than creating the code to manage an account. To begin, a sync adapter service must be declared in the manifest, as shown in Listing 5-13.

LISTING 5-13: Adding synchronization service to the manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.enterpriseandroid.syncadaptercontacts"
    android:versionCode="4"
    android:versionName="1.0RC5" >
 
    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="17" />
 
    <!-- Network -->
    <uses-permission android:name="android.permission.INTERNET" />
 
    <!-- Accounts -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
 
    <!-- Account extra -->
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
 
    <!-- Sync -->
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
 
    <application
        android:name=".ContactsApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
 
<!-- ... component declarations omitted -->
 
     &#160; <provider
            android:name=".data.ContactsProvider"
            android:authorities="@string/contacts_authority"
            android:exported="false" />
 
       <service
            android:name=".sync.SyncService"
            android:exported="false" >
            <intent-filter>
                <action android:name="android.content.SyncAdapter" />
            </intent-filter>
 
            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/sync" />
        </service>
    </application>
 
</manifest>

This listing is similar to Listing 5-7, but there are several new components that are worthy of note. First, the application now requests three new permissions: android.permission.MANAGE_ACCOUNTS, android.permission.GET_ACCOUNTS, and android.permission.USE_CREDENTIALS. The first of these was added to support the menu item introduced in the previous section. The remaining two permissions support the sync adapter.

Next, note the declaration of the content provider. Its implementation was copied, very nearly verbatim, from the RESTContacts application and will not be explored here. The declaration, though, is important because it defines the provider’s authority. That authority will be used again, shortly.

A sync adapter declaration is, like an account manager declaration, a service that filters a particular intent and declares a special metadata resource. The android:name attribute for both filtered intent and the metadata declaration are the same: android.content.SyncAdapter. When the application is installed, the Android system will discover this declaration and, in addition to making it available as a bindable service, will record it as the service that supports the account type mentioned in the metadata resource.

A minimal sync adapter’s metadata resource looks like this (Listing 15-14):

LISTING 5-14: A sync adapter metadata resource

RES/XML/SYNC.XML


<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:contentAuthority="@string/contacts_authority" />
 

RES/VALUES/STRINGS.XML


<?xml version="1.0" encoding="utf-8"?>
<resources>
 
<!-- ... string declarations omitted -->
 
    <!-- Sync -->
    <string
        name="contacts_authority"
            >com.enterpriseandroid.syncadaptercontacts.CONTACTS</string>
    <string
        name="account_type"
            >com.enterpriseandroid.syncadaptercontacts.ACCOUNT</string>
 
</resources>

This declaration links the account type from the previous section with the authority for the content provider. The references to string resources used here reduce the possibility of problems caused by typos.

There are several other attributes that can be added to the sync-adapter element. For instance, although synchronizers are, by default, listed on the Sync Settings page (Figure 5-8), the android:userVisible attribute makes it possible to hide them. Setting the android:isAlwaysSyncable attribute to true — the default is false — has the same effect as that achieved in Listing 5-11 by calling ContentResolver.setIsSyncable. Although current documentation describes the attribute, android:syncAdapterSettingsAction, that specifies an intent that will start an activity to configure the sync adapter, the resource compiler will not compile it.

Again, like the account authenticator, the implementation of the sync adapter service is very simple. It is just a factory whose onBind method returns an IBinder object that supports the inter-process communications channel through which the Android framework’s SyncManager sends requests.

Listing 5-15 shows it in its entirety.

LISTING 5-15: A sync adapter service

public class SyncService extends Service {
    private volatile SyncAdapter synchronizer;
 
    @Override
    public void onCreate() {
        super.onCreate();
        synchronizer = new SyncAdapter(getApplicationContext(), true);
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        return synchronizer.getSyncAdapterBinder();
    }
}

As with the account manager, the inter-process communication interface used by the SyncManager to communicate with clients, ISyncAdapter, is not published as part of the Android API. You must subclass the framework class AbstractThreadedSyncAdapter, which wraps that interface, to implement a sync adapter. Its getSyncAdapterBinder method returns the IPC channel.

Perhaps a surprise — and unlike the account manager — the sync manager itself is also hidden from the API. It is used only by the framework to implement behavior accessible through other objects, mostly the ContentResolver.

AbstractThreadedSyncAdapter is an abstract class. Extending it requires you to implement only one method, onPerformSync. Listing 5-16 gives an implementation of that method. Finally! The implementation of a sync adapter!

LISTING 5-16: Implementing a sync adapter

@Override
public void onPerformSync(
        Account account,
        Bundle extras,
        String authority,
        ContentProviderClient provider,
        SyncResult syncResult)
{
    AccountManager mgr = AccountManager.get(ctxt);
    String tt = ctxt.getString(R.string.token_type);
 
    Exception e = null;
    String token = null;
    try { token = mgr.blockingGetAuthToken(account, tt, false); }
    catch (OperationCanceledException oce) { e = oce; }
    catch (AuthenticatorException ae) { e = ae; }
    catch (IOException ioe) { e = ioe; }
 
    if (null == token) {
        Log.e(TAG, "auth failed: " + AccountMgr.acctStr(account) + "#" + tt, e);
        return;
    }
 
    new RESTService(ctxt).sync(account, token);
 
    // force re-validation
    mgr.invalidateAuthToken(account.type, token);
}

There are many possibilities for the implementation of a sync adapter. This particular implementation is based on an architecture in which the authentication process results in a token, which is then used by the client to demonstrate to the remote service for a limited period of time that the requesting account has been authenticated. Calls to blockingGetAuthToken return a cached copy of that token.

In the example, the only failure mode occurs if the attempt to retrieve the token fails. If that happens, the service simply returns without attempting synchronization.

Real enterprise applications will encounter another, much more interesting failure mode. Once the Android framework gets an authentication token, it will cache it for a very long time. At some point, though, the token will by definition expire. When that happens, the upstream server will refuse to honor it any longer. In order to proceed, it is necessary to get a new token.

To do this, an application must, first, remove the existing token from the cache. It does this by marking the token invalid, with the account manager method invalidateAuthToken.

The code in Listing 5-16 illustrated the use of this method by implementing a one-time token. It invalidates the existing token after every successful server transaction. While an interesting demonstration, this is almost certainly not a good idea for production code. Instead, a real enterprise application will invalidate the token in response to an HTTP 401 status (Unauthorized) from the server.

When the new token is requested, the Android framework forwards the request to the authenticator built in the previous section. When you left that section, there was only one method implemented in the authenticator class, addAccount. It is now necessary to implement a second, as shown in Listing 5-17.

LISTING 5-17: Obtaining authentication tokens

@Override
public Bundle getAuthToken(
        AccountAuthenticatorResponse response,
        Account account,
        String authTokenType,
        Bundle options)
{
    Bundle reply = new Bundle();
    reply.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
    reply.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
    reply.putString(KEY_TOKEN_TYPE, authTokenType);
 
    String tt = ctxt.getString(R.string.account_type);
    if (!tt.equals(account.type)
        || !ctxt.getString(R.string.app_name).equals(account.name))
    {
        reply.putInt(AccountManager.KEY_ERROR_CODE, -1);
        reply.putString(AccountManager.KEY_ERROR_MESSAGE, "Unrecognized account");
        return reply;
    }
 
    String token = obtainToken(authTokenType);
    if (null == token) {
        reply.putInt(AccountManager.KEY_ERROR_CODE, -1);
           reply.putString(
                   AccountManager.KEY_ERROR_MESSAGE,
                   "Unrecognized token type");
        return reply;
    }
 
    reply.putString(AccountManager.KEY_AUTHTOKEN, token);
    return reply;
}

This implementation is, as was the implementation of addAccount, very simple. It delegates the actual task of obtaining a token to the obtainToken method. In this example, the implementation of that method (not shown here) simply looks up a per-installation unique id string. A more realistic implementation might, instead, use Google’s utility class GoogleAuthUtil.

With this addition to the authentication manager, you can return to the examination of the sync adapter. Notice, in passing, that all of the warnings from the previous section about the length of time required to complete an authenticator method apply in spades here. The getAuthToken method might have to go through the entire authentication process again. It might require a series of activities and several round trips to the remote server to present credentials and, perhaps, to confirm their receipt in order to get a new token. The call to blockingGetAuthToken in the sync adapter might not return for quite a long time.

As the name of the sync adapter base class, AbstractThreadedSyncAdapter, suggests a sync adapter does not run on the UI thread. Long running processes are, while never desirable, at least tolerable.

This completes the essentials of sync adapter construction. At this point, returning to the Settings application and navigating through the process of account creation will result in pages like those shown in Figure 5-9.

Though the figure is in black and white here, the sync icon in the left window in this figure is green instead of gray and the Sync Settings page now has information about the last synchronization time and a check box checked to indicate that the account is being synchronized.

The implementation of the RESTService.sync method — the method called from the sync adapter of this new application to perform the actual synchronization with the remote service — is very similar to other methods built for the RESTfulContacts application in the first half of this chapter. It will use JSON over HTTP to communicate change to the upstream service for incorporation into the remote database.

The architecture for the new application, however, is dramatically simpler. RESTfulContacts used a “CRUD over the wire” design. As a first foray into RESTful design, the content provider in that application is a strict, local, in-line, RESTful cache for calls to the remote service. It proxies the local calls nearly verbatim to the network. While queries are handled locally, inserts, updates, and deletes are all applied to the local cache and then forwarded, literally, to the remote.

SyncContacts, on the other hand, uses the single method, sync, to handle any necessary changes. With careful design it is even possible to implement a synchronization protocol that supports multiple sources for changes and handles conflicts in a convergent manner. While the sync method is more complex than any one of the methods it replaces, the overall architecture is much simpler. There is one isolated component that is responsible for synchronizing differences between a client and a server. In order for this to work though, the content provider has to be able to schedule synchronization when the local database is changed.

Scheduling the Sync Adapter

There are several ways to schedule a run of a sync adapter. The Sync now item in the Settings application Sync settings menu, shown in Figure 5-10, will work, for any visible account.

That’s fine, as a last resort. An application must have a way, though, to schedule synchronizations dynamically. There are two ways to do that. The first is brute force and the second is pure Android magic.

Listing 5-18 demonstrates scheduling a synchronization by brute force.

LISTING 5-18: Scheduling a synchronization by force

private void requestSync() {
    Account[] accounts = AccountManager.get(this)
        .getAccountsByType(getString(R.string.account_type));
    if ((null == accounts) || (0 >= accounts.length)) {
        Toast.makeText(this, R.string.msg_no_account, Toast.LENGTH_SHORT).show();
        return;
    }
 
    // Just use the first account of our type.
    // This works because there should be at most one.
    // If there were more, we'd have to choose an account in prefs or something.
    ContentResolver.requestSync(accounts[0],
    ContactsContract.AUTHORITY, 
    new Bundle());
}

This method, called from a menu item in the SyncContacts application, simply requests a list of accounts of the type it declares. In order to do this, it must use the permission android.permission.GET_ACCOUNTS, as shown in Listing 5-13. Once it has the account, it uses a content resolver method to request that a synchronization be run. The method’s second argument, the authority for the content provider to be synchronized, is simply the authority from the content provider’s contract. If it were necessary to pass parameters to the sync adapter, they would be passed in the bundle supplied as the third argument. Even if there are no parameters for the sync adapter, the third argument cannot be null.

Saving the best for last, the other way to schedule synchronization between a remote service and the local data model employs exactly the same code used back in Chapter 4 to schedule synchronization between the local data model and the local view components: the content resolver method notifyChange.

That is nearly all there is to it. Because a sync adapter is tied to a content provider authority, a change notification for that authority will cause the sync adapter to be scheduled. The content provider code from the previous client nearly works without any change.

The one change that is necessary prevents extraneous updates. Consider, for instance, what happens when an application user adds a new contact from the UI. Adding the record to the content provider database generates a change notification. The change notification schedules the sync adapter. The sync adapter run is scheduled a bit in the future, in case, as is likely, the user makes other changes at about the same time. In many versions of Android, this delay is 30 seconds.

When the sync adapter runs, it looks in the database for records that are marked as dirty, bundles them up into a JSON document, and sends them off to the server. When the server replies, the sync adapter marks the records as synced with a call to the content provider’s update method. That call generates another change notification, as it must, to update the record state in the UI: the status bar changes from yellow, syncing, to green, synced.

It would be bad, however, if that second notification caused the sync adapter to be scheduled again! In order to prevent that, there are two, overloaded versions of ContentResolver.notifyChange.

Recall that the version of this method used in the code in Chapter 4 takes two arguments: a URI and a content observer. It signals a change in the dataset that backs the URI. That version is an abbreviation for the three argument version:

 notifyChange(Uri uri, ContentObserver observer, boolean syncToNet)

The third argument indicates whether or not this notification should be broadcast to sync adapters. Listing 5-19 shows its use in one of the content provider methods from the SyncContacts, updated from the similar method in the RESTfulContacts application shown in Listing 5-5.

LISTING 5-19: Using notifyChange’s third argument

SYNCUTIL.JAVA


private static final Uri CONTENT_URI = ContactsContract.URI.buildUpon()
        .appendQueryParameter(ContactsProvider.SYNC_UPDATE, "true")
        .build();
 

CONTACTSPROVIDER.JAVA


private Uri localInsert(Uri uri, ContentValues vals) {
    long pk = localInsertRow(getDb(), vals);
 
    if (0 > pk) { uri = null; }
    else {
        uri = uri.buildUpon().appendPath(String.valueOf(pk)).build();
        getContext().getContentResolver()
            .notifyChange(uri, null, !isSyncUpdate(uri));
    }
 
    return uri;
}
 
private boolean isSyncUpdate(Uri uri) {
    return null != uri.getQueryParameter(ContactsProvider.SYNC_UPDATE);
}

The methods in SyncUtil, part of the sync adapter, call content provider methods with a version of the URI that has a query parameter appended to it. Within the content provider, code that must notify observers that changes have taken place simply checks for the query parameter. If the query parameter is not present, the call should schedule a sync. If the query parameter is present, the call is already from the sync adapter and should not schedule additional synchronizations.

SUMMARY

This chapter reviewed the RESTful architectural style and demonstrated its use, both using HTTP as transport and within Android itself. You’ve read about both the basics of network client implementation and the essentials of Java concurrency and how they apply in the context of an Android application.

From this basis, the chapter turned to a discussion of specific challenges inherent in the Android architecture — the UI-imposed concurrency requirements and their relation to objects with managed lifecycles — and introduced three powerful approaches to solving them.

While differing in the specifics of their implementation, the three approaches all share a few common features. All are based on RESTful architecture and the fact that a content provider can be used as a caching proxy for a remote service. All represent the need to synchronize data with a remote server as persistent data stored locally in a content provider database. Perhaps most important, all three move network communications out of the UI layer and into a separate, independent service component. In none of these approaches is UI code cluttered with the details of initiating or managing network connections.

This chapter also introduced Android’s complex, powerful, and poorly documented synchronization framework, sync adapters.

Finally there was in-depth discussion of two different concrete example implementations of a client that demonstrated the use of these concepts. These examples not only establish the efficacy of the approach, they also show, in their simplicity, that the overhead for adopting the RESTful approach is not significant. Instead, the RESTful approach leads to clear, elegant maintainable code.


SPRING FOR ANDROID
The Spring project is well known among Enterprise Android developers. It provides a well-known and widely used Java technology used for developing backend services. You’ll use it in the next chapter for exactly that purpose.
SpringSource, the organization that manages Spring, has ported parts of it to the Android platform. The resulting library provides a less well known but excellent networking framework for Android application development. The Android version uses the same metaphors found in the server-side counterpart. Some of its desirable characteristics include:
  • Code that is automatically up to date with networking changes in Android. As noted previously, recommendations about whether to use the core Java networking APIs or their Apache equivalents have changed over time. A Spring client will always make the best choice.
  • A rich and convenient API, called RestTemplate.
  • Parameter-based method invocation.
The following code is a brief example of the use of the Spring RestTemplate. For more information on Spring for Android, visit the project’s website:
// Add a message converter
restTemplate.getMessageConverters()
    .add(new MappingJacksonHttpMessageConverter());
 
// Make the HTTP GET request, marshaling the response
// from JSON to an array of Contacts
Contact[] contacts = restTemplate.getForObject(url, Contact[].class);
 
// Set the Accept header
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(
    Collections.singletonList(new MediaType("application", "json")));
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
 
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
 
// Add the Jackson message converter
restTemplate.getMessageConverters()
    .add(new MappingJacksonHttpMessageConverter());
 
// Make the HTTP GET request, marshaling the response
// from JSON to an array of Events
ResponseEntity<Contact[]> responseEntity = restTemplate.exchange(
    url,
    HttpMethod.GET,
    requestEntity,
    Contact[].class);
Contact[] contacts = responseEntity.getBody();

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset