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  */
16 package com.android.vcard;
17 
18 import android.text.TextUtils;
19 import android.util.Log;
20 
21 import java.util.Arrays;
22 import java.util.HashSet;
23 import java.util.List;
24 import 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  */
42 public class VCardSourceDetector implements VCardInterpreter {
43     private static final String LOG_TAG = VCardConstants.LOG_TAG;
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.
74     private static final int PARSE_TYPE_DOCOMO_FOMA = 3;
75     // For Japanese Windows Mobile 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 = PARSE_TYPE_UNKNOWN;
79 
80     private int mVersion = -1;  // -1 == unknown
81 
82     // Some mobile phones (like FOMA) tells us the charset of the data.
83     private String mSpecifiedCharset;
84 
85     @Override
onVCardStarted()86     public void onVCardStarted() {
87     }
88 
89     @Override
onVCardEnded()90     public void onVCardEnded() {
91     }
92 
93     @Override
onEntryStarted()94     public void onEntryStarted() {
95     }
96 
97     @Override
onEntryEnded()98     public void onEntryEnded() {
99     }
100 
101     @Override
onPropertyCreated(VCardProperty property)102     public void onPropertyCreated(VCardProperty property) {
103         final String propertyName = property.getName();
104         final List<String> valueList = property.getValueList();
105 
106         if (propertyName.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)
107                 && valueList.size() > 0) {
108             final String versionString = valueList.get(0);
109             if (versionString.equals(VCardConstants.VERSION_V21)) {
110                 mVersion = VCardConfig.VERSION_21;
111             } else if (versionString.equals(VCardConstants.VERSION_V30)) {
112                 mVersion = VCardConfig.VERSION_30;
113             } else if (versionString.equals(VCardConstants.VERSION_V40)) {
114                 mVersion = VCardConfig.VERSION_40;
115             } else {
116                 Log.w(LOG_TAG, "Invalid version string: " + versionString);
117             }
118         } else if (propertyName.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
119             mParseType = PARSE_TYPE_DOCOMO_FOMA;
120             if (valueList.size() > 0) {
121                 mSpecifiedCharset = valueList.get(0);
122             }
123         }
124         if (mParseType != PARSE_TYPE_UNKNOWN) {
125             return;
126         }
127         if (WINDOWS_MOBILE_PHONE_SIGNS.contains(propertyName)) {
128             mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
129         } else if (FOMA_SIGNS.contains(propertyName)) {
130             mParseType = PARSE_TYPE_DOCOMO_FOMA;
131         } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(propertyName)) {
132             mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
133         } else if (APPLE_SIGNS.contains(propertyName)) {
134             mParseType = PARSE_TYPE_APPLE;
135         }
136     }
137 
138     /**
139      * @return The available type can be used with vCard parser. You probably need to
140      * use {{@link #getEstimatedCharset()} to understand the charset to be used.
141      */
getEstimatedType()142     public int getEstimatedType() {
143         switch (mParseType) {
144             case PARSE_TYPE_DOCOMO_FOMA:
145                 return VCardConfig.VCARD_TYPE_DOCOMO;
146             case PARSE_TYPE_MOBILE_PHONE_JP:
147                 return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
148             case PARSE_TYPE_APPLE:
149             case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
150             default: {
151                 if (mVersion == VCardConfig.VERSION_21) {
152                     return VCardConfig.VCARD_TYPE_V21_GENERIC;
153                 } else if (mVersion == VCardConfig.VERSION_30) {
154                     return VCardConfig.VCARD_TYPE_V30_GENERIC;
155                 } else if (mVersion == VCardConfig.VERSION_40) {
156                     return VCardConfig.VCARD_TYPE_V40_GENERIC;
157                 } else {
158                     return VCardConfig.VCARD_TYPE_UNKNOWN;
159                 }
160             }
161         }
162     }
163 
164     /**
165      * <p>
166      * Returns charset String guessed from the source's properties.
167      * This method must be called after parsing target file(s).
168      * </p>
169      * @return Charset String. Null is returned if guessing the source fails.
170      */
getEstimatedCharset()171     public String getEstimatedCharset() {
172         if (TextUtils.isEmpty(mSpecifiedCharset)) {
173             return mSpecifiedCharset;
174         }
175         switch (mParseType) {
176             case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
177             case PARSE_TYPE_DOCOMO_FOMA:
178             case PARSE_TYPE_MOBILE_PHONE_JP:
179                 return "SHIFT_JIS";
180             case PARSE_TYPE_APPLE:
181                 return "UTF-8";
182             default:
183                 return null;
184         }
185     }
186 }
187