VCardEntryConstructor.java revision 02117b3d19787ff65486b9f9db8abd338ae4c9f9
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.Log;
22
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.List;
26
27/**
28 * <p>
29 * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
30 * to easily handle each vCard entry.
31 * </p>
32 * <p>
33 * This class understand details inside vCard and translates it to {@link VCardEntry}.
34 * Then the class throw it to {@link VCardEntryHandler} registered via
35 * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
36 * are able to handle the {@link VCardEntry} object.
37 * </p>
38 * <p>
39 * If you want to know the detail inside vCard, it would be better to implement
40 * {@link VCardInterpreter} directly, instead of relying on this class and
41 * {@link VCardEntry} created by the object.
42 * </p>
43 */
44public class VCardEntryConstructor implements VCardInterpreter {
45    private static String LOG_TAG = VCardConstants.LOG_TAG;
46
47    private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
48    private VCardEntry mCurrentVCardEntry;
49    private String mParamType;
50
51    // The charset using which {@link VCardInterpreter} parses the text.
52    // Each String is first decoded into binary stream with this charset, and encoded back
53    // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
54    // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
55    private final String mSourceCharset;
56
57    private final boolean mStrictLineBreaking;
58    private final int mVCardType;
59    private final Account mAccount;
60
61    // For measuring performance.
62    private long mTimePushIntoContentResolver;
63
64    private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
65
66    public VCardEntryConstructor() {
67        this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
68    }
69
70    public VCardEntryConstructor(final int vcardType) {
71        this(vcardType, null, null, false);
72    }
73
74    public VCardEntryConstructor(final int vcardType, final Account account) {
75        this(vcardType, account, null, false);
76    }
77
78    public VCardEntryConstructor(final int vcardType, final Account account,
79            final String inputCharset) {
80        this(vcardType, account, inputCharset, false);
81    }
82
83    /**
84     * @hide Just for testing.
85     */
86    public VCardEntryConstructor(final int vcardType, final Account account,
87            final String inputCharset, final boolean strictLineBreakParsing) {
88        if (inputCharset != null) {
89            mSourceCharset = inputCharset;
90        } else {
91            mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
92        }
93        mStrictLineBreaking = strictLineBreakParsing;
94        mVCardType = vcardType;
95        mAccount = account;
96    }
97
98    public void addEntryHandler(VCardEntryHandler entryHandler) {
99        mEntryHandlers.add(entryHandler);
100    }
101
102    @Override
103    public void start() {
104        for (VCardEntryHandler entryHandler : mEntryHandlers) {
105            entryHandler.onStart();
106        }
107    }
108
109    @Override
110    public void end() {
111        for (VCardEntryHandler entryHandler : mEntryHandlers) {
112            entryHandler.onEnd();
113        }
114    }
115
116    public void clear() {
117        mCurrentVCardEntry = null;
118        mCurrentProperty = new VCardEntry.Property();
119    }
120
121    @Override
122    public void startEntry() {
123        if (mCurrentVCardEntry != null) {
124            Log.e(LOG_TAG, "Nested VCard code is not supported now.");
125        }
126        mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
127    }
128
129    @Override
130    public void endEntry() {
131        mCurrentVCardEntry.consolidateFields();
132        for (VCardEntryHandler entryHandler : mEntryHandlers) {
133            entryHandler.onEntryCreated(mCurrentVCardEntry);
134        }
135        mCurrentVCardEntry = null;
136    }
137
138    @Override
139    public void startProperty() {
140        mCurrentProperty.clear();
141    }
142
143    @Override
144    public void endProperty() {
145        mCurrentVCardEntry.addProperty(mCurrentProperty);
146    }
147
148    @Override
149    public void propertyName(String name) {
150        mCurrentProperty.setPropertyName(name);
151    }
152
153    @Override
154    public void propertyGroup(String group) {
155    }
156
157    @Override
158    public void propertyParamType(String type) {
159        if (mParamType != null) {
160            Log.e(LOG_TAG, "propertyParamType() is called more than once " +
161                    "before propertyParamValue() is called");
162        }
163        mParamType = type;
164    }
165
166    @Override
167    public void propertyParamValue(String value) {
168        if (mParamType == null) {
169            // From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
170            mParamType = "TYPE";
171        }
172        if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
173            value = VCardUtils.convertStringCharset(
174                    value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET);
175        }
176        mCurrentProperty.addParameter(mParamType, value);
177        mParamType = null;
178    }
179
180    private String handleOneValue(String value,
181            String sourceCharset, String targetCharset, String encoding) {
182        // It is possible when some of multiple values are empty.
183        // e.g. N:;a;;; -> values are "", "a", "", "", and "".
184        if (TextUtils.isEmpty(value)) {
185            return "";
186        }
187
188        if (encoding != null) {
189            if (encoding.equals("BASE64") || encoding.equals("B")) {
190                mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
191                return value;
192            } else if (encoding.equals("QUOTED-PRINTABLE")) {
193                return VCardUtils.parseQuotedPrintable(
194                        value, mStrictLineBreaking, sourceCharset, targetCharset);
195            }
196            Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
197        }
198
199        // Just translate the charset of a given String from inputCharset to a system one.
200        return VCardUtils.convertStringCharset(value, sourceCharset, targetCharset);
201    }
202
203    @Override
204    public void propertyValues(List<String> values) {
205        if (values == null || values.isEmpty()) {
206            return;
207        }
208
209        final Collection<String> charsetCollection =
210                mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET);
211        final Collection<String> encodingCollection =
212                mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING);
213        final String encoding =
214            ((encodingCollection != null) ? encodingCollection.iterator().next() : null);
215
216        // String targetCharset = CharsetUtils.nameForDefaultVendor(
217        //    ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
218        String targetCharset =
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