package at.dasz.KolabDroid.ContactsContract; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import org.w3c.dom.Document; import org.w3c.dom.Element; //import android.content.ContentValues; //import android.provider.Contacts; //import android.provider.ContactsContract; //import android.provider.Contacts.People; import at.dasz.KolabDroid.Utils; import corinis.util.xml.ChildElementAdapter; public class ContactEntry implements Comparable<ContactEntry>, Serializable { /** * for serialization */ private static final long serialVersionUID = 857231805984141278L; /** * holds the data as StringBuffer */ StringBuffer data; /** * same as data, but buffers the converted xml */ Document xmlData; /** * uid to identify this contact */ String uid; /** * Array of all known fields. These will be used for comparison as well (see fieldnumber below) */ public String[] fields = new String[50]; public ArrayList<String> additonalEmail = new ArrayList<String>(); private static final int DisplayName=0; private static final int FirstName = 1; private static final int LastName = 2; private static final int PreferMailFormat =3; private static final int JobTitle = 4; private static final int NickName = 5; private static final int PrimaryEmail = 6; private static final int SecondEmail = 7; private static final int Category = 8; private static final int Company = 9; private static final int CellularNumber = 10; private static final int HomePhone = 11; private static final int FaxNumber = 12; private static final int WorkPhone = 13; private static final int PagerNumber = 14; private static final int BirthYear = 15; private static final int BirthMonth = 16; private static final int BirthDay = 17; private static final int AnniversaryYear = 18; private static final int AnniversaryMonth = 19; private static final int AnniversaryDay = 20; private static final int PhotoName = 21; private static final int Notes = 22; private static final int Department = 23; private static final int WebPage1 = 24; private static final int WebPage2 = 25; private static final int Custom1 = 26; private static final int Custom2 = 27; private static final int Custom3 = 28; private static final int Custom4 = 29; private static final int AimScreenName = 30; private static final int AllowRemoteContent = 31; private static final int HomeAddress = 32; private static final int HomeAddress2 = 33; private static final int HomeCity = 34; private static final int HomeState = 35; private static final int HomeZipCode = 36; private static final int HomeCountry = 37; private static final int WorkAddress = 38; private static final int WorkAddress2 = 39; private static final int WorkCity = 40; private static final int WorkState = 41; private static final int WorkZipCode = 42; private static final int WorkCountry = 43; private static final String MAIL_FORMAT_UNKNOWN = "0"; private static final String MAIL_FORMAT_PLAINTEXT = "1"; private static final String MAIL_FORMAT_HTML = "2"; // index holders for android urls TODO: extend this to all thats required to save as much as possible //private String contactURI; //private String emailURI; public ContactEntry (StringBuffer data, Document xmlData) { this.data = data; this.xmlData = xmlData; for (int i =0; i< this.fields.length; i++) fields[i] = null; } /** * checks if a property exists * @param prop the property index to check * @return true if the property is valid */ private boolean haveProperty(int prop) { return this.fields[prop]!=null && !this.fields[prop].equals(""); } /** * create a xml subnode in a stringbuffer * @param xml the stringbuffer to write into * @param node the name * @param content the content * @param b TODO: why to we need this? */ private void nodeWithContent (StringBuffer xml, String node, String content, boolean b) { // skip empty nodes if (node == null || content == null) return; xml.append('<'); xml.append(node); xml.append('>'); xml.append(content); xml.append('<'); xml.append('/'); xml.append(node); xml.append('>'); // add a newline xml.append('\n'); } /* private void fillContentValues(ContentValues values, String key, int field) { if (this.haveProperty(field)) values.put(key, this.getProperty(field)); } */ public void readFromAndroid () { // TODO: update the fields from android internally } /* public void writeToAndroid () { // default content ContentValues contact = new ContentValues(); fillContentValues(contact, ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, DisplayName); //fillContentValues(contact, People.NAME, DisplayName); //People.CONTENT_URI //People.PRIMARY_EMAIL_ID //People.PRIMARY_ORGANIZATION_ID //People.PRIMARY_PHONE_ID //People.IM_ACCOUNT //People.NAME //People.NOTES //People.NUMBER //People.TYPE_FAX_HOME //People.TYPE_FAX_WORK //People.TYPE_HOME //People.TYPE_HOME //People.TYPE_MOBILE //People.TYPE_WORK //People.TYPE_PAGER //People.TYPE_OTHER //Contacts.KIND_POSTAL // emails ContentValues data = new ContentValues(); //data.put(Contacts, value) data.put(Contacts.ContactMethods.KIND, Contacts.KIND_EMAIL); data.put(Contacts.ContactMethods.TYPE, People.ContactMethods.TYPE_OTHER); fillContentValues(data, Contacts.ContactMethods.DATA, PrimaryEmail); data = new ContentValues(); data.put(Contacts.ContactMethods.KIND, Contacts.KIND_EMAIL); data.put(Contacts.ContactMethods.TYPE, People.ContactMethods.TYPE_OTHER); fillContentValues(data, Contacts.ContactMethods.DATA, SecondEmail); // phone data = new ContentValues(); data.put(Contacts.ContactMethods.KIND, Contacts.KIND_PHONE); data.put(Contacts.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME); fillContentValues(data, Contacts.Phones.NUMBER, HomePhone); data = new ContentValues(); data.put(Contacts.ContactMethods.KIND, Contacts.KIND_PHONE); data.put(Contacts.ContactMethods.TYPE, People.ContactMethods.TYPE_WORK); fillContentValues(data, Contacts.Phones.NUMBER, WorkPhone); data = new ContentValues(); data.put(Contacts.ContactMethods.KIND, Contacts.KIND_PHONE); data.put(Contacts.ContactMethods.TYPE, People.ContactMethods.TYPE_OTHER); fillContentValues(data, Contacts.Phones.NUMBER, CellularNumber); // postal is one string - max 4 lines in 1.6 } */ /** * @param xml the stringbuffer to create the xml into * @return a kolab xml representation in a stringbuffer * TODO: dont we actually want a writer for this? */ public void toStringBuffer (StringBuffer xml) { xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); xml.append("<contact version=\"1.0\" >\n"); xml.append(" <product-id>SyncKolab, Kolab resource</product-id>\n"); nodeWithContent(xml, "uid", uid, false); nodeWithContent(xml, "categories", this.getProperty(Category), false); // TODO do we have/need these? //xml.append(" <creation-date>"+date2String(new Date(this.getProperty(LastModifiedDate")*1000))+"T"+time2String(new Date(this.getProperty(LastModifiedDate")*1000))+"Z</creation-date>\n"; //xml.append(" <last-modification-date>"+date2String(new Date(this.getProperty(LastModifiedDate)*1000))+"T"+time2String(new Date(this.getProperty(LastModifiedDate)*1000))+"Z</last-modification-date>\n"); // ?? xml.append(" <sensitivity>public</sensitivity>\n"); if (this.haveProperty(Notes)) nodeWithContent(xml, "body", this.getProperty(Notes), false); if (this.haveProperty(FirstName) || this.haveProperty(LastName) ||this.haveProperty(DisplayName) || this.haveProperty(NickName)) { xml.append(" <name>\n"); if (this.haveProperty(FirstName)) nodeWithContent(xml, "given-name", getProperty(FirstName), false); // xml.append(" <middle-names>"+this.getProperty(NickName")+"</middle-names>\n"; // not really correct... if (this.haveProperty(LastName)) nodeWithContent(xml, "last-name", getProperty(LastName), false); if (this.haveProperty(DisplayName)) nodeWithContent(xml, "full-name", getProperty(DisplayName), false); else if (this.haveProperty(FirstName) || this.haveProperty(LastName)) { String displayName = getProperty(FirstName) + " " + getProperty(LastName); nodeWithContent(xml, "full-name", displayName, false); } xml.append(" </name>\n"); } nodeWithContent(xml, "organization", getProperty(Company), false); nodeWithContent(xml, "web-page", getProperty(WebPage1), false); // not really kolab.. but we need that somewhere nodeWithContent(xml, "business-web-page", getProperty(WebPage2), false); nodeWithContent(xml, "im-address", getProperty(AimScreenName), false); nodeWithContent(xml, "department", getProperty(Department), false); //" <office-location>zuhaus</office-location>\n"; //" <profession>programmierer</profession>\n"; nodeWithContent(xml, "job-title", getProperty(JobTitle), false); nodeWithContent(xml, "nick-name", getProperty(NickName), false); if (this.haveProperty(BirthYear) && this.haveProperty(BirthMonth) && this.haveProperty(BirthDay)) { String adate = getProperty(BirthYear) + "-" + getProperty(BirthMonth) + "-" + getProperty(BirthDay); nodeWithContent(xml, "birthday", adate, false); } if(this.haveProperty(AnniversaryYear) && this.haveProperty(AnniversaryMonth) && this.haveProperty(AnniversaryDay)) { String adate = getProperty(AnniversaryYear) + "-" + getProperty(AnniversaryMonth) + "-" + getProperty(AnniversaryDay); nodeWithContent(xml, "anniversary", adate, false); } if (this.haveProperty(HomePhone)) { xml.append(" <phone>\n"); xml.append(" <type>home1</type>\n"); nodeWithContent(xml, "number", getProperty(HomePhone), false); xml.append(" </phone>\n"); } if (this.haveProperty(WorkPhone)) { xml.append(" <phone>\n"); xml.append(" <type>business1</type>\n"); nodeWithContent(xml, "number", getProperty(WorkPhone), false); xml.append(" </phone>\n"); } if (this.haveProperty(FaxNumber)) { xml.append(" <phone>\n"); xml.append(" <type>fax</type>\n"); nodeWithContent(xml, "number", getProperty(FaxNumber), false); xml.append(" </phone>\n"); } if (this.haveProperty(CellularNumber)) { xml.append(" <phone>\n"); xml.append(" <type>mobile</type>\n"); nodeWithContent(xml, "number", getProperty(CellularNumber), false); xml.append(" </phone>\n"); } if (this.haveProperty(PagerNumber)) { xml.append(" <phone>\n"); xml.append(" <type>page</type>\n"); nodeWithContent(xml, "number", getProperty(PagerNumber), false); xml.append(" </phone>\n"); } if (this.haveProperty(PrimaryEmail)) { xml.append(" <email type=\"primary\">\n"); nodeWithContent(xml, "display-name", getProperty(DisplayName), false); nodeWithContent(xml, "smtp-address", getProperty(PrimaryEmail), false); xml.append(" </email>\n"); } if (this.haveProperty(SecondEmail)) { xml.append(" <email>\n"); nodeWithContent(xml, "display-name", getProperty(DisplayName), false); nodeWithContent(xml, "smtp-address", getProperty(SecondEmail), false); xml.append(" </email>\n"); } // if the mail format is set... if (!this.getProperty(PreferMailFormat).equals(ContactEntry.MAIL_FORMAT_UNKNOWN)) { if (this.getProperty(PreferMailFormat).equals(ContactEntry.MAIL_FORMAT_PLAINTEXT)) { nodeWithContent(xml, "prefer-mail-format", "text", false); } else { nodeWithContent(xml, "prefer-mail-format", "html", false); } } if (this.haveProperty(HomeAddress) || this.haveProperty(HomeAddress2) || this.haveProperty(HomeCity) || this.haveProperty(HomeState) || this.haveProperty(HomeZipCode) || this.haveProperty(HomeCountry)) { xml.append(" <address>\n"); xml.append(" <type>home</type>\n"); nodeWithContent(xml, "street", this.getProperty(HomeAddress), false); nodeWithContent(xml, "street2", this.getProperty(HomeAddress2), false); nodeWithContent(xml, "locality", this.getProperty(HomeCity), false); nodeWithContent(xml, "region", this.getProperty(HomeState), false); nodeWithContent(xml, "postal-code", this.getProperty(HomeZipCode), false); nodeWithContent(xml, "country", this.getProperty(HomeCountry), false); xml.append(" </address>\n"); } if (this.haveProperty(WorkAddress) || this.haveProperty(WorkAddress2) || this.haveProperty(WorkCity) || this.haveProperty(WorkState) || this.haveProperty(WorkZipCode) || this.haveProperty(WorkCountry)) { xml.append(" <address>\n"); xml.append(" <type>business</type>\n"); nodeWithContent(xml, "street", this.getProperty(WorkAddress), false); nodeWithContent(xml, "street2", this.getProperty(WorkAddress2), false); nodeWithContent(xml, "locality", this.getProperty(WorkCity), false); nodeWithContent(xml, "region", this.getProperty(WorkState), false); nodeWithContent(xml, "postal-code", this.getProperty(WorkZipCode), false); nodeWithContent(xml, "country", this.getProperty(WorkCountry), false); xml.append(" </address>\n"); } // photo name = photo - this is an attachment (handled outside) // TODO handle this nodeWithContent(xml, "picture", this.getProperty(PhotoName), false); //nodeWithContent(xml, "preferred-address", this.getProperty(DefaultAddress"), false); @deprecated nodeWithContent(xml, "custom1", this.getProperty(Custom1), false); nodeWithContent(xml, "custom2", this.getProperty(Custom2), false); nodeWithContent(xml, "custom3", this.getProperty(Custom3), false); nodeWithContent(xml, "custom4", this.getProperty(Custom4), false); if (this.getProperty(AllowRemoteContent).equals("true")) nodeWithContent(xml, "allow-remote-content", "true", false); else nodeWithContent(xml, "allow-remote-content", "false", false); // add extra/missing fields /* * TODO: there shoudlnt be any extra fields if (fields != null) { xml.append(fields.toXmlString()); } */ xml.append("</contact>\n"); } public void setProperty(int field, String value) { fields[field] = value; } private String getProperty(int field) { return fields[field]; } public boolean parse () throws Exception { if (xmlData == null && data == null) { throw new Exception("No data available"); } if (xmlData == null) { InputStream xmlinput = new ByteArrayInputStream(data.toString().getBytes()); // Parse XML this.xmlData = Utils.getDocument(xmlinput); } /** * <contact> * <uid> * <sensitivity> * <name> * <given-name> * <last-name> * <full-name> * </name> * ... * </contact> */ boolean found = false; // set to true when valid data has been found; int email = 0; for(Element cur : new ChildElementAdapter(xmlData.getFirstChild())) { String name = cur.getNodeName().toUpperCase(); if(name.equals("LAST-MODIFICATION-DATE")) { // ignore continue; } if (name.equals("NAME")) { this.setProperty(DisplayName, Utils.getXmlElementString(cur, "FULL-NAME")); // additionaly save given this.setProperty(FirstName, Utils.getXmlElementString(cur, "GIVEN-NAME")); this.setProperty(LastName, Utils.getXmlElementString(cur, "LAST-NAME")); found = true; continue; } if (name.equals("PREFER-MAIL-FORMAT")) { String format = cur.getNodeValue(); if (format == null) continue; format = format.toUpperCase(); this.setProperty(PreferMailFormat, ContactEntry.MAIL_FORMAT_UNKNOWN); if (format.equals("PLAINTEXT") || format.equals("TEXT") || format.equals("TXT") || format.equals("PLAIN") || format.equals("1")) this.setProperty(PreferMailFormat, ContactEntry.MAIL_FORMAT_PLAINTEXT); if (format.equals("HTML") || format.equals("RUCHTEXT") || format.equals("RICH") || format.equals("2")) this.setProperty(PreferMailFormat, ContactEntry.MAIL_FORMAT_HTML); } if (name.equals("JOB-TITLE")) { this.setProperty(JobTitle, cur.getNodeValue()); found = true; continue; } if (name.equals("NICK-NAME")) { this.setProperty(NickName, cur.getNodeValue()); found = true; continue; } if (name.equals("EMAIL")) { //com.synckolab.tools.logMessage("email: " + email + " - " + cur.getXmlResult("SMTP-ADDRESS", ""), this.global.LOG_DEBUG + this.global.LOG_AB); switch (email) { case 0: this.setProperty(PrimaryEmail, Utils.getXmlElementString(cur, "SMTP-ADDRESS")); break; case 1: this.setProperty(SecondEmail, Utils.getXmlElementString(cur, "SMTP-ADDRESS")); break; default: // remember other emails // TODO: show some error (what to do?) //extraFields.addField("EMAIL", Utils.getXmlElementString(cur, "SMTP-ADDRESS")); break; } email++; found = true; continue; } if (name.equals("CATEGORIES")) { this.setProperty(Category, cur.getNodeValue()); continue; } if (name.equals("ORGANIZATION")) { this.setProperty(Company, cur.getNodeValue()); found = true; continue; } // these two are the same if (name.equals("PHONE")) { String num = Utils.getXmlElementString(cur, "NUMBER"); String type = Utils.getXmlElementString(cur, "TYPE"); if (type == null) { this.setProperty(CellularNumber, num); found = true; continue; } type = type.toUpperCase(); if (type.equals("MOBILE") || type.equals("CELLULAR")){ this.setProperty(CellularNumber, num); } else if (type.equals("HOME") || type.equals("HOME1")){ this.setProperty(HomePhone, num); } if (type.equals("FAX") || type.equals("BUSINESSFAX")){ this.setProperty(FaxNumber, num); } if (type.equals("BUSINESS") || type.equals("BUSINESS1")){ this.setProperty(WorkPhone, num); } if (type.equals("PAGE")){ this.setProperty(PagerNumber, num); } else { //TODO show some error continue; } found = true; } if (name.equals("BIRTHDAY")) { if (cur.getNodeValue() == null) continue; String[] tok = cur.getNodeValue().split("-"); this.setProperty(BirthYear, tok[0]); this.setProperty(BirthMonth, tok[1]); // BDAY: 1987-09-27 this.setProperty(BirthDay, tok[2]); found = true; continue; } // anniversary - not in vcard rfc?? if (name.equals("ANNIVERSARY")) { if (cur.getNodeValue() == null) continue; String tok[] = cur.getNodeValue().split("-"); this.setProperty(AnniversaryYear, tok[0]); this.setProperty(AnniversaryMonth, tok[1]); // BDAY:1987-09-27T08:30:00-06:00 this.setProperty(AnniversaryDay, tok[2]); found = true; continue; } if (name.equals("ADDRESS")) { String type = Utils.getXmlElementString(cur, "TYPE"); if ("HOME".equalsIgnoreCase(type)) { this.setProperty(HomeAddress, Utils.getXmlElementString(cur, "STREET")); this.setProperty(HomeAddress2, Utils.getXmlElementString(cur, "STREET2")); this.setProperty(HomeCity, Utils.getXmlElementString(cur, "LOCALITY")); this.setProperty(HomeState, Utils.getXmlElementString(cur, "REGION")); this.setProperty(HomeZipCode, Utils.getXmlElementString(cur, "POSTAL-CODE")); this.setProperty(HomeCountry, Utils.getXmlElementString(cur, "COUNTRY")); } else { this.setProperty(WorkAddress, Utils.getXmlElementString(cur, "STREET")); this.setProperty(WorkAddress2, Utils.getXmlElementString(cur, "STREET2")); this.setProperty(WorkCity, Utils.getXmlElementString(cur, "LOCALITY")); this.setProperty(WorkState, Utils.getXmlElementString(cur, "REGION")); this.setProperty(WorkZipCode, Utils.getXmlElementString(cur, "POSTAL-CODE")); this.setProperty(WorkCountry, Utils.getXmlElementString(cur, "COUNTRY")); } found = true; } if (name.equals("PICTURE")) { // TODO we should have a picture named /tmp/synckolab.img - this will be moved if we keep this contact this.setProperty(PhotoName, cur.getNodeValue()); continue; } if (name.equals("BODY")) { String cnotes = cur.getNodeValue(); // bugfix for invalid client data this.setProperty(Notes, cnotes.replaceAll("\\n", "\n")); found = true; continue; } if (name.equals("DEPARTMENT")) { this.setProperty(Department, cur.getNodeValue()); found = true; continue; } if (name.equals("WEB-PAGE")) { this.setProperty(WebPage1, cur.getNodeValue()); found = true; continue; } if (name.equals("BUSINESS-WEB-PAGE")) { this.setProperty(WebPage2, cur.getNodeValue()); found = true; continue; } if (name.equals("UID")) { uid = cur.getNodeValue(); continue; } if (name.equals("CUSTOM1")) { this.setProperty(Custom1, cur.getNodeValue()); continue; } if (name.equals("CUSTOM2")) { this.setProperty(Custom2, cur.getNodeValue()); continue; } if (name.equals("CUSTOM3")) { this.setProperty(Custom3, cur.getNodeValue()); continue; } if (name.equals("CUSTOM4")) { this.setProperty(Custom4, cur.getNodeValue()); continue; } if (name.equals("IM-ADDRESS")) { this.setProperty(AimScreenName, cur.getNodeValue()); continue; } if (name.equals("ALLOW-REMOTE-CONTENT")) { if ("TRUE".equalsIgnoreCase(cur.getNodeValue())) this.setProperty(AllowRemoteContent, "true"); else this.setProperty(AllowRemoteContent, "false"); continue; } // end parsing } return found; } /** * compares two sets of contacts * @return 0 if both are the same; != 0 is the index+1 thats not equals */ public int compareTo(ContactEntry another) { for(int i=0; i < this.fields.length; i++) { if (this.fields[i] == null && another.fields[i] == null) continue; if (this.fields[i] == null && another.fields[i] != null) return i+1; if (this.fields[i] != null && another.fields[i] == null) return i+1; if (!this.fields[i].equals(another.fields[i])) return i+1; } return 0; } /** * @param another the other contact entry * @return true if the same (see: compareTo() ) */ public boolean equals(ContactEntry another) { return compareTo(another) == 0; } }