Automated import from //branches/donutburger/...@142509,142509
diff --git a/core/java/android/syncml/pim/PropertyNode.java b/core/java/android/syncml/pim/PropertyNode.java
index 13d4930..cc52499 100644
--- a/core/java/android/syncml/pim/PropertyNode.java
+++ b/core/java/android/syncml/pim/PropertyNode.java
@@ -16,25 +16,142 @@
 
 package android.syncml.pim;
 
-import java.util.ArrayList;
-import java.util.Collection;
-
 import android.content.ContentValues;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 public class PropertyNode {
 
     public String propName;
 
-    public String propValue = "";
+    public String propValue;
 
-    public Collection<String> propValue_vector;
+    public List<String> propValue_vector;
 
-    /** Store value as byte[],after decode. */
-    public byte[] propValue_byts;
+    /** Store value as byte[],after decode.
+     * Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc.
+     */
+    public byte[] propValue_bytes;
 
-    /** param store: key=paramType, value=paramValue */
-    public ContentValues paraMap = new ContentValues();
+    /** param store: key=paramType, value=paramValue
+     * Note that currently PropertyNode class does not support multiple param-values
+     * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
+     * one String value like "A,B", not ["A", "B"]...
+     * TODO: fix this. 
+     */
+    public ContentValues paramMap;
 
     /** Only for TYPE=??? param store. */
-    public ArrayList<String> paraMap_TYPE = new ArrayList<String>();
+    public Set<String> paramMap_TYPE;
+
+    /** Store group values. Used only in VCard. */
+    public Set<String> propGroupSet;
+    
+    public PropertyNode() {
+        propValue = "";
+        paramMap = new ContentValues();
+        paramMap_TYPE = new HashSet<String>();
+        propGroupSet = new HashSet<String>();
+    }
+    
+    public PropertyNode(
+            String propName, String propValue, List<String> propValue_vector,
+            byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE,
+            Set<String> propGroupSet) {
+        this.propName = propName;
+        if (propValue != null) {
+            this.propValue = propValue;
+        } else {
+            this.propValue = "";
+        }
+        this.propValue_vector = propValue_vector;
+        this.propValue_bytes = propValue_bytes;
+        if (paramMap != null) {
+            this.paramMap = paramMap;
+        } else {
+            this.paramMap = new ContentValues();
+        }
+        if (paramMap_TYPE != null) {
+            this.paramMap_TYPE = paramMap_TYPE;
+        } else {
+            this.paramMap_TYPE = new HashSet<String>();
+        }
+        if (propGroupSet != null) {
+            this.propGroupSet = propGroupSet;
+        } else {
+            this.propGroupSet = new HashSet<String>();
+        }
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PropertyNode)) {
+            return false;
+        }
+        
+        PropertyNode node = (PropertyNode)obj;
+        
+        if (propName == null || !propName.equals(node.propName)) {
+            return false;
+        } else if (!paramMap.equals(node.paramMap)) {
+            return false;
+        } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
+            return false;
+        } else if (!propGroupSet.equals(node.propGroupSet)) {
+            return false;
+        }
+        
+        if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) {
+            return true;
+        } else {
+            // Log.d("@@@", propValue + ", " + node.propValue);
+            if (!propValue.equals(node.propValue)) {
+                return false;
+            }
+
+            // The value in propValue_vector is not decoded even if it should be
+            // decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector
+            // is 1, the encoded value is stored in propValue, so we do not have to
+            // check it.
+            if (propValue_vector != null) {
+                // Log.d("@@@", "===" + propValue_vector + ", " + node.propValue_vector);
+                return (propValue_vector.equals(node.propValue_vector) ||
+                        (propValue_vector.size() == 1));
+            } else if (node.propValue_vector != null) {
+                // Log.d("@@@", "===" + propValue_vector + ", " + node.propValue_vector);
+                return (node.propValue_vector.equals(propValue_vector) ||
+                        (node.propValue_vector.size() == 1));
+            } else {
+                return true;
+            }
+        }
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("propName: ");
+        builder.append(propName);
+        builder.append(", paramMap: ");
+        builder.append(paramMap.toString());
+        builder.append(", propmMap_TYPE: ");
+        builder.append(paramMap_TYPE.toString());
+        builder.append(", propGroupSet: ");
+        builder.append(propGroupSet.toString());
+        if (propValue_vector != null && propValue_vector.size() > 1) {
+            builder.append(", propValue_vector size: ");
+            builder.append(propValue_vector.size());
+        }
+        if (propValue_bytes != null) {
+            builder.append(", propValue_bytes size: ");
+            builder.append(propValue_bytes.length);
+        }
+        builder.append(", propValue: ");
+        builder.append(propValue);
+        return builder.toString();
+    }
 }
diff --git a/core/java/android/syncml/pim/VBuilder.java b/core/java/android/syncml/pim/VBuilder.java
index 822c2ce..4528645 100644
--- a/core/java/android/syncml/pim/VBuilder.java
+++ b/core/java/android/syncml/pim/VBuilder.java
@@ -16,7 +16,7 @@
 
 package android.syncml.pim;
 
-import java.util.Collection;
+import java.util.List;
 
 public interface VBuilder {
     void start();
@@ -38,9 +38,14 @@
     void endProperty();
 
     /**
+     * @param group 
+     */
+    void propertyGroup(String group);
+    
+    /**
      * @param name
-     *            a.N <br>
-     *            a.N
+     *            N <br>
+     *            N
      */
     void propertyName(String name);
 
@@ -58,5 +63,5 @@
      */
     void propertyParamValue(String value);
 
-    void propertyValues(Collection<String> values);
+    void propertyValues(List<String> values);
 }
diff --git a/core/java/android/syncml/pim/VDataBuilder.java b/core/java/android/syncml/pim/VDataBuilder.java
index f0a0cb9..8c67cf5 100644
--- a/core/java/android/syncml/pim/VDataBuilder.java
+++ b/core/java/android/syncml/pim/VDataBuilder.java
@@ -16,11 +16,19 @@
 
 package android.syncml.pim;
 
+import android.content.ContentValues;
+import android.util.Log;
+
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.codec.net.QuotedPrintableCodec;
 
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
+import java.util.Vector;
 
 /**
  * Store the parse result to custom datastruct: VNode, PropertyNode
@@ -29,14 +37,32 @@
  * PropertyNode: standy by a property line of a card.
  */
 public class VDataBuilder implements VBuilder {
+    static private String LOG_TAG = "VDATABuilder"; 
 
     /** type=VNode */
-    public ArrayList<VNode> vNodeList = new ArrayList<VNode>();
-    int nodeListPos = 0;
-    VNode curVNode;
-    PropertyNode curPropNode;
-    String curParamType;
+    public List<VNode> vNodeList = new ArrayList<VNode>();
+    private int mNodeListPos = 0;
+    private VNode mCurrentVNode;
+    private PropertyNode mCurrentPropNode;
+    private String mCurrentParamType;
+    
+    /**
+     * Assumes that each String can be encoded into byte array using this encoding.
+     */
+    private String mCharset;
+    
+    private boolean mStrictLineBreakParsing;
+    
+    public VDataBuilder() {
+        mCharset = "ISO-8859-1";
+        mStrictLineBreakParsing = false;
+    }
 
+    public VDataBuilder(String encoding, boolean strictLineBreakParsing) {
+        mCharset = encoding;
+        mStrictLineBreakParsing = strictLineBreakParsing;
+    }
+    
     public void start() {
     }
 
@@ -48,79 +74,171 @@
         vnode.parseStatus = 1;
         vnode.VName = type;
         vNodeList.add(vnode);
-        nodeListPos = vNodeList.size()-1;
-        curVNode = vNodeList.get(nodeListPos);
+        mNodeListPos = vNodeList.size()-1;
+        mCurrentVNode = vNodeList.get(mNodeListPos);
     }
 
     public void endRecord() {
-        VNode endNode = vNodeList.get(nodeListPos);
+        VNode endNode = vNodeList.get(mNodeListPos);
         endNode.parseStatus = 0;
-        while(nodeListPos > 0){
-            nodeListPos--;
-            if((vNodeList.get(nodeListPos)).parseStatus == 1)
+        while(mNodeListPos > 0){
+            mNodeListPos--;
+            if((vNodeList.get(mNodeListPos)).parseStatus == 1)
                 break;
         }
-        curVNode = vNodeList.get(nodeListPos);
+        mCurrentVNode = vNodeList.get(mNodeListPos);
     }
 
     public void startProperty() {
-    //  System.out.println("+ startProperty. ");
+        //  System.out.println("+ startProperty. ");
     }
 
     public void endProperty() {
-    //  System.out.println("- endProperty. ");
+        //  System.out.println("- endProperty. ");
     }
-
+    
     public void propertyName(String name) {
-        curPropNode = new PropertyNode();
-        curPropNode.propName = name;
+        mCurrentPropNode = new PropertyNode();
+        mCurrentPropNode.propName = name;
     }
 
+    // Used only in VCard.
+    public void propertyGroup(String group) {
+        mCurrentPropNode.propGroupSet.add(group);
+    }
+    
     public void propertyParamType(String type) {
-        curParamType = type;
+        mCurrentParamType = type;
     }
 
     public void propertyParamValue(String value) {
-        if(curParamType == null)
-            curPropNode.paraMap_TYPE.add(value);
-        else if(curParamType.equalsIgnoreCase("TYPE"))
-            curPropNode.paraMap_TYPE.add(value);
-        else
-            curPropNode.paraMap.put(curParamType, value);
+        if (mCurrentParamType == null ||
+                mCurrentParamType.equalsIgnoreCase("TYPE")) {
+            mCurrentPropNode.paramMap_TYPE.add(value);
+        } else {
+            mCurrentPropNode.paramMap.put(mCurrentParamType, value);
+        }
 
-        curParamType = null;
+        mCurrentParamType = null;
     }
 
-    public void propertyValues(Collection<String> values) {
-        curPropNode.propValue_vector = values;
-        curPropNode.propValue = listToString(values);
-        //decode value string to propValue_byts
-        if(curPropNode.paraMap.containsKey("ENCODING")){
-            if(curPropNode.paraMap.getAsString("ENCODING").
-                                        equalsIgnoreCase("BASE64")){
-                curPropNode.propValue_byts =
-                    Base64.decodeBase64(curPropNode.propValue.
+    private String encodeString(String originalString, String targetEncoding) {
+        Charset charset = Charset.forName(mCharset);
+        ByteBuffer byteBuffer = charset.encode(originalString);
+        // byteBuffer.array() "may" return byte array which is larger than
+        // byteBuffer.remaining(). Here, we keep on the safe side.
+        byte[] bytes = new byte[byteBuffer.remaining()];
+        byteBuffer.get(bytes);
+        try {
+            return new String(bytes, targetEncoding);
+        } catch (UnsupportedEncodingException e) {
+            return null;
+        }
+    }
+    
+    public void propertyValues(List<String> values) {
+        ContentValues paramMap = mCurrentPropNode.paramMap;
+        
+        String charsetString = paramMap.getAsString("CHARSET"); 
+
+        boolean setupParamValues = false;
+        //decode value string to propValue_bytes
+        if (paramMap.containsKey("ENCODING")) {
+            String encoding = paramMap.getAsString("ENCODING"); 
+            if (encoding.equalsIgnoreCase("BASE64") ||
+                    encoding.equalsIgnoreCase("B")) {
+                if (values.size() > 1) {
+                    Log.e(LOG_TAG,
+                            ("BASE64 encoding is used while " +
+                             "there are multiple values (" + values.size()));
+                }
+                mCurrentPropNode.propValue_bytes =
+                    Base64.decodeBase64(values.get(0).
                             replaceAll(" ","").replaceAll("\t","").
                             replaceAll("\r\n","").
                             getBytes());
             }
-            if(curPropNode.paraMap.getAsString("ENCODING").
-                                        equalsIgnoreCase("QUOTED-PRINTABLE")){
+
+            if(encoding.equalsIgnoreCase("QUOTED-PRINTABLE")){
+                // if CHARSET is defined, we translate each String into the Charset.
+                List<String> tmpValues = new ArrayList<String>();
+                Vector<byte[]> byteVector = new Vector<byte[]>();
+                int size = 0;
                 try{
-                    curPropNode.propValue_byts =
-                        QuotedPrintableCodec.decodeQuotedPrintable(
-                                curPropNode.propValue.
-                                replaceAll("= ", " ").replaceAll("=\t", "\t").
-                                getBytes() );
-                    curPropNode.propValue =
-                        new String(curPropNode.propValue_byts);
-                }catch(Exception e){
-                    System.out.println("=Decode quoted-printable exception.");
-                    e.printStackTrace();
+                    for (String value : values) {                                    
+                        String quotedPrintable = value
+                        .replaceAll("= ", " ").replaceAll("=\t", "\t");
+                        String[] lines;
+                        if (mStrictLineBreakParsing) {
+                            lines = quotedPrintable.split("\r\n");
+                        } else {
+                            lines = quotedPrintable
+                            .replace("\r\n", "\n").replace("\r", "\n").split("\n");
+                        }
+                        StringBuilder builder = new StringBuilder();
+                        for (String line : lines) {
+                            if (line.endsWith("=")) {
+                                line = line.substring(0, line.length() - 1);
+                            }
+                            builder.append(line);
+                        }
+                        byte[] bytes = QuotedPrintableCodec.decodeQuotedPrintable(
+                                builder.toString().getBytes());
+                        if (charsetString != null) {
+                            try {
+                                tmpValues.add(new String(bytes, charsetString));
+                            } catch (UnsupportedEncodingException e) {
+                                Log.e(LOG_TAG, "Failed to encode: charset=" + charsetString);
+                                tmpValues.add(new String(bytes));
+                            }
+                        } else {
+                            tmpValues.add(new String(bytes));
+                        }
+                        byteVector.add(bytes);
+                        size += bytes.length;
+                    }  // for (String value : values) {
+                    mCurrentPropNode.propValue_vector = tmpValues;
+                    mCurrentPropNode.propValue = listToString(tmpValues);
+
+                    mCurrentPropNode.propValue_bytes = new byte[size];
+
+                    {
+                        byte[] tmpBytes = mCurrentPropNode.propValue_bytes;
+                        int index = 0;
+                        for (byte[] bytes : byteVector) {
+                            int length = bytes.length;
+                            for (int i = 0; i < length; i++, index++) {
+                                tmpBytes[index] = bytes[i];
+                            }
+                        }
+                    }
+                    setupParamValues = true;
+                } catch(Exception e) {
+                    Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
                 }
+            }  // QUOTED-PRINTABLE
+        }  //  ENCODING
+        
+        if (!setupParamValues) {
+            // if CHARSET is defined, we translate each String into the Charset.
+            if (charsetString != null) {
+                List<String> tmpValues = new ArrayList<String>();
+                for (String value : values) {
+                    String result = encodeString(value, charsetString);
+                    if (result != null) {
+                        tmpValues.add(result);
+                    } else {
+                        Log.e(LOG_TAG, "Failed to encode: charset=" + charsetString);
+                        tmpValues.add(value);
+                    }
+                }
+                values = tmpValues;
             }
+            
+            mCurrentPropNode.propValue_vector = values;
+            mCurrentPropNode.propValue = listToString(values);
         }
-        curVNode.propList.add(curPropNode);
+        mCurrentVNode.propList.add(mCurrentPropNode);
     }
 
     private String listToString(Collection<String> list){
@@ -134,7 +252,7 @@
         }
         return typeListB.toString();
     }
-
+    
     public String getResult(){
         return null;
     }
diff --git a/core/java/android/syncml/pim/vcard/VCardParser.java b/core/java/android/syncml/pim/vcard/VCardParser.java
index 3926243c..6dad852d 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser.java
@@ -26,7 +26,8 @@
 
 public class VCardParser {
 
-    VParser mParser = null;
+    // TODO: fix this.
+    VCardParser_V21 mParser = null;
 
     public final static String VERSION_VCARD21 = "vcard2.1";
 
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V21.java b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
index b6fa032..f853c5e 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
@@ -16,21 +16,24 @@
 
 package android.syncml.pim.vcard;
 
-import android.syncml.pim.VParser;
+import android.syncml.pim.VBuilder;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
  * This class is used to parse vcard. Please refer to vCard Specification 2.1
  */
-public class VCardParser_V21 extends VParser {
+public class VCardParser_V21 {
 
     /** Store the known-type */
-    private static final HashSet<String> mKnownTypeSet = new HashSet<String>(
+    private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
             Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
                     "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
                     "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
@@ -39,13 +42,40 @@
                     "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
                     "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
                     "WAVE", "AIFF", "PCM", "X509", "PGP"));
+    
+    /** Store the known-value */
+    private static final HashSet<String> sKnownValueSet = new HashSet<String>(
+            Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
+        
+    /** Store the property name available in vCard 2.1 */
+    // NICKNAME is not supported in vCard 2.1, but some vCard may contain.
+    private static final HashSet<String> sAvailablePropertyNameV21 =
+        new HashSet<String>(Arrays.asList(
+                "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+                "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+                "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER",
+                "NICKNAME"));
 
-    /** Store the name */
-    private static final HashSet<String> mName = new HashSet<String>(Arrays
-            .asList("LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
-                    "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
-                    "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
+    // Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
+    // We allow it for safety...
+    private static final HashSet<String> sAvailableEncodingV21 =
+        new HashSet<String>(Arrays.asList(
+                "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
+    
+    // Used only for parsing END:VCARD.
+    private String mPreviousLine;
+    
+    /** The builder to build parsed data */
+    protected VBuilder mBuilder = null;
 
+    /** The encoding type */
+    protected String mEncoding = null;
+    
+    protected final String sDefaultEncoding = "8BIT";
+    
+    // Should not directly read a line from this. Use getLine() instead.
+    protected BufferedReader mReader;
+    
     /**
      * Create a new VCard parser.
      */
@@ -55,916 +85,597 @@
 
     /**
      * Parse the file at the given position
-     *
-     * @param offset
-     *            the given position to parse
-     * @return vcard length
+     * vcard_file   = [wsls] vcard [wsls]
      */
-    protected int parseVFile(int offset) {
-        return parseVCardFile(offset);
+    protected void parseVCardFile() throws IOException, VCardException {
+        while (parseOneVCard()) {
+        }
+    }
+
+    protected String getVersion() {
+        return "2.1";
+    }
+    
+    /**
+     * @return true when the propertyName is a valid property name.
+     */
+    protected boolean isValidPropertyName(String propertyName) {
+        return sAvailablePropertyNameV21.contains(propertyName.toUpperCase());
     }
 
     /**
-     * [wsls] vcard [wsls]
+     * @return true when the encoding is a valid encoding.
      */
-    int parseVCardFile(int offset) {
-        int ret = 0, sum = 0;
-
-        /* remove \t \r\n */
-        while ((ret = parseWsls(offset)) != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-
-        ret = parseVCard(offset); // BEGIN:VCARD ... END:VCARD
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        } else {
-            return PARSE_ERROR;
-        }
-
-        /* remove \t \r\n */
-        while ((ret = parseWsls(offset)) != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-        return sum;
+    protected boolean isValidEncoding(String encoding) {
+        return sAvailableEncodingV21.contains(encoding.toUpperCase());
     }
-
+    
     /**
-     * "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":"
-     * "VCARD"
+     * @return String. It may be null, or its length may be 0
+     * @throws IOException
      */
-    private int parseVCard(int offset) {
-        int ret = 0, sum = 0;
-
-        /* BEGIN */
-        ret = parseString(offset, "BEGIN", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
+    protected String getLine() throws IOException {
+        return mReader.readLine();
+    }
+    
+    /**
+     * @return String with it's length > 0
+     * @throws IOException
+     * @throws VCardException when the stream reached end of line
+     */
+    protected String getNonEmptyLine() throws IOException, VCardException {
+        String line;
+        while (true) {
+            line = getLine();
+            if (line == null) {
+                throw new VCardException("Reached end of buffer.");
+            } else if (line.trim().length() > 0) {
+                return line;
+            }
         }
-        offset += ret;
-        sum += ret;
-
-        /* [ws] */
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        /* colon */
-        ret = parseString(offset, ":", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
+    }
+    
+    /**
+     *  vcard        = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+     *                 items *CRLF
+     *                 "END" [ws] ":" [ws] "VCARD"
+     */
+    private boolean parseOneVCard() throws IOException, VCardException {
+        if (!readBeginVCard()) {
+            return false;
         }
-        offset += ret;
-        sum += ret;
-
-        /* [ws] */
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        /* VCARD */
-        ret = parseString(offset, "VCARD", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
+        parseItems();
+        readEndVCard();
+        return true;
+    }
+    
+    /**
+     * @return True when successful. False when reaching the end of line  
+     * @throws IOException
+     * @throws VCardException
+     */
+    protected boolean readBeginVCard() throws IOException, VCardException {
+        String line;
+        while (true) {
+            line = getLine();
+            if (line == null) {
+                return false;
+            } else if (line.trim().length() > 0) {
+                break;
+            }
         }
-        offset += ret;
-        sum += ret;
+        String[] strArray = line.split(":", 2);
+        
+        // Though vCard specification does not allow lower cases,
+        // some data may have them, so we allow it.
+        if (!(strArray.length == 2 &&
+                strArray[0].trim().equalsIgnoreCase("BEGIN") && 
+                strArray[1].trim().equalsIgnoreCase("VCARD"))) {
+            throw new VCardException("BEGIN:VCARD != \"" + line + "\"");
+        }
+        
         if (mBuilder != null) {
             mBuilder.startRecord("VCARD");
         }
 
-        /* [ws] */
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        /* 1*CRLF */
-        ret = parseCrlf(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
+        return true;
+    }
+    
+    protected void readEndVCard() throws VCardException {
+        // Though vCard specification does not allow lower cases,
+        // some data may have them, so we allow it.
+        String[] strArray = mPreviousLine.split(":", 2);
+        if (!(strArray.length == 2 &&
+                strArray[0].trim().equalsIgnoreCase("END") &&
+                strArray[1].trim().equalsIgnoreCase("VCARD"))) {
+            throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
         }
-        offset += ret;
-        sum += ret;
-        while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-
-        ret = parseItems(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        /* *CRLF */
-        while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-
-        /* END */
-        ret = parseString(offset, "END", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        /* [ws] */
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        /* colon */
-        ret = parseString(offset, ":", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        /* [ws] */
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        /* VCARD */
-        ret = parseString(offset, "VCARD", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        // offset += ret;
-        sum += ret;
+        
         if (mBuilder != null) {
             mBuilder.endRecord();
         }
-
-        return sum;
     }
-
+    
     /**
-     * items *CRLF item / item
+     * items = *CRLF item 
+     *       / item
      */
-    private int parseItems(int offset) {
+    protected void parseItems() throws IOException, VCardException {
         /* items *CRLF item / item */
-        int ret = 0, sum = 0;
-
+        boolean ended = false;
+        
         if (mBuilder != null) {
             mBuilder.startProperty();
         }
-        ret = parseItem(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.endProperty();
-        }
 
-        for (;;) {
-            while ((ret = parseCrlf(offset)) != PARSE_ERROR) {
-                offset += ret;
-                sum += ret;
-            }
-            // follow VCARD ,it wont reach endProperty
-            if (mBuilder != null) {
-                mBuilder.startProperty();
-            }
-
-            ret = parseItem(offset);
-            if (ret == PARSE_ERROR) {
-                break;
-            }
-            offset += ret;
-            sum += ret;
+        try {
+            ended = parseItem();
+        } finally {
             if (mBuilder != null) {
                 mBuilder.endProperty();
             }
         }
 
-        return sum;
+        while (!ended) {
+            // follow VCARD ,it wont reach endProperty
+            if (mBuilder != null) {
+                mBuilder.startProperty();
+            }
+            try {
+                ended = parseItem();
+            } finally {
+                if (mBuilder != null) {
+                    mBuilder.endProperty();
+                }
+            }
+        }
     }
 
     /**
-     * item0 / item1 / item2
+     * item      = [groups "."] name    [params] ":" value CRLF
+     *           / [groups "."] "ADR"   [params] ":" addressparts CRLF
+     *           / [groups "."] "ORG"   [params] ":" orgparts CRLF
+     *           / [groups "."] "N"     [params] ":" nameparts CRLF
+     *           / [groups "."] "AGENT" [params] ":" vcard CRLF 
      */
-    private int parseItem(int offset) {
-        int ret = 0, sum = 0;
-        mEncoding = mDefaultEncoding;
+    protected boolean parseItem() throws IOException, VCardException {
+        mEncoding = sDefaultEncoding;
 
-        ret = parseItem0(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
+        // params    = ";" [ws] paramlist
+        String line = getNonEmptyLine();
+        String[] strArray = line.split(":", 2);
+        if (strArray.length < 2) {
+            throw new VCardException("Invalid line(\":\" does not exist): " + line);
         }
-
-        ret = parseItem1(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
+        String propertyValue = strArray[1];
+        String[] groupNameParamsArray = strArray[0].split(";");
+        String groupAndName = groupNameParamsArray[0].trim();
+        String[] groupNameArray = groupAndName.split("\\.");
+        int length = groupNameArray.length;
+        String propertyName = groupNameArray[length - 1];
+        if (mBuilder != null) {
+            mBuilder.propertyName(propertyName);
+            for (int i = 0; i < length - 1; i++) {
+                mBuilder.propertyGroup(groupNameArray[i]);
+            }
         }
-
-        ret = parseItem2(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
+        if (propertyName.equalsIgnoreCase("END")) {
+            mPreviousLine = line;
+            return true;
         }
-
-        return PARSE_ERROR;
+        
+        length = groupNameParamsArray.length;
+        for (int i = 1; i < length; i++) {
+            handleParams(groupNameParamsArray[i]);
+        }
+        
+        if (isValidPropertyName(propertyName) ||
+                propertyName.startsWith("X-")) {
+            if (propertyName.equals("VERSION") &&
+                    !propertyValue.equals(getVersion())) {
+                throw new VCardVersionException("Incompatible version: " + 
+                        propertyValue + " != " + getVersion());
+            }
+            handlePropertyValue(propertyName, propertyValue);
+            return false;
+        } else if (propertyName.equals("ADR") ||
+                propertyName.equals("ORG") ||
+                propertyName.equals("N")) {
+            handleMultiplePropertyValue(propertyName, propertyValue);
+            return false;
+        } else if (propertyName.equals("AGENT")) {
+            handleAgent(propertyValue);
+            return false;
+        }
+        
+        throw new VCardException("Unknown property name: \"" + 
+                propertyName + "\"");
     }
 
-    /** [groups "."] name [params] ":" value CRLF */
-    private int parseItem0(int offset) {
-        int ret = 0, sum = 0, start = offset;
-        String proName = "", proValue = "";
-
-        ret = parseGroupsWithDot(offset);
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
+    /**
+     * params      = ";" [ws] paramlist
+     * paramlist   = paramlist [ws] ";" [ws] param
+     *             / param
+     * param       = "TYPE" [ws] "=" [ws] ptypeval
+     *             / "VALUE" [ws] "=" [ws] pvalueval
+     *             / "ENCODING" [ws] "=" [ws] pencodingval
+     *             / "CHARSET" [ws] "=" [ws] charsetval
+     *             / "LANGUAGE" [ws] "=" [ws] langval
+     *             / "X-" word [ws] "=" [ws] word
+     *             / knowntype
+     */
+    protected void handleParams(String params) throws VCardException {
+        String[] strArray = params.split("=", 2);
+        if (strArray.length == 2) {
+            String paramName = strArray[0].trim();
+            String paramValue = strArray[1].trim();
+            if (paramName.equals("TYPE")) {
+                handleType(paramValue);
+            } else if (paramName.equals("VALUE")) {
+                handleValue(paramValue);
+            } else if (paramName.equals("ENCODING")) {
+                handleEncoding(paramValue);
+            } else if (paramName.equals("CHARSET")) {
+                handleCharset(paramValue);
+            } else if (paramName.equals("LANGUAGE")) {
+                handleLanguage(paramValue);
+            } else if (paramName.startsWith("X-")) {
+                handleAnyParam(paramName, paramValue);
+            } else {
+                throw new VCardException("Unknown type \"" + paramName + "\"");
+            }
+        } else {
+            handleType(strArray[0]);
         }
-
-        ret = parseName(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
+    }
+    
+    /**
+     * typeval  = knowntype / "X-" word
+     */
+    protected void handleType(String ptypeval) throws VCardException {
+        if (sKnownTypeSet.contains(ptypeval.toUpperCase()) ||
+                ptypeval.startsWith("X-")) {
+            if (mBuilder != null) {
+                mBuilder.propertyParamType("TYPE");
+                mBuilder.propertyParamValue(ptypeval.toUpperCase());
+            }
+        } else {
+            throw new VCardException("Unknown type: \"" + ptypeval + "\"");
+        }        
+    }
+    
+    /**
+     * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+     */
+    protected void handleValue(String pvalueval) throws VCardException {
+        if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
+                pvalueval.startsWith("X-")) {
+            if (mBuilder != null) {
+                mBuilder.propertyParamType("VALUE");
+                mBuilder.propertyParamValue(pvalueval);
+            }
+        } else {
+            throw new VCardException("Unknown value \"" + pvalueval + "\"");
         }
-        offset += ret;
-        sum += ret;
+    }
+    
+    /**
+     * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
+     */
+    protected void handleEncoding(String pencodingval) throws VCardException {
+        if (isValidEncoding(pencodingval) ||
+                pencodingval.startsWith("X-")) {
+            if (mBuilder != null) {
+                mBuilder.propertyParamType("ENCODING");
+                mBuilder.propertyParamValue(pencodingval);
+            }
+            mEncoding = pencodingval;
+        } else {
+            throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
+        }
+    }
+    
+    /**
+     * vCard specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+     * but some vCard contains other charset, so we allow them. 
+     */
+    protected void handleCharset(String charsetval) {
         if (mBuilder != null) {
-            proName = mBuffer.substring(start, offset).trim();
-            mBuilder.propertyName(proName);
+            mBuilder.propertyParamType("CHARSET");
+            mBuilder.propertyParamValue(charsetval);
         }
-
-        ret = parseParams(offset);
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
+    }
+    
+    /**
+     * See also Section 7.1 of RFC 1521
+     */
+    protected void handleLanguage(String langval) throws VCardException {
+        String[] strArray = langval.split("-");
+        if (strArray.length != 2) {
+            throw new VCardException("Invalid Language: \"" + langval + "\"");
         }
-
-        ret = parseString(offset, ":", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
+        String tmp = strArray[0];
+        int length = tmp.length();
+        for (int i = 0; i < length; i++) {
+            if (!isLetter(tmp.charAt(i))) {
+                throw new VCardException("Invalid Language: \"" + langval + "\"");
+            }
         }
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parseValue(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        proValue = mBuffer.substring(start, offset);
-        if (proName.equals("VERSION") && !proValue.equals("2.1")) {
-            return PARSE_ERROR;
+        tmp = strArray[1];
+        length = tmp.length();
+        for (int i = 0; i < length; i++) {
+            if (!isLetter(tmp.charAt(i))) {
+                throw new VCardException("Invalid Language: \"" + langval + "\"");
+            }
         }
         if (mBuilder != null) {
-            ArrayList<String> v = new ArrayList<String>();
-            v.add(proValue);
-            mBuilder.propertyValues(v);
+            mBuilder.propertyParamType("LANGUAGE");
+            mBuilder.propertyParamValue(langval);
         }
-
-        ret = parseCrlf(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        sum += ret;
-
-        return sum;
     }
 
-    /** "ADR" "ORG" "N" with semi-colon separated content */
-    private int parseItem1(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseGroupsWithDot(offset);
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-
-        if ((ret = parseString(offset, "ADR", true)) == PARSE_ERROR
-                && (ret = parseString(offset, "ORG", true)) == PARSE_ERROR
-                && (ret = parseString(offset, "N", true)) == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
+    /**
+     * Mainly for "X-" type. This accepts any kind of type without check.
+     */
+    protected void handleAnyParam(String paramName, String paramValue) {
         if (mBuilder != null) {
-            mBuilder.propertyName(mBuffer.substring(start, offset).trim());
+            mBuilder.propertyParamType(paramName);
+            mBuilder.propertyParamValue(paramValue);
         }
-
-        ret = parseParams(offset);
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
+    }
+    
+    protected void handlePropertyValue(
+            String propertyName, String propertyValue) throws
+            IOException, VCardException {
+        if (mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
+                || mEncoding.equalsIgnoreCase("8BIT")
+                || mEncoding.toUpperCase().startsWith("X-")) {
+            if (mBuilder != null) {
+                ArrayList<String> v = new ArrayList<String>();
+                v.add(maybeUnescapeText(propertyValue));
+                mBuilder.propertyValues(v);
+            }
+        } else if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+            String result = getQuotedPrintable(propertyValue);
+            if (mBuilder != null) {
+                ArrayList<String> v = new ArrayList<String>();
+                v.add(result);
+                mBuilder.propertyValues(v);
+            }
+        } else if (mEncoding.equalsIgnoreCase("BASE64") ||
+                mEncoding.equalsIgnoreCase("B")) {
+            String result = getBase64(propertyValue);
+            if (mBuilder != null) {
+                ArrayList<String> v = new ArrayList<String>();
+                v.add(result);
+                mBuilder.propertyValues(v);
+            }            
+        } else {
+            throw new VCardException("Unknown encoding: \"" + mEncoding + "\"");
         }
-
-        ret = parseString(offset, ":", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parseValue(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            int end = 0;
-            ArrayList<String> v = new ArrayList<String>();
-            Pattern p = Pattern
-                    .compile("([^;\\\\]*(\\\\[\\\\;:,])*[^;\\\\]*)(;?)");
-            Matcher m = p.matcher(mBuffer.substring(start, offset));
-            while (m.find()) {
-                String s = escapeTranslator(m.group(1));
-                v.add(s);
-                end = m.end();
-                if (offset == start + end) {
-                    String endValue = m.group(3);
-                    if (";".equals(endValue)) {
-                        v.add("");
+    }
+    
+    protected String getQuotedPrintable(String firstString) throws IOException, VCardException {
+        // Specifically, there may be some padding between = and CRLF.
+        // See the following:
+        //
+        // qp-line := *(qp-segment transport-padding CRLF)
+        //            qp-part transport-padding
+        // qp-segment := qp-section *(SPACE / TAB) "="
+        //             ; Maximum length of 76 characters
+        //
+        // e.g. (from RFC 2045)
+        // Now's the time =
+        // for all folk to come=
+        //  to the aid of their country.
+        if (firstString.trim().endsWith("=")) {
+            // remove "transport-padding"
+            int pos = firstString.length() - 1;
+            while(firstString.charAt(pos) != '=') {
+            }
+            StringBuilder builder = new StringBuilder();
+            builder.append(firstString.substring(0, pos + 1));
+            builder.append("\r\n");
+            String line;
+            while (true) {
+                line = getLine();
+                if (line == null) {
+                    throw new VCardException(
+                            "File ended during parsing quoted-printable String");
+                }
+                if (line.trim().endsWith("=")) {
+                    // remove "transport-padding"
+                    pos = line.length() - 1;
+                    while(line.charAt(pos) != '=') {
                     }
+                    builder.append(line.substring(0, pos + 1));
+                    builder.append("\r\n");
+                } else {
+                    builder.append(line);
                     break;
                 }
             }
-            mBuilder.propertyValues(v);
+            return builder.toString(); 
+        } else {
+            return firstString;
         }
-
-        ret = parseCrlf(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        sum += ret;
-
-        return sum;
     }
-
-    /** [groups] "." "AGENT" [params] ":" vcard CRLF */
-    private int parseItem2(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseGroupsWithDot(offset);
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-
-        ret = parseString(offset, "AGENT", true);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyName(mBuffer.substring(start, offset));
-        }
-
-        ret = parseParams(offset);
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-
-        ret = parseString(offset, ":", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = parseCrlf(offset);
-        if (ret != PARSE_ERROR) {
-            offset += ret;
-            sum += ret;
-        }
-
-        ret = parseVCard(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyValues(new ArrayList<String>());
-        }
-
-        ret = parseCrlf(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        sum += ret;
-
-        return sum;
-    }
-
-    private int parseGroupsWithDot(int offset) {
-        int ret = 0, sum = 0;
-        /* [groups "."] */
-        ret = parseGroups(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = parseString(offset, ".", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        sum += ret;
-
-        return sum;
-    }
-
-    /** ";" [ws] paramlist */
-    private int parseParams(int offset) {
-        int ret = 0, sum = 0;
-
-        ret = parseString(offset, ";", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        ret = parseParamList(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        sum += ret;
-
-        return sum;
-    }
-
-    /**
-     * paramlist [ws] ";" [ws] param / param
-     */
-    private int parseParamList(int offset) {
-        int ret = 0, sum = 0;
-
-        ret = parseParam(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        int offsetTemp = offset;
-        int sumTemp = sum;
-        for (;;) {
-            ret = removeWs(offsetTemp);
-            offsetTemp += ret;
-            sumTemp += ret;
-
-            ret = parseString(offsetTemp, ";", false);
-            if (ret == PARSE_ERROR) {
-                return sum;
+    
+    protected String getBase64(String firstString) throws IOException, VCardException {
+        StringBuilder builder = new StringBuilder();
+        builder.append(firstString);
+        
+        while (true) {
+            String line = getLine();
+            if (line == null) {
+                throw new VCardException(
+                        "File ended during parsing BASE64 binary");
             }
-            offsetTemp += ret;
-            sumTemp += ret;
-
-            ret = removeWs(offsetTemp);
-            offsetTemp += ret;
-            sumTemp += ret;
-
-            ret = parseParam(offsetTemp);
-            if (ret == PARSE_ERROR) {
+            if (line.length() == 0) {
                 break;
             }
-            offsetTemp += ret;
-            sumTemp += ret;
-
-            // offset = offsetTemp;
-            sum = sumTemp;
+            builder.append(line);
         }
-        return sum;
+        
+        return builder.toString();
     }
-
+    
     /**
-     * param0 / param1 / param2 / param3 / param4 / param5 / knowntype<BR>
-     * TYPE / VALUE / ENDCODING / CHARSET / LANGUAGE ...
+     * Mainly for "ADR", "ORG", and "N"
+     * We do not care the number of strnosemi here.
+     * 
+     * addressparts = 0*6(strnosemi ";") strnosemi
+     *              ; PO Box, Extended Addr, Street, Locality, Region,
+     *                Postal Code, Country Name
+     * orgparts     = *(strnosemi ";") strnosemi
+     *              ; First is Organization Name,
+     *                remainder are Organization Units.
+     * nameparts    = 0*4(strnosemi ";") strnosemi
+     *              ; Family, Given, Middle, Prefix, Suffix.
+     *              ; Example:Public;John;Q.;Reverend Dr.;III, Esq.
+     * strnosemi    = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi
+     *              ; To include a semicolon in this string, it must be escaped
+     *              ; with a "\" character.
+     *              
+     * We are not sure whether we should add "\" CRLF to each value.
+     * For now, we exclude them.               
      */
-    private int parseParam(int offset) {
-        int ret = 0, sum = 0;
-
-        ret = parseParam0(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
+    protected void handleMultiplePropertyValue(
+            String propertyName, String propertyValue) throws IOException, VCardException {
+        // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some data have it.
+        if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+            propertyValue = getQuotedPrintable(propertyValue);
         }
-
-        ret = parseParam1(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
-        }
-
-        ret = parseParam2(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
-        }
-
-        ret = parseParam3(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
-        }
-
-        ret = parseParam4(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
-        }
-
-        ret = parseParam5(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
-        }
-
-        int start = offset;
-        ret = parseKnownType(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(null);
-            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
-        }
-
-        return sum;
-    }
-
-    /** "TYPE" [ws] "=" [ws] ptypeval */
-    private int parseParam0(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseString(offset, "TYPE", true);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(mBuffer.substring(start, offset));
-        }
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        ret = parseString(offset, "=", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parsePTypeVal(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
-        }
-
-        return sum;
-
-    }
-
-    /** "VALUE" [ws] "=" [ws] pvalueval */
-    private int parseParam1(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseString(offset, "VALUE", true);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(mBuffer.substring(start, offset));
-        }
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        ret = parseString(offset, "=", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parsePValueVal(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
-        }
-
-        return sum;
-    }
-
-    /** "ENCODING" [ws] "=" [ws] pencodingval */
-    private int parseParam2(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseString(offset, "ENCODING", true);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(mBuffer.substring(start, offset));
-        }
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        ret = parseString(offset, "=", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parsePEncodingVal(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
-        }
-
-        return sum;
-
-    }
-
-    /** "CHARSET" [ws] "=" [ws] charsetval */
-    private int parseParam3(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseString(offset, "CHARSET", true);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(mBuffer.substring(start, offset));
-        }
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        ret = parseString(offset, "=", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parseCharsetVal(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
-        }
-
-        return sum;
-    }
-
-    /** "LANGUAGE" [ws] "=" [ws] langval */
-    private int parseParam4(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseString(offset, "LANGUAGE", true);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(mBuffer.substring(start, offset));
-        }
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        ret = parseString(offset, "=", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parseLangVal(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
-        }
-
-        return sum;
-
-    }
-
-    /** "X-" word [ws] "=" [ws] word */
-    private int parseParam5(int offset) {
-        int ret = 0, sum = 0, start = offset;
-
-        ret = parseXWord(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(mBuffer.substring(start, offset));
-        }
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        ret = parseString(offset, "=", false);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        ret = removeWs(offset);
-        offset += ret;
-        sum += ret;
-
-        start = offset;
-        ret = parseWord(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-        if (mBuilder != null) {
-            mBuilder.propertyParamValue(mBuffer.substring(start, offset));
-        }
-
-        return sum;
-    }
-
-    /**
-     * knowntype: "DOM" / "INTL" / ...
-     */
-    private int parseKnownType(int offset) {
-        String word = getWord(offset);
-
-        if (mKnownTypeSet.contains(word.toUpperCase())) {
-            return word.length();
-        }
-        return PARSE_ERROR;
-    }
-
-    /** knowntype / "X-" word */
-    private int parsePTypeVal(int offset) {
-        int ret = 0, sum = 0;
-
-        ret = parseKnownType(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
-        }
-
-        ret = parseXWord(offset);
-        if (ret != PARSE_ERROR) {
-            sum += ret;
-            return sum;
-        }
-        sum += ret;
-
-        return sum;
-    }
-
-    /** "LOGO" /.../ XWord, case insensitive */
-    private int parseName(int offset) {
-        int ret = 0;
-        ret = parseXWord(offset);
-        if (ret != PARSE_ERROR) {
-            return ret;
-        }
-        String word = getWord(offset).toUpperCase();
-        if (mName.contains(word)) {
-            return word.length();
-        }
-        return PARSE_ERROR;
-    }
-
-    /** groups "." word / word */
-    private int parseGroups(int offset) {
-        int ret = 0, sum = 0;
-
-        ret = parseWord(offset);
-        if (ret == PARSE_ERROR) {
-            return PARSE_ERROR;
-        }
-        offset += ret;
-        sum += ret;
-
-        for (;;) {
-            ret = parseString(offset, ".", false);
-            if (ret == PARSE_ERROR) {
-                break;
+        
+        if (propertyValue.endsWith("\\")) {
+            StringBuilder builder = new StringBuilder();
+            // builder.append(propertyValue);
+            builder.append(propertyValue.substring(0, propertyValue.length() - 1));
+            try {
+                String line;
+                while (true) {
+                    line = getNonEmptyLine();
+                    // builder.append("\r\n");
+                    // builder.append(line);
+                    if (!line.endsWith("\\")) {
+                        builder.append(line);
+                        break;
+                    } else {
+                        builder.append(line.substring(0, line.length() - 1));
+                    }
+                }
+            } catch (IOException e) {
+                throw new VCardException(
+                        "IOException is throw during reading propertyValue" + e);
             }
-
-            int ret1 = parseWord(offset);
-            if (ret1 == PARSE_ERROR) {
-                break;
-            }
-            offset += ret + ret1;
-            sum += ret + ret1;
+            // Now, propertyValue may contain "\r\n"
+            propertyValue = builder.toString();
         }
-        return sum;
+
+        if (mBuilder != null) {
+            // In String#replaceAll() and Pattern class, "\\\\" means single slash. 
+
+            final String IMPOSSIBLE_STRING = "\0";
+            // First replace two backslashes with impossible strings.
+            propertyValue = propertyValue.replaceAll("\\\\\\\\", IMPOSSIBLE_STRING);
+
+            // Now, split propertyValue with ; whose previous char is not back slash.
+            Pattern pattern = Pattern.compile("(?<!\\\\);");
+            // TODO: limit should be set in accordance with propertyName?
+            String[] strArray = pattern.split(propertyValue, -1); 
+            ArrayList<String> arrayList = new ArrayList<String>();
+            for (String str : strArray) {
+                // Replace impossible strings with original two backslashes
+                arrayList.add(
+                        unescapeText(str.replaceAll(IMPOSSIBLE_STRING, "\\\\\\\\")));
+            }
+            mBuilder.propertyValues(arrayList);
+        }
     }
-
+    
     /**
-     * Translate escape characters("\\", "\;") which define in vcard2.1 spec.
-     * But for fault tolerance, we will translate "\:" and "\,", which isn't
-     * define in vcard2.1 explicitly, as the same behavior as other client.
-     *
-     * @param str:
-     *            the string will be translated.
-     * @return the string which do not contain any escape character in vcard2.1
+     * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
      */
-    private String escapeTranslator(String str) {
-        if (null == str)
-            return null;
+    protected void handleAgent(String propertyValue) throws IOException, VCardException {
+        String[] strArray = propertyValue.split(":", 2);
+        if (!(strArray.length == 2 ||
+                strArray[0].trim().equalsIgnoreCase("BEGIN") && 
+                strArray[1].trim().equalsIgnoreCase("VCARD"))) {
+            throw new VCardException("BEGIN:VCARD != \"" + propertyValue + "\"");
+        }
+        parseItems();
+        readEndVCard();
+    }
+    
+    /**
+     * For vCard 3.0.
+     */
+    protected String maybeUnescapeText(String text) {
+        return text;
+    }
+    
+    /**
+     * Convert escaped text into unescaped text.
+     */
+    protected String unescapeText(String text) {
+        // Original vCard 2.1 specification does not allow transformation
+        // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
+        // this class allowed them, so keep it as is.
+        // In String#replaceAll(), "\\\\" means single slash. 
+        return text.replaceAll("\\\\;", ";")
+            .replaceAll("\\\\:", ":")
+            .replaceAll("\\\\,", ",")
+            .replaceAll("\\\\\\\\", "\\\\");
+    }
+    
+    /**
+     * Parse the given stream and constructs VCardDataBuilder object.
+     * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
+     * local encoding to it. For example, Japanese phone career uses Shift_JIS, which
+     * is not formally allowed in vCard specification.
+     * As a result, there is a case where the encoding given here does not do well with
+     * the "CHARSET".
+     * 
+     * In order to avoid such cases, It may be fine to use "ISO-8859-1" as an encoding,
+     * and to encode each localized String afterward.
+     * 
+     * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
+     * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
+     * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
+     * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification
+     * (e.g. W53K). 
+     *      
+     * @param is
+     *            The source to parse.
+     * @param charset
+     *            The charset.
+     * @param builder
+     *            The v builder which used to construct data.
+     * @return Return true for success, otherwise false.
+     * @throws IOException
+     */
+    public boolean parse(InputStream is, String charset, VBuilder builder)
+            throws IOException, VCardException {
+        // TODO: If we really need to allow only CRLF as line break,
+        // we will have to develop our own BufferedReader().
+        mReader = new BufferedReader(new InputStreamReader(is, charset));
+        
+        mBuilder = builder;
 
-        String tmp = str.replace("\\\\", "\n\r\n");
-        tmp = tmp.replace("\\;", ";");
-        tmp = tmp.replace("\\:", ":");
-        tmp = tmp.replace("\\,", ",");
-        tmp = tmp.replace("\n\r\n", "\\");
-        return tmp;
+        if (mBuilder != null) {
+            mBuilder.start();
+        }
+        parseVCardFile();
+        if (mBuilder != null) {
+            mBuilder.end();
+        }
+        return true;
+    }
+    
+    private boolean isLetter(char ch) {
+        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+            return true;
+        }
+        return false;
     }
 }
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V30.java b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
index c56cfed..901bd49 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
@@ -16,142 +16,270 @@
 
 package android.syncml.pim.vcard;
 
-import android.syncml.pim.VBuilder;
-
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 
 /**
  * This class is used to parse vcard3.0. <br>
- * It get useful data refer from android contact, and alter to vCard 2.1 format.
- * Then reuse vcard 2.1 parser to analyze the result.<br>
- * Please refer to vCard Specification 3.0
+ * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426)
  */
 public class VCardParser_V30 extends VCardParser_V21 {
-    private static final String V21LINEBREAKER = "\r\n";
-
     private static final HashSet<String> acceptablePropsWithParam = new HashSet<String>(
-            Arrays.asList("PHOTO", "LOGO", "TEL", "EMAIL", "ADR"));
+            Arrays.asList(
+                    "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", 
+                    "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+                    "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
+                    "NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
+                    "SORT-STRING", "CATEGORIES", "PRODID")); // 3.0
+    
+    // Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety.
+    private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>(
+            Arrays.asList("7BIT", "8BIT", "BASE64", "B"));
+    
+    // Although RFC 2426 specifies some property must not have parameters, we allow it, 
+    // since there may be some careers which violates the RFC...
+    private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
 
-    private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>(
-            Arrays.asList("ORG", "NOTE", "TITLE", "FN", "N"));
+    private String mPreviousLine;
+    
+    @Override
+    protected String getVersion() {
+        return "3.0";
+    }
+    
+    @Override
+    protected boolean isValidPropertyName(String propertyName) {
+        return acceptablePropsWithParam.contains(propertyName) ||
+            acceptablePropsWithoutParam.contains(propertyName);
+    }
+    
+    @Override
+    protected boolean isValidEncoding(String encoding) {
+        return sAcceptableEncodingV30.contains(encoding.toUpperCase());
+    }
+    
+    @Override
+    protected String getLine() throws IOException {
+        if (mPreviousLine != null) {
+            String ret = mPreviousLine;
+            mPreviousLine = null;
+            return ret;
+        } else {
+            return mReader.readLine();
+        }
+    }
+    
+    /**
+     * vCard 3.0 requires that the line with space at the beginning of the line
+     * must be combined with previous line. 
+     */
+    @Override
+    protected String getNonEmptyLine() throws IOException, VCardException {
+        String line;
+        StringBuilder builder = null;
+        while (true) {
+            line = mReader.readLine();
+            if (line == null) {
+                if (builder != null) {
+                    return builder.toString();
+                } else if (mPreviousLine != null) {
+                    String ret = mPreviousLine;
+                    mPreviousLine = null;
+                    return ret;
+                }
+                throw new VCardException("Reached end of buffer.");
+            } else if (line.length() == 0) {
+                if (builder != null) {
+                    return builder.toString();
+                } else if (mPreviousLine != null) {
+                    String ret = mPreviousLine;
+                    mPreviousLine = null;
+                    return ret;
+                }
+            } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
+                if (builder != null) {
+                    // TODO: Check whether MIME requires only one whitespace.
+                    builder.append(line.substring(1));
+                } else if (mPreviousLine != null) {
+                    builder = new StringBuilder();
+                    builder.append(mPreviousLine);
+                    mPreviousLine = null;
+                    builder.append(line.substring(1));
+                } else {
+                    throw new VCardException("Space exists at the beginning of the line");
+                }
+            } else {
+                if (mPreviousLine == null) {
+                    mPreviousLine = line;
+                } else {
+                    String ret = mPreviousLine;
+                    mPreviousLine = line;
+                    return ret;                    
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * vcard = [group "."] "BEGIN" ":" "VCARD" 1*CRLF
+     *         1*(contentline)
+     *         ;A vCard object MUST include the VERSION, FN and N types.
+     *         [group "."] "END" ":" "VCARD" 1*CRLF
+     */
+    @Override
+    protected boolean readBeginVCard() throws IOException, VCardException {
+        // TODO: vCard 3.0 supports group.
+        return super.readBeginVCard();
+    }
+    
+    @Override
+    protected void readEndVCard() throws VCardException {
+        // TODO: vCard 3.0 supports group.
+        super.readEndVCard();
+    }
 
-    private static final HashMap<String, String> propV30ToV21Map = new HashMap<String, String>();
-
-    static {
-        propV30ToV21Map.put("PHOTO", "PHOTO");
-        propV30ToV21Map.put("LOGO", "PHOTO");
+    /**
+     * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
+     */
+    @Override
+    protected void handleParams(String params) throws VCardException {
+        try {
+            super.handleParams(params);
+        } catch (VCardException e) {
+            // maybe IANA type
+            String[] strArray = params.split("=", 2);
+            if (strArray.length == 2) {
+                handleAnyParam(strArray[0], strArray[1]);
+            } else {
+                // Must not come here in the current implementation.
+                throw new VCardException(
+                        "Unknown params value: " + params);
+            }
+        }
+    }
+    
+    @Override
+    protected void handleAnyParam(String paramName, String paramValue) {
+        // vCard 3.0 accept comma-separated multiple values, but
+        // current PropertyNode does not accept it.
+        // For now, we do not split the values.
+        //
+        // TODO: fix this.
+        super.handleAnyParam(paramName, paramValue);
+    }
+    
+    /**
+     *  vCard 3.0 defines
+     *  
+     *  param         = param-name "=" param-value *("," param-value)
+     *  param-name    = iana-token / x-name
+     *  param-value   = ptext / quoted-string
+     *  quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+     */
+    @Override
+    protected void handleType(String ptypevalues) {
+        String[] ptypeArray = ptypevalues.split(",");
+        mBuilder.propertyParamType("TYPE");
+        for (String value : ptypeArray) {
+            int length = value.length();
+            if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
+                mBuilder.propertyParamValue(value.substring(1, value.length() - 1));
+            } else {
+                mBuilder.propertyParamValue(value);
+            }
+        }
     }
 
     @Override
-    public boolean parse(InputStream is, String encoding, VBuilder builder)
-            throws IOException {
-        // get useful info for android contact, and alter to vCard 2.1
-        byte[] bytes = new byte[is.available()];
-        is.read(bytes);
-        String scStr = new String(bytes);
-        StringBuilder v21str = new StringBuilder("");
-
-        String[] strlist = splitProperty(scStr);
-
-        if ("BEGIN:vCard".equals(strlist[0])
-                || "BEGIN:VCARD".equals(strlist[0])) {
-            v21str.append("BEGIN:VCARD" + V21LINEBREAKER);
-        } else {
-            return false;
-        }
-
-        for (int i = 1; i < strlist.length - 1; i++) {// for ever property
-            // line
-            String propName;
-            String params;
-            String value;
-
-            String line = strlist[i];
-            if ("".equals(line)) { // line breaker is useful in encoding string
-                v21str.append(V21LINEBREAKER);
-                continue;
+    protected void handleAgent(String propertyValue) throws VCardException {
+        // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.0.
+        //
+        // e.g.
+        // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
+        //  TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
+        //  ET:jfriday@host.com\nEND:VCARD\n
+        //
+        // TODO: fix this.
+        //
+        // issue:
+        //  vCard 3.0 also allows this as an example.
+        //
+        // AGENT;VALUE=uri:
+        //  CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
+        //
+        // This is not VCARD. Should we support this?
+        throw new VCardException("AGENT in vCard 3.0 is not supported yet.");
+    }
+    
+    // vCard 3.0 supports "B" as BASE64 encoding.
+    @Override
+    protected void handlePropertyValue(
+            String propertyName, String propertyValue) throws
+            IOException, VCardException {
+        if (mEncoding != null && mEncoding.equalsIgnoreCase("B")) {
+            String result = getBase64(propertyValue);
+            if (mBuilder != null) {
+                ArrayList<String> v = new ArrayList<String>();
+                v.add(result);
+                mBuilder.propertyValues(v);
             }
-
-            String[] contentline = line.split(":", 2);
-            String propNameAndParam = contentline[0];
-            value = (contentline.length > 1) ? contentline[1] : "";
-            if (propNameAndParam.length() > 0) {
-                String[] nameAndParams = propNameAndParam.split(";", 2);
-                propName = nameAndParams[0];
-                params = (nameAndParams.length > 1) ? nameAndParams[1] : "";
-
-                if (acceptablePropsWithParam.contains(propName)
-                        || acceptablePropsWithoutParam.contains(propName)) {
-                    v21str.append(mapContentlineV30ToV21(propName, params,
-                            value));
-                }
-            }
-        }// end for
-
-        if ("END:vCard".equals(strlist[strlist.length - 1])
-                || "END:VCARD".equals(strlist[strlist.length - 1])) {
-            v21str.append("END:VCARD" + V21LINEBREAKER);
-        } else {
-            return false;
         }
-
-        return super.parse(
-        // use vCard 2.1 parser
-                new ByteArrayInputStream(v21str.toString().getBytes()),
-                encoding, builder);
+        
+        super.handlePropertyValue(propertyName, propertyValue);
+    }
+    
+    /**
+     * vCard 3.0 does not require two CRLF at the last of BASE64 data.
+     * It only requires that data should be MIME-encoded.
+     */
+    @Override
+    protected String getBase64(String firstString) throws IOException, VCardException {
+        StringBuilder builder = new StringBuilder();
+        builder.append(firstString);
+        
+        while (true) {
+            String line = getLine();
+            if (line == null) {
+                throw new VCardException(
+                        "File ended during parsing BASE64 binary");
+            }
+            if (line.length() == 0) {
+                break;
+            } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
+                mPreviousLine = line;
+                break;
+            }
+            builder.append(line);
+        }
+        
+        return builder.toString();
+    }
+    
+    /**
+     * Return unescapeText(text).
+     * In vCard 3.0, 8bit text is always encoded.
+     */
+    @Override
+    protected String maybeUnescapeText(String text) {
+        return unescapeText(text);
     }
 
     /**
-     * Convert V30 string to V21 string
-     *
-     * @param propName
-     *            The name of property
-     * @param params
-     *            parameter of property
-     * @param value
-     *            value of property
-     * @return the converted string
+     * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
+     *              ; \\ encodes \, \n or \N encodes newline
+     *              ; \; encodes ;, \, encodes ,
      */
-    private String mapContentlineV30ToV21(String propName, String params,
-            String value) {
-        String result;
-
-        if (propV30ToV21Map.containsKey(propName)) {
-            result = propV30ToV21Map.get(propName);
-        } else {
-            result = propName;
-        }
-        // Alter parameter part of property to vCard 2.1 format
-        if (acceptablePropsWithParam.contains(propName) && params.length() > 0)
-            result = result
-                    + ";"
-                    + params.replaceAll(",", ";").replaceAll("ENCODING=B",
-                            "ENCODING=BASE64").replaceAll("ENCODING=b",
-                            "ENCODING=BASE64");
-
-        return result + ":" + value + V21LINEBREAKER;
-    }
-
-    /**
-     * Split ever property line to Stringp[], not split folding line.
-     *
-     * @param scStr
-     *            the string to be splitted
-     * @return a list of splitted string
-     */
-    private String[] splitProperty(String scStr) {
-        /*
-         * Property splitted by \n, and unfold folding lines by removing
-         * CRLF+LWSP-char
-         */
-        scStr = scStr.replaceAll("\r\n", "\n").replaceAll("\n ", "")
-                .replaceAll("\n\t", "");
-        String[] strs = scStr.split("\n");
-        return strs;
+    @Override
+    protected String unescapeText(String text) {
+        // In String#replaceAll(), "\\\\" means single slash. 
+        return text.replaceAll("\\\\;", ";")
+            .replaceAll("\\\\:", ":")
+            .replaceAll("\\\\,", ",")
+            .replaceAll("\\\\n", "\r\n")
+            .replaceAll("\\\\N", "\r\n")
+            .replaceAll("\\\\\\\\", "\\\\");
     }
 }
diff --git a/core/java/android/syncml/pim/vcard/VCardVersionException.java b/core/java/android/syncml/pim/vcard/VCardVersionException.java
new file mode 100644
index 0000000..1ca88d1
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardVersionException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2009 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;
+
+/**
+ * VCardException used only when the version of the vCard is different. 
+ */
+public class VCardVersionException extends VCardException {
+    public VCardVersionException() {
+    }
+
+    public VCardVersionException(String message) {
+        super(message);
+    }
+}
diff --git a/tests/AndroidTests/res/raw/v21_backslash.vcf b/tests/AndroidTests/res/raw/v21_backslash.vcf
new file mode 100644
index 0000000..bd3002b
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_backslash.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD

+VERSION:2.1

+N:;A\;B\\;C\\\;;D;\:E;\\\\;

+FN:A;B\C\;D:E\\

+END:VCARD

diff --git a/tests/AndroidTests/res/raw/v21_complicated.vcf b/tests/AndroidTests/res/raw/v21_complicated.vcf
new file mode 100644
index 0000000..de34e16
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_complicated.vcf
@@ -0,0 +1,106 @@
+BEGIN:VCARD

+VERSION:2.1

+N:Gump;Forrest;Hoge;Pos;Tao

+FN:Joe Due

+ORG:Gump Shrimp Co.;Sales Dept.\;Manager;Fish keeper

+ROLE:Fish Cake Keeper!

+X-CLASS:PUBLIC

+TITLE:Shrimp Man

+TEL;WORK;VOICE:(111) 555-1212

+TEL;HOME;VOICE:(404) 555-1212

+TEL;CELL:0311111111

+TEL;VIDEO:0322222222

+TEL;VOICE:0333333333

+ADR;WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America

+LABEL;WORK;ENCODING=QUOTED-PRINTABLE:100 Waters Edge=0D=0ABaytown, LA 30314=0D=0AUnited  States of America

+ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America

+LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A=

+Baytown, LA 30314=0D=0A=

+United  States of America

+EMAIL;PREF;INTERNET:forrestgump@walladalla.com

+EMAIL;CELL:cell@example.com

+NOTE:The following note is the example from RFC 2045.

+NOTE;ENCODING=QUOTED-PRINTABLE:Now's the time =

+for all folk to come=

+ to the aid of their country.

+

+PHOTO;ENCODING=BASE64;TYPE=JPEG:

+ /9j/4QoPRXhpZgAATU0AKgAAAAgADQEOAAIAAAAPAAAAqgEPAAIAAAAHAAAAugEQAAIAAAAG

+ AAAAwgESAAMAAAABAAEAAAEaAAUAAAABAAAAyAEbAAUAAAABAAAA0AEoAAMAAAABAAIAAAEx

+ AAIAAAAOAAAA2AEyAAIAAAAUAAAA5gITAAMAAAABAAEAAIKYAAIAAAAOAAAA+odpAAQAAAAB

+ AAABhMSlAAcAAAB8AAABCAAABB4yMDA4MTAyOTEzNTUzMQAARG9Db01vAABEOTA1aQAAAABI

+ AAAAAQAAAEgAAAABRDkwNWkgVmVyMS4wMAAyMDA4OjEwOjI5IDEzOjU1OjQ3ACAgICAgICAg

+ ICAgICAAUHJpbnRJTQAwMzAwAAAABgABABQAFAACAQAAAAADAAAANAEABQAAAAEBAQAAAAEQ

+ gAAAAAAAEQkAACcQAAAPCwAAJxAAAAWXAAAnEAAACLAAACcQAAAcAQAAJxAAAAJeAAAnEAAA

+ AIsAACcQAAADywAAJxAAABvlAAAnEAAogpoABQAAAAEAAANqgp0ABQAAAAEAAANyiCIAAwAA

+ AAEAAgAAkAAABwAAAAQwMjIwkAMAAgAAABQAAAN6kAQAAgAAABQAAAOOkQEABwAAAAQBAgMA

+ kQIABQAAAAEAAAOikgEACgAAAAEAAAOqkgIABQAAAAEAAAOykgQACgAAAAEAAAO6kgUABQAA

+ AAEAAAPCkgcAAwAAAAEAAgAAkggAAwAAAAEAAAAAkgkAAwAAAAEAAAAAkgoABQAAAAEAAAPK

+ knwABwAAAAEAAAAAkoYABwAAABYAAAPSoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIAAwAA

+ AAEAYAAAoAMAAwAAAAEASAAAoAUABAAAAAEAAAQAog4ABQAAAAEAAAPoog8ABQAAAAEAAAPw

+ ohAAAwAAAAEAAgAAohcAAwAAAAEAAgAAowAABwAAAAEDAAAAowEABwAAAAEBAAAApAEAAwAA

+ AAEAAAAApAIAAwAAAAEAAAAApAMAAwAAAAEAAAAApAQABQAAAAEAAAP4pAUAAwAAAAEAHQAA

+ pAYAAwAAAAEAAAAApAcAAwAAAAEAAAAApAgAAwAAAAEAAAAApAkAAwAAAAEAAAAApAoAAwAA

+ AAEAAAAApAwAAwAAAAEAAgAAAAAAAAAAAFMAACcQAAABXgAAAGQyMDA4OjEwOjI5IDEzOjU1

+ OjMxADIwMDg6MTA6MjkgMTM6NTU6NDcAAAApiAAAGwAAAAKyAAAAZAAAAV4AAABkAAAAAAAA

+ AGQAAAAlAAAACgAADpIAAAPoAAAAAAAAAAAyMDA4MTAyOTEzNTUzMQAAICoAAAAKAAAq4gAA

+ AAoAAAAAAAAAAQACAAEAAgAAAARSOTgAAAIABwAAAAQwMTAwAAAAAAAGAQMAAwAAAAEABgAA

+ ARoABQAAAAEAAARsARsABQAAAAEAAAR0ASgAAwAAAAEAAgAAAgEABAAAAAEAAAR8AgIABAAA

+ AAEAAAWLAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAIQAIBYYHBgUIBwaHCQiICYwUDQwLCww

+ YkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxgEiJCQwKjBeNDRe

+ xoRwhMbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbG

+ /8AAEQgAeACgAwEhAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAA

+ AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK

+ FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG

+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl

+ 5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQH

+ BQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka

+ JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT

+ lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz

+ 9PX29/j5+v/aAAwDAQACEQMRAD8AFFSqKkZIoqRVpgSKKeBTEOApwFADsUYpgIRSEUANIppF

+ ICNhUTCgCMio2FICJhULCgC0oqVaAJFFSqKBkgFOApiHCnCgB2KMUCENJQA0imEUDGMKiYUA

+ RtUbUgIWqJhQBZSpFoAlWpVoGPFPFMQ7tSK2ODQA4yKO9HmKe9FxAzDHFIOlAAaYaAGNUTUD

+ ImqNqQETVE1AE6VKKAJFNSqaAHg08GmANIFFQM5Y5qJMBuT60ZNQIcrkVYSQMKuLGKaaasQx

+ qiagZE1RtSAjaomoAkQ1KpoAlU1IpoAkU07OBTArO+5qkV12Y71lfUBmaKkCRSuznrTFba2a

+ oCwGyM0E1qIjY1GxoGRNUZNICNqiagByGplNAEimpFNMB4YDvSucpxSYEIU04KazsAu1qArU

+ WELtPpTSposBNETt5pxNaoCNjUbGgCNjUZoGRtUTUgFU1KpoAkBqQHigCFnO7rUqOdlZp6gA

+ c+tODn1pXAXzD60eYfWncQvmNSGQ07gOMhCVEJGz1ptgS5yKYxqwGE1GxoAiamGkMapqVTQB

+ Kpp+eKAICfmqWM/Kaz6gANOBqQFzRmmAuaTNACsfkqMHmm9wJs8U0mtRDGNRsaAI2phpDI1N

+ SqaAJFNSA8UCISfmqSM/Kaz6jAHmnA1ICg0uaAFzSZpgKx+SmDrTe4E2eKaTWoiMmmMaAIzT

+ DSGRKakU0ASKaeDTERseakjPyms+oxAacDUgOBpc0gFzSZpgOY/KKYv3qrqIlpprQBjGoyaA

+ GGmmkMgU1IppgPBqQGgQu0Gn4wvFKwEQpwNZDHZpc0ALmigRKBleaQKBWtgA001QDGqM0gGm

+ mGkMrqakBoAepp4NMRIDTwaAE2A008GokgHxjd1pzKFpW0uAg5NSBBTirgOpDWgDTTTQAw0w

+ 0gGGmmgZWBp4pASKaeDTEOBp4NADwajbrUyBEkXWnSUdAGr1qeiAMSkNWAhphoAaaYaQDDTT

+ SGVRTwaYDxTwaBDwaeDQA4GlK5oauIeo20pGaLaAKqgU6hKwBSGmAhphoAaaYaQxhpppDKgN

+ PFMB4p4oEPFOBpgPBp4NAhwpwoAWloAKSgBDTTQMYaYaQDTTTSGA/9n/2wCEAAoHBwgHBgoI

+ CAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9

+ PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7

+ Ozs7Ozs7Ozs7Ozs7O//AABEIAEgAYAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAA

+ AQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNC

+ scEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp

+ anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS

+ 09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI

+ CQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVi

+ ctEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4

+ eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY

+ 2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AJ7SLgcVr20I4rNFGvbQAAHFaEUX

+ SrQi5HCMdKk8oY6VSJYx4hjpVaWMelAFC4iGDxWPdR8mkxmRdxjBrEvI+tZjN20xtHNbNqAc

+ UIDXg6Cr0WKtCY8XKQOyzOB3FKNWsyceZ+lS6sY6NkjvPSdwImBHUmmy4q076oCjOODWPdgc

+ 0MpGPdAYNYl4o5rNjNKzkyorZtXxihAa1vIDip7m9Frb7/4jwKcnyxbEzN3ieJppZsyZ4U1H

+ urzZau4mWVlNrGk0UuWPVa1YroXEIkHfrXZh5W90RWncAHmsi6bJNdQ0ZNw3BrGuiMGs2Mks

+ puBzWzbzdOaEBeOpR2oUtkk9hTru7iuo4m8wgemKyqTi04sBsfkEf68j8KlUQZz9o/SuZRj3

+ JYriAji4/Sp7W6htbV2aXcu70ramoxle4gN7HcIXjbis+4k5NdaaauhmVcv1rHuW61DGiG1m

+ 6c1s20/TmgAv5vmj57VKk3+ixnPc1xVV70h9CVJuOtSrL71hFgxzScUkkn+iY/2q1i9xDrGT

+ 9y31pJ5Otd1L+GhMy7mTrWXO2SapjRn28vTmta3nxjmgGOvJd2w1Kkv+ipz/ABGuOoveYdCe

+ ObjrU6y5rlsA8ycUksn+ij/eNaw6iJLNsW59zTJn6816FP4EJmbO+Saz5m602UjIgk4HNadv

+ LwKaBl+MpIMOMipp490SCJeF7CoqQvF2JuRqWQ4YEGrSiQJuKnHrXByMpki73GFBNXIoh9n2

+ SrnnOK6MPTbd3sSwIVF2qMCqkzHmuy1lYRnTHrVGWpZaMKB+BWlbycYoQM0IZDxzV+GU8c1a

+ IYy5Y+dnHatAsfsAHfArmS1mPoh1gT8x9qtk1rQX7tCe5DIapzGtGBQm71SlqGWjnIH6Vowt

+ zmhAy/E3vV6F6tEMuxlWIyAfrVxCCAO1VZEEyYA4AApxNGwyJ+lVJRUsaKMw61SlFQzRAP/Z

+

+X-ATTRIBUTE:Some String

+BDAY:19800101

+GEO:35.6563854,139.6994233

+URL:http://www.example.com/

+REV:20080424T195243Z

+END:VCARD
\ No newline at end of file
diff --git a/tests/AndroidTests/res/raw/v21_japanese_1.vcf b/tests/AndroidTests/res/raw/v21_japanese_1.vcf
new file mode 100644
index 0000000..d05e2ff
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_japanese_1.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD

+VERSION:2.1

+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh;;;;

+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ;;;;

+TEL;PREF;VOICE:0300000000

+END:VCARD

diff --git a/tests/AndroidTests/res/raw/v21_japanese_2.vcf b/tests/AndroidTests/res/raw/v21_japanese_2.vcf
new file mode 100644
index 0000000..fa54acb
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_japanese_2.vcf
@@ -0,0 +1,10 @@
+BEGIN:VCARD

+VERSION:2.1

+FN;CHARSET=SHIFT_JIS:ˆÀ“¡ ƒƒCƒh 1

+N;CHARSET=SHIFT_JIS:ˆÀ“¡;ƒƒCƒh1;;;

+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³;Û²ÄÞ1;;;

+ADR;HOME;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:;=93=8C=8B=9E=93=73=

+=8F=61=92=4A=8B=E6=8D=F7=8B=75=92=AC26-1=83=5A=83=8B=83=8A=83=41=83=93=

+=83=5E=83=8F=81=5B6=8A=4B;;;;150-8512;

+NOTE;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:=83=81=83=82

+END:VCARD

diff --git a/tests/AndroidTests/res/raw/v21_multiple_entry.vcf b/tests/AndroidTests/res/raw/v21_multiple_entry.vcf
new file mode 100644
index 0000000..ebbb19a
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_multiple_entry.vcf
@@ -0,0 +1,33 @@
+BEGIN:VCARD

+VERSION:2.1

+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh3;;;;

+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ3;;;;

+TEL;X-NEC-SECRET:9

+TEL;X-NEC-HOTEL:10

+TEL;X-NEC-SCHOOL:11

+TEL;HOME;FAX:12

+END:VCARD

+

+

+BEGIN:VCARD

+VERSION:2.1

+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh4;;;;

+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ4;;;;

+TEL;MODEM:13

+TEL;PAGER:14

+TEL;X-NEC-FAMILY:15

+TEL;X-NEC-GIRL:16

+END:VCARD

+

+

+BEGIN:VCARD

+VERSION:2.1

+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh5;;;;

+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ5;;;;

+TEL;X-NEC-BOY:17

+TEL;X-NEC-FRIEND:18

+TEL;X-NEC-PHS:19

+TEL;X-NEC-RESTAURANT:20

+END:VCARD

+

+

diff --git a/tests/AndroidTests/res/raw/v21_simple.vcf b/tests/AndroidTests/res/raw/v21_simple.vcf
new file mode 100644
index 0000000..beddabb
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_simple.vcf
@@ -0,0 +1,4 @@
+BEGIN:VCARD

+N:Ando;Roid;

+FN:Ando Roid

+END:VCARD

diff --git a/tests/AndroidTests/res/raw/v30_simple.vcf b/tests/AndroidTests/res/raw/v30_simple.vcf
new file mode 100644
index 0000000..418661f
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v30_simple.vcf
@@ -0,0 +1,13 @@
+BEGIN:VCARD

+VERSION:3.0

+FN:And Roid

+N:And;Roid;;;

+ORG:Open;Handset; Alliance

+SORT-STRING:android

+TEL;TYPE=PREF;TYPE=VOICE:0300000000

+CLASS:PUBLIC

+X-GNO:0

+X-GN:group0

+X-REDUCTION:0

+REV:20081031T065854Z

+END:VCARD

diff --git a/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java b/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java
new file mode 100644
index 0000000..b7f562d
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2009 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 com.android.unit_tests;
+
+import android.content.ContentValues;
+import android.syncml.pim.PropertyNode;
+import android.syncml.pim.VDataBuilder;
+import android.syncml.pim.VNode;
+import android.syncml.pim.vcard.VCardException;
+import android.syncml.pim.vcard.VCardParser_V21;
+import android.syncml.pim.vcard.VCardParser_V30;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+
+public class VCardTests extends AndroidTestCase {
+
+    private class PropertyNodesVerifier {
+        private HashMap<String, Vector<PropertyNode>> mPropertyNodeMap;
+        public PropertyNodesVerifier(PropertyNode... nodes) {
+            mPropertyNodeMap = new HashMap<String, Vector<PropertyNode>>();
+            for (PropertyNode propertyNode : nodes) {
+                String propName = propertyNode.propName;
+                Vector<PropertyNode> expectedNodes =
+                    mPropertyNodeMap.get(propName);
+                if (expectedNodes == null) {
+                    expectedNodes = new Vector<PropertyNode>();
+                    mPropertyNodeMap.put(propName, expectedNodes);
+                }
+                expectedNodes.add(propertyNode);
+            }
+        }
+        
+        public void verify(VNode vnode) {
+            for (PropertyNode propertyNode : vnode.propList) {
+                String propName = propertyNode.propName;
+                Vector<PropertyNode> nodes = mPropertyNodeMap.get(propName);
+                if (nodes == null) {
+                    fail("Unexpected propName \"" + propName + "\" exists.");
+                }
+                boolean successful = false;
+                int size = nodes.size();
+                for (int i = 0; i < size; i++) {
+                    PropertyNode expectedNode = nodes.get(i);
+                    if (expectedNode.propName.equals(propName)) {
+                        if (expectedNode.equals(propertyNode)) {
+                            successful = true;
+                            nodes.remove(i);
+                            if (nodes.size() == 0) {
+                                mPropertyNodeMap.remove(propName);
+                            }
+                            break;
+                        } else {
+                            fail("Property \"" + propName + "\" has wrong value.\n" 
+                                    + "expected: " + expectedNode.toString() 
+                                    + "\n  actual: " + propertyNode.toString());
+                        }
+                    }
+                }
+                if (!successful) {
+                    fail("Unexpected property \"" + propName + "\" exists.");
+                }
+            }
+            if (mPropertyNodeMap.size() != 0) {
+                Vector<String> expectedProps = new Vector<String>();
+                for (Vector<PropertyNode> nodes : mPropertyNodeMap.values()) {
+                    for (PropertyNode node : nodes) {
+                        expectedProps.add(node.propName);
+                    }
+                }
+                fail("expected props " + Arrays.toString(expectedProps.toArray()) +
+                        " was not found");
+            }
+        }
+    }
+    
+    public void testV21SimpleCase() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V21();
+        VDataBuilder builder = new VDataBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(1, builder.vNodeList.size());
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+                new PropertyNode("N", "Ando;Roid;",
+                        Arrays.asList("Ando", "Roid", ""),
+                        null, null, null, null),
+                new PropertyNode("FN", "Ando Roid",
+                        null, null, null, null, null));
+        verifier.verify(builder.vNodeList.get(0));
+    }
+    
+    public void testV21BackslashCase() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V21();
+        VDataBuilder builder = new VDataBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v21_backslash);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(1, builder.vNodeList.size());
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "2.1",
+                        null, null, null, null, null),
+                new PropertyNode("N", ";A;B\\;C\\;;D;:E;\\\\;",
+                        Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""),
+                        null, null, null, null),
+                new PropertyNode("FN", "A;B\\C\\;D:E\\\\",
+                        null, null, null, null, null));
+        verifier.verify(builder.vNodeList.get(0));
+    }
+    
+    public void testV21ComplicatedCase() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V21();
+        VDataBuilder builder = new VDataBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v21_complicated);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(1, builder.vNodeList.size());
+        ContentValues contentValuesForQP = new ContentValues();
+        contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+        ContentValues contentValuesForPhoto = new ContentValues();
+        contentValuesForPhoto.put("ENCODING", "BASE64");
+        // Push data into int array at first since values like 0x80 are
+        // interpreted as int by the compiler and casting all of them is
+        // cumbersome...
+        int[] photoIntArray = {
+                0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00,
+                0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d,
+                0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+                0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
+                0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00,
+                0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+                0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00,
+                0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00,
+                0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02,
+                0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00,
+                0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13,
+                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82,
+                0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa,
+                0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+                0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
+                0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31,
+                0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00,
+                0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30,
+                0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
+                0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30,
+                0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00,
+                0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+                0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20,
+                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+                0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33,
+                0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00,
+                0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+                0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
+                0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00,
+                0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10,
+                0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c,
+                0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00,
+                0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00,
+                0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5,
+                0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00,
+                0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05,
+                0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00,
+                0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00,
+                0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90,
+                0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a,
+                0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03,
+                0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02,
+                0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+                0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01,
+                0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00,
+                0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00,
+                0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03,
+                0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00,
+                0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09,
+                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92,
+                0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca,
+                0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+                0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00,
+                0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30,
+                0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+                0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00,
+                0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00,
+                0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05,
+                0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00,
+                0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10,
+                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2,
+                0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
+                0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
+                0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+                0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
+                0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+                0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00,
+                0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00,
+                0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03,
+                0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00,
+                0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08,
+                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4,
+                0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+                0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+                0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00,
+                0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64,
+                0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+                0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30,
+                0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33,
+                0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88,
+                0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00,
+                0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00,
+                0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30,
+                0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31,
+                0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a,
+                0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+                0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00,
+                0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06,
+                0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+                0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
+                0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00,
+                0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00,
+                0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00,
+                0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+                0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84,
+                0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
+                0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30,
+                0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
+                0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e,
+                0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c,
+                0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01,
+                0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6,
+                0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0,
+                0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00,
+                0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00,
+                0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+                0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03,
+                0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
+                0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
+                0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
+                0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+                0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
+                0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
+                0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+                0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+                0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+                0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92,
+                0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
+                0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+                0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
+                0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+                0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
+                0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00,
+                0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+                0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
+                0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
+                0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
+                0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
+                0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
+                0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
+                0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
+                0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+                0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+                0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+                0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+                0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
+                0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
+                0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+                0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+                0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
+                0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
+                0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
+                0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04,
+                0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1,
+                0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45,
+                0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52,
+                0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00,
+                0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87,
+                0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00,
+                0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46,
+                0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9,
+                0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4,
+                0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4,
+                0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5,
+                0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a,
+                0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28,
+                0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80,
+                0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4,
+                0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30,
+                0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0,
+                0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44,
+                0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53,
+                0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76,
+                0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b,
+                0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8,
+                0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d,
+                0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99,
+                0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd,
+                0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3,
+                0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94,
+                0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a,
+                0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06,
+                0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40,
+                0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39,
+                0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69,
+                0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b,
+                0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10,
+                0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0,
+                0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa,
+                0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09,
+                0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81,
+                0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b,
+                0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2,
+                0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69,
+                0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5,
+                0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c,
+                0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73,
+                0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81,
+                0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00,
+                0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a,
+                0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b,
+                0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2,
+                0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7,
+                0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26,
+                0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80,
+                0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5,
+                0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40,
+                0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a,
+                0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6,
+                0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e,
+                0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3,
+                0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69,
+                0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03,
+                0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2,
+                0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a,
+                0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8,
+                0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00,
+                0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69,
+                0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65,
+                0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69,
+                0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8,
+                0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12,
+                0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61,
+                0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01,
+                0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e,
+                0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a,
+                0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3,
+                0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a,
+                0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a,
+                0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
+                0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21,
+                0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30,
+                0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44,
+                0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b,
+                0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22,
+                0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11,
+                0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11,
+                0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01,
+                0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
+                0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
+                0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+                0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
+                0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
+                0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
+                0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+                0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+                0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+                0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+                0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
+                0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
+                0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
+                0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
+                0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+                0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
+                0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01,
+                0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
+                0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
+                0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+                0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+                0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
+                0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19,
+                0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
+                0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+                0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+                0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+                0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
+                0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+                0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+                0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+                0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
+                0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
+                0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
+                0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2,
+                0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6,
+                0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4,
+                0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31,
+                0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88,
+                0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77,
+                0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31,
+                0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0,
+                0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc,
+                0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52,
+                0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60,
+                0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38,
+                0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18,
+                0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a,
+                0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a,
+                0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27,
+                0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc,
+                0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59,
+                0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58,
+                0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f,
+                0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35,
+                0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac,
+                0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6,
+                0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85,
+                0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a,
+                0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf,
+                0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65,
+                0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b,
+                0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6,
+                0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d,
+                0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66,
+                0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d,
+                0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6,
+                0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc,
+                0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4,
+                0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92,
+                0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93,
+                0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68,
+                0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d,
+                0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0,
+                0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c,
+                0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39,
+                0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14,
+                0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92,
+                0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14,
+                0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4,
+                0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02,
+                0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3,
+                0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76,
+                0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02,
+                0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc,
+                0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7,
+                0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51,
+                0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61,
+                0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e,
+                0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c,
+                0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63,
+                0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b,
+                0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab,
+                0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7,
+                0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3,
+                0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1,
+                0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23,
+                0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04,
+                0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9,
+                0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15,
+                0x0c, 0xd1, 0x00, 0xff, 0xd9};
+        int length = photoIntArray.length;
+        byte[] photoByteArray = new byte[length];
+        for (int i = 0; i < length; i++) {
+            photoByteArray[i] = (byte)photoIntArray[i];
+        }
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "2.1",
+                        null, null, null, null, null),
+                new PropertyNode("N", "Gump;Forrest;Hoge;Pos;Tao",
+                        Arrays.asList("Gump", "Forrest",
+                                "Hoge", "Pos", "Tao"),
+                        null, null, null, null),
+                new PropertyNode("FN", "Joe Due",
+                        null, null, null, null, null),
+                new PropertyNode("ORG", 
+                        "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
+                        Arrays.asList("Gump Shrimp Co.",
+                                "Sales Dept.;Manager",
+                                "Fish keeper"),
+                        null, null, null, null),
+                new PropertyNode("ROLE", "Fish Cake Keeper!",
+                        null, null, null, null, null),
+                new PropertyNode("TITLE", "Shrimp Man",
+                        null, null, null, null, null),
+                new PropertyNode("X-CLASS", "PUBLIC",
+                        null, null, null, null, null),
+                new PropertyNode("TEL", "(111) 555-1212",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("WORK", "VOICE")), null),
+                new PropertyNode("TEL", "(404) 555-1212",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("HOME", "VOICE")), null),
+                new PropertyNode("TEL", "0311111111",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("CELL")), null),
+                new PropertyNode("TEL", "0322222222",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("VIDEO")), null),
+                new PropertyNode("TEL", "0333333333",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("VOICE")), null),     
+                new PropertyNode("ADR",
+                        ";;100 Waters Edge;Baytown;LA;30314;United States of America",
+                        Arrays.asList("", "", "100 Waters Edge", "Baytown",
+                                "LA", "30314", "United States of America"),
+                                null, null,
+                new HashSet<String>(Arrays.asList("WORK")), null),
+                new PropertyNode("LABEL",
+                        "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited  States of America",
+                        null, null, contentValuesForQP,
+                        new HashSet<String>(Arrays.asList("WORK")), null),
+                new PropertyNode("ADR",
+                        ";;42 Plantation St.;Baytown;LA;30314;United States of America",
+                        Arrays.asList("", "", "42 Plantation St.", "Baytown",
+                                "LA", "30314", "United States of America"), null, null,
+                        new HashSet<String>(Arrays.asList("HOME")), null),
+                new PropertyNode("LABEL",
+                        "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited  States of America",
+                        null, null, contentValuesForQP,
+                        new HashSet<String>(Arrays.asList("HOME")), null),
+                new PropertyNode("EMAIL", "forrestgump@walladalla.com",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("PREF", "INTERNET")), null),
+                new PropertyNode("EMAIL", "cell@example.com",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("CELL")), null),
+                new PropertyNode("NOTE", "The following note is the example from RFC 2045.",
+                        null, null, null, null, null),
+                new PropertyNode("NOTE",
+                        "Now's the time for all folk to come to the aid of their country.",
+                        null, null, contentValuesForQP, null, null),
+                new PropertyNode("PHOTO", null,
+                        null, photoByteArray, contentValuesForPhoto,
+                        new HashSet<String>(Arrays.asList("JPEG")), null),
+                new PropertyNode("X-ATTRIBUTE", "Some String",
+                        null, null, null, null, null),
+                new PropertyNode("BDAY", "19800101", 
+                        null, null, null, null, null),
+                new PropertyNode("GEO", "35.6563854,139.6994233",
+                        null, null, null, null, null),
+                new PropertyNode("URL", "http://www.example.com/", 
+                        null, null, null, null, null),
+                new PropertyNode("REV", "20080424T195243Z",
+                        null, null, null, null, null));
+        verifier.verify(builder.vNodeList.get(0));
+    }
+    
+    public void testV21Japanese1() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V21();
+        VDataBuilder builder = new VDataBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_1);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(1, builder.vNodeList.size());
+        ContentValues contentValuesForShiftJis = new ContentValues();
+        contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
+        ContentValues contentValuesForQP = new ContentValues();
+        contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+        contentValuesForQP.put("CHARSET", "SHIFT_JIS");
+        // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
+        // vCard 2.1/3.0 specification does not allow multiple values.
+        // Do not need to handle it as multiple values. 
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "2.1",
+                        null, null, null, null, null),
+                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
+                        null, contentValuesForShiftJis, null, null),
+                new PropertyNode("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
+                        null, null, contentValuesForShiftJis,
+                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+                new PropertyNode("TEL", "0300000000",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("VOICE", "PREF")), null));
+        verifier.verify(builder.vNodeList.get(0));
+    }
+    
+    public void testV21Japanese2() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V21();
+        VDataBuilder builder = new VDataBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_2);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(1, builder.vNodeList.size());
+        ContentValues contentValuesForShiftJis = new ContentValues();
+        contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
+        ContentValues contentValuesForQP = new ContentValues();
+        contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+        contentValuesForQP.put("CHARSET", "SHIFT_JIS");
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "2.1",
+                        null, null, null, null, null),
+                new PropertyNode("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
+                        Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
+                                "", "", ""),
+                        null, contentValuesForShiftJis, null, null),
+                new PropertyNode("FN",
+                        "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
+                        null, null, contentValuesForShiftJis, null, null),
+                new PropertyNode("SOUND",
+                        ("\uFF71\uFF9D\uFF84\uFF9E\uFF73" +
+                        ";\uFF9B\uFF72\uFF84\uFF9E\u0031;;;"),
+                        null, null, contentValuesForShiftJis,
+                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+                new PropertyNode("ADR",
+                        (";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" +
+                        "\u968E;;;;150-8512;"),
+                        Arrays.asList("",
+                                "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+                                "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+                                "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+                                "\u0036\u968E", "", "", "", "150-8512", ""),
+                        null, contentValuesForQP,
+                        new HashSet<String>(Arrays.asList("HOME")), null),
+                new PropertyNode("NOTE", "\u30E1\u30E2",
+                        null, null, contentValuesForQP, null, null));
+        verifier.verify(builder.vNodeList.get(0));
+    }
+    
+    public void testV21MultipleEntryCase() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V21();
+        VDataBuilder builder = new VDataBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v21_multiple_entry);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(3, builder.vNodeList.size());
+        ContentValues contentValuesForShiftJis = new ContentValues();
+        contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "2.1",
+                        null, null, null, null, null),
+                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
+                        null, contentValuesForShiftJis, null, null),
+                new PropertyNode("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
+                        null, null, contentValuesForShiftJis,
+                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+                new PropertyNode("TEL", "9",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("X-NEC-SECRET")), null),
+               new PropertyNode("TEL", "10",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("X-NEC-HOTEL")), null),
+               new PropertyNode("TEL", "11",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("X-NEC-SCHOOL")), null),
+               new PropertyNode("TEL", "12",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("FAX", "HOME")), null));
+        verifier.verify(builder.vNodeList.get(0));
+        
+        verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "2.1",
+                        null, null, null, null, null),
+                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
+                        null, contentValuesForShiftJis, null, null),
+                new PropertyNode("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
+                        null, null, contentValuesForShiftJis,
+                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+                new PropertyNode("TEL", "13",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("MODEM")), null),
+               new PropertyNode("TEL", "14",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("PAGER")), null),
+               new PropertyNode("TEL", "15",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("X-NEC-FAMILY")), null),
+               new PropertyNode("TEL", "16",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("X-NEC-GIRL")), null));
+        verifier.verify(builder.vNodeList.get(1));
+        verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "2.1",
+                        null, null, null, null, null),
+                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
+                        null, contentValuesForShiftJis, null, null),
+                new PropertyNode("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
+                        null, null, contentValuesForShiftJis,
+                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
+                new PropertyNode("TEL", "17",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("X-NEC-BOY")), null),
+               new PropertyNode("TEL", "18",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("X-NEC-FRIEND")), null),
+               new PropertyNode("TEL", "19",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("X-NEC-PHS")), null),
+               new PropertyNode("TEL", "20",
+                       null, null, null,
+                       new HashSet<String>(Arrays.asList("X-NEC-RESTAURANT")), null));
+        verifier.verify(builder.vNodeList.get(2));
+    }
+    
+    public void testV30SimpleCase() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V30();
+        VDataBuilder builder = new VDataBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v30_simple);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(1, builder.vNodeList.size());
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
+                new PropertyNode("VERSION", "3.0",
+                        null, null, null, null, null),
+                new PropertyNode("FN", "And Roid",
+                        null, null, null, null, null),
+                new PropertyNode("N", "And;Roid;;;",
+                        Arrays.asList("And", "Roid", "", "", ""),
+                        null, null, null, null),
+                new PropertyNode("ORG", "Open;Handset; Alliance",
+                        Arrays.asList("Open", "Handset", " Alliance"),
+                        null, null, null, null),
+                new PropertyNode("SORT-STRING", "android", null, null, null, null, null),
+                new PropertyNode("TEL", "0300000000",
+                        null, null, null,
+                        new HashSet<String>(Arrays.asList("PREF", "VOICE")), null),
+                new PropertyNode("CLASS", "PUBLIC", null, null, null, null, null),
+                new PropertyNode("X-GNO", "0", null, null, null, null, null),
+                new PropertyNode("X-GN", "group0", null, null, null, null, null),
+                new PropertyNode("X-REDUCTION", "0",
+                        null, null, null, null, null),
+                new PropertyNode("REV", "20081031T065854Z",
+                        null, null, null, null, null));
+        verifier.verify(builder.vNodeList.get(0));
+    }
+}