Authenticating an account connects you to the remote server, but by itself does noth- ing. The real power comes from an account’s ability to synchronize data onto and off of the phone. Android 2.0 added the ability to synchronize custom data from arbitrary accounts.
15.6.1 The synchronizing lifecycle
Synchronizing will generally happen in the background, similarly to authentication.
The authenticator and the synchronizer are loosely coupled; the synchronizer will retrieve necessary information from the AccountManager instead of directly from the authenticator. Again, this is done to keep the user’s private information secure.
To perform synchronization, your service should return an IBinder obtained from a class you define that extends AbstractThreadedSyncAdapter. This defines a single method, onPerformSync, which allows you to perform all synching activities.
TIP Synchronizing operations can differ drastically, depending on what type of data you’re synching. Though most accounts are oriented around personal information, an account could also be used to deliver daily recipes to an application, or to upload usage reports. The authenticator/synchronizer combo is best for situations where a password is required and you want to transfer data silently in the background. In other cases, a standard service would work better.
15.6.2 Synchronizing LinkedIn data
Now that you’ve written an account and utilities for our LinkedIn connections, all that remains is to tie the two together. You can accomplish this with a few final classes for synchronization. The most important is SyncAdapter, shown in this listing.
package com.manning.unlockingandroid.linkedin.sync;
// Imports omitted for brevity
public class SyncAdapter extends AbstractThreadedSyncAdapter { private final AccountManager manager;
private final LinkedInApiClientFactory factory;
private final ContentResolver resolver;
String[] idSelection = new String[] { ContactsContract.RawContacts.SYNC1 };
String[] idValue = new String[1];
public SyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize);
resolver = context.getContentResolver();
manager = AccountManager.get(context);
factory = LinkedInApiClientFactory.newInstance(LinkedIn.API_KEY, LinkedIn.SECRET_KEY);
}
Listing 15.17 Synchronizing LinkedIn connections to contacts
SQL selection to find contacts
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
String authToken = null;
try {
authToken = manager.blockingGetAuthToken(
account, LinkedIn.TYPE, true);
if (authToken == null) {
syncResult.stats.numAuthExceptions++;
return;
}
authToken = manager.getUserData(account, LinkedIn.AUTH_TOKEN);
String authTokenSecret = manager.getUserData(
account, LinkedIn.AUTH_TOKEN_SECRET);
LinkedInApiClient client = factory.createLinkedInApiClient(
authToken, authTokenSecret);
Connections people = client.getConnectionsForCurrentUser();
for (Person person:people.getPersonList()) { String id = person.getId();
String firstName = person.getFirstName();
String lastName = person.getLastName();
String headline = person.getHeadline();
idValue[0] = id;
Cursor matches = resolver.query(
ContactsContract.RawContacts.CONTENT_URI, idSelection, ContactsContract.RawContacts.SYNC1 + "=?", idValue, null);
if (matches.moveToFirst()) { ContactHelper.updateContact(
resolver, account, id, headline);
} else {
ContactHelper.addContact(resolver, account, firstName + " "
+ lastName, id, headline);
} }
} catch (AuthenticatorException e) {
manager.invalidateAuthToken(LinkedIn.TYPE, authToken);
syncResult.stats.numAuthExceptions++;
} catch (IOException ioe) {
syncResult.stats.numIoExceptions++;
} catch (OperationCanceledException ioe) { syncResult.stats.numIoExceptions++;
} catch (LinkedInApiClientException liace) {
manager.invalidateAuthToken(LinkedIn.TYPE, authToken);
syncResult.stats.numAuthExceptions++;
} } }
When performing a sync, we first verify a working auth token B and then retrieve the two auth tokens C that are needed to interact with LinkedIn. The linkedin-jAPIs simplify retrieving and manipulating data model objects for the user’s connections.
Ensure established connection
B
Retrieve credentials
C
Examine all connections
D
Already exists?
E
Update headline
F
Insert data and create contact
G
We iterate through these models D, check to see whether they’re already in our spe- cial LinkedIn contacts list E, and then add F or update G the contacts as appropri- ate, using the ContactHelper class from listing 15.8. Android will read the syncResult variable to determine if and why the sync failed; this can cause the OS to prompt the user to reauthenticate if necessary.
As with authentication, a lightweight wrapper service, shown in the next listing, manages the sync adapter.
package com.manning.unlockingandroid.linkedin.sync;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class SyncService extends Service { private static final Object syncAdapterLock = new Object();
private static SyncAdapter syncAdapter = null;
@Override
public void onCreate() {
synchronized (syncAdapterLock) { if (syncAdapter == null) {
syncAdapter = new SyncAdapter(getApplicationContext(), true);
} } }
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
} }
And, last but not least, a final piece of XML is shown in the following listing to describe the synchronization service’s capabilities.
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.contacts"
android:accountType=
"com.manning.unlockingandroid.linkedin"
android:supportsUploading="false"
/>
The content authority tells Android what type of data can be updated by this service;
contacts are by far the most common. The account type B links the synchronizer to its corresponding authenticator. Finally, the XML describes whether the synchronizer sup- ports one-way downloading only, or whether it also supports uploading changes to data.
Listing 15.18 Defining the synchronization service
Listing 15.19 syncadapter.xml
Singleton
Uses the linkedin account
B