VCardSourceDetector.java revision 677ef21613a9d35053ec098444832ce4125a847e
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.text.TextUtils;
19import android.util.Log;
20
21import java.util.Arrays;
22import java.util.HashSet;
23import java.util.List;
24import java.util.Set;
25
26/**
27 * <p>
28 * The class which tries to detects the source of a vCard file from its contents.
29 * </p>
30 * <p>
31 * The specification of vCard (including both 2.1 and 3.0) is not so strict as to
32 * guess its format just by reading beginning few lines (usually we can, but in
33 * some most pessimistic case, we cannot until at almost the end of the file).
34 * Also we cannot store all vCard entries in memory, while there's no specification
35 * how big the vCard entry would become after the parse.
36 * </p>
37 * <p>
38 * This class is usually used for the "first scan", in which we can understand which vCard
39 * version is used (and how many entries exist in a file).
40 * </p>
41 */
42public class VCardSourceDetector implements VCardInterpreter {
43    private static final String LOG_TAG = "VCardSourceDetector";
44
45    private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
46            "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
47            "X-ABADR", "X-ABUID"));
48
49    private static Set<String> JAPANESE_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
50            "X-GNO", "X-GN", "X-REDUCTION"));
51
52    private static Set<String> WINDOWS_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
53            "X-MICROSOFT-ASST_TEL", "X-MICROSOFT-ASSISTANT", "X-MICROSOFT-OFFICELOC"));
54
55    // Note: these signes appears before the signs of the other type (e.g. "X-GN").
56    // In other words, Japanese FOMA mobile phones are detected as FOMA, not JAPANESE_MOBILE_PHONES.
57    private static Set<String> FOMA_SIGNS = new HashSet<String>(Arrays.asList(
58            "X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
59            "X-SD-DESCRIPTION"));
60    private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
61
62    /**
63     * Represents that no estimation is available. Users of this class is able to this
64     * constant when you don't want to let a vCard parser rely on estimation for parse type.
65     */
66    public static final int PARSE_TYPE_UNKNOWN = 0;
67
68    // For Apple's software, which does not mean this type is effective for all its products.
69    // We confirmed they usually use UTF-8, but not sure about vCard type.
70    private static final int PARSE_TYPE_APPLE = 1;
71    // For Japanese mobile phones, which are usually using Shift_JIS as a charset.
72    private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
73    // For some of mobile phones released from DoCoMo, which use nested vCard.
74    private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
75    // For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
76    private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
77
78    private int mParseType = 0;  // Not sure.
79
80    private boolean mNeedToParseVersion = false;
81    private int mVersion = -1;  // -1 == unknown
82
83    // Some mobile phones (like FOMA) tells us the charset of the data.
84    private boolean mNeedToParseCharset;
85    private String mSpecifiedCharset;
86
87    @Override
88    public void start() {
89    }
90
91    @Override
92    public void end() {
93    }
94
95    @Override
96    public void startEntry() {
97    }
98
99    @Override
100    public void startProperty() {
101        mNeedToParseCharset = false;
102        mNeedToParseVersion = false;
103    }
104
105    @Override
106    public void endProperty() {
107    }
108
109    @Override
110    public void endEntry() {
111    }
112
113    @Override
114    public void propertyGroup(String group) {
115    }
116
117    @Override
118    public void propertyName(String name) {
119        if (name.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)) {
120            mNeedToParseVersion = true;
121            return;
122        } else if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
123            mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
124            // Probably Shift_JIS is used, but we should double confirm.
125            mNeedToParseCharset = true;
126            return;
127        }
128        if (mParseType != PARSE_TYPE_UNKNOWN) {
129            return;
130        }
131        if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
132            mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
133        } else if (FOMA_SIGNS.contains(name)) {
134            mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
135        } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
136            mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
137        } else if (APPLE_SIGNS.contains(name)) {
138            mParseType = PARSE_TYPE_APPLE;
139        }
140    }
141
142    @Override
143    public void propertyParamType(String type) {
144    }
145
146    @Override
147    public void propertyParamValue(String value) {
148    }
149
150    @Override
151    public void propertyValues(List<String> values) {
152        if (mNeedToParseVersion && values.size() > 0) {
153            final String versionString = values.get(0);
154            if (versionString.equals(VCardConstants.VERSION_V21)) {
155                mVersion = VCardConfig.VERSION_21;
156            } else if (versionString.equals(VCardConstants.VERSION_V30)) {
157                mVersion = VCardConfig.VERSION_30;
158            } else if (versionString.equals(VCardConstants.VERSION_V40)) {
159                mVersion = VCardConfig.VERSION_40;
160            } else {
161                Log.w(LOG_TAG, "Invalid version string: " + versionString);
162            }
163        } else if (mNeedToParseCharset && values.size() > 0) {
164            mSpecifiedCharset = values.get(0);
165        }
166    }
167
168    /**
169     * @return The available type can be used with vCard parser. You probably need to
170     * use {{@link #getEstimatedCharset()} to understand the charset to be used.
171     */
172    public int getEstimatedType() {
173        switch (mParseType) {
174            case PARSE_TYPE_DOCOMO_TORELATE_NEST:
175                return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
176            case PARSE_TYPE_MOBILE_PHONE_JP:
177                return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
178            case PARSE_TYPE_APPLE:
179            case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
180            default: {
181                if (mVersion == VCardConfig.VERSION_21) {
182                    return VCardConfig.VCARD_TYPE_V21_GENERIC;
183                } else if (mVersion == VCardConfig.VERSION_30) {
184                    return VCardConfig.VCARD_TYPE_V30_GENERIC;
185                } else if (mVersion == VCardConfig.VERSION_40) {
186                    return VCardConfig.VCARD_TYPE_V40_GENERIC;
187                } else {
188                    return VCardConfig.VCARD_TYPE_UNKNOWN;
189                }
190            }
191        }
192    }
193
194    /**
195     * <p>
196     * Returns charset String guessed from the source's properties.
197     * This method must be called after parsing target file(s).
198     * </p>
199     * @return Charset String. Null is returned if guessing the source fails.
200     */
201    public String getEstimatedCharset() {
202        if (TextUtils.isEmpty(mSpecifiedCharset)) {
203            return mSpecifiedCharset;
204        }
205        switch (mParseType) {
206            case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
207            case PARSE_TYPE_DOCOMO_TORELATE_NEST:
208            case PARSE_TYPE_MOBILE_PHONE_JP:
209                return "SHIFT_JIS";
210            case PARSE_TYPE_APPLE:
211                return "UTF-8";
212            default:
213                return null;
214        }
215    }
216}
217