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 60 public SyncAdapter(Context context, boolean autoInitialize) { 61 super(context, autoInitialize); 62 mContext = context; 63 mAccountManager = AccountManager.get(context); 64 } 65 66 @Override 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 // This is a demo of how you can add stream items for contacts on 123 // the client. This probably won't apply to 124 // 2-way contact sync providers - it's more likely that one-way 125 // sync providers (IM clients, social networking apps, etc) would 126 // use this feature. This is only supported in ICS MR1 or above. 127 128 if (Build.VERSION.SDK_INT >= 129 Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { 130 ContactManager.addStreamItems(mContext, updatedContacts, 131 account.name, account.type); 132 } 133 134 // Save off the new sync marker. On our next sync, we only want to receive 135 // contacts that have changed since this sync... 136 setServerSyncMarker(account, newSyncState); 137 138 if (dirtyContacts.size() > 0) { 139 ContactManager.clearSyncFlags(mContext, dirtyContacts); 140 } 141 142 } catch (final AuthenticatorException e) { 143 Log.e(TAG, "AuthenticatorException", e); 144 syncResult.stats.numParseExceptions++; 145 } catch (final OperationCanceledException e) { 146 Log.e(TAG, "OperationCanceledExcetpion", e); 147 } catch (final IOException e) { 148 Log.e(TAG, "IOException", e); 149 syncResult.stats.numIoExceptions++; 150 } catch (final AuthenticationException e) { 151 Log.e(TAG, "AuthenticationException", e); 152 syncResult.stats.numAuthExceptions++; 153 } catch (final ParseException e) { 154 Log.e(TAG, "ParseException", e); 155 syncResult.stats.numParseExceptions++; 156 } catch (final JSONException e) { 157 Log.e(TAG, "JSONException", e); 158 syncResult.stats.numParseExceptions++; 159 } 160 } 161 162 /** 163 * This helper function fetches the last known high-water-mark 164 * we received from the server - or 0 if we've never synced. 165 * @param account the account we're syncing 166 * @return the change high-water-mark 167 */ 168 private long getServerSyncMarker(Account account) { 169 String markerString = mAccountManager.getUserData(account, SYNC_MARKER_KEY); 170 if (!TextUtils.isEmpty(markerString)) { 171 return Long.parseLong(markerString); 172 } 173 return 0; 174 } 175 176 /** 177 * Save off the high-water-mark we receive back from the server. 178 * @param account The account we're syncing 179 * @param marker The high-water-mark we want to save. 180 */ 181 private void setServerSyncMarker(Account account, long marker) { 182 mAccountManager.setUserData(account, SYNC_MARKER_KEY, Long.toString(marker)); 183 } 184 } 185 186