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