ContactsSyncAdapterpublic class ContactsSyncAdapter extends com.google.android.providers.AbstractGDataSyncAdapter Implements a SyncAdapter for Contacts |
Fields Summary |
---|
private static final String | USER_AGENT_APP_VERSION | private static final String | CONTACTS_FEED_URL | private static final String | GROUPS_FEED_URL | private static final String | PHOTO_FEED_URL | private final com.google.wireless.gdata.contacts.client.ContactsClient | mContactsClient | private static final String[] | sSubscriptionProjection | 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 | private boolean | mPerformedGetServerDiffs | private boolean | mSyncForced | private int | mPhotoDownloads | private int | mPhotoUploads | private static final String | IMAGE_MIME_TYPE |
Constructors Summary |
---|
protected ContactsSyncAdapter(android.content.Context context, android.content.SyncableContentProvider provider)
super(context, provider);
mContactsClient = new ContactsClient(
new AndroidGDataClient(context, USER_AGENT_APP_VERSION),
new XmlContactsGDataParserFactory(new AndroidXmlParserFactory()));
|
Methods Summary |
---|
private static void | addContactMethodsToContactEntry(android.content.ContentResolver cr, long personId, com.google.wireless.gdata.contacts.data.ContactEntry entry)
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();
}
| public static void | addContactsFeedsToSync(android.content.ContentResolver cr, java.lang.String account, java.util.Collection feedsToSync)Look at the groups sync settings and the overall sync preference to determine which
feeds to sync and add them to the feedsToSync list.
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 should_sync>0", new String[]{account}, null);
try {
while (cursor.moveToNext()) {
feedsToSync.add(getContactsFeedForGroup(account, cursor.getString(0)));
}
} finally {
cursor.close();
}
| private static void | addExtensionsToContactEntry(android.content.ContentResolver cr, long personId, com.google.wireless.gdata.contacts.data.ContactEntry entry)
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 | addGroupMembershipToContactEntry(java.lang.String account, android.content.ContentResolver cr, long personId, com.google.wireless.gdata.contacts.data.ContactEntry entry)
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 | addOrganizationsToContactEntry(android.content.ContentResolver cr, long personId, com.google.wireless.gdata.contacts.data.ContactEntry entry)
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 | addPhonesToContactEntry(android.content.ContentResolver cr, long personId, com.google.wireless.gdata.contacts.data.ContactEntry entry)
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();
}
| private static void | contactsElementToValues(android.content.ContentValues values, com.google.wireless.gdata.contacts.data.ContactsElement element, java.util.HashMap map)
values.put("type", map.get(element.getType()));
values.put("label", element.getLabel());
values.put("isprimary", element.isPrimary() ? 1 : 0);
| private static void | cursorToBaseEntry(com.google.wireless.gdata.data.Entry entry, java.lang.String account, android.database.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 | cursorToContactEntry(java.lang.String account, android.content.ContentResolver cr, android.database.Cursor c, com.google.wireless.gdata.contacts.data.ContactEntry entry)
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);
| private static void | cursorToContactsElement(com.google.wireless.gdata.contacts.data.ContactsElement element, android.database.Cursor c, java.util.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);
| protected java.lang.String | cursorToEntry(android.content.SyncContext context, android.database.Cursor c, com.google.wireless.gdata.data.Entry baseEntry, java.lang.Object syncInfo)
return cursorToEntryImpl(getContext().getContentResolver(), c, baseEntry, getAccount());
| protected static java.lang.String | cursorToEntryImpl(android.content.ContentResolver cr, android.database.Cursor c, com.google.wireless.gdata.data.Entry entry, java.lang.String account)
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(android.database.Cursor c, com.google.wireless.gdata.contacts.data.GroupEntry entry)
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);
| protected void | deletedCursorToEntry(android.content.SyncContext context, android.database.Cursor c, com.google.wireless.gdata.data.Entry entry)
deletedCursorToEntryImpl(c, entry, getAccount());
| protected static void | deletedCursorToEntryImpl(android.database.Cursor c, com.google.wireless.gdata.data.Entry entry, java.lang.String account)
cursorToBaseEntry(entry, account, c);
| public static java.lang.String | getCanonicalGroupsFeedForAccount(java.lang.String account)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.
return GROUPS_FEED_URL + account + "/base";
| public static java.lang.String | getContactsFeedForAccount(java.lang.String account)Returns the contacts feed url for a specific account.
String url = CONTACTS_FEED_URL + account + "/base2_property-android";
return rewriteUrlforAccount(account, url);
| public static java.lang.String | getContactsFeedForGroup(java.lang.String account, java.lang.String groupSyncId)Returns the contacts group feed url for a specific account.
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;
| protected android.database.Cursor | getCursorForDeletedTable(android.content.ContentProvider cp, java.lang.Class entryClass)
return getCursorForDeletedTableImpl(cp, entryClass);
| protected static android.database.Cursor | getCursorForDeletedTableImpl(android.content.ContentProvider cp, java.lang.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());
| protected android.database.Cursor | getCursorForTable(android.content.ContentProvider cp, java.lang.Class entryClass)
return getCursorForTableImpl(cp, entryClass);
| protected static android.database.Cursor | getCursorForTableImpl(android.content.ContentProvider cp, java.lang.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());
| protected java.lang.Class | getFeedEntryClass()
throw new UnsupportedOperationException("this should never be used");
| protected java.lang.Class | getFeedEntryClass(java.lang.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;
| protected static boolean | getFeedReturnsPartialDiffs()
return true;
| protected java.lang.String | getFeedUrl(java.lang.String account)
throw new UnsupportedOperationException("this should never be used");
| protected com.google.wireless.gdata.client.GDataServiceClient | getGDataServiceClient()
return mContactsClient;
| public static java.lang.String | getGroupsFeedForAccount(java.lang.String account)Returns the groups feed url for a specific account.
String url = GROUPS_FEED_URL + account + "/base2_property-android";
return rewriteUrlforAccount(account, url);
| public static java.lang.String | getPhotosFeedForAccount(java.lang.String account)Returns the photo feed url for a specific account.
String url = PHOTO_FEED_URL + account;
return rewriteUrlforAccount(account, url);
| public void | getServerDiffs(android.content.SyncContext context, SyncData baseSyncData, android.content.SyncableContentProvider tempProvider, android.os.Bundle extras, java.lang.Object syncInfo, android.content.SyncResult syncResult)
mPerformedGetServerDiffs = true;
GDataSyncData syncData = (GDataSyncData)baseSyncData;
ArrayList<String> feedsToSync = new ArrayList<String>();
if (extras != null && extras.containsKey("feed")) {
feedsToSync.add((String) extras.get("feed"));
} else {
feedsToSync.add(getGroupsFeedForAccount(getAccount()));
addContactsFeedsToSync(getContext().getContentResolver(), getAccount(), feedsToSync);
feedsToSync.add(getPhotosFeedForAccount(getAccount()));
}
for (String feed : feedsToSync) {
context.setStatusText("Downloading\u2026");
if (getPhotosFeedForAccount(getAccount()).equals(feed)) {
getServerPhotos(context, feed, MAX_MEDIA_ENTRIES_PER_SYNC, syncData, syncResult);
} else {
final Class feedEntryClass = getFeedEntryClass(feed);
if (feedEntryClass != null) {
getServerDiffsImpl(context, tempProvider, feedEntryClass,
feed, null, getMaxEntriesPerSync(), syncData, syncResult);
} else {
if (Config.LOGD) {
Log.d(TAG, "ignoring sync request for unknown feed " + feed);
}
}
}
if (syncResult.hasError()) {
break;
}
}
| private void | getServerPhotos(android.content.SyncContext context, java.lang.String feedUrl, int maxDownloads, GDataSyncData syncData, android.content.SyncResult syncResult)
final ContentResolver cr = getContext().getContentResolver();
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 download_required != 0",
new String[]{getAccount()}, 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();
}
| private static boolean | getShouldSyncEverything(android.content.ContentResolver cr, java.lang.String account)
String value = Contacts.Settings.getSetting(cr, account, Contacts.Settings.SYNC_EVERYTHING);
return !TextUtils.isEmpty(value) && !"0".equals(value);
| protected void | getStatsString(java.lang.StringBuffer sb, android.content.SyncResult result)
super.getStatsString(sb, result);
if (mPhotoUploads > 0) {
sb.append("p").append(mPhotoUploads);
}
if (mPhotoDownloads > 0) {
sb.append("P").append(mPhotoDownloads);
}
| protected boolean | handleAllDeletedUnavailable(GDataSyncData syncData, java.lang.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;
| private static java.lang.String | lastItemFromUri(java.lang.String url)
return url.substring(url.lastIndexOf('/") + 1);
| protected com.google.wireless.gdata.data.Entry | newEntry()
throw new UnsupportedOperationException("this should never be used");
| public void | onAccountsChanged(java.lang.String[] accountsArray)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.
if (!"yes".equals(SystemProperties.get("ro.config.sync"))) {
return;
}
ContentResolver cr = getContext().getContentResolver();
for (String account : accountsArray) {
String value = Contacts.Settings.getSetting(cr, account,
Contacts.Settings.SYNC_EVERYTHING);
if (value == null) {
Contacts.Settings.setSetting(cr, account, Contacts.Settings.SYNC_EVERYTHING, "1");
}
updateSubscribedFeeds(cr, account);
}
| public void | onSyncEnding(android.content.SyncContext context, boolean success)
final ContentResolver cr = getContext().getContentResolver();
if (success && mPerformedGetServerDiffs && !mSyncCanceled) {
Cursor cursor = cr.query(
Photos.CONTENT_URI,
new String[]{Photos._SYNC_ID, Photos._SYNC_VERSION, Photos.PERSON_ID,
Photos.DOWNLOAD_REQUIRED}, ""
+ "_sync_account=? AND download_required != 0",
new String[]{getAccount()}, null);
try {
if (cursor.getCount() != 0) {
Bundle extras = new Bundle();
extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, getAccount());
extras.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, mSyncForced);
extras.putString("feed",
ContactsSyncAdapter.getPhotosFeedForAccount(getAccount()));
getContext().getContentResolver().startSync(Contacts.CONTENT_URI, extras);
}
} finally {
cursor.close();
}
}
super.onSyncEnding(context, success);
| public void | onSyncStarting(android.content.SyncContext context, java.lang.String account, boolean forced, android.content.SyncResult result)
mPerformedGetServerDiffs = false;
mSyncForced = forced;
mPhotoDownloads = 0;
mPhotoUploads = 0;
super.onSyncStarting(context, account, forced, result);
| protected void | savePhoto(long person, java.io.InputStream photoInput, java.lang.String photoVersion)
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
}
}
| public void | sendClientDiffs(android.content.SyncContext context, android.content.SyncableContentProvider clientDiffs, android.content.SyncableContentProvider serverDiffs, android.content.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(android.content.SyncContext context, android.content.ContentProvider clientDiffs, java.lang.Object syncInfo, android.content.SyncResult syncResult)
Entry entry = new MediaEntry();
GDataServiceClient client = getGDataServiceClient();
String authToken = getAuthToken();
ContentResolver cr = getContext().getContentResolver();
final String 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++;
try {
GoogleLoginServiceBlockingHelper.invalidateAuthToken(getContext(),
authToken);
} catch (GoogleLoginServiceNotFoundException e1) {
if (Config.LOGD) {
Log.d(TAG, "could not invalidate auth token", e1);
}
}
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();
}
| private static java.util.HashMap | swapMap(java.util.HashMap originalMap)
HashMap<Byte, Integer> map;
map = new HashMap<Byte, Integer>();
map.put(ImAddress.PROTOCOL_AIM, ContactMethods.PROTOCOL_AIM);
map.put(ImAddress.PROTOCOL_GOOGLE_TALK, ContactMethods.PROTOCOL_GOOGLE_TALK);
map.put(ImAddress.PROTOCOL_ICQ, ContactMethods.PROTOCOL_ICQ);
map.put(ImAddress.PROTOCOL_JABBER, ContactMethods.PROTOCOL_JABBER);
map.put(ImAddress.PROTOCOL_MSN, ContactMethods.PROTOCOL_MSN);
map.put(ImAddress.PROTOCOL_QQ, ContactMethods.PROTOCOL_QQ);
map.put(ImAddress.PROTOCOL_SKYPE, ContactMethods.PROTOCOL_SKYPE);
map.put(ImAddress.PROTOCOL_YAHOO, ContactMethods.PROTOCOL_YAHOO);
ENTRY_IM_PROTOCOL_TO_PROVIDER_PROTOCOL = map;
PROVIDER_IM_PROTOCOL_TO_ENTRY_PROTOCOL = swapMap(map);
map = new HashMap<Byte, Integer>();
map.put(EmailAddress.TYPE_HOME, ContactMethods.TYPE_HOME);
map.put(EmailAddress.TYPE_WORK, ContactMethods.TYPE_WORK);
map.put(EmailAddress.TYPE_OTHER, ContactMethods.TYPE_OTHER);
map.put(EmailAddress.TYPE_NONE, ContactMethods.TYPE_CUSTOM);
ENTRY_TYPE_TO_PROVIDER_EMAIL = map;
PROVIDER_TYPE_TO_ENTRY_EMAIL = swapMap(map);
map = new HashMap<Byte, Integer>();
map.put(PhoneNumber.TYPE_HOME, Phones.TYPE_HOME);
map.put(PhoneNumber.TYPE_MOBILE, Phones.TYPE_MOBILE);
map.put(PhoneNumber.TYPE_PAGER, Phones.TYPE_PAGER);
map.put(PhoneNumber.TYPE_WORK, Phones.TYPE_WORK);
map.put(PhoneNumber.TYPE_HOME_FAX, Phones.TYPE_FAX_HOME);
map.put(PhoneNumber.TYPE_WORK_FAX, Phones.TYPE_FAX_WORK);
map.put(PhoneNumber.TYPE_OTHER, Phones.TYPE_OTHER);
map.put(PhoneNumber.TYPE_NONE, Phones.TYPE_CUSTOM);
ENTRY_TYPE_TO_PROVIDER_PHONE = map;
PROVIDER_TYPE_TO_ENTRY_PHONE = swapMap(map);
map = new HashMap<Byte, Integer>();
map.put(PostalAddress.TYPE_HOME, ContactMethods.TYPE_HOME);
map.put(PostalAddress.TYPE_WORK, ContactMethods.TYPE_WORK);
map.put(PostalAddress.TYPE_OTHER, ContactMethods.TYPE_OTHER);
map.put(PostalAddress.TYPE_NONE, ContactMethods.TYPE_CUSTOM);
ENTRY_TYPE_TO_PROVIDER_POSTAL = map;
PROVIDER_TYPE_TO_ENTRY_POSTAL = swapMap(map);
map = new HashMap<Byte, Integer>();
map.put(ImAddress.TYPE_HOME, ContactMethods.TYPE_HOME);
map.put(ImAddress.TYPE_WORK, ContactMethods.TYPE_WORK);
map.put(ImAddress.TYPE_OTHER, ContactMethods.TYPE_OTHER);
map.put(ImAddress.TYPE_NONE, ContactMethods.TYPE_CUSTOM);
ENTRY_TYPE_TO_PROVIDER_IM = map;
PROVIDER_TYPE_TO_ENTRY_IM = swapMap(map);
map = new HashMap<Byte, Integer>();
map.put(Organization.TYPE_WORK, Organizations.TYPE_WORK);
map.put(Organization.TYPE_OTHER, Organizations.TYPE_OTHER);
map.put(Organization.TYPE_NONE, Organizations.TYPE_CUSTOM);
ENTRY_TYPE_TO_PROVIDER_ORGANIZATION = map;
PROVIDER_TYPE_TO_ENTRY_ORGANIZATION = swapMap(map);
HashMap<B, A> newMap = new HashMap<B,A>();
for (Map.Entry<A, B> entry : originalMap.entrySet()) {
final B originalValue = entry.getValue();
if (newMap.containsKey(originalValue)) {
throw new IllegalArgumentException("value " + originalValue
+ " was already encountered");
}
newMap.put(originalValue, entry.getKey());
}
return newMap;
| protected void | updateProvider(com.google.wireless.gdata.data.Feed feed, java.lang.Long syncLocalId, com.google.wireless.gdata.data.Entry baseEntry, android.content.ContentProvider provider, java.lang.Object syncInfo)
// 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(java.lang.String account, java.lang.Long syncLocalId, com.google.wireless.gdata.data.Entry entry, android.content.ContentProvider provider)
// 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);
}
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(java.lang.String account, java.lang.Long syncLocalId, com.google.wireless.gdata.contacts.data.ContactEntry entry, android.content.ContentProvider provider)
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);
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);
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);
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(java.lang.String account, java.lang.Long syncLocalId, com.google.wireless.gdata.contacts.data.GroupEntry entry, android.content.ContentProvider provider)
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);
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);
| protected void | updateQueryParameters(com.google.wireless.gdata.client.QueryParams params)
// 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");
}
| public static void | updateSubscribedFeeds(android.content.ContentResolver cr, java.lang.String account)
Set<String> feedsToSync = Sets.newHashSet();
feedsToSync.add(getGroupsFeedForAccount(account));
addContactsFeedsToSync(cr, account, feedsToSync);
Cursor c = SubscribedFeeds.Feeds.query(cr, sSubscriptionProjection,
SubscribedFeeds.Feeds.AUTHORITY + "=? AND "
+ SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?",
new String[]{Contacts.AUTHORITY, account}, 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(ContentResolver.SYNC_EXTRAS_ACCOUNT, account);
extras.putString("feed", feed);
cr.startSync(Contacts.CONTENT_URI, extras);
}
|
|