VCardEntryConstructor.java revision 4199c54c527330ac01699b176e7bca186a3aa3a4
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.vcard; 17 18import android.accounts.Account; 19import android.text.TextUtils; 20import android.util.Base64; 21import android.util.CharsetUtils; 22import android.util.Log; 23 24import java.io.UnsupportedEncodingException; 25import java.nio.ByteBuffer; 26import java.nio.charset.Charset; 27import java.util.ArrayList; 28import java.util.Collection; 29import java.util.List; 30 31/** 32 * <p> 33 * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects 34 * to easily handle each vCard entry. 35 * </p> 36 * <p> 37 * This class understand details inside vCard and translates it to {@link VCardEntry}. 38 * Then the class throw it to {@link VCardEntryHandler} registered via 39 * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects 40 * are able to handle the {@link VCardEntry} object. 41 * </p> 42 * <p> 43 * If you want to know the detail inside vCard, it would be better to implement 44 * {@link VCardInterpreter} directly, instead of relying on this class and 45 * {@link VCardEntry} created by the object. 46 * </p> 47 */ 48public class VCardEntryConstructor implements VCardInterpreter { 49 private static String LOG_TAG = "VCardEntryConstructor"; 50 51 private VCardEntry.Property mCurrentProperty = new VCardEntry.Property(); 52 private VCardEntry mCurrentVCardEntry; 53 private String mParamType; 54 55 // The charset using which {@link VCardInterpreter} parses the text. 56 // Each String is first decoded into binary stream with this charset, and encoded back 57 // to "target charset", which may be explicitly specified by the vCard with "CHARSET" 58 // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8). 59 private final String mSourceCharset; 60 61 private final boolean mStrictLineBreaking; 62 private final int mVCardType; 63 private final Account mAccount; 64 65 // For measuring performance. 66 private long mTimePushIntoContentResolver; 67 68 private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>(); 69 70 public VCardEntryConstructor() { 71 this(VCardConfig.VCARD_TYPE_V21_GENERIC, null); 72 } 73 74 public VCardEntryConstructor(final int vcardType) { 75 this(vcardType, null, null, false); 76 } 77 78 public VCardEntryConstructor(final int vcardType, final Account account) { 79 this(vcardType, account, null, false); 80 } 81 82 public VCardEntryConstructor(final int vcardType, final Account account, 83 final String inputCharset) { 84 this(vcardType, account, inputCharset, false); 85 } 86 87 /** 88 * @hide Just for testing. 89 */ 90 public VCardEntryConstructor(final int vcardType, final Account account, 91 final String inputCharset, final boolean strictLineBreakParsing) { 92 if (inputCharset != null) { 93 mSourceCharset = inputCharset; 94 } else { 95 mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET; 96 } 97 mStrictLineBreaking = strictLineBreakParsing; 98 mVCardType = vcardType; 99 mAccount = account; 100 } 101 102 public void addEntryHandler(VCardEntryHandler entryHandler) { 103 mEntryHandlers.add(entryHandler); 104 } 105 106 public void start() { 107 for (VCardEntryHandler entryHandler : mEntryHandlers) { 108 entryHandler.onStart(); 109 } 110 } 111 112 public void end() { 113 for (VCardEntryHandler entryHandler : mEntryHandlers) { 114 entryHandler.onEnd(); 115 } 116 } 117 118 public void clear() { 119 mCurrentVCardEntry = null; 120 mCurrentProperty = new VCardEntry.Property(); 121 } 122 123 public void startEntry() { 124 if (mCurrentVCardEntry != null) { 125 Log.e(LOG_TAG, "Nested VCard code is not supported now."); 126 } 127 mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount); 128 } 129 130 public void endEntry() { 131 mCurrentVCardEntry.consolidateFields(); 132 for (VCardEntryHandler entryHandler : mEntryHandlers) { 133 entryHandler.onEntryCreated(mCurrentVCardEntry); 134 } 135 mCurrentVCardEntry = null; 136 } 137 138 public void startProperty() { 139 mCurrentProperty.clear(); 140 } 141 142 public void endProperty() { 143 mCurrentVCardEntry.addProperty(mCurrentProperty); 144 } 145 146 public void propertyName(String name) { 147 mCurrentProperty.setPropertyName(name); 148 } 149 150 public void propertyGroup(String group) { 151 } 152 153 public void propertyParamType(String type) { 154 if (mParamType != null) { 155 Log.e(LOG_TAG, "propertyParamType() is called more than once " + 156 "before propertyParamValue() is called"); 157 } 158 mParamType = type; 159 } 160 161 public void propertyParamValue(String value) { 162 if (mParamType == null) { 163 // From vCard 2.1 specification. vCard 3.0 formally does not allow this case. 164 mParamType = "TYPE"; 165 } 166 mCurrentProperty.addParameter(mParamType, value); 167 mParamType = null; 168 } 169 170 private static String encodeToSystemCharset(String originalString, 171 String sourceCharset, String targetCharset) { 172 if (sourceCharset.equalsIgnoreCase(targetCharset)) { 173 return originalString; 174 } 175 final Charset charset = Charset.forName(sourceCharset); 176 final ByteBuffer byteBuffer = charset.encode(originalString); 177 // byteBuffer.array() "may" return byte array which is larger than 178 // byteBuffer.remaining(). Here, we keep on the safe side. 179 final byte[] bytes = new byte[byteBuffer.remaining()]; 180 byteBuffer.get(bytes); 181 try { 182 String ret = new String(bytes, targetCharset); 183 return ret; 184 } catch (UnsupportedEncodingException e) { 185 Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); 186 return null; 187 } 188 } 189 190 private String handleOneValue(String value, 191 String sourceCharset, String targetCharset, String encoding) { 192 if (value == null) { 193 Log.w(LOG_TAG, "Null is given."); 194 value = ""; 195 } 196 197 if (encoding != null) { 198 if (encoding.equals("BASE64") || encoding.equals("B")) { 199 mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT)); 200 return value; 201 } else if (encoding.equals("QUOTED-PRINTABLE")) { 202 return VCardUtils.parseQuotedPrintable( 203 value, mStrictLineBreaking, sourceCharset, targetCharset); 204 } 205 Log.w(LOG_TAG, "Unknown encoding. Fall back to default."); 206 } 207 208 // Just translate the charset of a given String from inputCharset to a system one. 209 return encodeToSystemCharset(value, sourceCharset, targetCharset); 210 } 211 212 public void propertyValues(List<String> values) { 213 if (values == null || values.isEmpty()) { 214 return; 215 } 216 217 final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET"); 218 final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING"); 219 final String encoding = 220 ((encodingCollection != null) ? encodingCollection.iterator().next() : null); 221 String targetCharset = CharsetUtils.nameForDefaultVendor( 222 ((charsetCollection != null) ? charsetCollection.iterator().next() : null)); 223 if (TextUtils.isEmpty(targetCharset)) { 224 targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET; 225 } 226 227 for (final String value : values) { 228 mCurrentProperty.addToPropertyValueList( 229 handleOneValue(value, mSourceCharset, targetCharset, encoding)); 230 } 231 } 232 233 /** 234 * @hide 235 */ 236 public void showPerformanceInfo() { 237 Log.d(LOG_TAG, "time for insert ContactStruct to database: " + 238 mTimePushIntoContentResolver + " ms"); 239 } 240} 241