1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.example.android.samplesync.syncadapter; 17 18 import com.example.android.samplesync.Constants; 19 import com.example.android.samplesync.client.NetworkUtilities; 20 import com.example.android.samplesync.client.RawContact; 21 import com.example.android.samplesync.platform.ContactManager; 22 23 import org.apache.http.ParseException; 24 import org.apache.http.auth.AuthenticationException; 25 import org.json.JSONException; 26 27 import android.accounts.Account; 28 import android.accounts.AccountManager; 29 import android.accounts.AuthenticatorException; 30 import android.accounts.OperationCanceledException; 31 import android.content.AbstractThreadedSyncAdapter; 32 import android.content.ContentProviderClient; 33 import android.content.Context; 34 import android.content.SyncResult; 35 import android.os.Bundle; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import java.io.IOException; 40 import java.util.List; 41 42 /** 43 * SyncAdapter implementation for syncing sample SyncAdapter contacts to the 44 * platform ContactOperations provider. This sample shows a basic 2-way 45 * sync between the client and a sample server. It also contains an 46 * example of how to update the contacts' status messages, which 47 * would be useful for a messaging or social networking client. 48 */ 49 public class SyncAdapter extends AbstractThreadedSyncAdapter { 50 51 private static final String TAG = "SyncAdapter"; 52 private static final String SYNC_MARKER_KEY = "com.example.android.samplesync.marker"; 53 private static final boolean NOTIFY_AUTH_FAILURE = true; 54 55 private final AccountManager mAccountManager; 56 57 private final Context mContext; 58 59 public SyncAdapter(Context context, boolean autoInitialize) { 60 super(context, autoInitialize); 61 mContext = context; 62 mAccountManager = AccountManager.get(context); 63 } 64 65 @Override 66 public void onPerformSync(Account account, Bundle extras, String authority, 67 ContentProviderClient provider, SyncResult syncResult) { 68 69 try { 70 // see if we already have a sync-state attached to this account. By handing 71 // This value to the server, we can just get the contacts that have 72 // been updated on the server-side since our last sync-up 73 long lastSyncMarker = getServerSyncMarker(account); 74 75 // By default, contacts from a 3rd party provider are hidden in the contacts 76 // list. So let's set the flag that causes them to be visible, so that users 77 // can actually see these contacts. 78 if (lastSyncMarker == 0) { 79 ContactManager.setAccountContactsVisibility(getContext(), account, true); 80 } 81 82 List<RawContact> dirtyContacts; 83 List<RawContact> updatedContacts; 84 85 // Use the account manager to request the AuthToken we'll need 86 // to talk to our sample server. If we don't have an AuthToken 87 // yet, this could involve a round-trip to the server to request 88 // and AuthToken. 89 final String authtoken = mAccountManager.blockingGetAuthToken(account, 90 Constants.AUTHTOKEN_TYPE, NOTIFY_AUTH_FAILURE); 91 92 // Make sure that the sample group exists 93 final long groupId = ContactManager.ensureSampleGroupExists(mContext, account); 94 95 // Find the local 'dirty' contacts that we need to tell the server about... 96 // Find the local users that need to be sync'd to the server... 97 dirtyContacts = ContactManager.getDirtyContacts(mContext, account); 98 99 // Send the dirty contacts to the server, and retrieve the server-side changes 100 updatedContacts = NetworkUtilities.syncContacts(account, authtoken, 101 lastSyncMarker, dirtyContacts); 102 103 // Update the local contacts database with the changes. updateContacts() 104 // returns a syncState value that indicates the high-water-mark for 105 // the changes we received. 106 Log.d(TAG, "Calling contactManager's sync contacts"); 107 long newSyncState = ContactManager.updateContacts(mContext, 108 account.name, 109 updatedContacts, 110 groupId, 111 lastSyncMarker); 112 113 // This is a demo of how you can update IM-style status messages 114 // for contacts on the client. This probably won't apply to 115 // 2-way contact sync providers - it's more likely that one-way 116 // sync providers (IM clients, social networking apps, etc) would 117 // use this feature. 118 ContactManager.updateStatusMessages(mContext, updatedContacts); 119 120 // Save off the new sync marker. On our next sync, we only want to receive 121 // contacts that have changed since this sync... 122 setServerSyncMarker(account, newSyncState); 123 124 if (dirtyContacts.size() > 0) { 125 ContactManager.clearSyncFlags(mContext, dirtyContacts); 126 } 127 128 } catch (final AuthenticatorException e) { 129 Log.e(TAG, "AuthenticatorException", e); 130 syncResult.stats.numParseExceptions++; 131 } catch (final OperationCanceledException e) { 132 Log.e(TAG, "OperationCanceledExcetpion", e); 133 } catch (final IOException e) { 134 Log.e(TAG, "IOException", e); 135 syncResult.stats.numIoExceptions++; 136 } catch (final AuthenticationException e) { 137 Log.e(TAG, "AuthenticationException", e); 138 syncResult.stats.numAuthExceptions++; 139 } catch (final ParseException e) { 140 Log.e(TAG, "ParseException", e); 141 syncResult.stats.numParseExceptions++; 142 } catch (final JSONException e) { 143 Log.e(TAG, "JSONException", e); 144 syncResult.stats.numParseExceptions++; 145 } 146 } 147 148 /** 149 * This helper function fetches the last known high-water-mark 150 * we received from the server - or 0 if we've never synced. 151 * @param account the account we're syncing 152 * @return the change high-water-mark 153 */ 154 private long getServerSyncMarker(Account account) { 155 String markerString = mAccountManager.getUserData(account, SYNC_MARKER_KEY); 156 if (!TextUtils.isEmpty(markerString)) { 157 return Long.parseLong(markerString); 158 } 159 return 0; 160 } 161 162 /** 163 * Save off the high-water-mark we receive back from the server. 164 * @param account The account we're syncing 165 * @param marker The high-water-mark we want to save. 166 */ 167 private void setServerSyncMarker(Account account, long marker) { 168 mAccountManager.setUserData(account, SYNC_MARKER_KEY, Long.toString(marker)); 169 } 170 } 171