/* * Copyright (C) 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, * 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 android.syncml.pim.vcard; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.apache.commons.codec.binary.Base64; import android.provider.Contacts; import android.syncml.pim.vcard.ContactStruct.PhoneData; /** * Compose VCard string */ public class VCardComposer { final public static int VERSION_VCARD21_INT = 1; final public static int VERSION_VCARD30_INT = 2; /** * A new line */ private String mNewline; /** * The composed string */ private StringBuilder mResult; /** * The email's type */ static final private HashSet<String> emailTypes = new HashSet<String>( Arrays.asList("CELL", "AOL", "APPLELINK", "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL", "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400")); static final private HashSet<String> phoneTypes = new HashSet<String>( Arrays.asList("PREF", "WORK", "HOME", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO")); static final private String TAG = "VCardComposer"; public VCardComposer() { } private static final HashMap<Integer, String> phoneTypeMap = new HashMap<Integer, String>(); private static final HashMap<Integer, String> emailTypeMap = new HashMap<Integer, String>(); static { phoneTypeMap.put(Contacts.Phones.TYPE_HOME, "HOME"); phoneTypeMap.put(Contacts.Phones.TYPE_MOBILE, "CELL"); phoneTypeMap.put(Contacts.Phones.TYPE_WORK, "WORK"); // FAX_WORK not exist in vcard spec. The approximate is the combine of // WORK and FAX, here only map to FAX phoneTypeMap.put(Contacts.Phones.TYPE_FAX_WORK, "WORK;FAX"); phoneTypeMap.put(Contacts.Phones.TYPE_FAX_HOME, "HOME;FAX"); phoneTypeMap.put(Contacts.Phones.TYPE_PAGER, "PAGER"); phoneTypeMap.put(Contacts.Phones.TYPE_OTHER, "X-OTHER"); emailTypeMap.put(Contacts.ContactMethods.TYPE_HOME, "HOME"); emailTypeMap.put(Contacts.ContactMethods.TYPE_WORK, "WORK"); } /** * Create a vCard String. * * @param struct * see more from ContactStruct class * @param vcardversion * MUST be VERSION_VCARD21 /VERSION_VCARD30 * @return vCard string * @throws VCardException * struct.name is null /vcardversion not match */ public String createVCard(ContactStruct struct, int vcardversion) throws VCardException { mResult = new StringBuilder(); // check exception: if (struct.name == null || struct.name.trim().equals("")) { throw new VCardException(" struct.name MUST have value."); } if (vcardversion == VERSION_VCARD21_INT) { mNewline = "\r\n"; } else if (vcardversion == VERSION_VCARD30_INT) { mNewline = "\n"; } else { throw new VCardException( " version not match VERSION_VCARD21 or VERSION_VCARD30."); } // build vcard: mResult.append("BEGIN:VCARD").append(mNewline); if (vcardversion == VERSION_VCARD21_INT) { mResult.append("VERSION:2.1").append(mNewline); } else { mResult.append("VERSION:3.0").append(mNewline); } if (!isNull(struct.name)) { appendNameStr(struct.name); } if (!isNull(struct.company)) { mResult.append("ORG:").append(struct.company).append(mNewline); } if (!isNull(struct.notes)) { mResult.append("NOTE:").append( foldingString(struct.notes, vcardversion)).append(mNewline); } if (!isNull(struct.title)) { mResult.append("TITLE:").append( foldingString(struct.title, vcardversion)).append(mNewline); } if (struct.photoBytes != null) { appendPhotoStr(struct.photoBytes, struct.photoType, vcardversion); } if (struct.phoneList != null) { appendPhoneStr(struct.phoneList, vcardversion); } if (struct.contactmethodList != null) { appendContactMethodStr(struct.contactmethodList, vcardversion); } mResult.append("END:VCARD").append(mNewline); return mResult.toString(); } /** * Alter str to folding supported format. * * @param str * the string to be folded * @param version * the vcard version * @return the folded string */ private String foldingString(String str, int version) { if (str.endsWith("\r\n")) { str = str.substring(0, str.length() - 2); } else if (str.endsWith("\n")) { str = str.substring(0, str.length() - 1); } else { return null; } str = str.replaceAll("\r\n", "\n"); if (version == VERSION_VCARD21_INT) { return str.replaceAll("\n", "\r\n "); } else if (version == VERSION_VCARD30_INT) { return str.replaceAll("\n", "\n "); } else { return null; } } /** * Build LOGO property. format LOGO's param and encode value as base64. * * @param bytes * the binary string to be converted * @param type * the type of the content * @param version * the version of vcard */ private void appendPhotoStr(byte[] bytes, String type, int version) throws VCardException { String value, apptype, encodingStr; try { value = foldingString(new String(Base64.encodeBase64(bytes, true)), version); } catch (Exception e) { throw new VCardException(e.getMessage()); } if (isNull(type)) { type = "image/jpeg"; } if (type.indexOf("jpeg") > 0) { apptype = "JPEG"; } else if (type.indexOf("gif") > 0) { apptype = "GIF"; } else if (type.indexOf("bmp") > 0) { apptype = "BMP"; } else { apptype = type.substring(type.indexOf("/")).toUpperCase(); } mResult.append("LOGO;TYPE=").append(apptype); if (version == VERSION_VCARD21_INT) { encodingStr = ";ENCODING=BASE64:"; value = value + mNewline; } else if (version == VERSION_VCARD30_INT) { encodingStr = ";ENCODING=b:"; } else { return; } mResult.append(encodingStr).append(value).append(mNewline); } private boolean isNull(String str) { if (str == null || str.trim().equals("")) { return true; } return false; } /** * Build FN and N property. format N's value. * * @param name * the name of the contact */ private void appendNameStr(String name) { mResult.append("FN:").append(name).append(mNewline); mResult.append("N:").append(name).append(mNewline); /* * if(name.indexOf(";") > 0) * mResult.append("N:").append(name).append(mNewline); else * if(name.indexOf(" ") > 0) mResult.append("N:").append(name.replace(' ', * ';')). append(mNewline); else * mResult.append("N:").append(name).append("; ").append(mNewline); */ } /** Loop append TEL property. */ private void appendPhoneStr(List<ContactStruct.PhoneData> phoneList, int version) { HashMap<String, String> numMap = new HashMap<String, String>(); String joinMark = version == VERSION_VCARD21_INT ? ";" : ","; for (ContactStruct.PhoneData phone : phoneList) { String type; if (!isNull(phone.data)) { type = getPhoneTypeStr(phone); if (version == VERSION_VCARD30_INT && type.indexOf(";") != -1) { type = type.replace(";", ","); } if (numMap.containsKey(phone.data)) { type = numMap.get(phone.data) + joinMark + type; } numMap.put(phone.data, type); } } for (Map.Entry<String, String> num : numMap.entrySet()) { if (version == VERSION_VCARD21_INT) { mResult.append("TEL;"); } else { // vcard3.0 mResult.append("TEL;TYPE="); } mResult.append(num.getValue()).append(":").append(num.getKey()) .append(mNewline); } } private String getPhoneTypeStr(PhoneData phone) { int phoneType = Integer.parseInt(phone.type); String typeStr, label; if (phoneTypeMap.containsKey(phoneType)) { typeStr = phoneTypeMap.get(phoneType); } else if (phoneType == Contacts.Phones.TYPE_CUSTOM) { label = phone.label.toUpperCase(); if (phoneTypes.contains(label) || label.startsWith("X-")) { typeStr = label; } else { typeStr = "X-CUSTOM-" + label; } } else { // TODO: need be updated with the provider's future changes typeStr = "VOICE"; // the default type is VOICE in spec. } return typeStr; } /** Loop append ADR / EMAIL property. */ private void appendContactMethodStr( List<ContactStruct.ContactMethod> contactMList, int version) { HashMap<String, String> emailMap = new HashMap<String, String>(); String joinMark = version == VERSION_VCARD21_INT ? ";" : ","; for (ContactStruct.ContactMethod contactMethod : contactMList) { // same with v2.1 and v3.0 switch (Integer.parseInt(contactMethod.kind)) { case Contacts.KIND_EMAIL: String mailType = "INTERNET"; if (!isNull(contactMethod.data)) { int methodType = new Integer(contactMethod.type).intValue(); if (emailTypeMap.containsKey(methodType)) { mailType = emailTypeMap.get(methodType); } else if (emailTypes.contains(contactMethod.label .toUpperCase())) { mailType = contactMethod.label.toUpperCase(); } if (emailMap.containsKey(contactMethod.data)) { mailType = emailMap.get(contactMethod.data) + joinMark + mailType; } emailMap.put(contactMethod.data, mailType); } break; case Contacts.KIND_POSTAL: if (!isNull(contactMethod.data)) { mResult.append("ADR;TYPE=POSTAL:").append( foldingString(contactMethod.data, version)).append( mNewline); } break; default: break; } } for (Map.Entry<String, String> email : emailMap.entrySet()) { if (version == VERSION_VCARD21_INT) { mResult.append("EMAIL;"); } else { mResult.append("EMAIL;TYPE="); } mResult.append(email.getValue()).append(":").append(email.getKey()) .append(mNewline); } } }