diff options
Diffstat (limited to 'src/com/android/providers/contacts/ContactsSyncAdapter.java')
-rw-r--r-- | src/com/android/providers/contacts/ContactsSyncAdapter.java | 1295 |
1 files changed, 0 insertions, 1295 deletions
diff --git a/src/com/android/providers/contacts/ContactsSyncAdapter.java b/src/com/android/providers/contacts/ContactsSyncAdapter.java deleted file mode 100644 index c15f2da..0000000 --- a/src/com/android/providers/contacts/ContactsSyncAdapter.java +++ /dev/null @@ -1,1295 +0,0 @@ -/* -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** See the License for the specific language governing permissions and -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** limitations under the License. -*/ - -package com.android.providers.contacts; - -import com.google.android.collect.Sets; -import com.google.android.gdata.client.AndroidGDataClient; -import com.google.android.gdata.client.AndroidXmlParserFactory; -import com.google.android.providers.AbstractGDataSyncAdapter; -import com.google.wireless.gdata.client.GDataServiceClient; -import com.google.wireless.gdata.client.QueryParams; -import com.google.wireless.gdata.client.HttpException; -import com.google.wireless.gdata.contacts.client.ContactsClient; -import com.google.wireless.gdata.contacts.data.ContactEntry; -import com.google.wireless.gdata.contacts.data.ContactsElement; -import com.google.wireless.gdata.contacts.data.EmailAddress; -import com.google.wireless.gdata.contacts.data.GroupEntry; -import com.google.wireless.gdata.contacts.data.GroupMembershipInfo; -import com.google.wireless.gdata.contacts.data.ImAddress; -import com.google.wireless.gdata.contacts.data.Organization; -import com.google.wireless.gdata.contacts.data.PhoneNumber; -import com.google.wireless.gdata.contacts.data.PostalAddress; -import com.google.wireless.gdata.contacts.parser.xml.XmlContactsGDataParserFactory; -import com.google.wireless.gdata.data.Entry; -import com.google.wireless.gdata.data.ExtendedProperty; -import com.google.wireless.gdata.data.Feed; -import com.google.wireless.gdata.data.MediaEntry; -import com.google.wireless.gdata.parser.ParseException; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.SyncContext; -import android.content.SyncResult; -import android.content.SyncableContentProvider; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.os.Bundle; -import android.os.SystemProperties; -import android.provider.Contacts; -import android.provider.Contacts.ContactMethods; -import android.provider.Contacts.Extensions; -import android.provider.Contacts.GroupMembership; -import android.provider.Contacts.Groups; -import android.provider.Contacts.Organizations; -import android.provider.Contacts.People; -import android.provider.Contacts.Phones; -import android.provider.Contacts.Photos; -import android.provider.SubscribedFeeds; -import android.provider.SyncConstValue; -import android.text.TextUtils; -import android.util.Config; -import android.util.Log; -import android.accounts.AccountManager; -import android.accounts.Account; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -/** - * Implements a SyncAdapter for Contacts - */ -public class ContactsSyncAdapter extends AbstractGDataSyncAdapter { - - private static final String USER_AGENT_APP_VERSION = "Android-GData-Contacts/1.1"; - - private static final String CONTACTS_FEED_URL = "http://www.google.com/m8/feeds/contacts/"; - private static final String GROUPS_FEED_URL = "http://www.google.com/m8/feeds/groups/"; - private static final String PHOTO_FEED_URL = "http://www.google.com/m8/feeds/photos/media/"; - - private final ContactsClient mContactsClient; - - private static final String[] sSubscriptionProjection = - new String[] { - SubscribedFeeds.Feeds._SYNC_ACCOUNT, - SubscribedFeeds.Feeds.FEED, - SubscribedFeeds.Feeds._ID}; - - private static final HashMap<Byte, Integer> ENTRY_TYPE_TO_PROVIDER_PHONE; - private static final HashMap<Byte, Integer> ENTRY_TYPE_TO_PROVIDER_EMAIL; - private static final HashMap<Byte, Integer> ENTRY_TYPE_TO_PROVIDER_IM; - private static final HashMap<Byte, Integer> ENTRY_TYPE_TO_PROVIDER_POSTAL; - private static final HashMap<Byte, Integer> ENTRY_TYPE_TO_PROVIDER_ORGANIZATION; - private static final HashMap<Integer, Byte> PROVIDER_TYPE_TO_ENTRY_PHONE; - private static final HashMap<Integer, Byte> PROVIDER_TYPE_TO_ENTRY_EMAIL; - private static final HashMap<Integer, Byte> PROVIDER_TYPE_TO_ENTRY_IM; - private static final HashMap<Integer, Byte> PROVIDER_TYPE_TO_ENTRY_POSTAL; - private static final HashMap<Integer, Byte> PROVIDER_TYPE_TO_ENTRY_ORGANIZATION; - - private static final HashMap<Byte, Integer> ENTRY_IM_PROTOCOL_TO_PROVIDER_PROTOCOL; - private static final HashMap<Integer, Byte> PROVIDER_IM_PROTOCOL_TO_ENTRY_PROTOCOL; - - private static final int MAX_MEDIA_ENTRIES_PER_SYNC = 10; - - // Only valid during a sync operation. - // If set then a getServerDiffs() was performed during this sync. - private boolean mPerformedGetServerDiffs; - - // Only valid during a sync. If set then this sync was a forced sync request - private boolean mIsManualSync; - - private int mPhotoDownloads; - private int mPhotoUploads; - - private static final String IMAGE_MIME_TYPE = "image/*"; - - static { - HashMap<Byte, Integer> map; - - map = new HashMap<Byte, Integer>(); - map.put(ImAddress.PROTOCOL_AIM, ContactMethods.PROTOCOL_AIM); - map.put(ImAddress.PROTOCOL_GOOGLE_TALK, ContactMethods.PROTOCOL_GOOGLE_TALK); - map.put(ImAddress.PROTOCOL_ICQ, ContactMethods.PROTOCOL_ICQ); - map.put(ImAddress.PROTOCOL_JABBER, ContactMethods.PROTOCOL_JABBER); - map.put(ImAddress.PROTOCOL_MSN, ContactMethods.PROTOCOL_MSN); - map.put(ImAddress.PROTOCOL_QQ, ContactMethods.PROTOCOL_QQ); - map.put(ImAddress.PROTOCOL_SKYPE, ContactMethods.PROTOCOL_SKYPE); - map.put(ImAddress.PROTOCOL_YAHOO, ContactMethods.PROTOCOL_YAHOO); - ENTRY_IM_PROTOCOL_TO_PROVIDER_PROTOCOL = map; - PROVIDER_IM_PROTOCOL_TO_ENTRY_PROTOCOL = swapMap(map); - - map = new HashMap<Byte, Integer>(); - map.put(EmailAddress.TYPE_HOME, ContactMethods.TYPE_HOME); - map.put(EmailAddress.TYPE_WORK, ContactMethods.TYPE_WORK); - map.put(EmailAddress.TYPE_OTHER, ContactMethods.TYPE_OTHER); - map.put(EmailAddress.TYPE_NONE, ContactMethods.TYPE_CUSTOM); - ENTRY_TYPE_TO_PROVIDER_EMAIL = map; - PROVIDER_TYPE_TO_ENTRY_EMAIL = swapMap(map); - - map = new HashMap<Byte, Integer>(); - map.put(PhoneNumber.TYPE_HOME, Phones.TYPE_HOME); - map.put(PhoneNumber.TYPE_MOBILE, Phones.TYPE_MOBILE); - map.put(PhoneNumber.TYPE_PAGER, Phones.TYPE_PAGER); - map.put(PhoneNumber.TYPE_WORK, Phones.TYPE_WORK); - map.put(PhoneNumber.TYPE_HOME_FAX, Phones.TYPE_FAX_HOME); - map.put(PhoneNumber.TYPE_WORK_FAX, Phones.TYPE_FAX_WORK); - map.put(PhoneNumber.TYPE_OTHER, Phones.TYPE_OTHER); - map.put(PhoneNumber.TYPE_NONE, Phones.TYPE_CUSTOM); - ENTRY_TYPE_TO_PROVIDER_PHONE = map; - PROVIDER_TYPE_TO_ENTRY_PHONE = swapMap(map); - - map = new HashMap<Byte, Integer>(); - map.put(PostalAddress.TYPE_HOME, ContactMethods.TYPE_HOME); - map.put(PostalAddress.TYPE_WORK, ContactMethods.TYPE_WORK); - map.put(PostalAddress.TYPE_OTHER, ContactMethods.TYPE_OTHER); - map.put(PostalAddress.TYPE_NONE, ContactMethods.TYPE_CUSTOM); - ENTRY_TYPE_TO_PROVIDER_POSTAL = map; - PROVIDER_TYPE_TO_ENTRY_POSTAL = swapMap(map); - - map = new HashMap<Byte, Integer>(); - map.put(ImAddress.TYPE_HOME, ContactMethods.TYPE_HOME); - map.put(ImAddress.TYPE_WORK, ContactMethods.TYPE_WORK); - map.put(ImAddress.TYPE_OTHER, ContactMethods.TYPE_OTHER); - map.put(ImAddress.TYPE_NONE, ContactMethods.TYPE_CUSTOM); - ENTRY_TYPE_TO_PROVIDER_IM = map; - PROVIDER_TYPE_TO_ENTRY_IM = swapMap(map); - - map = new HashMap<Byte, Integer>(); - map.put(Organization.TYPE_WORK, Organizations.TYPE_WORK); - map.put(Organization.TYPE_OTHER, Organizations.TYPE_OTHER); - map.put(Organization.TYPE_NONE, Organizations.TYPE_CUSTOM); - ENTRY_TYPE_TO_PROVIDER_ORGANIZATION = map; - PROVIDER_TYPE_TO_ENTRY_ORGANIZATION = swapMap(map); - } - - private static <A, B> HashMap<B, A> swapMap(HashMap<A, B> originalMap) { - HashMap<B, A> newMap = new HashMap<B,A>(); - for (Map.Entry<A, B> entry : originalMap.entrySet()) { - final B originalValue = entry.getValue(); - if (newMap.containsKey(originalValue)) { - throw new IllegalArgumentException("value " + originalValue - + " was already encountered"); - } - newMap.put(originalValue, entry.getKey()); - } - return newMap; - } - - protected ContactsSyncAdapter(Context context, SyncableContentProvider provider) { - super(context, provider); - mContactsClient = new ContactsClient( - new AndroidGDataClient(context, USER_AGENT_APP_VERSION), - new XmlContactsGDataParserFactory(new AndroidXmlParserFactory())); - } - - protected GDataServiceClient getGDataServiceClient() { - return mContactsClient; - } - - @Override - protected Entry newEntry() { - throw new UnsupportedOperationException("this should never be used"); - } - - protected String getFeedUrl(Account account) { - throw new UnsupportedOperationException("this should never be used"); - } - - protected Class getFeedEntryClass() { - throw new UnsupportedOperationException("this should never be used"); - } - - protected Class getFeedEntryClass(String feed) { - if (feed.startsWith(rewriteUrlforAccount(getAccount(), GROUPS_FEED_URL))) { - return GroupEntry.class; - } - if (feed.startsWith(rewriteUrlforAccount(getAccount(), CONTACTS_FEED_URL))) { - return ContactEntry.class; - } - return null; - } - - @Override - public void getServerDiffs(SyncContext context, SyncData baseSyncData, - SyncableContentProvider tempProvider, - Bundle extras, Object syncInfo, SyncResult syncResult) { - mPerformedGetServerDiffs = true; - GDataSyncData syncData = (GDataSyncData)baseSyncData; - - ArrayList<String> feedsToSync = new ArrayList<String>(); - - if (extras != null && extras.containsKey("feed")) { - feedsToSync.add((String) extras.get("feed")); - } else { - feedsToSync.add(getGroupsFeedForAccount(getAccount())); - addContactsFeedsToSync(getContext().getContentResolver(), getAccount(), feedsToSync); - feedsToSync.add(getPhotosFeedForAccount(getAccount())); - } - - for (String feed : feedsToSync) { - context.setStatusText("Downloading\u2026"); - if (getPhotosFeedForAccount(getAccount()).equals(feed)) { - getServerPhotos(context, feed, MAX_MEDIA_ENTRIES_PER_SYNC, syncData, syncResult); - } else { - final Class feedEntryClass = getFeedEntryClass(feed); - if (feedEntryClass != null) { - getServerDiffsImpl(context, tempProvider, feedEntryClass, - feed, null, getMaxEntriesPerSync(), syncData, syncResult); - } else { - if (Config.LOGD) { - Log.d(TAG, "ignoring sync request for unknown feed " + feed); - } - } - } - if (syncResult.hasError()) { - break; - } - } - } - - /** - * Look at the groups sync settings and the overall sync preference to determine which - * feeds to sync and add them to the feedsToSync list. - */ - public static void addContactsFeedsToSync(ContentResolver cr, Account account, - Collection<String> feedsToSync) { - boolean shouldSyncEverything = getShouldSyncEverything(cr, account); - if (shouldSyncEverything) { - feedsToSync.add(getContactsFeedForAccount(account)); - return; - } - - Cursor cursor = cr.query(Contacts.Groups.CONTENT_URI, new String[]{Groups._SYNC_ID}, - "_sync_account=? AND _sync_account_type=? AND should_sync>0", - new String[]{account.name, account.type}, null); - try { - while (cursor.moveToNext()) { - feedsToSync.add(getContactsFeedForGroup(account, cursor.getString(0))); - } - } finally { - cursor.close(); - } - } - - private static boolean getShouldSyncEverything(ContentResolver cr, Account account) { - // TODO(fredq) should be using account instead of null - String value = Contacts.Settings.getSetting(cr, null, Contacts.Settings.SYNC_EVERYTHING); - return !TextUtils.isEmpty(value) && !"0".equals(value); - } - - private void getServerPhotos(SyncContext context, String feedUrl, int maxDownloads, - GDataSyncData syncData, SyncResult syncResult) { - final ContentResolver cr = getContext().getContentResolver(); - final Account account = getAccount(); - Cursor cursor = cr.query( - Photos.CONTENT_URI, - new String[]{Photos._SYNC_ID, Photos._SYNC_VERSION, Photos.PERSON_ID, - Photos.DOWNLOAD_REQUIRED, Photos._ID}, "" - + "_sync_account=? AND _sync_account_type=? AND download_required != 0", - new String[]{account.name, account.type}, null); - try { - int numFetched = 0; - while (cursor.moveToNext()) { - if (numFetched >= maxDownloads) { - break; - } - String photoSyncId = cursor.getString(0); - String photoVersion = cursor.getString(1); - long person = cursor.getLong(2); - String photoUrl = feedUrl + "/" + photoSyncId; - long photoId = cursor.getLong(4); - - try { - context.setStatusText("Downloading photo " + photoSyncId); - ++numFetched; - ++mPhotoDownloads; - InputStream inputStream = mContactsClient.getMediaEntryAsStream( - photoUrl, getAuthToken()); - savePhoto(person, inputStream, photoVersion); - syncResult.stats.numUpdates++; - } catch (IOException e) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.d(TAG, "error downloading " + photoUrl, e); - } - syncResult.stats.numIoExceptions++; - return; - } catch (HttpException e) { - switch (e.getStatusCode()) { - case HttpException.SC_UNAUTHORIZED: - if (Config.LOGD) { - Log.d(TAG, "not authorized to download " + photoUrl, e); - } - syncResult.stats.numAuthExceptions++; - return; - case HttpException.SC_FORBIDDEN: - case HttpException.SC_NOT_FOUND: - final String exceptionMessage = e.getMessage(); - if (Config.LOGD) { - Log.d(TAG, "unable to download photo " + photoUrl + ", " - + exceptionMessage + ", ignoring"); - } - ContentValues values = new ContentValues(); - values.put(Photos.SYNC_ERROR, exceptionMessage); - Uri photoUri = Uri.withAppendedPath( - ContentUris.withAppendedId(People.CONTENT_URI, photoId), - Photos.CONTENT_DIRECTORY); - cr.update(photoUri, values, null /* where */, null /* where args */); - break; - default: - if (Config.LOGD) { - Log.d(TAG, "error downloading " + photoUrl, e); - } - syncResult.stats.numIoExceptions++; - return; - } - } - } - final boolean hasMoreToSync = numFetched < cursor.getCount(); - GDataSyncData.FeedData feedData = - new GDataSyncData.FeedData(0 /* no update time */, - numFetched, hasMoreToSync, null /* no lastId */, - 0 /* no feed index */); - syncData.feedData.put(feedUrl, feedData); - } finally { - cursor.close(); - } - } - - @Override - protected void getStatsString(StringBuffer sb, SyncResult result) { - super.getStatsString(sb, result); - if (mPhotoUploads > 0) { - sb.append("p").append(mPhotoUploads); - } - if (mPhotoDownloads > 0) { - sb.append("P").append(mPhotoDownloads); - } - } - - @Override - public void sendClientDiffs(SyncContext context, SyncableContentProvider clientDiffs, - SyncableContentProvider serverDiffs, SyncResult syncResult, - boolean dontSendDeletes) { - initTempProvider(clientDiffs); - - sendClientDiffsImpl(context, clientDiffs, new GroupEntry(), null /* no syncInfo */, - serverDiffs, syncResult, dontSendDeletes); - - // lets go ahead and commit what we have if we successfully made a change - if (syncResult.madeSomeProgress()) { - return; - } - - sendClientPhotos(context, clientDiffs, null /* no syncInfo */, syncResult); - - // lets go ahead and commit what we have if we successfully made a change - if (syncResult.madeSomeProgress()) { - return; - } - - sendClientDiffsImpl(context, clientDiffs, new ContactEntry(), null /* no syncInfo */, - serverDiffs, syncResult, dontSendDeletes); - } - - protected void sendClientPhotos(SyncContext context, ContentProvider clientDiffs, - Object syncInfo, SyncResult syncResult) { - Entry entry = new MediaEntry(); - - GDataServiceClient client = getGDataServiceClient(); - String authToken = getAuthToken(); - ContentResolver cr = getContext().getContentResolver(); - final Account account = getAccount(); - - Cursor c = clientDiffs.query(Photos.CONTENT_URI, null /* all columns */, - null /* no where */, null /* no where args */, null /* default sort order */); - try { - int personColumn = c.getColumnIndexOrThrow(Photos.PERSON_ID); - int dataColumn = c.getColumnIndexOrThrow(Photos.DATA); - int numRows = c.getCount(); - while (c.moveToNext()) { - if (mSyncCanceled) { - if (Config.LOGD) Log.d(TAG, "stopping since the sync was canceled"); - break; - } - - entry.clear(); - context.setStatusText("Updating, " + (numRows - 1) + " to go"); - - cursorToBaseEntry(entry, account, c); - String editUrl = entry.getEditUri(); - - if (TextUtils.isEmpty(editUrl)) { - if (Config.LOGD) { - Log.d(TAG, "skipping photo edit for unsynced contact"); - } - continue; - } - - // Send the request and receive the response - InputStream inputStream = null; - byte[] imageData = c.getBlob(dataColumn); - if (imageData != null) { - inputStream = new ByteArrayInputStream(imageData); - } - Uri photoUri = Uri.withAppendedPath(People.CONTENT_URI, - c.getString(personColumn) + "/" + Photos.CONTENT_DIRECTORY); - try { - if (inputStream != null) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Updating photo " + entry.toString()); - } - ++mPhotoUploads; - client.updateMediaEntry(editUrl, inputStream, IMAGE_MIME_TYPE, authToken); - } else { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Deleting photo " + entry.toString()); - } - client.deleteEntry(editUrl, authToken); - } - - // Mark that this photo is no longer dirty. The next time we sync (which - // should be soon), we will get the new version of the photo and whether - // or not there is a new one to download (e.g. if we deleted our version - // yet there is an evergreen version present). - ContentValues values = new ContentValues(); - values.put(Photos.EXISTS_ON_SERVER, inputStream == null ? 0 : 1); - values.put(Photos._SYNC_DIRTY, 0); - if (cr.update(photoUri, values, - null /* no where */, null /* no where args */) != 1) { - Log.e(TAG, "error updating photo " + photoUri + " with values " + values); - syncResult.stats.numParseExceptions++; - } else { - syncResult.stats.numUpdates++; - } - continue; - } catch (ParseException e) { - Log.e(TAG, "parse error during update of " + ", skipping"); - syncResult.stats.numParseExceptions++; - } catch (IOException e) { - if (Config.LOGD) { - Log.d(TAG, "io error during update of " + entry.toString() - + ", skipping"); - } - syncResult.stats.numIoExceptions++; - } catch (HttpException e) { - switch (e.getStatusCode()) { - case HttpException.SC_UNAUTHORIZED: - if (syncResult.stats.numAuthExceptions == 0) { - if (Config.LOGD) { - Log.d(TAG, "auth error during update of " + entry - + ", skipping"); - } - } - syncResult.stats.numAuthExceptions++; - AccountManager.get(getContext()).invalidateAuthToken( - "com.google", authToken); - return; - - case HttpException.SC_CONFLICT: - if (Config.LOGD) { - Log.d(TAG, "conflict detected during update of " + entry - + ", skipping"); - } - syncResult.stats.numConflictDetectedExceptions++; - break; - case HttpException.SC_BAD_REQUEST: - case HttpException.SC_FORBIDDEN: - case HttpException.SC_NOT_FOUND: - case HttpException.SC_INTERNAL_SERVER_ERROR: - default: - if (Config.LOGD) { - Log.d(TAG, "error " + e.getMessage() + " during update of " - + entry.toString() + ", skipping"); - } - syncResult.stats.numIoExceptions++; - } - } - } - } finally { - c.close(); - } - } - - @Override - protected Cursor getCursorForTable(ContentProvider cp, Class entryClass) { - return getCursorForTableImpl(cp, entryClass); - } - - protected static Cursor getCursorForTableImpl(ContentProvider cp, Class entryClass) { - if (entryClass == ContactEntry.class) { - return cp.query(People.CONTENT_URI, null, null, null, null); - } - if (entryClass == GroupEntry.class) { - return cp.query(Groups.CONTENT_URI, null, null, null, null); - } - throw new IllegalArgumentException("unexpected entry class, " + entryClass.getName()); - } - - @Override - protected Cursor getCursorForDeletedTable(ContentProvider cp, Class entryClass) { - return getCursorForDeletedTableImpl(cp, entryClass); - } - - protected static Cursor getCursorForDeletedTableImpl(ContentProvider cp, Class entryClass) { - if (entryClass == ContactEntry.class) { - return cp.query(People.DELETED_CONTENT_URI, null, null, null, null); - } - if (entryClass == GroupEntry.class) { - return cp.query(Groups.DELETED_CONTENT_URI, null, null, null, null); - } - throw new IllegalArgumentException("unexpected entry class, " + entryClass.getName()); - } - - @Override - protected String cursorToEntry(SyncContext context, Cursor c, Entry baseEntry, - Object syncInfo) throws ParseException { - return cursorToEntryImpl(getContext().getContentResolver(), c, baseEntry, getAccount()); - } - - static protected String cursorToEntryImpl(ContentResolver cr, Cursor c, Entry entry, - Account account) throws ParseException { - cursorToBaseEntry(entry, account, c); - String createUrl = null; - if (entry instanceof ContactEntry) { - cursorToContactEntry(account, cr, c, (ContactEntry) entry); - if (entry.getEditUri() == null) { - createUrl = getContactsFeedForAccount(account); - } - } else if (entry instanceof MediaEntry) { - if (entry.getEditUri() == null) { - createUrl = getPhotosFeedForAccount(account); - } - } else { - cursorToGroupEntry(c, (GroupEntry) entry); - if (entry.getEditUri() == null) { - createUrl = getGroupsFeedForAccount(account); - } - } - - return createUrl; - } - - private static void cursorToGroupEntry(Cursor c, GroupEntry entry) throws ParseException { - if (!TextUtils.isEmpty(c.getString(c.getColumnIndexOrThrow(Groups.SYSTEM_ID)))) { - throw new ParseException("unable to modify system groups"); - } - entry.setTitle(c.getString(c.getColumnIndexOrThrow(Groups.NAME))); - entry.setContent(c.getString(c.getColumnIndexOrThrow(Groups.NOTES))); - entry.setSystemGroup(null); - } - - private static void cursorToContactEntry(Account account, ContentResolver cr, Cursor c, - ContactEntry entry) - throws ParseException { - entry.setTitle(c.getString(c.getColumnIndexOrThrow(People.NAME))); - entry.setContent(c.getString(c.getColumnIndexOrThrow(People.NOTES))); - entry.setYomiName(c.getString(c.getColumnIndexOrThrow(People.PHONETIC_NAME))); - - long syncLocalId = c.getLong(c.getColumnIndexOrThrow(SyncConstValue._SYNC_LOCAL_ID)); - addContactMethodsToContactEntry(cr, syncLocalId, entry); - addPhonesToContactEntry(cr, syncLocalId, entry); - addOrganizationsToContactEntry(cr, syncLocalId, entry); - addGroupMembershipToContactEntry(account, cr, syncLocalId, entry); - addExtensionsToContactEntry(cr, syncLocalId, entry); - } - - @Override - protected void deletedCursorToEntry(SyncContext context, Cursor c, Entry entry) { - deletedCursorToEntryImpl(c, entry, getAccount()); - } - - protected boolean handleAllDeletedUnavailable(GDataSyncData syncData, String feed) { - // Contacts has no way to clear the contacts for just a given feed so it is unable - // to handle this condition itself. Instead it returns false, which tell the - // sync framework that it must handle it. - return false; - } - - protected static void deletedCursorToEntryImpl(Cursor c, Entry entry, Account account) { - cursorToBaseEntry(entry, account, c); - } - - private static void cursorToBaseEntry(Entry entry, Account account, Cursor c) { - String feedUrl; - if (entry instanceof ContactEntry) { - feedUrl = getContactsFeedForAccount(account); - } else if (entry instanceof GroupEntry) { - feedUrl = getGroupsFeedForAccount(account); - } else if (entry instanceof MediaEntry) { - feedUrl = getPhotosFeedForAccount(account); - } else { - throw new IllegalArgumentException("bad entry type: " + entry.getClass().getName()); - } - - String syncId = c.getString(c.getColumnIndexOrThrow(SyncConstValue._SYNC_ID)); - if (syncId != null) { - String syncVersion = c.getString(c.getColumnIndexOrThrow(SyncConstValue._SYNC_VERSION)); - entry.setId(feedUrl + "/" + syncId); - entry.setEditUri(entry.getId() + "/" + syncVersion); - } - } - - private static void addPhonesToContactEntry(ContentResolver cr, long personId, - ContactEntry entry) - throws ParseException { - Cursor c = cr.query(Phones.CONTENT_URI, null, "person=" + personId, null, null); - int numberIndex = c.getColumnIndexOrThrow(People.Phones.NUMBER); - try { - while (c.moveToNext()) { - PhoneNumber phoneNumber = new PhoneNumber(); - cursorToContactsElement(phoneNumber, c, PROVIDER_TYPE_TO_ENTRY_PHONE); - phoneNumber.setPhoneNumber(c.getString(numberIndex)); - entry.addPhoneNumber(phoneNumber); - } - } finally { - if (c != null) c.close(); - } - } - - - static private void addContactMethodsToContactEntry(ContentResolver cr, long personId, - ContactEntry entry) throws ParseException { - Cursor c = cr.query(ContactMethods.CONTENT_URI, null, - "person=" + personId, null, null); - int kindIndex = c.getColumnIndexOrThrow(ContactMethods.KIND); - int dataIndex = c.getColumnIndexOrThrow(ContactMethods.DATA); - int auxDataIndex = c.getColumnIndexOrThrow(ContactMethods.AUX_DATA); - try { - while (c.moveToNext()) { - int kind = c.getInt(kindIndex); - switch (kind) { - case Contacts.KIND_IM: { - ImAddress address = new ImAddress(); - cursorToContactsElement(address, c, PROVIDER_TYPE_TO_ENTRY_IM); - address.setAddress(c.getString(dataIndex)); - Object object = ContactMethods.decodeImProtocol(c.getString(auxDataIndex)); - if (object == null) { - address.setProtocolPredefined(ImAddress.PROTOCOL_NONE); - } else if (object instanceof Integer) { - address.setProtocolPredefined( - PROVIDER_IM_PROTOCOL_TO_ENTRY_PROTOCOL.get((Integer)object)); - } else { - if (!(object instanceof String)) { - throw new IllegalArgumentException("expected an String, " + object); - } - address.setProtocolPredefined(ImAddress.PROTOCOL_CUSTOM); - address.setProtocolCustom((String)object); - } - entry.addImAddress(address); - break; - } - case Contacts.KIND_POSTAL: { - PostalAddress address = new PostalAddress(); - cursorToContactsElement(address, c, PROVIDER_TYPE_TO_ENTRY_POSTAL); - address.setValue(c.getString(dataIndex)); - entry.addPostalAddress(address); - break; - } - case Contacts.KIND_EMAIL: { - EmailAddress address = new EmailAddress(); - cursorToContactsElement(address, c, PROVIDER_TYPE_TO_ENTRY_EMAIL); - address.setAddress(c.getString(dataIndex)); - entry.addEmailAddress(address); - break; - } - } - } - } finally { - if (c != null) c.close(); - } - } - - private static void addOrganizationsToContactEntry(ContentResolver cr, long personId, - ContactEntry entry) throws ParseException { - Cursor c = cr.query(Organizations.CONTENT_URI, null, - "person=" + personId, null, null); - try { - int companyIndex = c.getColumnIndexOrThrow(Organizations.COMPANY); - int titleIndex = c.getColumnIndexOrThrow(Organizations.TITLE); - while (c.moveToNext()) { - Organization organization = new Organization(); - cursorToContactsElement(organization, c, PROVIDER_TYPE_TO_ENTRY_ORGANIZATION); - organization.setName(c.getString(companyIndex)); - organization.setTitle(c.getString(titleIndex)); - entry.addOrganization(organization); - } - } finally { - if (c != null) c.close(); - } - } - - private static void addGroupMembershipToContactEntry(Account account, ContentResolver cr, - long personId, ContactEntry entry) throws ParseException { - Cursor c = cr.query(GroupMembership.RAW_CONTENT_URI, null, - "person=" + personId, null, null); - try { - int serverIdIndex = c.getColumnIndexOrThrow(GroupMembership.GROUP_SYNC_ID); - int localIdIndex = c.getColumnIndexOrThrow(GroupMembership.GROUP_ID); - while (c.moveToNext()) { - String serverId = c.getString(serverIdIndex); - if (serverId == null) { - final Uri groupUri = ContentUris - .withAppendedId(Groups.CONTENT_URI, c.getLong(localIdIndex)); - Cursor groupCursor = cr.query(groupUri, new String[]{Groups._SYNC_ID}, - null, null, null); - try { - if (groupCursor.moveToNext()) { - serverId = groupCursor.getString(0); - } - } finally { - groupCursor.close(); - } - } - if (serverId == null) { - // the group hasn't been synced yet, we can't complete this operation since - // we don't know what server id to use for the group - throw new ParseException("unable to construct GroupMembershipInfo since the " - + "group _sync_id isn't known yet, will retry later"); - } - GroupMembershipInfo groupMembershipInfo = new GroupMembershipInfo(); - String groupId = getCanonicalGroupsFeedForAccount(account) + "/" + serverId; - groupMembershipInfo.setGroup(groupId); - groupMembershipInfo.setDeleted(false); - entry.addGroup(groupMembershipInfo); - } - } finally { - if (c != null) c.close(); - } - } - - private static void addExtensionsToContactEntry(ContentResolver cr, long personId, - ContactEntry entry) throws ParseException { - Cursor c = cr.query(Extensions.CONTENT_URI, null, "person=" + personId, null, null); - try { - JSONObject jsonObject = new JSONObject(); - int nameIndex = c.getColumnIndexOrThrow(Extensions.NAME); - int valueIndex = c.getColumnIndexOrThrow(Extensions.VALUE); - if (c.getCount() == 0) return; - while (c.moveToNext()) { - try { - jsonObject.put(c.getString(nameIndex), c.getString(valueIndex)); - } catch (JSONException e) { - throw new ParseException("bad key or value", e); - } - } - ExtendedProperty extendedProperty = new ExtendedProperty(); - extendedProperty.setName("android"); - final String jsonString = jsonObject.toString(); - if (jsonString == null) { - throw new ParseException("unable to convert cursor into a JSON string, " - + DatabaseUtils.dumpCursorToString(c)); - } - extendedProperty.setXmlBlob(jsonString); - entry.addExtendedProperty(extendedProperty); - } finally { - if (c != null) c.close(); - } - } - - private static void cursorToContactsElement(ContactsElement element, - Cursor c, HashMap<Integer, Byte> map) { - final int typeIndex = c.getColumnIndexOrThrow("type"); - final int labelIndex = c.getColumnIndexOrThrow("label"); - final int isPrimaryIndex = c.getColumnIndexOrThrow("isprimary"); - - element.setLabel(c.getString(labelIndex)); - element.setType(map.get(c.getInt(typeIndex))); - element.setIsPrimary(c.getInt(isPrimaryIndex) != 0); - } - - private static void contactsElementToValues(ContentValues values, ContactsElement element, - HashMap<Byte, Integer> map) { - values.put("type", map.get(element.getType())); - values.put("label", element.getLabel()); - values.put("isprimary", element.isPrimary() ? 1 : 0); - } - - /* - * Takes the entry, casts it to a ContactEntry and executes the appropriate - * actions on the ContentProvider to represent the entry. - */ - protected void updateProvider(Feed feed, Long syncLocalId, - Entry baseEntry, ContentProvider provider, Object syncInfo, - GDataSyncData.FeedData feedSyncData) throws ParseException { - - // This is a hack to delete these incorrectly created contacts named "Starred in Android" - if (baseEntry instanceof ContactEntry - && "Starred in Android".equals(baseEntry.getTitle())) { - Log.i(TAG, "Deleting incorrectly created contact from the server: " + baseEntry); - GDataServiceClient client = getGDataServiceClient(); - try { - client.deleteEntry(baseEntry.getEditUri(), getAuthToken()); - } catch (IOException e) { - Log.i(TAG, " exception while deleting contact: " + baseEntry, e); - } catch (com.google.wireless.gdata.client.HttpException e) { - Log.i(TAG, " exception while deleting contact: " + baseEntry, e); - } - } - - updateProviderImpl(getAccount(), syncLocalId, baseEntry, provider); - } - - protected static void updateProviderImpl(Account account, Long syncLocalId, - Entry entry, ContentProvider provider) throws ParseException { - // If this is a deleted entry then add it to the DELETED_CONTENT_URI - ContentValues deletedValues = null; - if (entry.isDeleted()) { - deletedValues = new ContentValues(); - deletedValues.put(SyncConstValue._SYNC_LOCAL_ID, syncLocalId); - final String id = entry.getId(); - final String editUri = entry.getEditUri(); - if (!TextUtils.isEmpty(id)) { - deletedValues.put(SyncConstValue._SYNC_ID, lastItemFromUri(id)); - } - if (!TextUtils.isEmpty(editUri)) { - deletedValues.put(SyncConstValue._SYNC_VERSION, lastItemFromUri(editUri)); - } - deletedValues.put(SyncConstValue._SYNC_ACCOUNT, account.name); - deletedValues.put(SyncConstValue._SYNC_ACCOUNT_TYPE, account.type); - } - - if (entry instanceof ContactEntry) { - if (deletedValues != null) { - provider.insert(People.DELETED_CONTENT_URI, deletedValues); - return; - } - updateProviderWithContactEntry(account, syncLocalId, (ContactEntry) entry, provider); - return; - } - if (entry instanceof GroupEntry) { - if (deletedValues != null) { - provider.insert(Groups.DELETED_CONTENT_URI, deletedValues); - return; - } - updateProviderWithGroupEntry(account, syncLocalId, (GroupEntry) entry, provider); - return; - } - throw new IllegalArgumentException("unknown entry type, " + entry.getClass().getName()); - } - - protected static void updateProviderWithContactEntry(Account account, Long syncLocalId, - ContactEntry entry, ContentProvider provider) throws ParseException { - final String name = entry.getTitle(); - final String notes = entry.getContent(); - final String yomiName = entry.getYomiName(); - final String personSyncId = lastItemFromUri(entry.getId()); - final String personSyncVersion = lastItemFromUri(entry.getEditUri()); - - // Store the info about the person - ContentValues values = new ContentValues(); - values.put(People.NAME, name); - values.put(People.NOTES, notes); - values.put(People.PHONETIC_NAME, yomiName); - values.put(SyncConstValue._SYNC_ACCOUNT, account.name); - values.put(SyncConstValue._SYNC_ACCOUNT_TYPE, account.type); - values.put(SyncConstValue._SYNC_ID, personSyncId); - values.put(SyncConstValue._SYNC_DIRTY, "0"); - values.put(SyncConstValue._SYNC_LOCAL_ID, syncLocalId); - values.put(SyncConstValue._SYNC_TIME, personSyncVersion); - values.put(SyncConstValue._SYNC_VERSION, personSyncVersion); - Uri personUri = provider.insert(People.CONTENT_URI, values); - - // Store the photo information - final boolean photoExistsOnServer = !TextUtils.isEmpty(entry.getLinkPhotoHref()); - final String photoVersion = lastItemFromUri(entry.getLinkEditPhotoHref()); - values.clear(); - values.put(Photos.PERSON_ID, ContentUris.parseId(personUri)); - values.put(Photos.EXISTS_ON_SERVER, photoExistsOnServer ? 1 : 0); - values.put(SyncConstValue._SYNC_ACCOUNT, account.name); - values.put(SyncConstValue._SYNC_ACCOUNT_TYPE, account.type); - values.put(SyncConstValue._SYNC_ID, personSyncId); - values.put(SyncConstValue._SYNC_DIRTY, 0); - values.put(SyncConstValue._SYNC_LOCAL_ID, syncLocalId); - values.put(SyncConstValue._SYNC_TIME, photoVersion); - values.put(SyncConstValue._SYNC_VERSION, photoVersion); - if (provider.insert(Photos.CONTENT_URI, values) == null) { - Log.e(TAG, "error inserting photo row, " + values); - } - - // Store each email address - for (Object object : entry.getEmailAddresses()) { - EmailAddress email = (EmailAddress) object; - values.clear(); - contactsElementToValues(values, email, ENTRY_TYPE_TO_PROVIDER_EMAIL); - values.put(ContactMethods.DATA, email.getAddress()); - values.put(ContactMethods.KIND, Contacts.KIND_EMAIL); - Uri uri = Uri.withAppendedPath(personUri, People.ContactMethods.CONTENT_DIRECTORY); - provider.insert(uri, values); - } - - // Store each postal address - for (Object object : entry.getPostalAddresses()) { - PostalAddress address = (PostalAddress) object; - values.clear(); - contactsElementToValues(values, address, ENTRY_TYPE_TO_PROVIDER_POSTAL); - values.put(ContactMethods.DATA, address.getValue()); - values.put(ContactMethods.KIND, Contacts.KIND_POSTAL); - Uri uri = Uri.withAppendedPath(personUri, People.ContactMethods.CONTENT_DIRECTORY); - provider.insert(uri, values); - } - - // Store each im address - for (Object object : entry.getImAddresses()) { - ImAddress address = (ImAddress) object; - values.clear(); - contactsElementToValues(values, address, ENTRY_TYPE_TO_PROVIDER_IM); - values.put(ContactMethods.DATA, address.getAddress()); - values.put(ContactMethods.KIND, Contacts.KIND_IM); - final byte protocolType = address.getProtocolPredefined(); - if (protocolType == ImAddress.PROTOCOL_NONE) { - // don't add anything - } else if (protocolType == ImAddress.PROTOCOL_CUSTOM) { - values.put(ContactMethods.AUX_DATA, - ContactMethods.encodeCustomImProtocol(address.getProtocolCustom())); - } else { - Integer providerProtocolType = - ENTRY_IM_PROTOCOL_TO_PROVIDER_PROTOCOL .get(protocolType); - if (providerProtocolType == null) { - throw new IllegalArgumentException("unknown protocol type, " + protocolType); - } - values.put(ContactMethods.AUX_DATA, - ContactMethods.encodePredefinedImProtocol(providerProtocolType)); - } - Uri uri = Uri.withAppendedPath(personUri, People.ContactMethods.CONTENT_DIRECTORY); - provider.insert(uri, values); - } - - // Store each organization - for (Object object : entry.getOrganizations()) { - Organization organization = (Organization) object; - values.clear(); - contactsElementToValues(values, organization, ENTRY_TYPE_TO_PROVIDER_ORGANIZATION); - values.put(Organizations.COMPANY, organization.getName()); - values.put(Organizations.TITLE, organization.getTitle()); - values.put(Organizations.COMPANY, organization.getName()); - Uri uri = Uri.withAppendedPath(personUri, Organizations.CONTENT_DIRECTORY); - provider.insert(uri, values); - } - - // Store each group - for (Object object : entry.getGroups()) { - GroupMembershipInfo groupMembershipInfo = (GroupMembershipInfo) object; - if (groupMembershipInfo.isDeleted()) { - continue; - } - values.clear(); - values.put(GroupMembership.GROUP_SYNC_ACCOUNT, account.name); - values.put(GroupMembership.GROUP_SYNC_ACCOUNT_TYPE, account.type); - values.put(GroupMembership.GROUP_SYNC_ID, - lastItemFromUri(groupMembershipInfo.getGroup())); - Uri uri = Uri.withAppendedPath(personUri, GroupMembership.CONTENT_DIRECTORY); - provider.insert(uri, values); - } - - // Store each phone number - for (Object object : entry.getPhoneNumbers()) { - PhoneNumber phone = (PhoneNumber) object; - values.clear(); - contactsElementToValues(values, phone, ENTRY_TYPE_TO_PROVIDER_PHONE); - values.put(People.Phones.NUMBER, phone.getPhoneNumber()); - values.put(People.Phones.LABEL, phone.getLabel()); - Uri uri = Uri.withAppendedPath(personUri, People.Phones.CONTENT_DIRECTORY); - provider.insert(uri, values); - } - - // Store the extended properties - for (Object object : entry.getExtendedProperties()) { - ExtendedProperty extendedProperty = (ExtendedProperty) object; - if (!"android".equals(extendedProperty.getName())) { - continue; - } - JSONObject jsonObject = null; - try { - jsonObject = new JSONObject(extendedProperty.getXmlBlob()); - } catch (JSONException e) { - Log.w(TAG, "error parsing the android extended property, dropping, entry is " - + entry.toString()); - continue; - } - Iterator jsonIterator = jsonObject.keys(); - while (jsonIterator.hasNext()) { - String key = (String)jsonIterator.next(); - values.clear(); - values.put(Extensions.NAME, key); - try { - values.put(Extensions.VALUE, jsonObject.getString(key)); - } catch (JSONException e) { - // this should never happen, since we just got the key from the iterator - } - Uri uri = Uri.withAppendedPath(personUri, People.Extensions.CONTENT_DIRECTORY); - if (null == provider.insert(uri, values)) { - Log.e(TAG, "Error inserting extension into provider, uri " - + uri + ", values " + values); - } - } - break; - } - } - - protected static void updateProviderWithGroupEntry(Account account, Long syncLocalId, - GroupEntry entry, ContentProvider provider) throws ParseException { - ContentValues values = new ContentValues(); - values.put(Groups.NAME, entry.getTitle()); - values.put(Groups.NOTES, entry.getContent()); - values.put(Groups.SYSTEM_ID, entry.getSystemGroup()); - values.put(Groups._SYNC_ACCOUNT, account.name); - values.put(Groups._SYNC_ACCOUNT_TYPE, account.type); - values.put(Groups._SYNC_ID, lastItemFromUri(entry.getId())); - values.put(Groups._SYNC_DIRTY, 0); - values.put(Groups._SYNC_LOCAL_ID, syncLocalId); - final String editUri = entry.getEditUri(); - final String syncVersion = editUri == null ? null : lastItemFromUri(editUri); - values.put(Groups._SYNC_TIME, syncVersion); - values.put(Groups._SYNC_VERSION, syncVersion); - provider.insert(Groups.CONTENT_URI, values); - } - - private static String lastItemFromUri(String url) { - return url.substring(url.lastIndexOf('/') + 1); - } - - protected void savePhoto(long person, InputStream photoInput, String photoVersion) - throws IOException { - try { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - byte[] data = new byte[1024]; - while(true) { - int bytesRead = photoInput.read(data); - if (bytesRead < 0) break; - byteStream.write(data, 0, bytesRead); - } - - ContentValues values = new ContentValues(); - // we have to include this here otherwise the provider will set it to 1 - values.put(Photos._SYNC_DIRTY, 0); - values.put(Photos.LOCAL_VERSION, photoVersion); - values.put(Photos.DATA, byteStream.toByteArray()); - Uri photoUri = Uri.withAppendedPath(People.CONTENT_URI, - "" + person + "/" + Photos.CONTENT_DIRECTORY); - if (getContext().getContentResolver().update(photoUri, values, - "_sync_dirty=0", null) > 0) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "savePhoto: updated " + photoUri + " with values " + values); - } - } else { - Log.e(TAG, "savePhoto: update of " + photoUri + " with values " + values - + " affected no rows"); - } - } finally { - try { - if (photoInput != null) photoInput.close(); - } catch (IOException e) { - // we don't care about exceptions here - } - } - } - - /** - * Make sure the contacts subscriptions we expect based on the current - * accounts are present and that there aren't any extra subscriptions - * that we don't expect. - */ - @Override - public void onAccountsChanged(Account[] accountsArray) { - if (!"yes".equals(SystemProperties.get("ro.config.sync"))) { - return; - } - - ContentResolver cr = getContext().getContentResolver(); - for (Account account : accountsArray) { - // TODO(fredq) should be using account instead of null - String value = Contacts.Settings.getSetting(cr, null, - Contacts.Settings.SYNC_EVERYTHING); - if (value == null) { - // TODO(fredq) should be using account instead of null - Contacts.Settings.setSetting(cr, null, Contacts.Settings.SYNC_EVERYTHING, "1"); - } - updateSubscribedFeeds(cr, account); - } - } - - /** - * Returns the contacts feed url for a specific account. - * @param account The account - * @return The contacts feed url for a specific account. - */ - public static String getContactsFeedForAccount(Account account) { - String url = CONTACTS_FEED_URL + account.name + "/base2_property-android"; - return rewriteUrlforAccount(account, url); - } - - /** - * Returns the contacts group feed url for a specific account. - * @param account The account - * @param groupSyncId The group id - * @return The contacts feed url for a specific account and group. - */ - public static String getContactsFeedForGroup(Account account, String groupSyncId) { - String groupId = getCanonicalGroupsFeedForAccount(account); - try { - groupId = URLEncoder.encode(groupId, "utf-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException("unable to url encode group: " + groupId); - } - return getContactsFeedForAccount(account) + "?group=" + groupId + "/" + groupSyncId; - } - - /** - * Returns the groups feed url for a specific account. - * @param account The account - * @return The groups feed url for a specific account. - */ - public static String getGroupsFeedForAccount(Account account) { - String url = GROUPS_FEED_URL + account.name + "/base2_property-android"; - return rewriteUrlforAccount(account, url); - } - - /** - * Returns the groups feed url for a specific account that should be - * used as the foreign reference to this group, e.g. in the - * group membership element of the ContactEntry. The canonical groups - * feed always uses http (so it doesn't need to be rewritten) and it always - * uses the base projection. - * @param account The account - * @return The groups feed url for a specific account. - */ - public static String getCanonicalGroupsFeedForAccount(Account account) { - return GROUPS_FEED_URL + account.name + "/base"; - } - - /** - * Returns the photo feed url for a specific account. - * @param account The account - * @return The photo feed url for a specific account. - */ - public static String getPhotosFeedForAccount(Account account) { - String url = PHOTO_FEED_URL + account.name; - return rewriteUrlforAccount(account, url); - } - - protected static boolean getFeedReturnsPartialDiffs() { - return true; - } - - @Override - protected void updateQueryParameters(QueryParams params, GDataSyncData.FeedData feedSyncData) { - // we want to get the events ordered by last modified, so we can - // recover in case we cannot process the entire feed. - params.setParamValue("orderby", "lastmodified"); - params.setParamValue("sortorder", "ascending"); - - // set showdeleted so that we get tombstones, only do this when we - // are doing an incremental sync - if (params.getUpdatedMin() != null) { - params.setParamValue("showdeleted", "true"); - } - } - - @Override - public void onSyncStarting(SyncContext context, Account account, boolean manualSync, - SyncResult result) { - mPerformedGetServerDiffs = false; - mIsManualSync = manualSync; - mPhotoDownloads = 0; - mPhotoUploads = 0; - super.onSyncStarting(context, account, manualSync, result); - } - - @Override - public void onSyncEnding(SyncContext context, boolean success) { - final ContentResolver cr = getContext().getContentResolver(); - - if (success && mPerformedGetServerDiffs && !mSyncCanceled) { - final Account account = getAccount(); - Cursor cursor = cr.query( - Photos.CONTENT_URI, - new String[]{Photos._SYNC_ID, Photos._SYNC_VERSION, Photos.PERSON_ID, - Photos.DOWNLOAD_REQUIRED}, "" - + "_sync_account=? AND _sync_account_type=? AND download_required != 0", - new String[]{account.name, account.type}, null); - try { - if (cursor.getCount() != 0) { - Bundle extras = new Bundle(); - extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, mIsManualSync); - extras.putString("feed", ContactsSyncAdapter.getPhotosFeedForAccount(account)); - ContentResolver.requestSync(account, Contacts.AUTHORITY, extras); - } - } finally { - cursor.close(); - } - } - - super.onSyncEnding(context, success); - } - - public static void updateSubscribedFeeds(ContentResolver cr, Account account) { - Set<String> feedsToSync = Sets.newHashSet(); - feedsToSync.add(getGroupsFeedForAccount(account)); - addContactsFeedsToSync(cr, account, feedsToSync); - - Cursor c = SubscribedFeeds.Feeds.query(cr, sSubscriptionProjection, - SubscribedFeeds.Feeds.AUTHORITY + "=?" - + " AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?" - + " AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?", - new String[]{Contacts.AUTHORITY, account.name, account.type}, null); - try { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "scanning over subscriptions with authority " - + Contacts.AUTHORITY + " and account " + account); - } - c.moveToNext(); - while (!c.isAfterLast()) { - String feedInCursor = c.getString(1); - if (feedsToSync.contains(feedInCursor)) { - feedsToSync.remove(feedInCursor); - c.moveToNext(); - } else { - c.deleteRow(); - } - } - c.commitUpdates(); - } finally { - c.close(); - } - - // any feeds remaining in feedsToSync need a subscription - for (String feed : feedsToSync) { - SubscribedFeeds.addFeed(cr, feed, account, Contacts.AUTHORITY, ContactsClient.SERVICE); - - // request a sync of this feed - Bundle extras = new Bundle(); - extras.putString("feed", feed); - ContentResolver.requestSync(account, Contacts.AUTHORITY, extras); - } - } -} |