Add support for note and category upload for Contacts
* Also fixed a few random bugs found while debugging
This commit is contained in:
parent
d1e7dc68f3
commit
b4d87dfdd0
@ -471,10 +471,11 @@ public class EasSyncService extends InteractiveSyncService {
|
||||
userLog(mVersions);
|
||||
userLog("Using version " + mProtocolVersion);
|
||||
} else {
|
||||
errorLog("No protocol versions in OPTIONS response");
|
||||
throw new IOException();
|
||||
}
|
||||
} else {
|
||||
userLog("OPTIONS command failed; throwing IOException");
|
||||
errorLog("OPTIONS command failed; throwing IOException");
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
@ -488,15 +488,17 @@ public class SyncManager extends Service implements Runnable {
|
||||
long id = c.getLong(Mailbox.CONTENT_ID_COLUMN);
|
||||
AbstractSyncService svc = INSTANCE.mServiceMap.get(id);
|
||||
// Tell the service we're done
|
||||
svc.stop();
|
||||
// Interrupt the thread so that it can stop
|
||||
Thread thread = svc.mThread;
|
||||
thread.setName(thread.getName() + " (Stopped)");
|
||||
thread.interrupt();
|
||||
// Abandon the service
|
||||
INSTANCE.mServiceMap.remove(id);
|
||||
// And have it start naturally
|
||||
kick();
|
||||
if (svc != null) {
|
||||
svc.stop();
|
||||
// Interrupt the thread so that it can stop
|
||||
Thread thread = svc.mThread;
|
||||
thread.setName(thread.getName() + " (Stopped)");
|
||||
thread.interrupt();
|
||||
// Abandon the service
|
||||
INSTANCE.mServiceMap.remove(id);
|
||||
// And have it start naturally
|
||||
kick();
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
@ -38,8 +38,10 @@ import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.Data;
|
||||
import android.provider.ContactsContract.Groups;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Email;
|
||||
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Im;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Nickname;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Note;
|
||||
@ -64,6 +66,7 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
private static final String TAG = "EasContactsSyncAdapter";
|
||||
private static final String SERVER_ID_SELECTION = RawContacts.SOURCE_ID + "=?";
|
||||
private static final String[] ID_PROJECTION = new String[] {RawContacts._ID};
|
||||
private static final String[] GROUP_PROJECTION = new String[] {Groups.SOURCE_ID};
|
||||
|
||||
// Note: These constants are likely to change; they are internal to this class now, but
|
||||
// may end up in the provider.
|
||||
@ -359,10 +362,6 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
childrenParser(children);
|
||||
break;
|
||||
|
||||
case Tags.CONTACTS_CATEGORIES:
|
||||
categoriesParser();
|
||||
break;
|
||||
|
||||
case Tags.CONTACTS_YOMI_COMPANY_NAME:
|
||||
yomiCompanyName = getValue();
|
||||
break;
|
||||
@ -434,6 +433,10 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
// TODO Handle Categories/Category
|
||||
// If we don't handle this properly, we'll lose the information if/when we
|
||||
// upload changes to the server!
|
||||
case Tags.CONTACTS_CATEGORIES:
|
||||
categoriesParser(ops, entity);
|
||||
break;
|
||||
|
||||
|
||||
case Tags.CONTACTS_COMPRESSED_RTF:
|
||||
// We don't use this, and it isn't necessary to upload, so we'll ignore it
|
||||
@ -488,10 +491,11 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private void categoriesParser() throws IOException {
|
||||
private void categoriesParser(ContactOperations ops, Entity entity) throws IOException {
|
||||
while (nextTag(Tags.CONTACTS_CATEGORIES) != END) {
|
||||
switch (tag) {
|
||||
case Tags.CONTACTS_CATEGORY:
|
||||
ops.addGroup(entity, getValue());
|
||||
default:
|
||||
skipTag();
|
||||
}
|
||||
@ -780,7 +784,7 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
* @return the matching NCV or null if not found
|
||||
*/
|
||||
private NamedContentValues findExistingData(ArrayList<NamedContentValues> list,
|
||||
String contentItemType, int type) {
|
||||
String contentItemType, int type, String stringType) {
|
||||
NamedContentValues result = null;
|
||||
|
||||
// Loop through the ncv's, looking for an existing row
|
||||
@ -790,13 +794,20 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
if (Data.CONTENT_URI.equals(uri)) {
|
||||
String mimeType = cv.getAsString(Data.MIMETYPE);
|
||||
if (mimeType.equals(contentItemType)) {
|
||||
if (type < 0 || cv.getAsInteger(Email.TYPE) == type) {
|
||||
if (stringType != null) {
|
||||
if (cv.getAsString(GroupMembership.GROUP_ROW_ID).equals(stringType)) {
|
||||
result = namedContentValues;
|
||||
}
|
||||
// Note Email.TYPE could be ANY type column; they are all defined in
|
||||
// the private CommonColumns class in ContactsContract
|
||||
} else if (type < 0 || cv.getAsInteger(Email.TYPE) == type) {
|
||||
result = namedContentValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Handle deleted items
|
||||
// If we've found an existing data row, we'll delete it. Any rows left at the
|
||||
// end should be deleted...
|
||||
if (result != null) {
|
||||
@ -819,15 +830,21 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
* @param entity the contact entity (or null if this is a new contact)
|
||||
* @param mimeType the mime type of this row
|
||||
* @param type the subtype of this row
|
||||
* @param stringType for groups, the name of the group (type will be ignored), or null
|
||||
* @return the created SmartBuilder
|
||||
*/
|
||||
public SmartBuilder createBuilder(Entity entity, String mimeType, int type) {
|
||||
return createBuilder(entity, mimeType, type, null);
|
||||
}
|
||||
|
||||
public SmartBuilder createBuilder(Entity entity, String mimeType, int type,
|
||||
String stringType) {
|
||||
int contactId = mContactBackValue;
|
||||
SmartBuilder builder = null;
|
||||
|
||||
if (entity != null) {
|
||||
NamedContentValues ncv =
|
||||
findExistingData(entity.getSubValues(), mimeType, type);
|
||||
findExistingData(entity.getSubValues(), mimeType, type, stringType);
|
||||
if (ncv != null) {
|
||||
builder = new SmartBuilder(
|
||||
ContentProviderOperation
|
||||
@ -894,6 +911,13 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
add(builder.build());
|
||||
}
|
||||
|
||||
public void addGroup(Entity entity, String group) {
|
||||
SmartBuilder builder =
|
||||
createBuilder(entity, GroupMembership.CONTENT_ITEM_TYPE, -1, group);
|
||||
builder.withValue(GroupMembership.GROUP_SOURCE_ID, group);
|
||||
add(builder.build());
|
||||
}
|
||||
|
||||
public void addName(Entity entity, String givenName, String familyName, String middleName,
|
||||
String suffix, String displayName) {
|
||||
SmartBuilder builder = createBuilder(entity, StructuredName.CONTENT_ITEM_TYPE, -1);
|
||||
@ -977,7 +1001,14 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
SmartBuilder builder = createBuilder(entity, Photo.CONTENT_ITEM_TYPE, -1);
|
||||
// We're always going to add this; it's not worth trying to figure out whether the
|
||||
// picture is the same as the one stored.
|
||||
builder.withValue(Photo.PHOTO, Base64.decodeToObject(photo));
|
||||
byte[] pic = Base64.decode(photo);
|
||||
// Bitmap b = BitmapFactory.decodeByteArray (pic, 0, pic.length);
|
||||
// if (b == null) {
|
||||
// mService.userLog("Bitmap creation failed");
|
||||
// } else {
|
||||
// mService.userLog("W00t! Bitmap creation worked!");
|
||||
// }
|
||||
builder.withValue(Photo.PHOTO, pic);
|
||||
add(builder.build());
|
||||
}
|
||||
|
||||
@ -1050,6 +1081,9 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
public void addNote(Entity entity, String note) {
|
||||
SmartBuilder builder = createBuilder(entity, Note.CONTENT_ITEM_TYPE, -1);
|
||||
ContentValues cv = builder.cv;
|
||||
if (note != null) {
|
||||
note.replace("\r\n", "\n");
|
||||
}
|
||||
if (cv != null && cvCompareString(cv, Note.NOTE, note)) {
|
||||
return;
|
||||
}
|
||||
@ -1245,6 +1279,21 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNote(Serializer s, ContentValues cv) throws IOException {
|
||||
if (cv.containsKey(Note.NOTE)) {
|
||||
// EAS won't accept note data with raw newline characters
|
||||
String note = cv.getAsString(Note.NOTE).replace("\n", "\r\n");
|
||||
// Format of data depends on protocol version
|
||||
if (mService.mProtocolVersionDouble >= 12.0) {
|
||||
s.start(Tags.BASE_BODY);
|
||||
s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_TEXT).data(Tags.BASE_DATA, note);
|
||||
s.end();
|
||||
} else {
|
||||
s.data(Tags.CONTACTS_BODY, note);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendChildren(Serializer s, ContentValues cv) throws IOException {
|
||||
boolean first = true;
|
||||
for (int i = 0; i < EasChildren.MAX_CHILDREN; i++) {
|
||||
@ -1335,12 +1384,13 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
// For each of these entities, create the change commands
|
||||
ContentValues entityValues = entity.getEntityValues();
|
||||
String serverId = entityValues.getAsString(RawContacts.SOURCE_ID);
|
||||
ArrayList<Integer> groupIds = new ArrayList<Integer>();
|
||||
if (first) {
|
||||
s.start(Tags.SYNC_COMMANDS);
|
||||
first = false;
|
||||
}
|
||||
s.start(Tags.SYNC_CHANGE).data(Tags.SYNC_SERVER_ID, serverId)
|
||||
.start(Tags.SYNC_APPLICATION_DATA);
|
||||
.start(Tags.SYNC_APPLICATION_DATA);
|
||||
// Write out the data here
|
||||
for (NamedContentValues ncv: entity.getSubValues()) {
|
||||
ContentValues cv = ncv.values;
|
||||
@ -1367,15 +1417,43 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
sendOrganization(s, cv);
|
||||
} else if (mimeType.equals(Im.CONTENT_ITEM_TYPE)) {
|
||||
sendIm(s, cv);
|
||||
} else if (mimeType.equals(GroupMembership.CONTENT_ITEM_TYPE)) {
|
||||
// We must gather these, and send them together (below)
|
||||
groupIds.add(cv.getAsInteger(GroupMembership.GROUP_ROW_ID));
|
||||
} else if (mimeType.equals(Note.CONTENT_ITEM_TYPE)) {
|
||||
// TODO Should we upload this (it will be plain text)
|
||||
sendNote(s, cv);
|
||||
} else if (mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
|
||||
// TODO Decide whether to upload new photos
|
||||
// For now, we're not going to upload photos
|
||||
// For now, the user can change the photo, but the change won't be
|
||||
// uploaded.
|
||||
} else {
|
||||
mService.userLog("Contacts upsync, unknown data: " + mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
// Now, we'll send up groups, if any
|
||||
if (!groupIds.isEmpty()) {
|
||||
boolean groupFirst = true;
|
||||
for (int id: groupIds) {
|
||||
// Since we get id's from the provider, we need to find their names
|
||||
Cursor c = cr.query(ContentUris.withAppendedId(Groups.CONTENT_URI, id),
|
||||
GROUP_PROJECTION, null, null, null);
|
||||
try {
|
||||
// Presumably, this should always succeed, but ...
|
||||
if (c.moveToFirst()) {
|
||||
if (groupFirst) {
|
||||
s.start(Tags.CONTACTS_CATEGORIES);
|
||||
groupFirst = false;
|
||||
}
|
||||
s.data(Tags.CONTACTS_CATEGORY, c.getString(0));
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
if (!groupFirst) {
|
||||
s.end();
|
||||
}
|
||||
}
|
||||
s.end().end(); // ApplicationData & Change
|
||||
mUpdatedIdList.add(entityValues.getAsLong(RawContacts._ID));
|
||||
}
|
||||
@ -1385,7 +1463,6 @@ public class ContactsSyncAdapter extends AbstractSyncAdapter {
|
||||
} finally {
|
||||
ei.close();
|
||||
}
|
||||
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not read dirty contacts.");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user