VCardEntryConstructor.java revision 58ca5f9943bb5c8aeeab3150ac96f1143dfd86ba
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 @Override 107 public void start() { 108 for (VCardEntryHandler entryHandler : mEntryHandlers) { 109 entryHandler.onStart(); 110 } 111 } 112 113 @Override 114 public void end() { 115 for (VCardEntryHandler entryHandler : mEntryHandlers) { 116 entryHandler.onEnd(); 117 } 118 } 119 120 public void clear() { 121 mCurrentVCardEntry = null; 122 mCurrentProperty = new VCardEntry.Property(); 123 } 124 125 @Override 126 public void startEntry() { 127 if (mCurrentVCardEntry != null) { 128 Log.e(LOG_TAG, "Nested VCard code is not supported now."); 129 } 130 mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount); 131 } 132 133 @Override 134 public void endEntry() { 135 mCurrentVCardEntry.consolidateFields(); 136 for (VCardEntryHandler entryHandler : mEntryHandlers) { 137 entryHandler.onEntryCreated(mCurrentVCardEntry); 138 } 139 mCurrentVCardEntry = null; 140 } 141 142 @Override 143 public void startProperty() { 144 mCurrentProperty.clear(); 145 } 146 147 @Override 148 public void endProperty() { 149 mCurrentVCardEntry.addProperty(mCurrentProperty); 150 } 151 152 @Override 153 public void propertyName(String name) { 154 mCurrentProperty.setPropertyName(name); 155 } 156 157 @Override 158 public void propertyGroup(String group) { 159 } 160 161 @Override 162 public void propertyParamType(String type) { 163 if (mParamType != null) { 164 Log.e(LOG_TAG, "propertyParamType() is called more than once " + 165 "before propertyParamValue() is called"); 166 } 167 mParamType = type; 168 } 169 170 @Override 171 public void propertyParamValue(String value) { 172 if (mParamType == null) { 173 // From vCard 2.1 specification. vCard 3.0 formally does not allow this case. 174 mParamType = "TYPE"; 175 } 176 if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) { 177 value = VCardUtils.convertStringCharset( 178 value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET); 179 } 180 mCurrentProperty.addParameter(mParamType, value); 181 mParamType = null; 182 } 183 184 private String handleOneValue(String value, 185 String sourceCharset, String targetCharset, String encoding) { 186 // It is possible when some of multiple values are empty. 187 // e.g. N:;a;;; -> values are "", "a", "", "", and "". 188 if (TextUtils.isEmpty(value)) { 189 return ""; 190 } 191 192 if (encoding != null) { 193 if (encoding.equals("BASE64") || encoding.equals("B")) { 194 mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT)); 195 return value; 196 } else if (encoding.equals("QUOTED-PRINTABLE")) { 197 return VCardUtils.parseQuotedPrintable( 198 value, mStrictLineBreaking, sourceCharset, targetCharset); 199 } 200 Log.w(LOG_TAG, "Unknown encoding. Fall back to default."); 201 } 202 203 // Just translate the charset of a given String from inputCharset to a system one. 204 return VCardUtils.convertStringCharset(value, sourceCharset, targetCharset); 205 } 206 207 public void propertyValues(List<String> values) { 208 if (values == null || values.isEmpty()) { 209 return; 210 } 211 212 final Collection<String> charsetCollection = 213 mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET); 214 final Collection<String> encodingCollection = 215 mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING); 216 final String encoding = 217 ((encodingCollection != null) ? encodingCollection.iterator().next() : null); 218 String targetCharset = CharsetUtils.nameForDefaultVendor( 219 ((charsetCollection != null) ? charsetCollection.iterator().next() : null)); 220 if (TextUtils.isEmpty(targetCharset)) { 221 targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET; 222 } 223 224 for (final String value : values) { 225 mCurrentProperty.addToPropertyValueList( 226 handleOneValue(value, mSourceCharset, targetCharset, encoding)); 227 } 228 } 229 230 /** 231 * @hide 232 */ 233 public void showPerformanceInfo() { 234 Log.d(LOG_TAG, "time for insert ContactStruct to database: " + 235 mTimePushIntoContentResolver + " ms"); 236 } 237} 238