/* * Copyright 2010 Arthur Zaczek <arthur@dasz.at>, dasz.at OG; All rights reserved. * Copyright 2010 David Schmitt <david@dasz.at>, dasz.at OG; All rights reserved. * * This file is part of Kolab Sync for Android. * Kolab Sync for Android is free software: you can redistribute it * and/or modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * Kolab Sync for Android is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * You should have received a copy of the GNU General Public License * along with Kolab Sync for Android. * If not, see <http://www.gnu.org/licenses/>. */ package at.dasz.KolabDroid.Contacts; import java.util.Date; import java.util.UUID; import javax.mail.MessagingException; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.Contacts; import android.provider.Contacts.People; import at.dasz.KolabDroid.Utils; import at.dasz.KolabDroid.Provider.LocalCacheProvider; import at.dasz.KolabDroid.Settings.Settings; import at.dasz.KolabDroid.Sync.AbstractSyncHandler; import at.dasz.KolabDroid.Sync.CacheEntry; import at.dasz.KolabDroid.Sync.SyncContext; import at.dasz.KolabDroid.Sync.SyncException; public class SyncContactsHandler extends AbstractSyncHandler { private static final String[] PHONE_PROJECTION = new String[] { Contacts.Phones.TYPE, Contacts.Phones.NUMBER }; private static final String[] EMAIL_PROJECTION = new String[] { Contacts.ContactMethods.TYPE, Contacts.ContactMethods.DATA }; private static final String[] PEOPLE_NAME_PROJECTION = new String[] { People.NAME }; private static final String[] PEOPLE_ID_PROJECTION = new String[] { People._ID }; private static final String[] ID_PROJECTION = new String[] { "_id" }; private static final String EMAIL_FILTER = Contacts.ContactMethods.KIND + "=" + Contacts.KIND_EMAIL; private final String defaultFolderName; private final LocalCacheProvider cacheProvider; private final ContentResolver cr; public SyncContactsHandler(Context context) { super(context); Settings s = new Settings(context); settings = s; defaultFolderName = s.getContactsFolder(); cacheProvider = new LocalCacheProvider.ContactsCacheProvider(context); cr = context.getContentResolver(); status.setTask("Contacts"); } public String getDefaultFolderName() { return defaultFolderName; } public boolean shouldProcess() { boolean hasFolder = (defaultFolderName != null && !"".equals(defaultFolderName)); return settings.getSyncContacts() && hasFolder; } public LocalCacheProvider getLocalCacheProvider() { return cacheProvider; } public Cursor getAllLocalItemsCursor() { return cr.query(People.CONTENT_URI, PEOPLE_ID_PROJECTION, null, null, null); } public int getIdColumnIndex(Cursor c) { return c.getColumnIndex(People._ID); } @Override protected void updateLocalItemFromServer(SyncContext sync, Document xml) throws SyncException { Contact contact = (Contact) sync.getLocalItem(); if (contact == null) { contact = new Contact(); } Element root = xml.getDocumentElement(); contact.setUid(Utils.getXmlElementString(root, "uid")); Element name = Utils.getXmlElement(root, "name"); if (name != null) { contact.setFullName(Utils.getXmlElementString(name, "full-name")); } contact.getContactMethods().clear(); NodeList nl = Utils.getXmlElements(root, "phone"); for (int i = 0; i < nl.getLength(); i++) { ContactMethod cm = new PhoneContact(); cm.fromXml((Element) nl.item(i)); contact.getContactMethods().add(cm); } nl = Utils.getXmlElements(root, "email"); for (int i = 0; i < nl.getLength(); i++) { ContactMethod cm = new EmailContact(); cm.fromXml((Element) nl.item(i)); contact.getContactMethods().add(cm); } sync.setCacheEntry(saveContact(contact)); } @Override protected void updateServerItemFromLocal(SyncContext sync, Document xml) throws SyncException, MessagingException { Contact source = getLocalItem(sync); CacheEntry entry = sync.getCacheEntry(); entry.setLocalHash(source.getLocalHash()); final Date lastChanged = new Date(); entry.setRemoteChangedDate(lastChanged); writeXml(xml, source, lastChanged); } private void writeXml(Document xml, Contact source, final Date lastChanged) { Element root = xml.getDocumentElement(); Utils.setXmlElementValue(xml, root, "last-modification-date", Utils .toUtc(lastChanged)); Utils.setXmlElementValue(xml, root, "uid", source.getUid()); Element name = Utils.getOrCreateXmlElement(xml, root, "name"); Utils.setXmlElementValue(xml, name, "full-name", source.getFullName()); Utils.deleteXmlElements(root, "phone"); Utils.deleteXmlElements(root, "email"); for (ContactMethod cm : source.getContactMethods()) { cm.toXml(xml, root, source.getFullName()); } } @Override protected String writeXml(SyncContext sync) throws ParserConfigurationException, SyncException, MessagingException { Contact source = getLocalItem(sync); CacheEntry entry = sync.getCacheEntry(); entry.setLocalHash(source.getLocalHash()); final Date lastChanged = new Date(); entry.setRemoteChangedDate(lastChanged); final String newUid = getNewUid(); entry.setRemoteId(newUid); source.setUid(newUid); Document xml = Utils.newDocument("contact"); writeXml(xml, source, lastChanged); return Utils.getXml(xml); } @Override protected String getMimeType() { return "application/x-vnd.kolab.contact"; } public boolean hasLocalItem(SyncContext sync) throws SyncException, MessagingException { return getLocalItem(sync) != null; } public boolean hasLocalChanges(SyncContext sync) throws SyncException, MessagingException { CacheEntry e = sync.getCacheEntry(); Contact contact = getLocalItem(sync);; String entryHash = e.getLocalHash(); String contactHash = contact != null ? contact.getLocalHash() : ""; return !entryHash.equals(contactHash); } @Override public void deleteLocalItem(int localId) { Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, localId); cr.delete(uri, null, null); } private CacheEntry saveContact(Contact contact) throws SyncException { Uri uri; if (contact.getId() == 0) { uri = Contacts.People.createPersonInMyContactsGroup(cr, contact .toContentValues()); } else { uri = ContentUris.withAppendedId(People.CONTENT_URI, contact .getId()); cr.update(uri, contact.toContentValues(), null, null); Cursor phoneCursor = null, emailCursor = null; try { Uri phoneDirectoryUri = Uri.withAppendedPath(uri, Contacts.People.Phones.CONTENT_DIRECTORY); phoneCursor = cr.query(phoneDirectoryUri, ID_PROJECTION, null, null, null); if (phoneCursor == null) throw new SyncException(contact.toString(), "cr.query returned null"); while (phoneCursor.moveToNext()) { Uri delUri = ContentUris.withAppendedId(phoneDirectoryUri, phoneCursor.getInt(0)); cr.delete(delUri, null, null); } Uri emailDirectoryUri = Uri.withAppendedPath(uri, Contacts.People.ContactMethods.CONTENT_DIRECTORY); emailCursor = cr.query(emailDirectoryUri, ID_PROJECTION, EMAIL_FILTER, null, null); if (emailCursor == null) throw new SyncException(contact.toString(), "cr.query returned null"); while (emailCursor.moveToNext()) { Uri delUri = ContentUris.withAppendedId(emailDirectoryUri, emailCursor.getInt(0)); cr.delete(delUri, null, null); } } finally { if (phoneCursor != null) phoneCursor.close(); if (emailCursor != null) emailCursor.close(); } } for (ContactMethod method : contact.getContactMethods()) { Uri methodUri = Uri.withAppendedPath(uri, method .getContentDirectory()); cr.insert(methodUri, method.toContentValues()); } CacheEntry result = new CacheEntry(); result.setLocalId((int) ContentUris.parseId(uri)); result.setLocalHash(contact.getLocalHash()); result.setRemoteId(contact.getUid()); return result; } private Contact getLocalItem(SyncContext sync) throws SyncException, MessagingException { if (sync.getLocalItem() != null) return (Contact) sync.getLocalItem(); Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, sync .getCacheEntry().getLocalId()); Cursor personCursor = null, phoneCursor = null, emailCursor = null; try { personCursor = cr.query(uri, PEOPLE_NAME_PROJECTION, null, null, null); if (personCursor == null) throw new SyncException( getItemText(sync), "cr.query returned null"); if (!personCursor.moveToFirst()) return null; Contact result = new Contact(); result.setId(sync.getCacheEntry().getLocalId()); result.setUid(sync.getCacheEntry().getRemoteId()); final int nameIdx = personCursor.getColumnIndex(People.NAME); result.setFullName(personCursor.getString(nameIdx)); { Uri phoneDirectoryUri = Uri.withAppendedPath(uri, Contacts.People.Phones.CONTENT_DIRECTORY); phoneCursor = cr.query(phoneDirectoryUri, PHONE_PROJECTION, null, null, Contacts.Phones.NUMBER); if (phoneCursor == null) throw new SyncException( getItemText(sync), "cr.query returned null"); final int typeIdx = phoneCursor .getColumnIndex(Contacts.Phones.TYPE); final int numberIdx = phoneCursor .getColumnIndex(Contacts.Phones.NUMBER); while (phoneCursor.moveToNext()) { PhoneContact pc = new PhoneContact(); pc.setData(phoneCursor.getString(numberIdx)); pc.setType(phoneCursor.getInt(typeIdx)); result.getContactMethods().add(pc); } } { Uri emailDirectoryUri = Uri.withAppendedPath(uri, Contacts.People.ContactMethods.CONTENT_DIRECTORY); emailCursor = cr.query(emailDirectoryUri, EMAIL_PROJECTION, EMAIL_FILTER, null, Contacts.ContactMethods.DATA); if (emailCursor == null) throw new SyncException( getItemText(sync), "cr.query returned null"); final int typeIdx = emailCursor .getColumnIndex(Contacts.ContactMethods.TYPE); final int dataIdx = emailCursor .getColumnIndex(Contacts.ContactMethods.DATA); while (emailCursor.moveToNext()) { EmailContact pc = new EmailContact(); pc.setData(emailCursor.getString(dataIdx)); pc.setType(emailCursor.getInt(typeIdx)); result.getContactMethods().add(pc); } } sync.setLocalItem(result); return result; } finally { if (personCursor != null) personCursor.close(); if (phoneCursor != null) phoneCursor.close(); if (emailCursor != null) emailCursor.close(); } } private String getNewUid() { // Create Application and Type specific id // kd == Kolab Droid, ct = contact return "kd-ct-" + UUID.randomUUID().toString(); } @Override protected String getMessageBodyText(SyncContext sync) throws SyncException, MessagingException { Contact contact = getLocalItem(sync); StringBuilder sb = new StringBuilder(); String fullName =contact.getFullName(); sb.append(fullName == null ? "(no name)" : fullName); sb.append("\n"); sb.append("----- Contact Methods -----\n"); for (ContactMethod cm : contact.getContactMethods()) { sb.append(cm.getData()); sb.append("\n"); } return sb.toString(); } @Override public String getItemText(SyncContext sync) throws MessagingException { if (sync.getLocalItem() != null) { Contact item = (Contact) sync.getLocalItem(); return item.getFullName(); } else { return sync.getMessage().getSubject(); } } }