• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.pim.vcard;
17 
18 import android.content.ContentProviderOperation;
19 import android.content.ContentValues;
20 import android.provider.ContactsContract.Data;
21 import android.provider.ContactsContract.CommonDataKinds.Phone;
22 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
23 import android.telephony.PhoneNumberUtils;
24 import android.text.TextUtils;
25 
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Map;
30 import java.util.Set;
31 
32 /**
33  * Utilities for VCard handling codes.
34  */
35 public class VCardUtils {
36     /*
37      * TODO: some of methods in this class should be placed to the more appropriate place...
38      */
39 
40     // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
41     // converted to two attribute Strings. These only contain some minor fields valid in both
42     // vCard and current (as of 2009-08-07) Contacts structure.
43     private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
44     private static final Set<String> sPhoneTypesSetUnknownToContacts;
45 
46     private static final Map<String, Integer> sKnownPhoneTypesMap_StoI;
47 
48     static {
49         sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
50         sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>();
51 
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR)52         sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR)53         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER)54         sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER)55         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN)56         sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN)57         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
58 
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME)59         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK)60         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE)61         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
62 
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER)63         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK)64         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK);
sKnownPhoneTypesMap_StoI.put( Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN)65         sKnownPhoneTypesMap_StoI.put(
66                 Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO)67         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX)68         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD)69         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD);
sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT)70         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT);
71 
72         sPhoneTypesSetUnknownToContacts = new HashSet<String>();
73         sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
74         sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MSG);
75         sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
76         sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
77     }
78 
getPhoneAttributeString(Integer type)79     public static String getPhoneAttributeString(Integer type) {
80         return sKnownPhoneTypesMap_ItoS.get(type);
81     }
82 
83     /**
84      * Returns Interger when the given types can be parsed as known type. Returns String object
85      * when not, which should be set to label.
86      */
getPhoneTypeFromStrings(Collection<String> types)87     public static Object getPhoneTypeFromStrings(Collection<String> types) {
88         int type = -1;
89         String label = null;
90         boolean isFax = false;
91         boolean hasPref = false;
92 
93         if (types != null) {
94             for (String typeString : types) {
95                 typeString = typeString.toUpperCase();
96                 if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
97                     hasPref = true;
98                 } else if (typeString.equals(Constants.ATTR_TYPE_FAX)) {
99                     isFax = true;
100                 } else {
101                     if (typeString.startsWith("X-") && type < 0) {
102                         typeString = typeString.substring(2);
103                     }
104                     Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
105                     if (tmp != null) {
106                         type = tmp;
107                     } else if (type < 0) {
108                         type = Phone.TYPE_CUSTOM;
109                         label = typeString;
110                     }
111                 }
112             }
113         }
114         if (type < 0) {
115             if (hasPref) {
116                 type = Phone.TYPE_MAIN;
117             } else {
118                 // default to TYPE_HOME
119                 type = Phone.TYPE_HOME;
120             }
121         }
122         if (isFax) {
123             if (type == Phone.TYPE_HOME) {
124                 type = Phone.TYPE_FAX_HOME;
125             } else if (type == Phone.TYPE_WORK) {
126                 type = Phone.TYPE_FAX_WORK;
127             } else if (type == Phone.TYPE_OTHER) {
128                 type = Phone.TYPE_OTHER_FAX;
129             }
130         }
131         if (type == Phone.TYPE_CUSTOM) {
132             return label;
133         } else {
134             return type;
135         }
136     }
137 
isValidPhoneAttribute(String phoneAttribute, int vcardType)138     public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) {
139         // TODO: check the following.
140         // - it may violate vCard spec
141         // - it may contain non-ASCII characters
142         //
143         // TODO: use vcardType
144         return (phoneAttribute.startsWith("X-") || phoneAttribute.startsWith("x-") ||
145                 sPhoneTypesSetUnknownToContacts.contains(phoneAttribute));
146     }
147 
sortNameElements(int vcardType, String familyName, String middleName, String givenName)148     public static String[] sortNameElements(int vcardType,
149             String familyName, String middleName, String givenName) {
150         String[] list = new String[3];
151         switch (VCardConfig.getNameOrderType(vcardType)) {
152         case VCardConfig.NAME_ORDER_JAPANESE:
153             // TODO: Should handle Ascii case?
154             list[0] = familyName;
155             list[1] = middleName;
156             list[2] = givenName;
157             break;
158         case VCardConfig.NAME_ORDER_EUROPE:
159             list[0] = middleName;
160             list[1] = givenName;
161             list[2] = familyName;
162             break;
163         default:
164             list[0] = givenName;
165             list[1] = middleName;
166             list[2] = familyName;
167             break;
168         }
169         return list;
170     }
171 
getPhoneNumberFormat(final int vcardType)172     public static int getPhoneNumberFormat(final int vcardType) {
173         if (VCardConfig.isJapaneseDevice(vcardType)) {
174             return PhoneNumberUtils.FORMAT_JAPAN;
175         } else {
176             return PhoneNumberUtils.FORMAT_NANP;
177         }
178     }
179 
180     /**
181      * Inserts postal data into the builder object.
182      *
183      * Note that the data structure of ContactsContract is different from that defined in vCard.
184      * So some conversion may be performed in this method. See also
185      * {{@link #getVCardPostalElements(ContentValues)}
186      */
insertStructuredPostalDataUsingContactsStruct(int vcardType, final ContentProviderOperation.Builder builder, final ContactStruct.PostalData postalData)187     public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
188             final ContentProviderOperation.Builder builder,
189             final ContactStruct.PostalData postalData) {
190         builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
191         builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
192 
193         builder.withValue(StructuredPostal.TYPE, postalData.type);
194         if (postalData.type == StructuredPostal.TYPE_CUSTOM) {
195             builder.withValue(StructuredPostal.LABEL, postalData.label);
196         }
197 
198         builder.withValue(StructuredPostal.POBOX, postalData.pobox);
199         // Extended address is dropped since there's no relevant entry in ContactsContract.
200         builder.withValue(StructuredPostal.STREET, postalData.street);
201         builder.withValue(StructuredPostal.CITY, postalData.localty);
202         builder.withValue(StructuredPostal.REGION, postalData.region);
203         builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
204         builder.withValue(StructuredPostal.COUNTRY, postalData.country);
205 
206         builder.withValue(StructuredPostal.FORMATTED_ADDRESS,
207                 postalData.getFormattedAddress(vcardType));
208         if (postalData.isPrimary) {
209             builder.withValue(Data.IS_PRIMARY, 1);
210         }
211     }
212 
213     /**
214      * Returns String[] containing address information based on vCard spec
215      * (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
216      * All String objects are non-null ("" is used when the relevant data is empty).
217      *
218      * Note that the data structure of ContactsContract is different from that defined in vCard.
219      * So some conversion may be performed in this method. See also
220      * {{@link #insertStructuredPostalDataUsingContactsStruct(int,
221      * android.content.ContentProviderOperation.Builder,
222      * android.pim.vcard.ContactStruct.PostalData)}
223      */
getVCardPostalElements(ContentValues contentValues)224     public static String[] getVCardPostalElements(ContentValues contentValues) {
225         String[] dataArray = new String[7];
226         dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
227         if (dataArray[0] == null) {
228             dataArray[0] = "";
229         }
230         // Extended addr. There's no relevant data in ContactsContract.
231         dataArray[1] = "";
232         dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
233         if (dataArray[2] == null) {
234             dataArray[2] = "";
235         }
236         // Assume that localty == city
237         dataArray[3] = contentValues.getAsString(StructuredPostal.CITY);
238         if (dataArray[3] == null) {
239             dataArray[3] = "";
240         }
241         String region = contentValues.getAsString(StructuredPostal.REGION);
242         if (!TextUtils.isEmpty(region)) {
243             dataArray[4] = region;
244         } else {
245             dataArray[4] = "";
246         }
247         dataArray[5] = contentValues.getAsString(StructuredPostal.POSTCODE);
248         if (dataArray[5] == null) {
249             dataArray[5] = "";
250         }
251         dataArray[6] = contentValues.getAsString(StructuredPostal.COUNTRY);
252         if (dataArray[6] == null) {
253             dataArray[6] = "";
254         }
255 
256         return dataArray;
257     }
258 
constructNameFromElements(int nameOrderType, String familyName, String middleName, String givenName)259     public static String constructNameFromElements(int nameOrderType,
260             String familyName, String middleName, String givenName) {
261         return constructNameFromElements(nameOrderType, familyName, middleName, givenName,
262                 null, null);
263     }
264 
constructNameFromElements(int nameOrderType, String familyName, String middleName, String givenName, String prefix, String suffix)265     public static String constructNameFromElements(int nameOrderType,
266             String familyName, String middleName, String givenName,
267             String prefix, String suffix) {
268         StringBuilder builder = new StringBuilder();
269         String[] nameList = sortNameElements(nameOrderType,
270                 familyName, middleName, givenName);
271         boolean first = true;
272         if (!TextUtils.isEmpty(prefix)) {
273             first = false;
274             builder.append(prefix);
275         }
276         for (String namePart : nameList) {
277             if (!TextUtils.isEmpty(namePart)) {
278                 if (first) {
279                     first = false;
280                 } else {
281                     builder.append(' ');
282                 }
283                 builder.append(namePart);
284             }
285         }
286         if (!TextUtils.isEmpty(suffix)) {
287             if (!first) {
288                 builder.append(' ');
289             }
290             builder.append(suffix);
291         }
292         return builder.toString();
293     }
294 
containsOnlyPrintableAscii(String str)295     public static boolean containsOnlyPrintableAscii(String str) {
296         if (TextUtils.isEmpty(str)) {
297             return true;
298         }
299 
300         final int length = str.length();
301         final int asciiFirst = 0x20;
302         final int asciiLast = 0x126;
303         for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
304             int c = str.codePointAt(i);
305             if (c < asciiFirst || asciiLast < c) {
306                 return false;
307             }
308         }
309         return true;
310     }
311 
312     /**
313      * This is useful when checking the string should be encoded into quoted-printable
314      * or not, which is required by vCard 2.1.
315      * See the definition of "7bit" in vCard 2.1 spec for more information.
316      */
containsOnlyNonCrLfPrintableAscii(String str)317     public static boolean containsOnlyNonCrLfPrintableAscii(String str) {
318         if (TextUtils.isEmpty(str)) {
319             return true;
320         }
321 
322         final int length = str.length();
323         final int asciiFirst = 0x20;
324         final int asciiLast = 0x126;
325         for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
326             int c = str.codePointAt(i);
327             if (c < asciiFirst || asciiLast < c || c == '\n' || c == '\r') {
328                 return false;
329             }
330         }
331         return true;
332     }
333 
334     /**
335      * This is useful since vCard 3.0 often requires the ("X-") properties and groups
336      * should contain only alphabets, digits, and hyphen.
337      *
338      * Note: It is already known some devices (wrongly) outputs properties with characters
339      *       which should not be in the field. One example is "X-GOOGLE TALK". We accept
340      *       such kind of input but must never output it unless the target is very specific
341      *       to the device which is able to parse the malformed input.
342      */
containsOnlyAlphaDigitHyphen(String str)343     public static boolean containsOnlyAlphaDigitHyphen(String str) {
344         if (TextUtils.isEmpty(str)) {
345             return true;
346         }
347 
348         final int lowerAlphabetFirst = 0x41;  // included ('A')
349         final int lowerAlphabetLast = 0x5b;  // not included ('[')
350         final int upperAlphabetFirst = 0x61;  // included ('a')
351         final int upperAlphabetLast = 0x7b;  // included ('{')
352         final int digitFirst = 0x30;  // included ('0')
353         final int digitLast = 0x39;  // included ('9')
354         final int hyphen = '-';
355         final int length = str.length();
356         for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
357             int codepoint = str.codePointAt(i);
358             if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetLast) ||
359                     (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetLast) ||
360                     (digitFirst <= codepoint && codepoint < digitLast) ||
361                     (codepoint == hyphen))) {
362                 return false;
363             }
364         }
365         return true;
366     }
367 
368     // TODO: Replace wth the method in Base64 class.
369     private static char PAD = '=';
370     private static final char[] ENCODE64 = {
371         'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
372         'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
373         'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
374         'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
375     };
376 
encodeBase64(byte[] data)377     static public String encodeBase64(byte[] data) {
378         if (data == null) {
379             return "";
380         }
381 
382         char[] charBuffer = new char[(data.length + 2) / 3 * 4];
383         int position = 0;
384         int _3byte = 0;
385         for (int i=0; i<data.length-2; i+=3) {
386             _3byte = ((data[i] & 0xFF) << 16) + ((data[i+1] & 0xFF) << 8) + (data[i+2] & 0xFF);
387             charBuffer[position++] = ENCODE64[_3byte >> 18];
388             charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
389             charBuffer[position++] = ENCODE64[(_3byte >>  6) & 0x3F];
390             charBuffer[position++] = ENCODE64[_3byte & 0x3F];
391         }
392         switch(data.length % 3) {
393         case 1: // [111111][11 0000][0000 00][000000]
394             _3byte = ((data[data.length-1] & 0xFF) << 16);
395             charBuffer[position++] = ENCODE64[_3byte >> 18];
396             charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
397             charBuffer[position++] = PAD;
398             charBuffer[position++] = PAD;
399             break;
400         case 2: // [111111][11 1111][1111 00][000000]
401             _3byte = ((data[data.length-2] & 0xFF) << 16) + ((data[data.length-1] & 0xFF) << 8);
402             charBuffer[position++] = ENCODE64[_3byte >> 18];
403             charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
404             charBuffer[position++] = ENCODE64[(_3byte >>  6) & 0x3F];
405             charBuffer[position++] = PAD;
406             break;
407         }
408 
409         return new String(charBuffer);
410     }
411 
toHalfWidthString(String orgString)412     static public String toHalfWidthString(String orgString) {
413         if (TextUtils.isEmpty(orgString)) {
414             return null;
415         }
416         StringBuilder builder = new StringBuilder();
417         int length = orgString.length();
418         for (int i = 0; i < length; i++) {
419             // All Japanese character is able to be expressed by char.
420             // Do not need to use String#codepPointAt().
421             char ch = orgString.charAt(i);
422             CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
423             if (halfWidthText != null) {
424                 builder.append(halfWidthText);
425             } else {
426                 builder.append(ch);
427             }
428         }
429         return builder.toString();
430     }
431 
VCardUtils()432     private VCardUtils() {
433     }
434 }
435 
436 /**
437  * TextUtils especially for Japanese.
438  * TODO: make this in android.text in the future
439  */
440 class JapaneseUtils {
441     static private final Map<Character, String> sHalfWidthMap =
442         new HashMap<Character, String>();
443 
444     static {
445         // There's no logical mapping rule in Unicode. Sigh.
446         sHalfWidthMap.put('\u3001', "\uFF64");
447         sHalfWidthMap.put('\u3002', "\uFF61");
448         sHalfWidthMap.put('\u300C', "\uFF62");
449         sHalfWidthMap.put('\u300D', "\uFF63");
450         sHalfWidthMap.put('\u301C', "~");
451         sHalfWidthMap.put('\u3041', "\uFF67");
452         sHalfWidthMap.put('\u3042', "\uFF71");
453         sHalfWidthMap.put('\u3043', "\uFF68");
454         sHalfWidthMap.put('\u3044', "\uFF72");
455         sHalfWidthMap.put('\u3045', "\uFF69");
456         sHalfWidthMap.put('\u3046', "\uFF73");
457         sHalfWidthMap.put('\u3047', "\uFF6A");
458         sHalfWidthMap.put('\u3048', "\uFF74");
459         sHalfWidthMap.put('\u3049', "\uFF6B");
460         sHalfWidthMap.put('\u304A', "\uFF75");
461         sHalfWidthMap.put('\u304B', "\uFF76");
462         sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
463         sHalfWidthMap.put('\u304D', "\uFF77");
464         sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
465         sHalfWidthMap.put('\u304F', "\uFF78");
466         sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
467         sHalfWidthMap.put('\u3051', "\uFF79");
468         sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
469         sHalfWidthMap.put('\u3053', "\uFF7A");
470         sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
471         sHalfWidthMap.put('\u3055', "\uFF7B");
472         sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
473         sHalfWidthMap.put('\u3057', "\uFF7C");
474         sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
475         sHalfWidthMap.put('\u3059', "\uFF7D");
476         sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
477         sHalfWidthMap.put('\u305B', "\uFF7E");
478         sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
479         sHalfWidthMap.put('\u305D', "\uFF7F");
480         sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
481         sHalfWidthMap.put('\u305F', "\uFF80");
482         sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
483         sHalfWidthMap.put('\u3061', "\uFF81");
484         sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
485         sHalfWidthMap.put('\u3063', "\uFF6F");
486         sHalfWidthMap.put('\u3064', "\uFF82");
487         sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
488         sHalfWidthMap.put('\u3066', "\uFF83");
489         sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
490         sHalfWidthMap.put('\u3068', "\uFF84");
491         sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
492         sHalfWidthMap.put('\u306A', "\uFF85");
493         sHalfWidthMap.put('\u306B', "\uFF86");
494         sHalfWidthMap.put('\u306C', "\uFF87");
495         sHalfWidthMap.put('\u306D', "\uFF88");
496         sHalfWidthMap.put('\u306E', "\uFF89");
497         sHalfWidthMap.put('\u306F', "\uFF8A");
498         sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
499         sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
500         sHalfWidthMap.put('\u3072', "\uFF8B");
501         sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
502         sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
503         sHalfWidthMap.put('\u3075', "\uFF8C");
504         sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
505         sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
506         sHalfWidthMap.put('\u3078', "\uFF8D");
507         sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
508         sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
509         sHalfWidthMap.put('\u307B', "\uFF8E");
510         sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
511         sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
512         sHalfWidthMap.put('\u307E', "\uFF8F");
513         sHalfWidthMap.put('\u307F', "\uFF90");
514         sHalfWidthMap.put('\u3080', "\uFF91");
515         sHalfWidthMap.put('\u3081', "\uFF92");
516         sHalfWidthMap.put('\u3082', "\uFF93");
517         sHalfWidthMap.put('\u3083', "\uFF6C");
518         sHalfWidthMap.put('\u3084', "\uFF94");
519         sHalfWidthMap.put('\u3085', "\uFF6D");
520         sHalfWidthMap.put('\u3086', "\uFF95");
521         sHalfWidthMap.put('\u3087', "\uFF6E");
522         sHalfWidthMap.put('\u3088', "\uFF96");
523         sHalfWidthMap.put('\u3089', "\uFF97");
524         sHalfWidthMap.put('\u308A', "\uFF98");
525         sHalfWidthMap.put('\u308B', "\uFF99");
526         sHalfWidthMap.put('\u308C', "\uFF9A");
527         sHalfWidthMap.put('\u308D', "\uFF9B");
528         sHalfWidthMap.put('\u308E', "\uFF9C");
529         sHalfWidthMap.put('\u308F', "\uFF9C");
530         sHalfWidthMap.put('\u3090', "\uFF72");
531         sHalfWidthMap.put('\u3091', "\uFF74");
532         sHalfWidthMap.put('\u3092', "\uFF66");
533         sHalfWidthMap.put('\u3093', "\uFF9D");
534         sHalfWidthMap.put('\u309B', "\uFF9E");
535         sHalfWidthMap.put('\u309C', "\uFF9F");
536         sHalfWidthMap.put('\u30A1', "\uFF67");
537         sHalfWidthMap.put('\u30A2', "\uFF71");
538         sHalfWidthMap.put('\u30A3', "\uFF68");
539         sHalfWidthMap.put('\u30A4', "\uFF72");
540         sHalfWidthMap.put('\u30A5', "\uFF69");
541         sHalfWidthMap.put('\u30A6', "\uFF73");
542         sHalfWidthMap.put('\u30A7', "\uFF6A");
543         sHalfWidthMap.put('\u30A8', "\uFF74");
544         sHalfWidthMap.put('\u30A9', "\uFF6B");
545         sHalfWidthMap.put('\u30AA', "\uFF75");
546         sHalfWidthMap.put('\u30AB', "\uFF76");
547         sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
548         sHalfWidthMap.put('\u30AD', "\uFF77");
549         sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
550         sHalfWidthMap.put('\u30AF', "\uFF78");
551         sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
552         sHalfWidthMap.put('\u30B1', "\uFF79");
553         sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
554         sHalfWidthMap.put('\u30B3', "\uFF7A");
555         sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
556         sHalfWidthMap.put('\u30B5', "\uFF7B");
557         sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
558         sHalfWidthMap.put('\u30B7', "\uFF7C");
559         sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
560         sHalfWidthMap.put('\u30B9', "\uFF7D");
561         sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
562         sHalfWidthMap.put('\u30BB', "\uFF7E");
563         sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
564         sHalfWidthMap.put('\u30BD', "\uFF7F");
565         sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
566         sHalfWidthMap.put('\u30BF', "\uFF80");
567         sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
568         sHalfWidthMap.put('\u30C1', "\uFF81");
569         sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
570         sHalfWidthMap.put('\u30C3', "\uFF6F");
571         sHalfWidthMap.put('\u30C4', "\uFF82");
572         sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
573         sHalfWidthMap.put('\u30C6', "\uFF83");
574         sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
575         sHalfWidthMap.put('\u30C8', "\uFF84");
576         sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
577         sHalfWidthMap.put('\u30CA', "\uFF85");
578         sHalfWidthMap.put('\u30CB', "\uFF86");
579         sHalfWidthMap.put('\u30CC', "\uFF87");
580         sHalfWidthMap.put('\u30CD', "\uFF88");
581         sHalfWidthMap.put('\u30CE', "\uFF89");
582         sHalfWidthMap.put('\u30CF', "\uFF8A");
583         sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
584         sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
585         sHalfWidthMap.put('\u30D2', "\uFF8B");
586         sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
587         sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
588         sHalfWidthMap.put('\u30D5', "\uFF8C");
589         sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
590         sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
591         sHalfWidthMap.put('\u30D8', "\uFF8D");
592         sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
593         sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
594         sHalfWidthMap.put('\u30DB', "\uFF8E");
595         sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
596         sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
597         sHalfWidthMap.put('\u30DE', "\uFF8F");
598         sHalfWidthMap.put('\u30DF', "\uFF90");
599         sHalfWidthMap.put('\u30E0', "\uFF91");
600         sHalfWidthMap.put('\u30E1', "\uFF92");
601         sHalfWidthMap.put('\u30E2', "\uFF93");
602         sHalfWidthMap.put('\u30E3', "\uFF6C");
603         sHalfWidthMap.put('\u30E4', "\uFF94");
604         sHalfWidthMap.put('\u30E5', "\uFF6D");
605         sHalfWidthMap.put('\u30E6', "\uFF95");
606         sHalfWidthMap.put('\u30E7', "\uFF6E");
607         sHalfWidthMap.put('\u30E8', "\uFF96");
608         sHalfWidthMap.put('\u30E9', "\uFF97");
609         sHalfWidthMap.put('\u30EA', "\uFF98");
610         sHalfWidthMap.put('\u30EB', "\uFF99");
611         sHalfWidthMap.put('\u30EC', "\uFF9A");
612         sHalfWidthMap.put('\u30ED', "\uFF9B");
613         sHalfWidthMap.put('\u30EE', "\uFF9C");
614         sHalfWidthMap.put('\u30EF', "\uFF9C");
615         sHalfWidthMap.put('\u30F0', "\uFF72");
616         sHalfWidthMap.put('\u30F1', "\uFF74");
617         sHalfWidthMap.put('\u30F2', "\uFF66");
618         sHalfWidthMap.put('\u30F3', "\uFF9D");
619         sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
620         sHalfWidthMap.put('\u30F5', "\uFF76");
621         sHalfWidthMap.put('\u30F6', "\uFF79");
622         sHalfWidthMap.put('\u30FB', "\uFF65");
623         sHalfWidthMap.put('\u30FC', "\uFF70");
624         sHalfWidthMap.put('\uFF01', "!");
625         sHalfWidthMap.put('\uFF02', "\"");
626         sHalfWidthMap.put('\uFF03', "#");
627         sHalfWidthMap.put('\uFF04', "$");
628         sHalfWidthMap.put('\uFF05', "%");
629         sHalfWidthMap.put('\uFF06', "&");
630         sHalfWidthMap.put('\uFF07', "'");
631         sHalfWidthMap.put('\uFF08', "(");
632         sHalfWidthMap.put('\uFF09', ")");
633         sHalfWidthMap.put('\uFF0A', "*");
634         sHalfWidthMap.put('\uFF0B', "+");
635         sHalfWidthMap.put('\uFF0C', ",");
636         sHalfWidthMap.put('\uFF0D', "-");
637         sHalfWidthMap.put('\uFF0E', ".");
638         sHalfWidthMap.put('\uFF0F', "/");
639         sHalfWidthMap.put('\uFF10', "0");
640         sHalfWidthMap.put('\uFF11', "1");
641         sHalfWidthMap.put('\uFF12', "2");
642         sHalfWidthMap.put('\uFF13', "3");
643         sHalfWidthMap.put('\uFF14', "4");
644         sHalfWidthMap.put('\uFF15', "5");
645         sHalfWidthMap.put('\uFF16', "6");
646         sHalfWidthMap.put('\uFF17', "7");
647         sHalfWidthMap.put('\uFF18', "8");
648         sHalfWidthMap.put('\uFF19', "9");
649         sHalfWidthMap.put('\uFF1A', ":");
650         sHalfWidthMap.put('\uFF1B', ";");
651         sHalfWidthMap.put('\uFF1C', "<");
652         sHalfWidthMap.put('\uFF1D', "=");
653         sHalfWidthMap.put('\uFF1E', ">");
654         sHalfWidthMap.put('\uFF1F', "?");
655         sHalfWidthMap.put('\uFF20', "@");
656         sHalfWidthMap.put('\uFF21', "A");
657         sHalfWidthMap.put('\uFF22', "B");
658         sHalfWidthMap.put('\uFF23', "C");
659         sHalfWidthMap.put('\uFF24', "D");
660         sHalfWidthMap.put('\uFF25', "E");
661         sHalfWidthMap.put('\uFF26', "F");
662         sHalfWidthMap.put('\uFF27', "G");
663         sHalfWidthMap.put('\uFF28', "H");
664         sHalfWidthMap.put('\uFF29', "I");
665         sHalfWidthMap.put('\uFF2A', "J");
666         sHalfWidthMap.put('\uFF2B', "K");
667         sHalfWidthMap.put('\uFF2C', "L");
668         sHalfWidthMap.put('\uFF2D', "M");
669         sHalfWidthMap.put('\uFF2E', "N");
670         sHalfWidthMap.put('\uFF2F', "O");
671         sHalfWidthMap.put('\uFF30', "P");
672         sHalfWidthMap.put('\uFF31', "Q");
673         sHalfWidthMap.put('\uFF32', "R");
674         sHalfWidthMap.put('\uFF33', "S");
675         sHalfWidthMap.put('\uFF34', "T");
676         sHalfWidthMap.put('\uFF35', "U");
677         sHalfWidthMap.put('\uFF36', "V");
678         sHalfWidthMap.put('\uFF37', "W");
679         sHalfWidthMap.put('\uFF38', "X");
680         sHalfWidthMap.put('\uFF39', "Y");
681         sHalfWidthMap.put('\uFF3A', "Z");
682         sHalfWidthMap.put('\uFF3B', "[");
683         sHalfWidthMap.put('\uFF3C', "\\");
684         sHalfWidthMap.put('\uFF3D', "]");
685         sHalfWidthMap.put('\uFF3E', "^");
686         sHalfWidthMap.put('\uFF3F', "_");
687         sHalfWidthMap.put('\uFF41', "a");
688         sHalfWidthMap.put('\uFF42', "b");
689         sHalfWidthMap.put('\uFF43', "c");
690         sHalfWidthMap.put('\uFF44', "d");
691         sHalfWidthMap.put('\uFF45', "e");
692         sHalfWidthMap.put('\uFF46', "f");
693         sHalfWidthMap.put('\uFF47', "g");
694         sHalfWidthMap.put('\uFF48', "h");
695         sHalfWidthMap.put('\uFF49', "i");
696         sHalfWidthMap.put('\uFF4A', "j");
697         sHalfWidthMap.put('\uFF4B', "k");
698         sHalfWidthMap.put('\uFF4C', "l");
699         sHalfWidthMap.put('\uFF4D', "m");
700         sHalfWidthMap.put('\uFF4E', "n");
701         sHalfWidthMap.put('\uFF4F', "o");
702         sHalfWidthMap.put('\uFF50', "p");
703         sHalfWidthMap.put('\uFF51', "q");
704         sHalfWidthMap.put('\uFF52', "r");
705         sHalfWidthMap.put('\uFF53', "s");
706         sHalfWidthMap.put('\uFF54', "t");
707         sHalfWidthMap.put('\uFF55', "u");
708         sHalfWidthMap.put('\uFF56', "v");
709         sHalfWidthMap.put('\uFF57', "w");
710         sHalfWidthMap.put('\uFF58', "x");
711         sHalfWidthMap.put('\uFF59', "y");
712         sHalfWidthMap.put('\uFF5A', "z");
713         sHalfWidthMap.put('\uFF5B', "{");
714         sHalfWidthMap.put('\uFF5C', "|");
715         sHalfWidthMap.put('\uFF5D', "}");
716         sHalfWidthMap.put('\uFF5E', "~");
717         sHalfWidthMap.put('\uFF61', "\uFF61");
718         sHalfWidthMap.put('\uFF62', "\uFF62");
719         sHalfWidthMap.put('\uFF63', "\uFF63");
720         sHalfWidthMap.put('\uFF64', "\uFF64");
721         sHalfWidthMap.put('\uFF65', "\uFF65");
722         sHalfWidthMap.put('\uFF66', "\uFF66");
723         sHalfWidthMap.put('\uFF67', "\uFF67");
724         sHalfWidthMap.put('\uFF68', "\uFF68");
725         sHalfWidthMap.put('\uFF69', "\uFF69");
726         sHalfWidthMap.put('\uFF6A', "\uFF6A");
727         sHalfWidthMap.put('\uFF6B', "\uFF6B");
728         sHalfWidthMap.put('\uFF6C', "\uFF6C");
729         sHalfWidthMap.put('\uFF6D', "\uFF6D");
730         sHalfWidthMap.put('\uFF6E', "\uFF6E");
731         sHalfWidthMap.put('\uFF6F', "\uFF6F");
732         sHalfWidthMap.put('\uFF70', "\uFF70");
733         sHalfWidthMap.put('\uFF71', "\uFF71");
734         sHalfWidthMap.put('\uFF72', "\uFF72");
735         sHalfWidthMap.put('\uFF73', "\uFF73");
736         sHalfWidthMap.put('\uFF74', "\uFF74");
737         sHalfWidthMap.put('\uFF75', "\uFF75");
738         sHalfWidthMap.put('\uFF76', "\uFF76");
739         sHalfWidthMap.put('\uFF77', "\uFF77");
740         sHalfWidthMap.put('\uFF78', "\uFF78");
741         sHalfWidthMap.put('\uFF79', "\uFF79");
742         sHalfWidthMap.put('\uFF7A', "\uFF7A");
743         sHalfWidthMap.put('\uFF7B', "\uFF7B");
744         sHalfWidthMap.put('\uFF7C', "\uFF7C");
745         sHalfWidthMap.put('\uFF7D', "\uFF7D");
746         sHalfWidthMap.put('\uFF7E', "\uFF7E");
747         sHalfWidthMap.put('\uFF7F', "\uFF7F");
748         sHalfWidthMap.put('\uFF80', "\uFF80");
749         sHalfWidthMap.put('\uFF81', "\uFF81");
750         sHalfWidthMap.put('\uFF82', "\uFF82");
751         sHalfWidthMap.put('\uFF83', "\uFF83");
752         sHalfWidthMap.put('\uFF84', "\uFF84");
753         sHalfWidthMap.put('\uFF85', "\uFF85");
754         sHalfWidthMap.put('\uFF86', "\uFF86");
755         sHalfWidthMap.put('\uFF87', "\uFF87");
756         sHalfWidthMap.put('\uFF88', "\uFF88");
757         sHalfWidthMap.put('\uFF89', "\uFF89");
758         sHalfWidthMap.put('\uFF8A', "\uFF8A");
759         sHalfWidthMap.put('\uFF8B', "\uFF8B");
760         sHalfWidthMap.put('\uFF8C', "\uFF8C");
761         sHalfWidthMap.put('\uFF8D', "\uFF8D");
762         sHalfWidthMap.put('\uFF8E', "\uFF8E");
763         sHalfWidthMap.put('\uFF8F', "\uFF8F");
764         sHalfWidthMap.put('\uFF90', "\uFF90");
765         sHalfWidthMap.put('\uFF91', "\uFF91");
766         sHalfWidthMap.put('\uFF92', "\uFF92");
767         sHalfWidthMap.put('\uFF93', "\uFF93");
768         sHalfWidthMap.put('\uFF94', "\uFF94");
769         sHalfWidthMap.put('\uFF95', "\uFF95");
770         sHalfWidthMap.put('\uFF96', "\uFF96");
771         sHalfWidthMap.put('\uFF97', "\uFF97");
772         sHalfWidthMap.put('\uFF98', "\uFF98");
773         sHalfWidthMap.put('\uFF99', "\uFF99");
774         sHalfWidthMap.put('\uFF9A', "\uFF9A");
775         sHalfWidthMap.put('\uFF9B', "\uFF9B");
776         sHalfWidthMap.put('\uFF9C', "\uFF9C");
777         sHalfWidthMap.put('\uFF9D', "\uFF9D");
778         sHalfWidthMap.put('\uFF9E', "\uFF9E");
779         sHalfWidthMap.put('\uFF9F', "\uFF9F");
780         sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
781     }
782 
783     /**
784      * Return half-width version of that character if possible. Return null if not possible
785      * @param ch input character
786      * @return CharSequence object if the mapping for ch exists. Return null otherwise.
787      */
tryGetHalfWidthText(char ch)788     public static CharSequence tryGetHalfWidthText(char ch) {
789         if (sHalfWidthMap.containsKey(ch)) {
790             return sHalfWidthMap.get(ch);
791         } else {
792             return null;
793         }
794     }
795 }