100bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi/*
200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * Copyright (C) 2010 The Android Open Source Project
315ef1a8091c1557f175575671a5af62420088944John Evans *
400bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * Licensed under the Apache License, Version 2.0 (the "License"); you may not
500bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * use this file except in compliance with the License. You may obtain a copy of
600bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * the License at
715ef1a8091c1557f175575671a5af62420088944John Evans *
800bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * http://www.apache.org/licenses/LICENSE-2.0
915ef1a8091c1557f175575671a5af62420088944John Evans *
1000bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * Unless required by applicable law or agreed to in writing, software
1100bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1300bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * License for the specific language governing permissions and limitations under
1400bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * the License.
1500bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi */
1600bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshipackage com.example.android.samplesync.syncadapter;
1700bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi
181cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmannimport com.example.android.samplesync.Constants;
191cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmannimport com.example.android.samplesync.client.NetworkUtilities;
201cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmannimport com.example.android.samplesync.client.RawContact;
211cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmannimport com.example.android.samplesync.platform.ContactManager;
221cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmann
231cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmannimport org.apache.http.ParseException;
241cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmannimport org.apache.http.auth.AuthenticationException;
251cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmannimport org.json.JSONException;
261cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmann
2700bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.accounts.Account;
2800bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.accounts.AccountManager;
2900bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.accounts.AuthenticatorException;
3000bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.accounts.OperationCanceledException;
3100bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.content.AbstractThreadedSyncAdapter;
3200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.content.ContentProviderClient;
3300bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.content.Context;
3400bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.content.SyncResult;
3574c1836184adccce4b876ddfb736567eb87c6e1dRobert Lyimport android.os.Build;
3600bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.os.Bundle;
3715ef1a8091c1557f175575671a5af62420088944John Evansimport android.text.TextUtils;
3800bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport android.util.Log;
3900bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi
4000bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport java.io.IOException;
4100bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshiimport java.util.List;
4200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi
4300bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi/**
4400bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi * SyncAdapter implementation for syncing sample SyncAdapter contacts to the
4515ef1a8091c1557f175575671a5af62420088944John Evans * platform ContactOperations provider.  This sample shows a basic 2-way
4615ef1a8091c1557f175575671a5af62420088944John Evans * sync between the client and a sample server.  It also contains an
4715ef1a8091c1557f175575671a5af62420088944John Evans * example of how to update the contacts' status messages, which
4815ef1a8091c1557f175575671a5af62420088944John Evans * would be useful for a messaging or social networking client.
4900bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi */
5000bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshipublic class SyncAdapter extends AbstractThreadedSyncAdapter {
51c51da235c20bab67ad1ef23884f36bb76740f3a0Megha Joshi
5200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    private static final String TAG = "SyncAdapter";
5315ef1a8091c1557f175575671a5af62420088944John Evans    private static final String SYNC_MARKER_KEY = "com.example.android.samplesync.marker";
5415ef1a8091c1557f175575671a5af62420088944John Evans    private static final boolean NOTIFY_AUTH_FAILURE = true;
5500bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi
5600bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    private final AccountManager mAccountManager;
57c51da235c20bab67ad1ef23884f36bb76740f3a0Megha Joshi
5800bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    private final Context mContext;
5900bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi
6000bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    public SyncAdapter(Context context, boolean autoInitialize) {
6100bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        super(context, autoInitialize);
6200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        mContext = context;
6300bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        mAccountManager = AccountManager.get(context);
6400bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    }
6500bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi
6600bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    @Override
6700bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    public void onPerformSync(Account account, Bundle extras, String authority,
6800bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        ContentProviderClient provider, SyncResult syncResult) {
69c51da235c20bab67ad1ef23884f36bb76740f3a0Megha Joshi
70c51da235c20bab67ad1ef23884f36bb76740f3a0Megha Joshi        try {
7115ef1a8091c1557f175575671a5af62420088944John Evans            // see if we already have a sync-state attached to this account. By handing
7215ef1a8091c1557f175575671a5af62420088944John Evans            // This value to the server, we can just get the contacts that have
7315ef1a8091c1557f175575671a5af62420088944John Evans            // been updated on the server-side since our last sync-up
7415ef1a8091c1557f175575671a5af62420088944John Evans            long lastSyncMarker = getServerSyncMarker(account);
7515ef1a8091c1557f175575671a5af62420088944John Evans
7615ef1a8091c1557f175575671a5af62420088944John Evans            // By default, contacts from a 3rd party provider are hidden in the contacts
7715ef1a8091c1557f175575671a5af62420088944John Evans            // list. So let's set the flag that causes them to be visible, so that users
7815ef1a8091c1557f175575671a5af62420088944John Evans            // can actually see these contacts.
7915ef1a8091c1557f175575671a5af62420088944John Evans            if (lastSyncMarker == 0) {
8015ef1a8091c1557f175575671a5af62420088944John Evans                ContactManager.setAccountContactsVisibility(getContext(), account, true);
8115ef1a8091c1557f175575671a5af62420088944John Evans            }
8215ef1a8091c1557f175575671a5af62420088944John Evans
8315ef1a8091c1557f175575671a5af62420088944John Evans            List<RawContact> dirtyContacts;
8415ef1a8091c1557f175575671a5af62420088944John Evans            List<RawContact> updatedContacts;
8515ef1a8091c1557f175575671a5af62420088944John Evans
8615ef1a8091c1557f175575671a5af62420088944John Evans            // Use the account manager to request the AuthToken we'll need
8715ef1a8091c1557f175575671a5af62420088944John Evans            // to talk to our sample server.  If we don't have an AuthToken
8815ef1a8091c1557f175575671a5af62420088944John Evans            // yet, this could involve a round-trip to the server to request
8915ef1a8091c1557f175575671a5af62420088944John Evans            // and AuthToken.
9015ef1a8091c1557f175575671a5af62420088944John Evans            final String authtoken = mAccountManager.blockingGetAuthToken(account,
9115ef1a8091c1557f175575671a5af62420088944John Evans                    Constants.AUTHTOKEN_TYPE, NOTIFY_AUTH_FAILURE);
9215ef1a8091c1557f175575671a5af62420088944John Evans
931cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmann            // Make sure that the sample group exists
941cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmann            final long groupId = ContactManager.ensureSampleGroupExists(mContext, account);
951cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmann
9615ef1a8091c1557f175575671a5af62420088944John Evans            // Find the local 'dirty' contacts that we need to tell the server about...
9715ef1a8091c1557f175575671a5af62420088944John Evans            // Find the local users that need to be sync'd to the server...
9815ef1a8091c1557f175575671a5af62420088944John Evans            dirtyContacts = ContactManager.getDirtyContacts(mContext, account);
9915ef1a8091c1557f175575671a5af62420088944John Evans
10015ef1a8091c1557f175575671a5af62420088944John Evans            // Send the dirty contacts to the server, and retrieve the server-side changes
10115ef1a8091c1557f175575671a5af62420088944John Evans            updatedContacts = NetworkUtilities.syncContacts(account, authtoken,
10215ef1a8091c1557f175575671a5af62420088944John Evans                    lastSyncMarker, dirtyContacts);
10315ef1a8091c1557f175575671a5af62420088944John Evans
10415ef1a8091c1557f175575671a5af62420088944John Evans            // Update the local contacts database with the changes. updateContacts()
10515ef1a8091c1557f175575671a5af62420088944John Evans            // returns a syncState value that indicates the high-water-mark for
10615ef1a8091c1557f175575671a5af62420088944John Evans            // the changes we received.
10700bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            Log.d(TAG, "Calling contactManager's sync contacts");
10815ef1a8091c1557f175575671a5af62420088944John Evans            long newSyncState = ContactManager.updateContacts(mContext,
10915ef1a8091c1557f175575671a5af62420088944John Evans                    account.name,
11015ef1a8091c1557f175575671a5af62420088944John Evans                    updatedContacts,
1111cf776aee1b2c0be0d9d7d2fdcb8696beefa5e1dDaniel Lehmann                    groupId,
11215ef1a8091c1557f175575671a5af62420088944John Evans                    lastSyncMarker);
11315ef1a8091c1557f175575671a5af62420088944John Evans
11415ef1a8091c1557f175575671a5af62420088944John Evans            // This is a demo of how you can update IM-style status messages
11515ef1a8091c1557f175575671a5af62420088944John Evans            // for contacts on the client. This probably won't apply to
11615ef1a8091c1557f175575671a5af62420088944John Evans            // 2-way contact sync providers - it's more likely that one-way
11715ef1a8091c1557f175575671a5af62420088944John Evans            // sync providers (IM clients, social networking apps, etc) would
11815ef1a8091c1557f175575671a5af62420088944John Evans            // use this feature.
11974c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly
12015ef1a8091c1557f175575671a5af62420088944John Evans            ContactManager.updateStatusMessages(mContext, updatedContacts);
12115ef1a8091c1557f175575671a5af62420088944John Evans
12274c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly            // This is a demo of how you can add stream items for contacts on
12374c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly            // the client. This probably won't apply to
12474c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly            // 2-way contact sync providers - it's more likely that one-way
12574c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly            // sync providers (IM clients, social networking apps, etc) would
12674c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly            // use this feature. This is only supported in ICS MR1 or above.
12774c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly
12874c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly            if (Build.VERSION.SDK_INT >=
12974c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly                    Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
13074c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly                ContactManager.addStreamItems(mContext, updatedContacts,
13174c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly                    account.name, account.type);
13274c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly            }
13374c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly
13415ef1a8091c1557f175575671a5af62420088944John Evans            // Save off the new sync marker. On our next sync, we only want to receive
13515ef1a8091c1557f175575671a5af62420088944John Evans            // contacts that have changed since this sync...
13615ef1a8091c1557f175575671a5af62420088944John Evans            setServerSyncMarker(account, newSyncState);
13715ef1a8091c1557f175575671a5af62420088944John Evans
13815ef1a8091c1557f175575671a5af62420088944John Evans            if (dirtyContacts.size() > 0) {
13915ef1a8091c1557f175575671a5af62420088944John Evans                ContactManager.clearSyncFlags(mContext, dirtyContacts);
14015ef1a8091c1557f175575671a5af62420088944John Evans            }
14115ef1a8091c1557f175575671a5af62420088944John Evans
14200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        } catch (final AuthenticatorException e) {
14300bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            Log.e(TAG, "AuthenticatorException", e);
14415ef1a8091c1557f175575671a5af62420088944John Evans            syncResult.stats.numParseExceptions++;
14500bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        } catch (final OperationCanceledException e) {
14600bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            Log.e(TAG, "OperationCanceledExcetpion", e);
14700bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        } catch (final IOException e) {
14800bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            Log.e(TAG, "IOException", e);
14900bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            syncResult.stats.numIoExceptions++;
15000bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        } catch (final AuthenticationException e) {
15100bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            Log.e(TAG, "AuthenticationException", e);
15215ef1a8091c1557f175575671a5af62420088944John Evans            syncResult.stats.numAuthExceptions++;
15300bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        } catch (final ParseException e) {
15400bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            Log.e(TAG, "ParseException", e);
15500bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            syncResult.stats.numParseExceptions++;
15615ef1a8091c1557f175575671a5af62420088944John Evans        } catch (final JSONException e) {
15700bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi            Log.e(TAG, "JSONException", e);
15815ef1a8091c1557f175575671a5af62420088944John Evans            syncResult.stats.numParseExceptions++;
15915ef1a8091c1557f175575671a5af62420088944John Evans        }
16015ef1a8091c1557f175575671a5af62420088944John Evans    }
16115ef1a8091c1557f175575671a5af62420088944John Evans
16215ef1a8091c1557f175575671a5af62420088944John Evans    /**
16315ef1a8091c1557f175575671a5af62420088944John Evans     * This helper function fetches the last known high-water-mark
16415ef1a8091c1557f175575671a5af62420088944John Evans     * we received from the server - or 0 if we've never synced.
16515ef1a8091c1557f175575671a5af62420088944John Evans     * @param account the account we're syncing
16615ef1a8091c1557f175575671a5af62420088944John Evans     * @return the change high-water-mark
16715ef1a8091c1557f175575671a5af62420088944John Evans     */
16815ef1a8091c1557f175575671a5af62420088944John Evans    private long getServerSyncMarker(Account account) {
16915ef1a8091c1557f175575671a5af62420088944John Evans        String markerString = mAccountManager.getUserData(account, SYNC_MARKER_KEY);
17015ef1a8091c1557f175575671a5af62420088944John Evans        if (!TextUtils.isEmpty(markerString)) {
17115ef1a8091c1557f175575671a5af62420088944John Evans            return Long.parseLong(markerString);
17200bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi        }
17315ef1a8091c1557f175575671a5af62420088944John Evans        return 0;
17415ef1a8091c1557f175575671a5af62420088944John Evans    }
17515ef1a8091c1557f175575671a5af62420088944John Evans
17615ef1a8091c1557f175575671a5af62420088944John Evans    /**
17715ef1a8091c1557f175575671a5af62420088944John Evans     * Save off the high-water-mark we receive back from the server.
17815ef1a8091c1557f175575671a5af62420088944John Evans     * @param account The account we're syncing
17915ef1a8091c1557f175575671a5af62420088944John Evans     * @param marker The high-water-mark we want to save.
18015ef1a8091c1557f175575671a5af62420088944John Evans     */
18115ef1a8091c1557f175575671a5af62420088944John Evans    private void setServerSyncMarker(Account account, long marker) {
18215ef1a8091c1557f175575671a5af62420088944John Evans        mAccountManager.setUserData(account, SYNC_MARKER_KEY, Long.toString(marker));
18300bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi    }
18400bf0f0296b6691a9ed93fa46eccf316f7b0222eMegha Joshi}
18574c1836184adccce4b876ddfb736567eb87c6e1dRobert Ly
186