diff --git a/Android.mk b/Android.mk deleted file mode 100644 index f59017c..0000000 --- a/Android.mk +++ /dev/null @@ -1,20 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -contacts_provider_files := ../ContactsProvider/src/com/android/providers/contacts/ContactsProvider.java - -LOCAL_SRC_FILES := $(call all-java-files-under,src) $(contacts_provider_files) - -LOCAL_JAVA_LIBRARIES := ext - -# We depend on googlelogin-client also, but that is already being included by google-framework -LOCAL_STATIC_JAVA_LIBRARIES := google-framework - -LOCAL_PACKAGE_NAME := GoogleContactsProvider -LOCAL_CERTIFICATE := shared - -LOCAL_OVERRIDES_PACKAGES := ContactsProvider - -include $(BUILD_PACKAGE) - -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index a66809f..0000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/drawable-hdpi/app_icon.png b/res/drawable-hdpi/app_icon.png deleted file mode 100755 index 0bcbca4..0000000 Binary files a/res/drawable-hdpi/app_icon.png and /dev/null differ diff --git a/res/drawable-mdpi/app_icon.png b/res/drawable-mdpi/app_icon.png deleted file mode 100644 index 826656f..0000000 Binary files a/res/drawable-mdpi/app_icon.png and /dev/null differ diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml deleted file mode 100644 index a76af52..0000000 --- a/res/values-cs/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Úložiště kontaktů" - diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml deleted file mode 100644 index c65fed4..0000000 --- a/res/values-da/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Lagring af kontakter" - diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml deleted file mode 100644 index 4a21e6e..0000000 --- a/res/values-de/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Kontakte-Speicher" - diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml deleted file mode 100644 index 31b89b4..0000000 --- a/res/values-el/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Χώρος αποθήκευσης επαφών" - diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml deleted file mode 100644 index 0170e69..0000000 --- a/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Espacio de almacenamiento para Contactos" - diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml deleted file mode 100644 index 3a0ebc7..0000000 --- a/res/values-es/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Información de los contactos" - diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml deleted file mode 100644 index d36d9ff..0000000 --- a/res/values-fr/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Liste des contacts" - diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml deleted file mode 100644 index fe2b515..0000000 --- a/res/values-it/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Archiviazione contatti" - diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml deleted file mode 100644 index ae23f6a..0000000 --- a/res/values-ja/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "アドレス帳" - diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml deleted file mode 100644 index ce2dc72..0000000 --- a/res/values-ko/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "주소록 저장소" - diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml deleted file mode 100644 index 14733fb..0000000 --- a/res/values-nb/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Kontaktlager" - diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml deleted file mode 100644 index 8117b29..0000000 --- a/res/values-nl/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Opslag contacten" - diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml deleted file mode 100644 index 969cfb1..0000000 --- a/res/values-pl/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Spis kontaktów" - diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml deleted file mode 100644 index 7e34855..0000000 --- a/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Armazenamento de contactos" - diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml deleted file mode 100644 index 81cd411..0000000 --- a/res/values-pt/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Armazenamento de contatos" - diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml deleted file mode 100644 index 9b296d9..0000000 --- a/res/values-ru/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Хранилище контактов" - diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml deleted file mode 100644 index 7cfd0e0..0000000 --- a/res/values-sv/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Kontakter" - diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml deleted file mode 100644 index f8db006..0000000 --- a/res/values-tr/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "Kişi Deposu" - diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml deleted file mode 100644 index 64b5bc8..0000000 --- a/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "联系人存储" - diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml deleted file mode 100644 index 14da964..0000000 --- a/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - "聯絡人儲存空間" - diff --git a/res/values/strings.xml b/res/values/strings.xml deleted file mode 100644 index 2ef1c9a..0000000 --- a/res/values/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - Contacts Storage - diff --git a/res/xml/syncadapter.xml b/res/xml/syncadapter.xml deleted file mode 100644 index d46be9a..0000000 --- a/res/xml/syncadapter.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - 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 ENTRY_TYPE_TO_PROVIDER_PHONE; - private static final HashMap ENTRY_TYPE_TO_PROVIDER_EMAIL; - private static final HashMap ENTRY_TYPE_TO_PROVIDER_IM; - private static final HashMap ENTRY_TYPE_TO_PROVIDER_POSTAL; - private static final HashMap ENTRY_TYPE_TO_PROVIDER_ORGANIZATION; - private static final HashMap PROVIDER_TYPE_TO_ENTRY_PHONE; - private static final HashMap PROVIDER_TYPE_TO_ENTRY_EMAIL; - private static final HashMap PROVIDER_TYPE_TO_ENTRY_IM; - private static final HashMap PROVIDER_TYPE_TO_ENTRY_POSTAL; - private static final HashMap PROVIDER_TYPE_TO_ENTRY_ORGANIZATION; - - private static final HashMap ENTRY_IM_PROTOCOL_TO_PROVIDER_PROTOCOL; - private static final HashMap 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 map; - - map = new HashMap(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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 HashMap swapMap(HashMap originalMap) { - HashMap newMap = new HashMap(); - for (Map.Entry 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 feedsToSync = new ArrayList(); - - 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 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 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 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 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); - } - } -} diff --git a/src/com/android/providers/contacts/ContactsSyncAdapterService.java b/src/com/android/providers/contacts/ContactsSyncAdapterService.java deleted file mode 100644 index ee95153..0000000 --- a/src/com/android/providers/contacts/ContactsSyncAdapterService.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.android.providers.contacts; - -import android.app.Service; -import android.os.IBinder; -import android.content.Intent; -import android.content.ContentProviderClient; -import android.content.ContentProvider; -import android.content.SyncableContentProvider; -import android.provider.Contacts; - -public class ContactsSyncAdapterService extends Service { - private ContentProviderClient mContentProviderClient = null; - - public void onCreate() { - mContentProviderClient = - getContentResolver().acquireContentProviderClient(Contacts.CONTENT_URI); - } - - public void onDestroy() { - mContentProviderClient.release(); - } - - public IBinder onBind(Intent intent) { - ContentProvider contentProvider = mContentProviderClient.getLocalContentProvider(); - if (contentProvider == null) throw new IllegalStateException(); - SyncableContentProvider syncableContentProvider = (SyncableContentProvider)contentProvider; - return syncableContentProvider.getTempProviderSyncAdapter().getISyncAdapter().asBinder(); - } -} diff --git a/src/com/android/providers/contacts/GoogleContactsProvider.java b/src/com/android/providers/contacts/GoogleContactsProvider.java deleted file mode 100644 index 77bce2c..0000000 --- a/src/com/android/providers/contacts/GoogleContactsProvider.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2008 Google Inc. - * - * 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, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.providers.contacts; - -import com.google.android.collect.Sets; -import com.google.android.providers.AbstractGDataSyncAdapter; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SyncContext; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.SystemClock; -import android.provider.Contacts; -import android.text.TextUtils; -import android.accounts.Account; - -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -/** - * A subclass of the platform contacts provider that adds the Google contacts - * sync adapter. - */ -public class GoogleContactsProvider extends ContactsProvider { - private static final int PURGE_CONTACTS_DELAY_IN_MS = 30000; - private static final String ACTION_PURGE_CONTACTS = - "com.android.providers.contacts.PURGE_CONTACTS"; - - /** - * SQL query that deletes all contacts for a given account that are not a member of - * at least one group that has the "should_sync" column set to a non-zero value. - */ - private static final String PURGE_UNSYNCED_CONTACTS_SQL = "" - + "DELETE FROM people " - + "WHERE (_id IN (" - + " SELECT person " - + " FROM (" - + " SELECT MAX(should_sync) AS max_should_sync, person " - + " FROM (" - + " SELECT should_sync, person " - + " FROM groupmembership AS gm " - + " OUTER JOIN groups AS g " - + " ON (gm.group_id=g._id " - + " OR (gm.group_sync_id=g._sync_id " - + " AND gm.group_sync_account=g._sync_account " - + " AND gm.group_sync_account_type=g._sync_account_type))) " - + " GROUP BY person) " - + " WHERE max_should_sync=0)" - + " OR _id NOT IN (SELECT person FROM groupmembership))" - + " AND _sync_dirty=0 " - + " AND _sync_account=? " - + " AND _sync_account_type=?"; - - private AlarmManager mAlarmService = null; - - @Override - public boolean onCreate() { - setTempProviderSyncAdapter(new ContactsSyncAdapter(getContext(), this)); - BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_PURGE_CONTACTS.equals(intent.getAction())) { - purgeContacts((Account) intent.getParcelableExtra("account")); - } - } - }; - getContext().registerReceiver(receiver, new IntentFilter(ACTION_PURGE_CONTACTS)); - return super.onCreate(); - } - - @Override - protected void onLocalChangesForAccount(final ContentResolver resolver, Account account, - boolean groupsModified) { - ContactsSyncAdapter.updateSubscribedFeeds(resolver, account); - if (groupsModified) { - schedulePurge(account); - } - } - - /** - * Delete any non-sync_dirty contacts associated with the given account - * that are not in any of the synced groups. - */ - private void schedulePurge(Account account) { - if (isTemporary()) { - throw new IllegalStateException("this must not be called on temp providers"); - } - ensureAlarmService(); - final Intent intent = new Intent(ACTION_PURGE_CONTACTS); - intent.putExtra("account", account); - final PendingIntent pendingIntent = PendingIntent.getBroadcast( - getContext(), 0 /* ignored */, intent, 0); - mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + PURGE_CONTACTS_DELAY_IN_MS, - pendingIntent); - } - - private void ensureAlarmService() { - if (mAlarmService == null) { - mAlarmService = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); - } - } - - @Override - public void onSyncStop(SyncContext context, boolean success) { - super.onSyncStop(context, success); - purgeContacts(getSyncingAccount()); - } - - private void purgeContacts(Account account) { - if (isTemporary()) { - throw new IllegalStateException("this must not be called on temp providers"); - } - SQLiteDatabase db = getDatabase(); - db.beginTransaction(); - try { - // TODO(fredq) should be using account instead of null - final String value = Contacts.Settings.getSetting(getContext().getContentResolver(), - null, Contacts.Settings.SYNC_EVERYTHING); - final boolean shouldSyncEverything = !TextUtils.isEmpty(value) && !"0".equals(value); - if (!shouldSyncEverything) { - db.execSQL(PURGE_UNSYNCED_CONTACTS_SQL, new String[]{account.name, account.type}); - } - - // remove any feeds in the SyncData that aren't in the current sync set. - Set feedsToSync = Sets.newHashSet(); - feedsToSync.add(ContactsSyncAdapter.getGroupsFeedForAccount(account)); - ContactsSyncAdapter.addContactsFeedsToSync(getContext().getContentResolver(), account, - feedsToSync); - AbstractGDataSyncAdapter.GDataSyncData syncData = readSyncData(account); - if (syncData != null) { - Iterator> iter = - syncData.feedData.entrySet().iterator(); - boolean updatedSyncData = false; - while (iter.hasNext()) { - Map.Entry entry = - iter.next(); - if (!feedsToSync.contains(entry.getKey())) { - iter.remove(); - updatedSyncData = true; - } - } - if (updatedSyncData) { - writeSyncData(account, syncData); - } - } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - private AbstractGDataSyncAdapter.GDataSyncData readSyncData(Account account) { - if (!getDatabase().inTransaction()) { - throw new IllegalStateException("you can only call this from within a transaction"); - } - Cursor c = getDatabase().query("_sync_state", new String[]{"data"}, - "_sync_account=? AND _sync_account_type=?", - new String[]{account.name, account.type}, null, null, null); - try { - byte[] data = null; - if (c.moveToNext()) data = c.getBlob(0); - return ContactsSyncAdapter.newGDataSyncDataFromBytes(data); - } finally { - c.close(); - } - } - - private void writeSyncData(Account account, AbstractGDataSyncAdapter.GDataSyncData syncData) { - final SQLiteDatabase db = getDatabase(); - if (!db.inTransaction()) { - throw new IllegalStateException("you can only call this from within a transaction"); - } - db.delete("_sync_state", "_sync_account=? AND _sync_account_type=?", - new String[]{account.name, account.type}); - ContentValues values = new ContentValues(); - values.put("data", ContactsSyncAdapter.newBytesFromGDataSyncData(syncData)); - values.put("_sync_account", account.name); - values.put("_sync_account_type", account.type); - db.insert("_sync_state", "_sync_account", values); - } -} - diff --git a/tests/Android.mk b/tests/Android.mk deleted file mode 100644 index 7a93ffd..0000000 --- a/tests/Android.mk +++ /dev/null @@ -1,15 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_JAVA_LIBRARIES := android.test.runner - -LOCAL_PACKAGE_NAME := GoogleContactsProviderTests -LOCAL_CERTIFICATE := shared - -LOCAL_INSTRUMENTATION_FOR := GoogleContactsProvider - -include $(BUILD_PACKAGE) diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml deleted file mode 100644 index c06a5e6..0000000 --- a/tests/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/src/com/android/providers/contacts/ContactsSyncAdapterTest.java b/tests/src/com/android/providers/contacts/ContactsSyncAdapterTest.java deleted file mode 100644 index 018907a..0000000 --- a/tests/src/com/android/providers/contacts/ContactsSyncAdapterTest.java +++ /dev/null @@ -1,870 +0,0 @@ -package com.android.providers.contacts; - -import com.google.wireless.gdata.contacts.data.ContactEntry; -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.data.Entry; -import com.google.wireless.gdata.data.ExtendedProperty; -import com.google.wireless.gdata.parser.ParseException; - -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.SyncContext; -import android.content.SyncResult; -import android.content.TempProviderSyncResult; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.provider.Contacts; -import android.provider.Contacts.ContactMethods; -import android.provider.Contacts.GroupMembership; -import android.provider.Contacts.Groups; -import android.provider.Contacts.People; -import android.provider.Contacts.Photos; -import android.provider.SyncConstValue; -import android.test.ProviderTestCase; -import android.util.Log; -import android.accounts.Account; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public class ContactsSyncAdapterTest extends ProviderTestCase { - private static final Account ACCOUNT = new Account("testaccount@example.com", "example.type"); - - private MockSyncContext mMockSyncContext = new MockSyncContext(); - private ContactsSyncAdapter mSyncAdapter = null; - - public ContactsSyncAdapterTest() { - super(ContactsProvider.class, Contacts.AUTHORITY); - } - - public void testUpdateProviderPeople() throws ParseException { - final ContactsProvider serverDiffs = newTemporaryProvider(); - - ContactEntry entry1 = newPerson("title1", "note1", "1", "a"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 11L, entry1, serverDiffs); - - ContactEntry entry2 = newPerson("title2", "note2", "2", "b"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, null, entry2, serverDiffs); - - ContactEntry entry3 = newPerson("title3", "note3", "3", "c"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 13L, entry3, serverDiffs); - - ContactEntry entry4 = newPerson("title4", "note4", "4", "d"); - entry4.setDeleted(true); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 14L, entry4, serverDiffs); - - ContactEntry entry5 = newDeletedPerson("5", "e"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 15L, entry5, serverDiffs); - - checkTableIsEmpty(getProvider(), Contacts.ContactMethods.CONTENT_URI); - checkTableIsEmpty(getProvider(), Contacts.Organizations.CONTENT_URI); - checkTableIsEmpty(getProvider(), Contacts.Phones.CONTENT_URI); - - checkEntries(serverDiffs, false, entry1, 11L, entry2, null, entry3, 13L); - checkEntries(serverDiffs, true, entry4, 14L, entry5, 15L); - - // Convert the provider back to an entry and check that they match - Cursor cursor; - cursor = ContactsSyncAdapter.getCursorForTableImpl(serverDiffs, ContactEntry.class); - try { - checkNextCursorToEntry(cursor, entry1); - checkNextCursorToEntry(cursor, entry2); - checkNextCursorToEntry(cursor, entry3); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - - cursor = ContactsSyncAdapter.getCursorForDeletedTableImpl(serverDiffs, ContactEntry.class); - try { - checkNextDeletedCursorToEntry(cursor, entry4); - checkNextDeletedCursorToEntry(cursor, entry5); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - } - - public void testUpdateProviderGroups() throws ParseException { - final ContactsProvider serverDiffs = newTemporaryProvider(); - - GroupEntry g1 = newGroup("g1", "n1", "i1", "v1"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 21L, g1, serverDiffs); - - GroupEntry g2 = newGroup("g2", "n2", "i2", "v2"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, null, g2, serverDiffs); - - GroupEntry g3 = newGroup("g3", "n3", "i3", "v3"); - g3.setDeleted(true); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 23L, g3, serverDiffs); - - GroupEntry g4 = newDeletedGroup("i4", "v4"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 24L, g4, serverDiffs); - - // confirm that the entries we expect are in the Groups and DeletedGroups - // tables - checkEntries(serverDiffs, false, g1, 21L, g2, null); - checkEntries(serverDiffs, true, g3, 23L, g4, 24L); - - // Convert the provider back to an entry and check that they match - Cursor cursor; - cursor = ContactsSyncAdapter.getCursorForTableImpl(serverDiffs, GroupEntry.class); - try { - checkNextCursorToEntry(cursor, g1); - checkNextCursorToEntry(cursor, g2); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - - cursor = ContactsSyncAdapter.getCursorForDeletedTableImpl(serverDiffs, GroupEntry.class); - try { - checkNextDeletedCursorToEntry(cursor, g3); - checkNextDeletedCursorToEntry(cursor, g4); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - } - - // The temp provider contains groups that mirror the GroupEntries - // The temp provider contains people rows that mirrow the ContactEntry people portion - // The temp provider contains groupmembership rows that mirror the group membership info - // These groupmembership rows have people._id as a foreign key - // and a group _sync_id, but not as a foreign key - - public void testUpdateProviderGroupMembership() throws ParseException { - final ContactsProvider serverDiffs = newTemporaryProvider(); - - ContactEntry p = newPerson("p1", "pn1", "pi1", "pv1"); - - addGroupMembership(p, ACCOUNT, "gsi1"); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, 31L, p, serverDiffs); - - // The provider should now have: - // - a row in the people table - // - a row in the groupmembership table - - checkEntries(serverDiffs, false, p, 31L); - - checkTableIsEmpty(serverDiffs, ContactsProvider.sPeopleTable); - checkTableIsEmpty(serverDiffs, ContactsProvider.sGroupmembershipTable); - - // copy the server diffs into the provider - getProvider().merge(mMockSyncContext, serverDiffs, null, new SyncResult()); - getProvider().getDatabase().execSQL("UPDATE people SET _sync_dirty=1"); - ContentProvider clientDiffs = getClientDiffs(); - - // Convert the provider back to an entry and check that they match - Cursor cursor = ContactsSyncAdapter.getCursorForTableImpl(clientDiffs, ContactEntry.class); - try { - checkNextCursorToEntry(cursor, p); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - } - - private static void addGroupMembership(ContactEntry p, Account account, String groupId) { - GroupMembershipInfo groupInfo = new GroupMembershipInfo(); - final String serverId = - ContactsSyncAdapter.getCanonicalGroupsFeedForAccount(account) + "/" + groupId; - groupInfo.setGroup(serverId); - p.addGroup(groupInfo); - } - - public void testUpdateProviderContactMethods() throws ParseException { - final String entryTitle = "title1"; - ContactEntry entry = newPerson(entryTitle, "note1", "2", "3"); - - addEmail(entry, "a11111", false, EmailAddress.TYPE_HOME, null); - addEmail(entry, "a22222", true, EmailAddress.TYPE_WORK, null); - addEmail(entry, "a33333", false, EmailAddress.TYPE_OTHER, null); - addEmail(entry, "a44444", false, EmailAddress.TYPE_NONE, "lucky"); - - addPostal(entry, "b11111", false, PostalAddress.TYPE_HOME, null); - addPostal(entry, "b22222", false, PostalAddress.TYPE_WORK, null); - addPostal(entry, "b33333", true, PostalAddress.TYPE_OTHER, null); - addPostal(entry, "b44444", false, PostalAddress.TYPE_NONE, "lucky"); - - addIm(entry, "c11111", ImAddress.PROTOCOL_CUSTOM, "p1", false, ImAddress.TYPE_HOME, null); - addIm(entry, "c22222", ImAddress.PROTOCOL_NONE, null, true, ImAddress.TYPE_WORK, null); - addIm(entry, "c33333", ImAddress.PROTOCOL_SKYPE, null, false, ImAddress.TYPE_OTHER, null); - addIm(entry, "c44444", ImAddress.PROTOCOL_ICQ, null, false, ImAddress.TYPE_NONE, "l2"); - - final ContactsProvider provider = newTemporaryProvider(); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, null, entry, provider); - - checkTableIsEmpty(getProvider(), Contacts.Phones.CONTENT_URI); - checkTableIsEmpty(getProvider(), Contacts.Organizations.CONTENT_URI); - - long personId = getSinglePersonId(provider, entryTitle); - - Cursor cursor; - cursor = provider.query(Contacts.ContactMethods.CONTENT_URI, null, null, null, - Contacts.ContactMethods.DATA); - try { - checkNextEmail(cursor, personId, "a11111", - false, Contacts.ContactMethods.TYPE_HOME, null); - checkNextEmail(cursor, personId, "a22222", - true, Contacts.ContactMethods.TYPE_WORK, null); - checkNextEmail(cursor, personId, "a33333", - false, Contacts.ContactMethods.TYPE_OTHER, null); - checkNextEmail(cursor, personId, "a44444", - false, Contacts.ContactMethods.TYPE_CUSTOM, "lucky"); - - checkNextPostal(cursor, personId, "b11111", - false, Contacts.ContactMethods.TYPE_HOME, null); - checkNextPostal(cursor, personId, "b22222", - false, Contacts.ContactMethods.TYPE_WORK, null); - checkNextPostal(cursor, personId, "b33333", - true, Contacts.ContactMethods.TYPE_OTHER, null); - checkNextPostal(cursor, personId, "b44444", - false, Contacts.ContactMethods.TYPE_CUSTOM, "lucky"); - - checkNextIm(cursor, personId, "c11111", ContactMethods.encodeCustomImProtocol("p1"), - false, Contacts.ContactMethods.TYPE_HOME, null); - checkNextIm(cursor, personId, "c22222", null, - true, Contacts.ContactMethods.TYPE_WORK, null); - checkNextIm(cursor, personId, "c33333", - ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_SKYPE), - false, Contacts.ContactMethods.TYPE_OTHER, null); - checkNextIm(cursor, personId, "c44444", - ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_ICQ), - false, Contacts.ContactMethods.TYPE_CUSTOM, "l2"); - - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - - checkCursorToEntry(provider, entry); - } - - public void testUpdateProviderPhones() throws ParseException { - final String entryTitle = "title1"; - ContactEntry entry = newPerson(entryTitle, "note1", "2", "3"); - addPhoneNumber(entry, "11111", false, PhoneNumber.TYPE_HOME, null); - addPhoneNumber(entry, "22222", false, PhoneNumber.TYPE_HOME_FAX, null); - addPhoneNumber(entry, "33333", false, PhoneNumber.TYPE_MOBILE, null); - addPhoneNumber(entry, "44444", true, PhoneNumber.TYPE_PAGER, null); - addPhoneNumber(entry, "55555", false, PhoneNumber.TYPE_WORK, null); - addPhoneNumber(entry, "66666", false, PhoneNumber.TYPE_WORK_FAX, null); - addPhoneNumber(entry, "77777", false, PhoneNumber.TYPE_OTHER, null); - addPhoneNumber(entry, "88888", false, PhoneNumber.TYPE_NONE, "lucky"); - final ContactsProvider provider = newTemporaryProvider(); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, null, entry, provider); - - checkTableIsEmpty(getProvider(), Contacts.ContactMethods.CONTENT_URI); - checkTableIsEmpty(getProvider(), Contacts.Organizations.CONTENT_URI); - - long personId = getSinglePersonId(provider, entryTitle); - - Cursor cursor; - cursor = provider.query(Contacts.Phones.CONTENT_URI, null, null, null, - Contacts.Phones.NUMBER); - try { - checkNextNumber(cursor, personId, "11111", false, Contacts.Phones.TYPE_HOME, null); - checkNextNumber(cursor, personId, "22222", false, Contacts.Phones.TYPE_FAX_HOME, null); - checkNextNumber(cursor, personId, "33333", false, Contacts.Phones.TYPE_MOBILE, null); - checkNextNumber(cursor, personId, "44444", true, Contacts.Phones.TYPE_PAGER, null); - checkNextNumber(cursor, personId, "55555", false, Contacts.Phones.TYPE_WORK, null); - checkNextNumber(cursor, personId, "66666", false, Contacts.Phones.TYPE_FAX_WORK, null); - checkNextNumber(cursor, personId, "77777", false, Contacts.Phones.TYPE_OTHER, null); - checkNextNumber(cursor, personId, "88888", false, Contacts.Phones.TYPE_CUSTOM, "lucky"); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - - checkCursorToEntry(provider, entry); - } - - public void testUpdateProviderOrganization() throws ParseException { - final String entryTitle = "title1"; - ContactEntry entry = newPerson(entryTitle, "note1", "2", "3"); - addOrganization(entry, "11111", "title1", true, Organization.TYPE_WORK, null); - addOrganization(entry, "22222", "title2", false, Organization.TYPE_OTHER, null); - addOrganization(entry, "33333", "title3", false, Organization.TYPE_NONE, "label1"); - final ContactsProvider provider = newTemporaryProvider(); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, null, entry, provider); - - checkTableIsEmpty(getProvider(), Contacts.ContactMethods.CONTENT_URI); - checkTableIsEmpty(getProvider(), Contacts.Phones.CONTENT_URI); - - long personId = getSinglePersonId(provider, entryTitle); - - Cursor cursor; - cursor = provider.query(Contacts.Organizations.CONTENT_URI, null, null, null, - Contacts.Organizations.COMPANY); - try { - checkNextOrganization(cursor, personId, "11111", "title1", true, - Contacts.Organizations.TYPE_WORK, null); - checkNextOrganization(cursor, personId, "22222", "title2", false, - Contacts.Organizations.TYPE_OTHER, null); - checkNextOrganization(cursor, personId, "33333", "title3", false, - Contacts.Organizations.TYPE_CUSTOM, "label1"); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - - checkCursorToEntry(provider, entry); - } - - public void testUpdateProviderExtensions() throws ParseException { - final String entryTitle = "title1"; - ContactEntry entry = newPerson(entryTitle, "note1", "2", "3"); - addExtendedProperty(entry, "android", null, "{\"other\":\"that\",\"more\":\"hello.mp3\"}"); - final ContactsProvider provider = newTemporaryProvider(); - ContactsSyncAdapter.updateProviderImpl(ACCOUNT, null, entry, provider); - - long personId = getSinglePersonId(provider, entryTitle); - - Cursor cursor; - cursor = provider.query(Contacts.Extensions.CONTENT_URI, null, null, null, - Contacts.Extensions.NAME); - try { - checkNextExtension(cursor, personId, "more", "hello.mp3"); - checkNextExtension(cursor, personId, "other", "that"); - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - - checkCursorToEntry(provider, entry); - } - - // test writing a photo via the content resolver - public void testPhotoAccess() throws IOException, InterruptedException { - // add a person to the real provider - Uri p = addPerson(getProvider(), ACCOUNT, "si1", "p1"); - Uri ph = Uri.withAppendedPath(p, Contacts.Photos.CONTENT_DIRECTORY); - - ContentValues values = new ContentValues(); - values.put(Photos._SYNC_DIRTY, 1); - values.put(Photos._SYNC_VERSION, "pv1"); - values.put(Photos.LOCAL_VERSION, "pv0"); - getMockContentResolver().update(ph, values, null, null); - // check that the photos rows look correct - Cursor cursor = getProvider().getDatabase().query(ContactsProvider.sPhotosTable, - null, null, null, null, null, null); - try { - checkNextPhoto(cursor, true, "pv0", "pv1"); - assertFalse(cursor.moveToNext()); - } finally { - cursor.close(); - } - - values.clear(); - values.put(Photos._SYNC_DIRTY, 0); - getMockContentResolver().update(ph, values, null, null); - // check that the photos rows look correct - cursor = getProvider().getDatabase().query(ContactsProvider.sPhotosTable, - null, null, null, null, null, null); - try { - checkNextPhoto(cursor, false, "pv0", "pv1"); - assertFalse(cursor.moveToNext()); - } finally { - cursor.close(); - } - - // save a downloaded photo for that person using the ContactsSyncAdapter - byte[] remotePhotoData = "remote photo data".getBytes(); - InputStream photoStream = new ByteArrayInputStream(remotePhotoData); - mSyncAdapter.savePhoto(ContentUris.parseId(p), photoStream, "pv1"); - - // check that the photos rows look correct - cursor = getProvider().getDatabase().query(ContactsProvider.sPhotosTable, - null, null, null, null, null, null); - try { - checkNextPhoto(cursor, false, "pv1", "pv1"); - assertFalse(cursor.moveToNext()); - } finally { - cursor.close(); - } - - InputStream inputStream = - Contacts.People.openContactPhotoInputStream(getMockContentResolver(), p); - byte[] inputBytes = new byte[100]; - int totalBytesRead = 0; - while (true) { - int numBytesRead = inputStream.read(inputBytes, totalBytesRead, - inputBytes.length - totalBytesRead); - if (numBytesRead < 0) break; - totalBytesRead += numBytesRead; - } - inputStream.close(); - - assertByteArrayEquals(remotePhotoData, inputBytes, totalBytesRead); - } - - private void assertByteArrayEquals(byte[] expected, byte[] actual, int lengthOfActual) { - assertEquals(expected.length, lengthOfActual); - for (int i = 0; i < expected.length; i++) assertEquals(expected[i], actual[i]); - } - - private void checkNextPhoto(Cursor cursor, boolean expectedDirty, String expectedLocalVersion, - String expectedServerVersion) { - assertTrue(cursor.moveToNext()); - assertEquals(expectedDirty, getLong(cursor, Photos._SYNC_DIRTY) != 0); - assertEquals(expectedLocalVersion, getString(cursor, Photos.LOCAL_VERSION)); - assertEquals(expectedServerVersion, getString(cursor, Photos._SYNC_VERSION)); - } - - private Uri addPerson(ContactsProvider provider, Account account, - String name, String syncId) { - ContentValues values = new ContentValues(); - values.put(People.NAME, name); - values.put(People._SYNC_ACCOUNT, account.name); - values.put(People._SYNC_ACCOUNT_TYPE, account.type); - values.put(People._SYNC_ID, syncId); - return provider.insert(People.CONTENT_URI, values); - } - - private static GroupEntry newGroup(String title, String notes, String id, String version) { - GroupEntry entry = new GroupEntry(); - entry.setTitle(title); - entry.setContent(notes); - entry.setId(ContactsSyncAdapter.getGroupsFeedForAccount(ACCOUNT) + "/" + id); - entry.setEditUri(entry.getId() + "/" + version); - return entry; - } - - private static GroupEntry newDeletedGroup(String id, String version) { - GroupEntry entry = new GroupEntry(); - entry.setDeleted(true); - entry.setId(ContactsSyncAdapter.getGroupsFeedForAccount(ACCOUNT) + "/" + id); - entry.setEditUri(entry.getId() + "/" + version); - return entry; - } - - private static void checkNextGroup(Cursor cursor, GroupEntry entry, Long syncLocalId) { - assertTrue(cursor.moveToNext()); - assertEquals(dumpRow(cursor), entry.getId(), - feedFromEntry(entry) + "/" + getString(cursor, SyncConstValue._SYNC_ID)); - assertEquals(dumpRow(cursor), entry.getEditUri(), - entry.getId() + "/" + getString(cursor, SyncConstValue._SYNC_VERSION)); - assertEquals(dumpRow(cursor), ACCOUNT.name, - getString(cursor, SyncConstValue._SYNC_ACCOUNT)); - assertEquals(dumpRow(cursor), ACCOUNT.type, - getString(cursor, SyncConstValue._SYNC_ACCOUNT_TYPE)); - assertEquals(dumpRow(cursor), entry.getTitle(), getString(cursor, Groups.NAME)); - assertEquals(dumpRow(cursor), entry.getSystemGroup(), getString(cursor, Groups.SYSTEM_ID)); - assertEquals(dumpRow(cursor), entry.getContent(), getString(cursor, Groups.NOTES)); - if (syncLocalId != null) { - assertEquals(dumpRow(cursor), - syncLocalId, getString(cursor, SyncConstValue._SYNC_LOCAL_ID)); - } - } - - private static ContactEntry newPerson(String title, String notes, String id, String version) { - ContactEntry entry = new ContactEntry(); - entry.setTitle(title); - entry.setContent(notes); - entry.setId(ContactsSyncAdapter.getContactsFeedForAccount(ACCOUNT) + "/" + id); - entry.setEditUri(entry.getId() + "/" + version); - entry.setLinkEditPhoto(ContactsSyncAdapter.getContactsFeedForAccount(ACCOUNT) - + "/" + id + "/v1", "image/jpg"); - return entry; - } - - private static ContactEntry newDeletedPerson(String id, String version) { - ContactEntry entry = new ContactEntry(); - entry.setDeleted(true); - entry.setId(ContactsSyncAdapter.getContactsFeedForAccount(ACCOUNT) + "/" + id); - entry.setEditUri(entry.getId() + "/" + version); - return entry; - } - - private void checkNextCursorToEntry(Cursor cursor, Entry expectedEntry) - throws ParseException { - Entry newEntry = newEmptyEntry(expectedEntry); - assertTrue(cursor.moveToNext()); - String createUri = ContactsSyncAdapter.cursorToEntryImpl(getMockContentResolver(), - cursor, newEntry, ACCOUNT); - // since this is an update the createUri should be null - assertNull(createUri); - assertEquals(expectedEntry.getEditUri(), newEntry.getEditUri()); - if (expectedEntry instanceof ContactEntry) { - ((ContactEntry)newEntry).setLinkEditPhoto( - ((ContactEntry)expectedEntry).getLinkEditPhotoHref(), - ((ContactEntry)expectedEntry).getLinkEditPhotoType()); - } - final String expected = expectedEntry.toString(); - String actual = newEntry.toString(); - assertEquals("\nexpected:\n" + expected + "\nactual:\n" + actual, expected, actual); - } - - private static void checkNextDeletedCursorToEntry(Cursor cursor, Entry expectedEntry) - throws ParseException { - Entry newEntry = newEmptyEntry(expectedEntry); - assertTrue(cursor.moveToNext()); - ContactsSyncAdapter.deletedCursorToEntryImpl(cursor, newEntry, ACCOUNT); - assertEquals(expectedEntry.getEditUri(), newEntry.getEditUri()); - } - - private static Entry newEmptyEntry(Entry expectedEntry) { - if (expectedEntry instanceof ContactEntry) { - return new ContactEntry(); - } else { - return new GroupEntry(); - } - } - - public static String feedFromEntry(Entry expectedEntry) { - if (expectedEntry instanceof ContactEntry) { - return ContactsSyncAdapter.getContactsFeedForAccount(ACCOUNT); - } else { - return ContactsSyncAdapter.getGroupsFeedForAccount(ACCOUNT); - } - } - - private static void checkNextPerson(ContactsProvider provider, Cursor cursor, - ContactEntry entry, Long syncLocalId) { - assertTrue(cursor.moveToNext()); - assertEquals(dumpRow(cursor), entry.getId(), - feedFromEntry(entry) + "/" + getString(cursor, SyncConstValue._SYNC_ID)); - assertEquals(dumpRow(cursor), entry.getEditUri(), - entry.getId() + "/" + getString(cursor, SyncConstValue._SYNC_VERSION)); - assertEquals(dumpRow(cursor), ACCOUNT.name, - getString(cursor, SyncConstValue._SYNC_ACCOUNT)); - assertEquals(dumpRow(cursor), ACCOUNT.type, - getString(cursor, SyncConstValue._SYNC_ACCOUNT_TYPE)); - assertEquals(dumpRow(cursor), entry.getTitle(), getString(cursor, People.NAME)); - assertEquals(dumpRow(cursor), entry.getContent(), getString(cursor, People.NOTES)); - if (syncLocalId != null) { - assertEquals(dumpRow(cursor), - syncLocalId, getString(cursor, SyncConstValue._SYNC_LOCAL_ID)); - } - - Cursor groupCursor = provider.getDatabase().query(ContactsProvider.sGroupmembershipTable, - null, Contacts.GroupMembership.PERSON_ID + "=?", - new String[]{getString(cursor, People._ID)}, null, null, null); - try { - for (Object object : entry.getGroups()) { - GroupMembershipInfo groupMembership = (GroupMembershipInfo) object; - assertTrue(groupCursor.moveToNext()); - assertEquals(groupMembership.getGroup(), - ContactsSyncAdapter.getCanonicalGroupsFeedForAccount(ACCOUNT) + "/" - + getString(groupCursor, GroupMembership.GROUP_SYNC_ID)); - } - assertFalse(groupCursor.moveToNext()); - } finally { - groupCursor.close(); - } - } - - private static void checkNextDeleted(Cursor cursor, Entry entry, Long syncLocalId) { - assertTrue(cursor.moveToNext()); - assertEquals(dumpRow(cursor), entry.getId(), - feedFromEntry(entry) + "/" + getString(cursor, SyncConstValue._SYNC_ID)); - assertEquals(dumpRow(cursor), entry.getEditUri(), - entry.getId() + "/" + getString(cursor, SyncConstValue._SYNC_VERSION)); - assertEquals(dumpRow(cursor), ACCOUNT.name, - getString(cursor, SyncConstValue._SYNC_ACCOUNT)); - assertEquals(dumpRow(cursor), ACCOUNT.type, - getString(cursor, SyncConstValue._SYNC_ACCOUNT_TYPE)); - if (syncLocalId != null) { - assertEquals(dumpRow(cursor), - syncLocalId.toString(), getString(cursor, SyncConstValue._SYNC_LOCAL_ID)); - } - } - - - private ContactsProvider newTemporaryProvider() { - return (ContactsProvider)getProvider().getTemporaryInstance(); - } - - private void copyTempProviderToReal(ContactsProvider provider) { - // copy the server diffs into the provider - getProvider().merge(mMockSyncContext, provider, null, new SyncResult()); - - // clear the _sync_id and _sync_local_id to act as if these are locally inserted - ContentValues values = new ContentValues(); - values.put(SyncConstValue._SYNC_ID, (String)null); - values.put(SyncConstValue._SYNC_LOCAL_ID, (String)null); - values.put(SyncConstValue._SYNC_DIRTY, "1"); - getProvider().getDatabase().update("people", values, null, null); - } - - private void checkCursorToEntry(ContactsProvider provider, ContactEntry entry) - throws ParseException { - copyTempProviderToReal(provider); - final ContentProvider clientDiffsProvider = getClientDiffs(); - - Cursor cursor = ContactsSyncAdapter.getCursorForTableImpl(clientDiffsProvider, - entry.getClass()); - try { - assertTrue(cursor.moveToFirst()); - assertEquals(dumpCursor(cursor), 1, cursor.getCount()); - ContactEntry newEntry = new ContactEntry(); - String editUrl = ContactsSyncAdapter.cursorToEntryImpl(getMockContentResolver(), cursor, - newEntry, ACCOUNT); - entry.setId(null); - entry.setEditUri(null); - entry.setLinkEditPhoto(null, null); - final String expected = entry.toString(); - final String actual = newEntry.toString(); - assertEquals("\nexpected:\n" + expected + "\nactual:\n" + actual, expected, actual); - } finally { - cursor.close(); - } - } - - private ContentProvider getClientDiffs() { - // query the provider for the client diffs - TempProviderSyncResult result = new TempProviderSyncResult(); - getProvider().merge(mMockSyncContext, null, result, new SyncResult()); - return result.tempContentProvider; - } - - private static long getSinglePersonId(ContactsProvider provider, String entryTitle) { - long personId; - Cursor cursor; - cursor = provider.query(People.CONTENT_URI, null, null, null, null); - try { - assertTrue(cursor.moveToFirst()); - assertEquals(dumpCursor(cursor), 1, cursor.getCount()); - assertEquals(dumpRow(cursor), entryTitle, getString(cursor, People.NAME)); - personId = getLong(cursor, People._ID); - } finally { - cursor.close(); - } - return personId; - } - - private static String dumpRow(Cursor cursor) { - return DatabaseUtils.dumpCurrentRowToString(cursor); - } - - private static String dumpCursor(Cursor cursor) { - return DatabaseUtils.dumpCursorToString(cursor); - } - - private static void checkNextNumber(Cursor cursor, long personId, String number, - boolean isPrimary, int type, String label) { - assertTrue(cursor.moveToNext()); - assertEquals(dumpRow(cursor), personId, getLong(cursor, Contacts.Phones.PERSON_ID)); - assertEquals(dumpRow(cursor), number, getString(cursor, Contacts.Phones.NUMBER)); - assertEquals(dumpRow(cursor), type, getLong(cursor, Contacts.Phones.TYPE)); - assertEquals(dumpCursor(cursor), isPrimary, - getLong(cursor, Contacts.Phones.ISPRIMARY) != 0); - assertEquals(dumpRow(cursor), label, getString(cursor, Contacts.Phones.LABEL)); - } - - private static long getLong(Cursor cursor, String column) { - return cursor.getLong(cursor.getColumnIndexOrThrow(column)); - } - - private static String getString(Cursor cursor, String column) { - return cursor.getString(cursor.getColumnIndexOrThrow(column)); - } - - private static boolean isNull(Cursor cursor, String column) { - return cursor.isNull(cursor.getColumnIndexOrThrow(column)); - } - - private static void checkNextContactMethod(Cursor cursor, long personId, String data, - String auxData, int kind, boolean isPrimary, int type, String label) { - assertTrue(cursor.moveToNext()); - assertEquals(dumpRow(cursor), personId, getLong(cursor, Contacts.ContactMethods.PERSON_ID)); - assertEquals(dumpRow(cursor), kind, getLong(cursor, Contacts.ContactMethods.KIND)); - assertEquals(dumpRow(cursor), data, getString(cursor, Contacts.ContactMethods.DATA)); - assertEquals(dumpRow(cursor), auxData, getString(cursor, Contacts.ContactMethods.AUX_DATA)); - assertEquals(dumpCursor(cursor), isPrimary, - getLong(cursor, Contacts.ContactMethods.ISPRIMARY) != 0); - assertEquals(dumpRow(cursor), type, getLong(cursor, Contacts.ContactMethods.TYPE)); - assertEquals(dumpRow(cursor), label, getString(cursor, Contacts.ContactMethods.LABEL)); - } - - private static void addPhoneNumber(ContactEntry entry, String number, boolean isPrimary, - byte rel, String label) { - PhoneNumber phoneNumber = new PhoneNumber(); - phoneNumber.setPhoneNumber(number); - phoneNumber.setIsPrimary(isPrimary); - phoneNumber.setType(rel); - phoneNumber.setLabel(label); - entry.addPhoneNumber(phoneNumber); - } - - private static void addPostal(ContactEntry entry, String address, boolean isPrimary, byte rel, - String label) { - PostalAddress item = new PostalAddress(); - item.setValue(address); - item.setType(rel); - item.setLabel(label); - item.setIsPrimary(isPrimary); - entry.addPostalAddress(item); - } - - private static void checkNextPostal(Cursor cursor, long personId, String address, - boolean isPrimary, int type, String label) { - checkNextContactMethod(cursor, personId, address, null, - Contacts.KIND_POSTAL, isPrimary, type, label); - } - - private static void addIm(ContactEntry entry, String address, byte protocol, - String protocolString, boolean isPrimary, byte rel, - String label) { - ImAddress item = new ImAddress(); - item.setAddress(address); - item.setProtocolPredefined(protocol); - item.setProtocolCustom(protocolString); - item.setLabel(label); - item.setType(rel); - item.setIsPrimary(isPrimary); - entry.addImAddress(item); - } - - private static void checkNextIm(Cursor cursor, long personId, String address, - String auxData, boolean isPrimary, int type, String label) { - checkNextContactMethod(cursor, personId, address, auxData, - Contacts.KIND_IM, isPrimary, type, label); - } - - private static void addEmail(ContactEntry entry, String address, boolean isPrimary, byte rel, - String label) { - EmailAddress item = new EmailAddress(); - item.setAddress(address); - item.setIsPrimary(isPrimary); - item.setType(rel); - item.setLabel(label); - entry.addEmailAddress(item); - } - - private static void checkNextEmail(Cursor cursor, long personId, String address, - boolean isPrimary, int type, String label) { - checkNextContactMethod(cursor, personId, address, null, - Contacts.KIND_EMAIL, isPrimary, type, label); - } - - private void checkTableIsEmpty(ContactsProvider provider, Uri uri) { - Cursor cursor; - cursor = getProvider().query(uri, null, null, null, null); - try { - assertEquals(0, cursor.getCount()); - } finally { - cursor.close(); - } - } - - private void checkTableIsEmpty(ContactsProvider provider, String table) { - Cursor cursor; - cursor = getProvider().getDatabase().query(table, null, null, null, null, null, null); - try { - assertEquals(DatabaseUtils.dumpCursorToString(cursor), 0, cursor.getCount()); - } finally { - cursor.close(); - } - } - - private static void addOrganization(ContactEntry entry, String name, String title, - boolean isPrimary, byte type, String label) { - Organization organization = new Organization(); - organization.setName(name); - organization.setTitle(title); - organization.setIsPrimary(isPrimary); - organization.setType(type); - organization.setLabel(label); - entry.addOrganization(organization); - } - - private static void addExtendedProperty(ContactEntry entry, String name, String value, - String blob) { - ExtendedProperty extendedProperty = new ExtendedProperty(); - extendedProperty.setName(name); - extendedProperty.setValue(value); - extendedProperty.setXmlBlob(blob); - entry.addExtendedProperty(extendedProperty); - } - - private static void checkNextOrganization(Cursor cursor, long personId, String company, - String title, boolean isPrimary, int type, String label) { - assertTrue(cursor.moveToNext()); - assertEquals(dumpRow(cursor), personId, getLong(cursor, Contacts.Organizations.PERSON_ID)); - assertEquals(dumpRow(cursor), company, getString(cursor, Contacts.Organizations.COMPANY)); - assertEquals(dumpRow(cursor), title, getString(cursor, Contacts.Organizations.TITLE)); - assertEquals(dumpRow(cursor), isPrimary, - getLong(cursor, Contacts.Organizations.ISPRIMARY) != 0); - assertEquals(dumpRow(cursor), type, getLong(cursor, Contacts.Phones.TYPE)); - assertEquals(dumpRow(cursor), label, getString(cursor, Contacts.Phones.LABEL)); - } - - private static void checkNextExtension(Cursor cursor, long personId, - String name, String value) { - assertTrue(cursor.moveToNext()); - assertEquals(dumpRow(cursor), personId, getLong(cursor, Contacts.Extensions.PERSON_ID)); - assertEquals(dumpRow(cursor), name, getString(cursor, Contacts.Extensions.NAME)); - assertEquals(dumpRow(cursor), value, getString(cursor, Contacts.Extensions.VALUE)); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - - mSyncAdapter = (ContactsSyncAdapter)getProvider().getTempProviderSyncAdapter(); - getProvider().onSyncStart(mMockSyncContext, ACCOUNT); - } - - void checkEntries(ContactsProvider provider, boolean deleted, - Object ... entriesAndLocalSyncIds) { - String table; - String sortOrder = deleted ? "_sync_id" : "name"; - final Entry firstEntry = (Entry)entriesAndLocalSyncIds[0]; - if (firstEntry instanceof GroupEntry) { - if (deleted) { - table = ContactsProvider.sDeletedGroupsTable; - } else { - table = ContactsProvider.sGroupsTable; - } - } else { - if (deleted) { - table = ContactsProvider.sDeletedPeopleTable; - } else { - table = ContactsProvider.sPeopleTable; - } - } - Cursor cursor; - cursor = provider.getDatabase().query(table, null, null, null, null, null, sortOrder); - try { - for (int i = 0; i < entriesAndLocalSyncIds.length; i += 2) { - Entry entry = (Entry)entriesAndLocalSyncIds[i]; - Long syncLocalId = (Long)entriesAndLocalSyncIds[i+1]; - if (deleted) { - checkNextDeleted(cursor, entry, syncLocalId); - } else { - if (firstEntry instanceof GroupEntry) { - checkNextGroup(cursor, (GroupEntry)entry, null); - } else { - checkNextPerson(provider, cursor, (ContactEntry)entry, null); - } - } - } - assertTrue(dumpCursor(cursor), cursor.isLast()); - } finally { - cursor.close(); - } - } -} - -class MockSyncContext extends SyncContext { - - MockSyncContext() { - super(null); - } - - @Override - public void setStatusText(String text) { - Log.i("SyncTest", text); - } -} \ No newline at end of file diff --git a/tests/src/com/android/providers/contacts/SyncContactsTest.java b/tests/src/com/android/providers/contacts/SyncContactsTest.java deleted file mode 100644 index 7456bfa..0000000 --- a/tests/src/com/android/providers/contacts/SyncContactsTest.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.android.providers.contacts; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.provider.Contacts; -import android.test.RenamingDelegatingContext; -import android.test.SyncBaseInstrumentation; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.OperationCanceledException; -import android.accounts.AuthenticatorException; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.io.IOException; - -import com.google.android.collect.Maps; -import com.google.android.googlelogin.GoogleLoginServiceConstants; - -public class SyncContactsTest extends SyncBaseInstrumentation { - private Context mTargetContext; - private String mAccount; - private static final String PEOPLE_PHONE_JOIN = - "people LEFT OUTER JOIN phones ON people.primary_phone=phones._id " + - " LEFT OUTER JOIN contact_methods ON people.primary_email=contact_methods._id" + - " LEFT OUTER JOIN organizations ON people.primary_organization=organizations._id" + - " ORDER BY people._sync_id;"; - - private static final Set PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP = new HashSet(); - - static { - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People._ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People.PRIMARY_PHONE_ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People._SYNC_MARK); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People._SYNC_TIME); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People._SYNC_LOCAL_ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People._SYNC_VERSION); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People._SYNC_DIRTY); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People.TIMES_CONTACTED); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People.LAST_TIME_CONTACTED); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.Phones._ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.Phones.PERSON_ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People.PRIMARY_EMAIL_ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People.PRIMARY_ORGANIZATION_ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.People.ContactMethods._ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.Organizations.PERSON_ID); - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP.add(Contacts.Organizations._ID); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - mTargetContext = getInstrumentation().getTargetContext(); - mAccount = getAccount(); - } - - /** - * A Simple test that syncs the contacts provider. - * @throws Exception - */ - public void testSync() throws Exception { - cancelSyncsandDisableAutoSync(); - syncProvider(Contacts.CONTENT_URI, mAccount, Contacts.AUTHORITY); - } - - private String getAccount() { - try { - String[] features = new String[]{GoogleLoginServiceConstants.FEATURE_HOSTED_OR_GOOGLE}; - Account[] accounts = AccountManager.get(mTargetContext).getAccountsByTypeAndFeatures( - GoogleLoginServiceConstants.ACCOUNT_TYPE, features, null, null).getResult(); - if (accounts.length > 0) { - return accounts[0].name; - } - } catch (OperationCanceledException e) { - // handle below - } catch (IOException e) { - // handle below - } catch (AuthenticatorException e) { - // handle below - } - return null; - } - - /** - * This test compares the two contacts databases. - * This works well with the puppetmaster automated script. - * @throws Exception - */ - public void testCompareResults() throws Exception { - ContactsProvider incrementalContentProvider = - RenamingDelegatingContext.providerWithRenamedContext(ContactsProvider.class, - mTargetContext, "", true); - - ContactsProvider initialSyncContentProvider = - RenamingDelegatingContext.providerWithRenamedContext(ContactsProvider.class, - mTargetContext, "initialsync.", true); - - SQLiteDatabase incrementalDb = incrementalContentProvider.getDatabase(); - SQLiteDatabase initialSyncDb = initialSyncContentProvider.getDatabase(); - - Cursor incrementalPeopleCursor = incrementalDb.rawQuery("select * from " + - PEOPLE_PHONE_JOIN, null); - Cursor initialPeopleCursor = initialSyncDb.rawQuery("select * from " + - PEOPLE_PHONE_JOIN, null); - - assertNotSame("Incremental db has no values - check test configuration", - incrementalPeopleCursor.getCount(), 0); - try { - compareCursors(incrementalPeopleCursor, initialPeopleCursor, - PEOPLE_PHONES_JOIN_COLUMNS_TO_SKIP, "People"); - } finally { - incrementalPeopleCursor.close(); - initialPeopleCursor.close(); - } - } - - private void compareCursors(Cursor incrementalCursor, - Cursor initialSyncCursor, Set columnsToSkip, - String tableName) { - - assertEquals(tableName + " count failed to match", incrementalCursor.getCount(), - initialSyncCursor.getCount()); - - String[] cols = incrementalCursor.getColumnNames(); - int length = cols.length; - Map row = Maps.newHashMap(); - - while (incrementalCursor.moveToNext() && initialSyncCursor.moveToNext()) { - for (int i = 0; i < length; i++) { - String col = cols[i]; - if (columnsToSkip != null && columnsToSkip.contains(col)) { - continue; - } - row.put(col, incrementalCursor.getString(i)); - assertEquals("Row: " + row + " .Column: " + cols[i] + " failed to match", - incrementalCursor.getString(i), initialSyncCursor.getString(i)); - } - } - } -} diff --git a/tests/src/com/android/providers/contactstests/SyncContactsInstrumentation.java b/tests/src/com/android/providers/contactstests/SyncContactsInstrumentation.java deleted file mode 100644 index 373523d..0000000 --- a/tests/src/com/android/providers/contactstests/SyncContactsInstrumentation.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.android.providers.contactstests; - -import com.android.providers.contacts.SyncContactsTest; - -import junit.framework.TestSuite; - -import android.test.InstrumentationTestRunner; -import android.test.InstrumentationTestSuite; -import android.test.suitebuilder.annotation.Suppress; - -@Suppress -public class SyncContactsInstrumentation extends InstrumentationTestRunner { - public TestSuite getAllTests() { - TestSuite suite = new InstrumentationTestSuite(this); - suite.addTestSuite(SyncContactsTest.class); - return suite; - } - - public ClassLoader getLoader() { - return SyncContactsTest.class.getClassLoader(); - } -}