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