Messaging: Vendor chips, colorpicker, photoviewer, and vcard in-tree
Clone no longer depends on LineageOS gitlinks; the libraries used by the app live in this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.vcard;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TextUtils especially for Japanese.
|
||||
*/
|
||||
/* package */ class JapaneseUtils {
|
||||
@VisibleForTesting
|
||||
static final Map<Character, String> sHalfWidthMap =
|
||||
new HashMap<Character, String>();
|
||||
|
||||
static {
|
||||
sHalfWidthMap.put('\u3001', "\uFF64");
|
||||
sHalfWidthMap.put('\u3002', "\uFF61");
|
||||
sHalfWidthMap.put('\u300C', "\uFF62");
|
||||
sHalfWidthMap.put('\u300D', "\uFF63");
|
||||
sHalfWidthMap.put('\u301C', "~");
|
||||
sHalfWidthMap.put('\u3041', "\uFF67");
|
||||
sHalfWidthMap.put('\u3042', "\uFF71");
|
||||
sHalfWidthMap.put('\u3043', "\uFF68");
|
||||
sHalfWidthMap.put('\u3044', "\uFF72");
|
||||
sHalfWidthMap.put('\u3045', "\uFF69");
|
||||
sHalfWidthMap.put('\u3046', "\uFF73");
|
||||
sHalfWidthMap.put('\u3047', "\uFF6A");
|
||||
sHalfWidthMap.put('\u3048', "\uFF74");
|
||||
sHalfWidthMap.put('\u3049', "\uFF6B");
|
||||
sHalfWidthMap.put('\u304A', "\uFF75");
|
||||
sHalfWidthMap.put('\u304B', "\uFF76");
|
||||
sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
|
||||
sHalfWidthMap.put('\u304D', "\uFF77");
|
||||
sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
|
||||
sHalfWidthMap.put('\u304F', "\uFF78");
|
||||
sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
|
||||
sHalfWidthMap.put('\u3051', "\uFF79");
|
||||
sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
|
||||
sHalfWidthMap.put('\u3053', "\uFF7A");
|
||||
sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
|
||||
sHalfWidthMap.put('\u3055', "\uFF7B");
|
||||
sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
|
||||
sHalfWidthMap.put('\u3057', "\uFF7C");
|
||||
sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
|
||||
sHalfWidthMap.put('\u3059', "\uFF7D");
|
||||
sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
|
||||
sHalfWidthMap.put('\u305B', "\uFF7E");
|
||||
sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
|
||||
sHalfWidthMap.put('\u305D', "\uFF7F");
|
||||
sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
|
||||
sHalfWidthMap.put('\u305F', "\uFF80");
|
||||
sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
|
||||
sHalfWidthMap.put('\u3061', "\uFF81");
|
||||
sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
|
||||
sHalfWidthMap.put('\u3063', "\uFF6F");
|
||||
sHalfWidthMap.put('\u3064', "\uFF82");
|
||||
sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
|
||||
sHalfWidthMap.put('\u3066', "\uFF83");
|
||||
sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
|
||||
sHalfWidthMap.put('\u3068', "\uFF84");
|
||||
sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
|
||||
sHalfWidthMap.put('\u306A', "\uFF85");
|
||||
sHalfWidthMap.put('\u306B', "\uFF86");
|
||||
sHalfWidthMap.put('\u306C', "\uFF87");
|
||||
sHalfWidthMap.put('\u306D', "\uFF88");
|
||||
sHalfWidthMap.put('\u306E', "\uFF89");
|
||||
sHalfWidthMap.put('\u306F', "\uFF8A");
|
||||
sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
|
||||
sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
|
||||
sHalfWidthMap.put('\u3072', "\uFF8B");
|
||||
sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
|
||||
sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
|
||||
sHalfWidthMap.put('\u3075', "\uFF8C");
|
||||
sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
|
||||
sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
|
||||
sHalfWidthMap.put('\u3078', "\uFF8D");
|
||||
sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
|
||||
sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
|
||||
sHalfWidthMap.put('\u307B', "\uFF8E");
|
||||
sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
|
||||
sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
|
||||
sHalfWidthMap.put('\u307E', "\uFF8F");
|
||||
sHalfWidthMap.put('\u307F', "\uFF90");
|
||||
sHalfWidthMap.put('\u3080', "\uFF91");
|
||||
sHalfWidthMap.put('\u3081', "\uFF92");
|
||||
sHalfWidthMap.put('\u3082', "\uFF93");
|
||||
sHalfWidthMap.put('\u3083', "\uFF6C");
|
||||
sHalfWidthMap.put('\u3084', "\uFF94");
|
||||
sHalfWidthMap.put('\u3085', "\uFF6D");
|
||||
sHalfWidthMap.put('\u3086', "\uFF95");
|
||||
sHalfWidthMap.put('\u3087', "\uFF6E");
|
||||
sHalfWidthMap.put('\u3088', "\uFF96");
|
||||
sHalfWidthMap.put('\u3089', "\uFF97");
|
||||
sHalfWidthMap.put('\u308A', "\uFF98");
|
||||
sHalfWidthMap.put('\u308B', "\uFF99");
|
||||
sHalfWidthMap.put('\u308C', "\uFF9A");
|
||||
sHalfWidthMap.put('\u308D', "\uFF9B");
|
||||
sHalfWidthMap.put('\u308E', "\uFF9C");
|
||||
sHalfWidthMap.put('\u308F', "\uFF9C");
|
||||
sHalfWidthMap.put('\u3090', "\uFF72");
|
||||
sHalfWidthMap.put('\u3091', "\uFF74");
|
||||
sHalfWidthMap.put('\u3092', "\uFF66");
|
||||
sHalfWidthMap.put('\u3093', "\uFF9D");
|
||||
sHalfWidthMap.put('\u309B', "\uFF9E");
|
||||
sHalfWidthMap.put('\u309C', "\uFF9F");
|
||||
sHalfWidthMap.put('\u30A1', "\uFF67");
|
||||
sHalfWidthMap.put('\u30A2', "\uFF71");
|
||||
sHalfWidthMap.put('\u30A3', "\uFF68");
|
||||
sHalfWidthMap.put('\u30A4', "\uFF72");
|
||||
sHalfWidthMap.put('\u30A5', "\uFF69");
|
||||
sHalfWidthMap.put('\u30A6', "\uFF73");
|
||||
sHalfWidthMap.put('\u30A7', "\uFF6A");
|
||||
sHalfWidthMap.put('\u30A8', "\uFF74");
|
||||
sHalfWidthMap.put('\u30A9', "\uFF6B");
|
||||
sHalfWidthMap.put('\u30AA', "\uFF75");
|
||||
sHalfWidthMap.put('\u30AB', "\uFF76");
|
||||
sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
|
||||
sHalfWidthMap.put('\u30AD', "\uFF77");
|
||||
sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
|
||||
sHalfWidthMap.put('\u30AF', "\uFF78");
|
||||
sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
|
||||
sHalfWidthMap.put('\u30B1', "\uFF79");
|
||||
sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
|
||||
sHalfWidthMap.put('\u30B3', "\uFF7A");
|
||||
sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
|
||||
sHalfWidthMap.put('\u30B5', "\uFF7B");
|
||||
sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
|
||||
sHalfWidthMap.put('\u30B7', "\uFF7C");
|
||||
sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
|
||||
sHalfWidthMap.put('\u30B9', "\uFF7D");
|
||||
sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
|
||||
sHalfWidthMap.put('\u30BB', "\uFF7E");
|
||||
sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
|
||||
sHalfWidthMap.put('\u30BD', "\uFF7F");
|
||||
sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
|
||||
sHalfWidthMap.put('\u30BF', "\uFF80");
|
||||
sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
|
||||
sHalfWidthMap.put('\u30C1', "\uFF81");
|
||||
sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
|
||||
sHalfWidthMap.put('\u30C3', "\uFF6F");
|
||||
sHalfWidthMap.put('\u30C4', "\uFF82");
|
||||
sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
|
||||
sHalfWidthMap.put('\u30C6', "\uFF83");
|
||||
sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
|
||||
sHalfWidthMap.put('\u30C8', "\uFF84");
|
||||
sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
|
||||
sHalfWidthMap.put('\u30CA', "\uFF85");
|
||||
sHalfWidthMap.put('\u30CB', "\uFF86");
|
||||
sHalfWidthMap.put('\u30CC', "\uFF87");
|
||||
sHalfWidthMap.put('\u30CD', "\uFF88");
|
||||
sHalfWidthMap.put('\u30CE', "\uFF89");
|
||||
sHalfWidthMap.put('\u30CF', "\uFF8A");
|
||||
sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
|
||||
sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
|
||||
sHalfWidthMap.put('\u30D2', "\uFF8B");
|
||||
sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
|
||||
sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
|
||||
sHalfWidthMap.put('\u30D5', "\uFF8C");
|
||||
sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
|
||||
sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
|
||||
sHalfWidthMap.put('\u30D8', "\uFF8D");
|
||||
sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
|
||||
sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
|
||||
sHalfWidthMap.put('\u30DB', "\uFF8E");
|
||||
sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
|
||||
sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
|
||||
sHalfWidthMap.put('\u30DE', "\uFF8F");
|
||||
sHalfWidthMap.put('\u30DF', "\uFF90");
|
||||
sHalfWidthMap.put('\u30E0', "\uFF91");
|
||||
sHalfWidthMap.put('\u30E1', "\uFF92");
|
||||
sHalfWidthMap.put('\u30E2', "\uFF93");
|
||||
sHalfWidthMap.put('\u30E3', "\uFF6C");
|
||||
sHalfWidthMap.put('\u30E4', "\uFF94");
|
||||
sHalfWidthMap.put('\u30E5', "\uFF6D");
|
||||
sHalfWidthMap.put('\u30E6', "\uFF95");
|
||||
sHalfWidthMap.put('\u30E7', "\uFF6E");
|
||||
sHalfWidthMap.put('\u30E8', "\uFF96");
|
||||
sHalfWidthMap.put('\u30E9', "\uFF97");
|
||||
sHalfWidthMap.put('\u30EA', "\uFF98");
|
||||
sHalfWidthMap.put('\u30EB', "\uFF99");
|
||||
sHalfWidthMap.put('\u30EC', "\uFF9A");
|
||||
sHalfWidthMap.put('\u30ED', "\uFF9B");
|
||||
sHalfWidthMap.put('\u30EE', "\uFF9C");
|
||||
sHalfWidthMap.put('\u30EF', "\uFF9C");
|
||||
sHalfWidthMap.put('\u30F0', "\uFF72");
|
||||
sHalfWidthMap.put('\u30F1', "\uFF74");
|
||||
sHalfWidthMap.put('\u30F2', "\uFF66");
|
||||
sHalfWidthMap.put('\u30F3', "\uFF9D");
|
||||
sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
|
||||
sHalfWidthMap.put('\u30F5', "\uFF76");
|
||||
sHalfWidthMap.put('\u30F6', "\uFF79");
|
||||
sHalfWidthMap.put('\u30FB', "\uFF65");
|
||||
sHalfWidthMap.put('\u30FC', "\uFF70");
|
||||
sHalfWidthMap.put('\uFF01', "!");
|
||||
sHalfWidthMap.put('\uFF02', "\"");
|
||||
sHalfWidthMap.put('\uFF03', "#");
|
||||
sHalfWidthMap.put('\uFF04', "$");
|
||||
sHalfWidthMap.put('\uFF05', "%");
|
||||
sHalfWidthMap.put('\uFF06', "&");
|
||||
sHalfWidthMap.put('\uFF07', "'");
|
||||
sHalfWidthMap.put('\uFF08', "(");
|
||||
sHalfWidthMap.put('\uFF09', ")");
|
||||
sHalfWidthMap.put('\uFF0A', "*");
|
||||
sHalfWidthMap.put('\uFF0B', "+");
|
||||
sHalfWidthMap.put('\uFF0C', ",");
|
||||
sHalfWidthMap.put('\uFF0D', "-");
|
||||
sHalfWidthMap.put('\uFF0E', ".");
|
||||
sHalfWidthMap.put('\uFF0F', "/");
|
||||
sHalfWidthMap.put('\uFF10', "0");
|
||||
sHalfWidthMap.put('\uFF11', "1");
|
||||
sHalfWidthMap.put('\uFF12', "2");
|
||||
sHalfWidthMap.put('\uFF13', "3");
|
||||
sHalfWidthMap.put('\uFF14', "4");
|
||||
sHalfWidthMap.put('\uFF15', "5");
|
||||
sHalfWidthMap.put('\uFF16', "6");
|
||||
sHalfWidthMap.put('\uFF17', "7");
|
||||
sHalfWidthMap.put('\uFF18', "8");
|
||||
sHalfWidthMap.put('\uFF19', "9");
|
||||
sHalfWidthMap.put('\uFF1A', ":");
|
||||
sHalfWidthMap.put('\uFF1B', ";");
|
||||
sHalfWidthMap.put('\uFF1C', "<");
|
||||
sHalfWidthMap.put('\uFF1D', "=");
|
||||
sHalfWidthMap.put('\uFF1E', ">");
|
||||
sHalfWidthMap.put('\uFF1F', "?");
|
||||
sHalfWidthMap.put('\uFF20', "@");
|
||||
sHalfWidthMap.put('\uFF21', "A");
|
||||
sHalfWidthMap.put('\uFF22', "B");
|
||||
sHalfWidthMap.put('\uFF23', "C");
|
||||
sHalfWidthMap.put('\uFF24', "D");
|
||||
sHalfWidthMap.put('\uFF25', "E");
|
||||
sHalfWidthMap.put('\uFF26', "F");
|
||||
sHalfWidthMap.put('\uFF27', "G");
|
||||
sHalfWidthMap.put('\uFF28', "H");
|
||||
sHalfWidthMap.put('\uFF29', "I");
|
||||
sHalfWidthMap.put('\uFF2A', "J");
|
||||
sHalfWidthMap.put('\uFF2B', "K");
|
||||
sHalfWidthMap.put('\uFF2C', "L");
|
||||
sHalfWidthMap.put('\uFF2D', "M");
|
||||
sHalfWidthMap.put('\uFF2E', "N");
|
||||
sHalfWidthMap.put('\uFF2F', "O");
|
||||
sHalfWidthMap.put('\uFF30', "P");
|
||||
sHalfWidthMap.put('\uFF31', "Q");
|
||||
sHalfWidthMap.put('\uFF32', "R");
|
||||
sHalfWidthMap.put('\uFF33', "S");
|
||||
sHalfWidthMap.put('\uFF34', "T");
|
||||
sHalfWidthMap.put('\uFF35', "U");
|
||||
sHalfWidthMap.put('\uFF36', "V");
|
||||
sHalfWidthMap.put('\uFF37', "W");
|
||||
sHalfWidthMap.put('\uFF38', "X");
|
||||
sHalfWidthMap.put('\uFF39', "Y");
|
||||
sHalfWidthMap.put('\uFF3A', "Z");
|
||||
sHalfWidthMap.put('\uFF3B', "[");
|
||||
sHalfWidthMap.put('\uFF3C', "\\");
|
||||
sHalfWidthMap.put('\uFF3D', "]");
|
||||
sHalfWidthMap.put('\uFF3E', "^");
|
||||
sHalfWidthMap.put('\uFF3F', "_");
|
||||
sHalfWidthMap.put('\uFF41', "a");
|
||||
sHalfWidthMap.put('\uFF42', "b");
|
||||
sHalfWidthMap.put('\uFF43', "c");
|
||||
sHalfWidthMap.put('\uFF44', "d");
|
||||
sHalfWidthMap.put('\uFF45', "e");
|
||||
sHalfWidthMap.put('\uFF46', "f");
|
||||
sHalfWidthMap.put('\uFF47', "g");
|
||||
sHalfWidthMap.put('\uFF48', "h");
|
||||
sHalfWidthMap.put('\uFF49', "i");
|
||||
sHalfWidthMap.put('\uFF4A', "j");
|
||||
sHalfWidthMap.put('\uFF4B', "k");
|
||||
sHalfWidthMap.put('\uFF4C', "l");
|
||||
sHalfWidthMap.put('\uFF4D', "m");
|
||||
sHalfWidthMap.put('\uFF4E', "n");
|
||||
sHalfWidthMap.put('\uFF4F', "o");
|
||||
sHalfWidthMap.put('\uFF50', "p");
|
||||
sHalfWidthMap.put('\uFF51', "q");
|
||||
sHalfWidthMap.put('\uFF52', "r");
|
||||
sHalfWidthMap.put('\uFF53', "s");
|
||||
sHalfWidthMap.put('\uFF54', "t");
|
||||
sHalfWidthMap.put('\uFF55', "u");
|
||||
sHalfWidthMap.put('\uFF56', "v");
|
||||
sHalfWidthMap.put('\uFF57', "w");
|
||||
sHalfWidthMap.put('\uFF58', "x");
|
||||
sHalfWidthMap.put('\uFF59', "y");
|
||||
sHalfWidthMap.put('\uFF5A', "z");
|
||||
sHalfWidthMap.put('\uFF5B', "{");
|
||||
sHalfWidthMap.put('\uFF5C', "|");
|
||||
sHalfWidthMap.put('\uFF5D', "}");
|
||||
sHalfWidthMap.put('\uFF5E', "~");
|
||||
sHalfWidthMap.put('\uFF61', "\uFF61");
|
||||
sHalfWidthMap.put('\uFF62', "\uFF62");
|
||||
sHalfWidthMap.put('\uFF63', "\uFF63");
|
||||
sHalfWidthMap.put('\uFF64', "\uFF64");
|
||||
sHalfWidthMap.put('\uFF65', "\uFF65");
|
||||
sHalfWidthMap.put('\uFF66', "\uFF66");
|
||||
sHalfWidthMap.put('\uFF67', "\uFF67");
|
||||
sHalfWidthMap.put('\uFF68', "\uFF68");
|
||||
sHalfWidthMap.put('\uFF69', "\uFF69");
|
||||
sHalfWidthMap.put('\uFF6A', "\uFF6A");
|
||||
sHalfWidthMap.put('\uFF6B', "\uFF6B");
|
||||
sHalfWidthMap.put('\uFF6C', "\uFF6C");
|
||||
sHalfWidthMap.put('\uFF6D', "\uFF6D");
|
||||
sHalfWidthMap.put('\uFF6E', "\uFF6E");
|
||||
sHalfWidthMap.put('\uFF6F', "\uFF6F");
|
||||
sHalfWidthMap.put('\uFF70', "\uFF70");
|
||||
sHalfWidthMap.put('\uFF71', "\uFF71");
|
||||
sHalfWidthMap.put('\uFF72', "\uFF72");
|
||||
sHalfWidthMap.put('\uFF73', "\uFF73");
|
||||
sHalfWidthMap.put('\uFF74', "\uFF74");
|
||||
sHalfWidthMap.put('\uFF75', "\uFF75");
|
||||
sHalfWidthMap.put('\uFF76', "\uFF76");
|
||||
sHalfWidthMap.put('\uFF77', "\uFF77");
|
||||
sHalfWidthMap.put('\uFF78', "\uFF78");
|
||||
sHalfWidthMap.put('\uFF79', "\uFF79");
|
||||
sHalfWidthMap.put('\uFF7A', "\uFF7A");
|
||||
sHalfWidthMap.put('\uFF7B', "\uFF7B");
|
||||
sHalfWidthMap.put('\uFF7C', "\uFF7C");
|
||||
sHalfWidthMap.put('\uFF7D', "\uFF7D");
|
||||
sHalfWidthMap.put('\uFF7E', "\uFF7E");
|
||||
sHalfWidthMap.put('\uFF7F', "\uFF7F");
|
||||
sHalfWidthMap.put('\uFF80', "\uFF80");
|
||||
sHalfWidthMap.put('\uFF81', "\uFF81");
|
||||
sHalfWidthMap.put('\uFF82', "\uFF82");
|
||||
sHalfWidthMap.put('\uFF83', "\uFF83");
|
||||
sHalfWidthMap.put('\uFF84', "\uFF84");
|
||||
sHalfWidthMap.put('\uFF85', "\uFF85");
|
||||
sHalfWidthMap.put('\uFF86', "\uFF86");
|
||||
sHalfWidthMap.put('\uFF87', "\uFF87");
|
||||
sHalfWidthMap.put('\uFF88', "\uFF88");
|
||||
sHalfWidthMap.put('\uFF89', "\uFF89");
|
||||
sHalfWidthMap.put('\uFF8A', "\uFF8A");
|
||||
sHalfWidthMap.put('\uFF8B', "\uFF8B");
|
||||
sHalfWidthMap.put('\uFF8C', "\uFF8C");
|
||||
sHalfWidthMap.put('\uFF8D', "\uFF8D");
|
||||
sHalfWidthMap.put('\uFF8E', "\uFF8E");
|
||||
sHalfWidthMap.put('\uFF8F', "\uFF8F");
|
||||
sHalfWidthMap.put('\uFF90', "\uFF90");
|
||||
sHalfWidthMap.put('\uFF91', "\uFF91");
|
||||
sHalfWidthMap.put('\uFF92', "\uFF92");
|
||||
sHalfWidthMap.put('\uFF93', "\uFF93");
|
||||
sHalfWidthMap.put('\uFF94', "\uFF94");
|
||||
sHalfWidthMap.put('\uFF95', "\uFF95");
|
||||
sHalfWidthMap.put('\uFF96', "\uFF96");
|
||||
sHalfWidthMap.put('\uFF97', "\uFF97");
|
||||
sHalfWidthMap.put('\uFF98', "\uFF98");
|
||||
sHalfWidthMap.put('\uFF99', "\uFF99");
|
||||
sHalfWidthMap.put('\uFF9A', "\uFF9A");
|
||||
sHalfWidthMap.put('\uFF9B', "\uFF9B");
|
||||
sHalfWidthMap.put('\uFF9C', "\uFF9C");
|
||||
sHalfWidthMap.put('\uFF9D', "\uFF9D");
|
||||
sHalfWidthMap.put('\uFF9E', "\uFF9E");
|
||||
sHalfWidthMap.put('\uFF9F', "\uFF9F");
|
||||
sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns half-width version of that character if possible. Returns null if not possible
|
||||
* @param ch input character
|
||||
* @return CharSequence object if the mapping for ch exists. Return null otherwise.
|
||||
*/
|
||||
public static String tryGetHalfWidthText(final char ch) {
|
||||
if (sHalfWidthMap.containsKey(ch)) {
|
||||
return sHalfWidthMap.get(ch);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,721 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Entity;
|
||||
import android.content.Entity.NamedContentValues;
|
||||
import android.content.EntityIterator;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Email;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Event;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Im;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Nickname;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Note;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Organization;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Photo;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Relation;
|
||||
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
|
||||
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
|
||||
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Website;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.provider.ContactsContract.Data;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
import android.provider.ContactsContract.RawContactsEntity;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The class for composing vCard from Contacts information.
|
||||
* </p>
|
||||
* <p>
|
||||
* Usually, this class should be used like this.
|
||||
* </p>
|
||||
* <pre class="prettyprint">VCardComposer composer = null;
|
||||
* try {
|
||||
* composer = new VCardComposer(context);
|
||||
* composer.addHandler(
|
||||
* composer.new HandlerForOutputStream(outputStream));
|
||||
* if (!composer.init()) {
|
||||
* // Do something handling the situation.
|
||||
* return;
|
||||
* }
|
||||
* while (!composer.isAfterLast()) {
|
||||
* if (mCanceled) {
|
||||
* // Assume a user may cancel this operation during the export.
|
||||
* return;
|
||||
* }
|
||||
* if (!composer.createOneEntry()) {
|
||||
* // Do something handling the error situation.
|
||||
* return;
|
||||
* }
|
||||
* }
|
||||
* } finally {
|
||||
* if (composer != null) {
|
||||
* composer.terminate();
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* Users have to manually take care of memory efficiency. Even one vCard may contain
|
||||
* image of non-trivial size for mobile devices.
|
||||
* </p>
|
||||
* <p>
|
||||
* {@link VCardBuilder} is used to build each vCard.
|
||||
* </p>
|
||||
*/
|
||||
public class VCardComposer {
|
||||
private static final String LOG_TAG = "VCardComposer";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
|
||||
"Failed to get database information";
|
||||
|
||||
public static final String FAILURE_REASON_NO_ENTRY =
|
||||
"There's no exportable in the database";
|
||||
|
||||
public static final String FAILURE_REASON_NOT_INITIALIZED =
|
||||
"The vCard composer object is not correctly initialized";
|
||||
|
||||
/** Should be visible only from developers... (no need to translate, hopefully) */
|
||||
public static final String FAILURE_REASON_UNSUPPORTED_URI =
|
||||
"The Uri vCard composer received is not supported by the composer.";
|
||||
|
||||
public static final String NO_ERROR = "No error";
|
||||
|
||||
// Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
|
||||
// since usual vCard devices for Japanese devices already use it.
|
||||
private static final String SHIFT_JIS = "SHIFT_JIS";
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
|
||||
private static final Map<Integer, String> sImMap;
|
||||
|
||||
static {
|
||||
sImMap = new HashMap<Integer, String>();
|
||||
sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
|
||||
sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
|
||||
sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
|
||||
sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
|
||||
sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
|
||||
sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
|
||||
// We don't add Google talk here since it has to be handled separately.
|
||||
}
|
||||
|
||||
private final int mVCardType;
|
||||
private final ContentResolver mContentResolver;
|
||||
|
||||
private final boolean mIsDoCoMo;
|
||||
/**
|
||||
* Used only when {@link #mIsDoCoMo} is true. Set to true when the first vCard for DoCoMo
|
||||
* vCard is emitted.
|
||||
*/
|
||||
private boolean mFirstVCardEmittedInDoCoMoCase;
|
||||
|
||||
private Cursor mCursor;
|
||||
private EntityIterator mEntityIterator;
|
||||
private Method mGetEntityIteratorMethod;
|
||||
private boolean mCursorSuppliedFromOutside;
|
||||
private int mIdColumn;
|
||||
private Uri mContentUriForRawContactsEntity;
|
||||
|
||||
private final String mCharset;
|
||||
|
||||
private boolean mInitDone;
|
||||
private String mErrorReason = NO_ERROR;
|
||||
|
||||
/**
|
||||
* Set to false when one of {@link #init()} variants is called, and set to true when
|
||||
* {@link #terminate()} is called. Initially set to true.
|
||||
*/
|
||||
private boolean mTerminateCalled = true;
|
||||
|
||||
private RawContactEntitlesInfoCallback mRawContactEntitlesInfoCallback;
|
||||
|
||||
private static final String[] sContactsProjection = new String[] {
|
||||
Contacts._ID,
|
||||
};
|
||||
|
||||
public VCardComposer(Context context) {
|
||||
this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The variant which sets charset to null and sets careHandlerErrors to true.
|
||||
*/
|
||||
public VCardComposer(Context context, int vcardType) {
|
||||
this(context, vcardType, null, true);
|
||||
}
|
||||
|
||||
public VCardComposer(Context context, int vcardType, String charset) {
|
||||
this(context, vcardType, charset, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The variant which sets charset to null.
|
||||
*/
|
||||
public VCardComposer(final Context context, final int vcardType,
|
||||
final boolean careHandlerErrors) {
|
||||
this(context, vcardType, null, careHandlerErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs for supporting call log entry vCard composing.
|
||||
*
|
||||
* @param context Context to be used during the composition.
|
||||
* @param vcardType The type of vCard, typically available via {@link VCardConfig}.
|
||||
* @param charset The charset to be used. Use null when you don't need the charset.
|
||||
* @param careHandlerErrors If true, This object returns false everytime
|
||||
*/
|
||||
public VCardComposer(final Context context, final int vcardType, String charset,
|
||||
final boolean careHandlerErrors) {
|
||||
this(context, context.getContentResolver(), vcardType, charset, careHandlerErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just for testing for now.
|
||||
* @param resolver {@link ContentResolver} which used by this object.
|
||||
* @hide
|
||||
*/
|
||||
public VCardComposer(final Context context, ContentResolver resolver,
|
||||
final int vcardType, String charset, final boolean careHandlerErrors) {
|
||||
// Not used right now
|
||||
// mContext = context;
|
||||
mVCardType = vcardType;
|
||||
mContentResolver = resolver;
|
||||
|
||||
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
|
||||
|
||||
charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
|
||||
final boolean shouldAppendCharsetParam = !(
|
||||
VCardConfig.isVersion30(vcardType) && UTF_8.equalsIgnoreCase(charset));
|
||||
|
||||
if (mIsDoCoMo || shouldAppendCharsetParam) {
|
||||
if (SHIFT_JIS.equalsIgnoreCase(charset)) {
|
||||
mCharset = charset;
|
||||
} else {
|
||||
/* Log.w(LOG_TAG,
|
||||
"The charset \"" + charset + "\" is used while "
|
||||
+ SHIFT_JIS + " is needed to be used."); */
|
||||
if (TextUtils.isEmpty(charset)) {
|
||||
mCharset = SHIFT_JIS;
|
||||
} else {
|
||||
mCharset = charset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (TextUtils.isEmpty(charset)) {
|
||||
mCharset = UTF_8;
|
||||
} else {
|
||||
mCharset = charset;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this object using default {@link Contacts#CONTENT_URI}.
|
||||
*
|
||||
* You can call this method or a variant of this method just once. In other words, you cannot
|
||||
* reuse this object.
|
||||
*
|
||||
* @return Returns true when initialization is successful and all the other
|
||||
* methods are available. Returns false otherwise.
|
||||
*/
|
||||
public boolean init() {
|
||||
return init(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special variant of init(), which accepts a Uri for obtaining {@link RawContactsEntity} from
|
||||
* {@link ContentResolver} with {@link Contacts#_ID}.
|
||||
* <code>
|
||||
* String selection = Data.CONTACT_ID + "=?";
|
||||
* String[] selectionArgs = new String[] {contactId};
|
||||
* Cursor cursor = mContentResolver.query(
|
||||
* contentUriForRawContactsEntity, null, selection, selectionArgs, null)
|
||||
* </code>
|
||||
*
|
||||
* You can call this method or a variant of this method just once. In other words, you cannot
|
||||
* reuse this object.
|
||||
*
|
||||
* @deprecated Use {@link #init(Uri, String[], String, String[], String, Uri)} if you really
|
||||
* need to change the default Uri.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean initWithRawContactsEntityUri(Uri contentUriForRawContactsEntity) {
|
||||
return init(Contacts.CONTENT_URI, sContactsProjection, null, null, null,
|
||||
contentUriForRawContactsEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this object using default {@link Contacts#CONTENT_URI} and given selection
|
||||
* arguments.
|
||||
*/
|
||||
public boolean init(final String selection, final String[] selectionArgs) {
|
||||
return init(Contacts.CONTENT_URI, sContactsProjection, selection, selectionArgs,
|
||||
null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that this is unstable interface, may be deleted in the future.
|
||||
*/
|
||||
public boolean init(final Uri contentUri, final String selection,
|
||||
final String[] selectionArgs, final String sortOrder) {
|
||||
return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param contentUri Uri for obtaining the list of contactId. Used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param selection selection used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param selectionArgs selectionArgs used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param sortOrder sortOrder used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param contentUriForRawContactsEntity Uri for obtaining entries relevant to each
|
||||
* contactId.
|
||||
* Note that this is an unstable interface, may be deleted in the future.
|
||||
*/
|
||||
public boolean init(final Uri contentUri, final String selection,
|
||||
final String[] selectionArgs, final String sortOrder,
|
||||
final Uri contentUriForRawContactsEntity) {
|
||||
return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder,
|
||||
contentUriForRawContactsEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of init(). Currently just for testing. Use other variants for init().
|
||||
*
|
||||
* First we'll create {@link Cursor} for the list of contactId.
|
||||
*
|
||||
* <code>
|
||||
* Cursor cursorForId = mContentResolver.query(
|
||||
* contentUri, projection, selection, selectionArgs, sortOrder);
|
||||
* </code>
|
||||
*
|
||||
* After that, we'll obtain data for each contactId in the list.
|
||||
*
|
||||
* <code>
|
||||
* Cursor cursorForContent = mContentResolver.query(
|
||||
* contentUriForRawContactsEntity, null,
|
||||
* Data.CONTACT_ID + "=?", new String[] {contactId}, null)
|
||||
* </code>
|
||||
*
|
||||
* {@link #createOneEntry()} or its variants let the caller obtain each entry from
|
||||
* <code>cursorForContent</code> above.
|
||||
*
|
||||
* @param contentUri Uri for obtaining the list of contactId. Used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param projection projection used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param selection selection used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param selectionArgs selectionArgs used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param sortOrder sortOrder used with
|
||||
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
|
||||
* @param contentUriForRawContactsEntity Uri for obtaining entries relevant to each
|
||||
* contactId.
|
||||
* @return true when successful
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean init(final Uri contentUri, final String[] projection,
|
||||
final String selection, final String[] selectionArgs,
|
||||
final String sortOrder, Uri contentUriForRawContactsEntity) {
|
||||
if (!ContactsContract.AUTHORITY.equals(contentUri.getAuthority())) {
|
||||
if (DEBUG) Log.d(LOG_TAG, "Unexpected contentUri: " + contentUri);
|
||||
mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initInterFirstPart(contentUriForRawContactsEntity)) {
|
||||
return false;
|
||||
}
|
||||
if (!initInterCursorCreationPart(contentUri, projection, selection, selectionArgs,
|
||||
sortOrder)) {
|
||||
return false;
|
||||
}
|
||||
if (!initInterMainPart()) {
|
||||
return false;
|
||||
}
|
||||
initEntityIterator();
|
||||
|
||||
return initInterLastPart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Just for testing for now. Do not use.
|
||||
* Execute before init.
|
||||
* @hide
|
||||
*/
|
||||
public void setGetEntityIteratorMethod(Method getEntityIteratorMethod) {
|
||||
mGetEntityIteratorMethod = getEntityIteratorMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just for testing for now. Do not use.
|
||||
* @hide
|
||||
*/
|
||||
public boolean init(Cursor cursor) {
|
||||
return initWithCallback(cursor, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cursor Cursor that used to get contact id
|
||||
* @param rawContactEntitlesInfoCallback Callback that return RawContactEntitlesInfo
|
||||
* Note that this is an unstable interface, may be deleted in the future.
|
||||
*
|
||||
* @return true when successful
|
||||
*/
|
||||
public boolean initWithCallback(Cursor cursor,
|
||||
RawContactEntitlesInfoCallback rawContactEntitlesInfoCallback) {
|
||||
if (!initInterFirstPart(null)) {
|
||||
return false;
|
||||
}
|
||||
mCursorSuppliedFromOutside = true;
|
||||
mCursor = cursor;
|
||||
mRawContactEntitlesInfoCallback = rawContactEntitlesInfoCallback;
|
||||
if (!initInterMainPart()) {
|
||||
return false;
|
||||
}
|
||||
initEntityIterator();
|
||||
return initInterLastPart();
|
||||
}
|
||||
|
||||
private boolean initInterFirstPart(Uri contentUriForRawContactsEntity) {
|
||||
mContentUriForRawContactsEntity =
|
||||
(contentUriForRawContactsEntity != null ? contentUriForRawContactsEntity :
|
||||
RawContactsEntity.CONTENT_URI);
|
||||
if (mInitDone) {
|
||||
Log.e(LOG_TAG, "init() is already called");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean initInterCursorCreationPart(
|
||||
final Uri contentUri, final String[] projection,
|
||||
final String selection, final String[] selectionArgs, final String sortOrder) {
|
||||
mCursorSuppliedFromOutside = false;
|
||||
mCursor = mContentResolver.query(
|
||||
contentUri, projection, selection, selectionArgs, sortOrder);
|
||||
if (mCursor == null) {
|
||||
Log.e(LOG_TAG, String.format("Cursor became null unexpectedly"));
|
||||
mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean initInterMainPart() {
|
||||
if (mCursor.getCount() == 0 || !mCursor.moveToFirst()) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG,
|
||||
String.format("mCursor has an error (getCount: %d): ", mCursor.getCount()));
|
||||
}
|
||||
mErrorReason = FAILURE_REASON_NO_ENTRY;
|
||||
closeCursorIfAppropriate();
|
||||
return false;
|
||||
}
|
||||
mIdColumn = mCursor.getColumnIndex(Data.CONTACT_ID);
|
||||
if (mIdColumn < 0) {
|
||||
mIdColumn = mCursor.getColumnIndex(Contacts._ID);
|
||||
}
|
||||
return mIdColumn >= 0;
|
||||
}
|
||||
|
||||
// Testing with 500 Contacts IDs, the export time using an EntityIterator containing all
|
||||
// Contacts IDs is approximately 120ms (each EntityIterator containing one Contacts ID takes
|
||||
// about 700ms).
|
||||
// Initializing an EntityIterator with 500 Contacts IDs takes about 10ms.
|
||||
private void initEntityIterator() {
|
||||
StringBuilder selection = new StringBuilder();
|
||||
StringBuilder contactIds = new StringBuilder();
|
||||
Uri uri = mContentUriForRawContactsEntity;
|
||||
|
||||
if (mRawContactEntitlesInfoCallback != null) {
|
||||
RawContactEntitlesInfo rawContactEntitlesInfo =
|
||||
mRawContactEntitlesInfoCallback.getRawContactEntitlesInfo(
|
||||
mCursor.getLong(mIdColumn));
|
||||
uri = rawContactEntitlesInfo.rawContactEntitlesUri;
|
||||
contactIds.append(rawContactEntitlesInfo.contactId);
|
||||
} else {
|
||||
do {
|
||||
contactIds.append(mCursor.getString(mIdColumn));
|
||||
if (!mCursor.isLast()) {
|
||||
contactIds.append(",");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (mCursor.moveToNext());
|
||||
// There are some callers that assume that the cursor is at its first position after
|
||||
// init.
|
||||
// See b/402311014
|
||||
mCursor.moveToFirst();
|
||||
}
|
||||
|
||||
selection.append(Data.CONTACT_ID).append(" IN (");
|
||||
selection.append(contactIds);
|
||||
selection.append(")");
|
||||
|
||||
if (mGetEntityIteratorMethod != null) {
|
||||
// Please note that this code is executed by unit tests only
|
||||
try {
|
||||
mEntityIterator = (EntityIterator) mGetEntityIteratorMethod.invoke(null,
|
||||
mContentResolver, uri, selection.toString(), null, null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(LOG_TAG, "IllegalArgumentException has been thrown: " +
|
||||
e.getMessage());
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(LOG_TAG, "IllegalAccessException has been thrown: " +
|
||||
e.getMessage());
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(LOG_TAG, "InvocationTargetException has been thrown: ", e);
|
||||
throw new RuntimeException("InvocationTargetException has been thrown");
|
||||
}
|
||||
} else {
|
||||
mEntityIterator = RawContacts
|
||||
.newEntityIterator(mContentResolver.query(uri, null,
|
||||
selection.toString(), null, Data.CONTACT_ID));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initInterLastPart() {
|
||||
mInitDone = true;
|
||||
mTerminateCalled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a vCard string.
|
||||
*/
|
||||
public String createOneEntry() {
|
||||
if (!mInitDone) {
|
||||
Log.w(LOG_TAG, "This object is not ready yet.");
|
||||
return "";
|
||||
}
|
||||
|
||||
if (mIsDoCoMo && !mFirstVCardEmittedInDoCoMoCase) {
|
||||
mFirstVCardEmittedInDoCoMoCase = true;
|
||||
// Previously we needed to emit empty data for this specific case, but actually
|
||||
// this doesn't work now, as resolver doesn't return any data with "-1" contactId.
|
||||
// TODO: re-introduce or remove this logic. Needs to modify unit test when we
|
||||
// re-introduce the logic.
|
||||
// return createOneEntryInternal("-1", getEntityIteratorMethod);
|
||||
}
|
||||
|
||||
return createOneEntryInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that store rawContactEntitlesUri and contactId
|
||||
*/
|
||||
public static class RawContactEntitlesInfo {
|
||||
public final Uri rawContactEntitlesUri;
|
||||
public final long contactId;
|
||||
public RawContactEntitlesInfo(Uri rawContactEntitlesUri, long contactId) {
|
||||
this.rawContactEntitlesUri = rawContactEntitlesUri;
|
||||
this.contactId = contactId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for getting raw contact entitles info
|
||||
*/
|
||||
public interface RawContactEntitlesInfoCallback {
|
||||
/**
|
||||
* Callback to get RawContactEntitlesInfo from contact id
|
||||
*
|
||||
* @param contactId Contact id that you want to process.
|
||||
* @return RawContactEntitlesInfo that ready to process.
|
||||
*/
|
||||
RawContactEntitlesInfo getRawContactEntitlesInfo(long contactId);
|
||||
}
|
||||
|
||||
private String createOneEntryInternal() {
|
||||
if (!mEntityIterator.hasNext()) {
|
||||
Log.w(LOG_TAG, "EntityIterator#hasNext() returned false");
|
||||
return "";
|
||||
}
|
||||
final Map<String, List<ContentValues>> contentValuesListMap =
|
||||
new HashMap<String, List<ContentValues>>();
|
||||
Entity entity = mEntityIterator.next();
|
||||
for (NamedContentValues namedContentValues : entity.getSubValues()) {
|
||||
ContentValues contentValues = namedContentValues.values;
|
||||
String key = contentValues.getAsString(Data.MIMETYPE);
|
||||
if (key != null) {
|
||||
List<ContentValues> contentValuesList = contentValuesListMap.get(key);
|
||||
if (contentValuesList == null) {
|
||||
contentValuesList = new ArrayList<ContentValues>();
|
||||
contentValuesListMap.put(key, contentValuesList);
|
||||
}
|
||||
contentValuesList.add(contentValues);
|
||||
}
|
||||
}
|
||||
|
||||
return buildVCard(contentValuesListMap);
|
||||
}
|
||||
|
||||
private VCardPhoneNumberTranslationCallback mPhoneTranslationCallback;
|
||||
/**
|
||||
* <p>
|
||||
* Set a callback for phone number formatting. It will be called every time when this object
|
||||
* receives a phone number for printing.
|
||||
* </p>
|
||||
* <p>
|
||||
* When this is set {@link VCardConfig#FLAG_REFRAIN_PHONE_NUMBER_FORMATTING} will be ignored
|
||||
* and the callback should be responsible for everything about phone number formatting.
|
||||
* </p>
|
||||
* <p>
|
||||
* Caution: This interface will change. Please don't use without any strong reason.
|
||||
* </p>
|
||||
*/
|
||||
public void setPhoneNumberTranslationCallback(VCardPhoneNumberTranslationCallback callback) {
|
||||
mPhoneTranslationCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
|
||||
* {ContactsContract}. Developers can override this method to customize the output.
|
||||
*/
|
||||
public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
|
||||
if (contentValuesListMap == null) {
|
||||
Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
|
||||
return "";
|
||||
} else {
|
||||
final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
|
||||
builder.appendNameProperties(contentValuesListMap.get(
|
||||
StructuredName.CONTENT_ITEM_TYPE))
|
||||
.appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE),
|
||||
mPhoneTranslationCallback)
|
||||
.appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE))
|
||||
.appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
|
||||
.appendSipAddresses(contentValuesListMap.get(SipAddress.CONTENT_ITEM_TYPE));
|
||||
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_NICKNAME_EXPORT) == 0) {
|
||||
builder.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_EMAIL_EXPORT) == 0) {
|
||||
builder.appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_ADDRESS_EXPORT) == 0) {
|
||||
builder.appendPostals(contentValuesListMap.get(
|
||||
StructuredPostal.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_ORGANIZATION_EXPORT) == 0) {
|
||||
builder.appendOrganizations(contentValuesListMap.get(
|
||||
Organization.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_WEBSITES_EXPORT) == 0) {
|
||||
builder.appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
|
||||
builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_NOTES_EXPORT) == 0) {
|
||||
builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
if ((mVCardType & VCardConfig.FLAG_REFRAIN_EVENTS_EXPORT) == 0) {
|
||||
builder.appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
closeCursorIfAppropriate();
|
||||
mTerminateCalled = true;
|
||||
}
|
||||
|
||||
private void closeCursorIfAppropriate() {
|
||||
if (!mCursorSuppliedFromOutside && mCursor != null) {
|
||||
try {
|
||||
mCursor.close();
|
||||
} catch (SQLiteException e) {
|
||||
Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
|
||||
}
|
||||
mCursor = null;
|
||||
}
|
||||
if (mEntityIterator != null) {
|
||||
try{
|
||||
mEntityIterator.close();
|
||||
} catch (SQLiteException e) {
|
||||
Log.e(LOG_TAG, "SQLiteException on EntityIterator#close(): " + e.getMessage());
|
||||
}
|
||||
mEntityIterator = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
if (!mTerminateCalled) {
|
||||
Log.e(LOG_TAG, "finalized() is called before terminate() being called");
|
||||
}
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return returns the number of available entities. The return value is undefined
|
||||
* when this object is not ready yet (typically when {{@link #init()} is not called
|
||||
* or when {@link #terminate()} is already called).
|
||||
*/
|
||||
public int getCount() {
|
||||
if (mCursor == null) {
|
||||
Log.w(LOG_TAG, "This object is not ready yet.");
|
||||
return 0;
|
||||
}
|
||||
return mCursor.getCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true when there's no entity to be built. The return value is undefined
|
||||
* when this object is not ready yet.
|
||||
*/
|
||||
public boolean isAfterLast() {
|
||||
if (mEntityIterator == null) {
|
||||
Log.w(LOG_TAG, "This object is not ready yet.");
|
||||
return false;
|
||||
}
|
||||
return !mEntityIterator.hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the error reason.
|
||||
*/
|
||||
public String getErrorReason() {
|
||||
return mErrorReason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The class representing VCard related configurations. Useful static methods are not in this class
|
||||
* but in VCardUtils.
|
||||
*/
|
||||
public class VCardConfig {
|
||||
private static final String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
|
||||
/* package */ static final int LOG_LEVEL_NONE = 0;
|
||||
/* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
|
||||
/* package */ static final int LOG_LEVEL_SHOW_WARNING = 0x2;
|
||||
/* package */ static final int LOG_LEVEL_VERBOSE =
|
||||
LOG_LEVEL_PERFORMANCE_MEASUREMENT | LOG_LEVEL_SHOW_WARNING;
|
||||
|
||||
/* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The charset used during import.
|
||||
* </p>
|
||||
* <p>
|
||||
* We cannot determine which charset should be used to interpret lines in vCard,
|
||||
* while Java requires us to specify it when InputStream is used.
|
||||
* We need to rely on the mechanism due to some performance reason.
|
||||
* </p>
|
||||
* <p>
|
||||
* In order to avoid "misinterpretation" of charset and lose any data in vCard,
|
||||
* "ISO-8859-1" is first used for reading the stream.
|
||||
* When a charset is specified in a property (with "CHARSET=..." parameter),
|
||||
* the string is decoded to raw bytes and encoded into the specific charset,
|
||||
* </p>
|
||||
* <p>
|
||||
* Unicode specification there's a one to one mapping between each byte in ISO-8859-1
|
||||
* and a codepoint, and Java specification requires runtime must have the charset.
|
||||
* Thus, ISO-8859-1 is one effective mapping for intermediate mapping.
|
||||
* </p>
|
||||
*/
|
||||
public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1";
|
||||
|
||||
/**
|
||||
* The charset used when there's no information affbout what charset should be used to
|
||||
* encode the binary given from vCard.
|
||||
*/
|
||||
public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
|
||||
public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
|
||||
|
||||
/**
|
||||
* Do not use statically like "version == VERSION_V21"
|
||||
*/
|
||||
public static final int VERSION_21 = 0;
|
||||
public static final int VERSION_30 = 1;
|
||||
public static final int VERSION_40 = 2;
|
||||
public static final int VERSION_MASK = 3;
|
||||
|
||||
public static final int NAME_ORDER_DEFAULT = 0;
|
||||
public static final int NAME_ORDER_EUROPE = 0x4;
|
||||
public static final int NAME_ORDER_JAPANESE = 0x8;
|
||||
private static final int NAME_ORDER_MASK = 0xC;
|
||||
|
||||
// 0x10 is reserved for safety
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The flag indicating the vCard composer will add some "X-" properties used only in Android
|
||||
* when the formal vCard specification does not have appropriate fields for that data.
|
||||
* </p>
|
||||
* <p>
|
||||
* For example, Android accepts nickname information while vCard 2.1 does not.
|
||||
* When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
|
||||
* instead of just dropping it.
|
||||
* </p>
|
||||
* <p>
|
||||
* vCard parser code automatically parses the field emitted even when this flag is off.
|
||||
* </p>
|
||||
*/
|
||||
private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The flag indicating the vCard composer will add some "X-" properties seen in the
|
||||
* vCard data emitted by the other softwares/devices when the formal vCard specification
|
||||
* does not have appropriate field(s) for that data.
|
||||
* </p>
|
||||
* <p>
|
||||
* One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
|
||||
* for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
|
||||
* non-Android devices/softwares. We chose to enable the vCard composer to use those
|
||||
* defact properties since they are also useful for Android devices.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
|
||||
* allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
|
||||
* in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
|
||||
* </p>
|
||||
*/
|
||||
private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
|
||||
* mobile careers) should be used. This flag does not include any other information like
|
||||
* that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
|
||||
* dialect but the name order should be European", but it is not recommended.
|
||||
* </p>
|
||||
*/
|
||||
private static final int FLAG_DOCOMO = 0x20000000;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
|
||||
* properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
|
||||
* </p>
|
||||
* <p>
|
||||
* We actually cannot define what is the "primary" property. Note that this is NOT defined
|
||||
* in vCard specification either. Also be aware that it is NOT related to "primary" notion
|
||||
* used in {@link android.provider.ContactsContract}.
|
||||
* This notion is just for vCard composition in Android.
|
||||
* </p>
|
||||
* <p>
|
||||
* We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
|
||||
* do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
|
||||
* even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
|
||||
* other properties like "ADR", "ORG", etc.
|
||||
* <p>
|
||||
* We are afraid of the case where some vCard importer also forget handling QP presuming QP is
|
||||
* not used in such fields.
|
||||
* </p>
|
||||
* <p>
|
||||
* This flag is useful when some target importer you are going to focus on does not accept
|
||||
* such properties with Quoted-Printable encoding.
|
||||
* </p>
|
||||
* <p>
|
||||
* Again, we should not use this flag at all for complying vCard 2.1 spec.
|
||||
* </p>
|
||||
* <p>
|
||||
* In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
|
||||
* kind of problem (hopefully).
|
||||
* </p>
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The flag indicating that phonetic name related fields must be converted to
|
||||
* appropriate form. Note that "appropriate" is not defined in any vCard specification.
|
||||
* This is Android-specific.
|
||||
* </p>
|
||||
* <p>
|
||||
* One typical (and currently sole) example where we need this flag is the time when
|
||||
* we need to emit Japanese phonetic names into vCard entries. The property values
|
||||
* should be encoded into half-width katakana when the target importer is Japanese mobile
|
||||
* phones', which are probably not able to parse full-width hiragana/katakana for
|
||||
* historical reasons, while the vCard importers embedded to softwares for PC should be
|
||||
* able to parse them as we expect.
|
||||
* </p>
|
||||
*/
|
||||
public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
|
||||
* every time possible. The default behavior does not emit it and is valid in the spec.
|
||||
* In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
|
||||
* </p>
|
||||
* <p>
|
||||
* Detail:
|
||||
* How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
|
||||
* </p>
|
||||
* <p>
|
||||
* e.g.
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
|
||||
* <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
|
||||
* <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* If you are targeting to the importer which cannot accept TYPE params without "TYPE="
|
||||
* strings (which should be rare though), please use this flag.
|
||||
* </p>
|
||||
* <p>
|
||||
* Example usage:
|
||||
* <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
|
||||
* </p>
|
||||
*/
|
||||
public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The flag indicating the vCard composer does touch nothing toward phone number Strings
|
||||
* but leave it as is.
|
||||
* </p>
|
||||
* <p>
|
||||
* The vCard specifications mention nothing toward phone numbers, while some devices
|
||||
* do (wrongly, but with innevitable reasons).
|
||||
* For example, there's a possibility Japanese mobile phones are expected to have
|
||||
* just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones
|
||||
* should get such characters. To make exported vCard simple for external parsers,
|
||||
* we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and
|
||||
* removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)"
|
||||
* becomes "111-222-3333").
|
||||
* Unfortunate side effect of that use was some control characters used in the other
|
||||
* areas may be badly affected by the formatting.
|
||||
* </p>
|
||||
* <p>
|
||||
* This flag disables that formatting, affecting both importer and exporter.
|
||||
* If the user is aware of some side effects due to the implicit formatting, use this flag.
|
||||
* </p>
|
||||
* <p>
|
||||
* Caution: this flag will be removed in the future, replaced by some richer functionality.
|
||||
* </p>
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain image export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x00800000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain events export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_EVENTS_EXPORT = 0x00400000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain addess export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_ADDRESS_EXPORT = 0x00200000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain email export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_EMAIL_EXPORT = 0x00100000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain organization export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_ORGANIZATION_EXPORT = 0x00080000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain notes export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_NOTES_EXPORT = 0x00040000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain phonetic name export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_PHONETIC_NAME_EXPORT = 0x00020000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain websites export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_WEBSITES_EXPORT = 0x00010000;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The flag asking exporter to refrain nickname export.
|
||||
* </P>
|
||||
* @hide will be deleted in the near future.
|
||||
*/
|
||||
public static final int FLAG_REFRAIN_NICKNAME_EXPORT = 0x00008000;
|
||||
|
||||
//// The followings are VCard types available from importer/exporter. ////
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The type indicating nothing. Used by {@link VCardSourceDetector} when it
|
||||
* was not able to guess the exact vCard type.
|
||||
* </p>
|
||||
*/
|
||||
public static final int VCARD_TYPE_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Generic vCard format with the vCard 2.1. When composing a vCard entry,
|
||||
* the US convension will be used toward formatting some values.
|
||||
* </p>
|
||||
* <p>
|
||||
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
|
||||
* while it should be "Prefix Family Middle Given Suffix" in Japan for example.
|
||||
* </p>
|
||||
* <p>
|
||||
* Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
|
||||
* outside Android cannot accept it since vCard 2.1 specifically does not allow
|
||||
* that charset, while we need to use it to support various languages around the world.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you want to use alternative charset, you should notify the charset to the other
|
||||
* compontent to be used.
|
||||
* </p>
|
||||
*/
|
||||
public static final int VCARD_TYPE_V21_GENERIC =
|
||||
(VERSION_21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
|
||||
|
||||
/* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
|
||||
* </p>
|
||||
* <p>
|
||||
* Not fully ready yet. Use with caution when you use this.
|
||||
* </p>
|
||||
*/
|
||||
public static final int VCARD_TYPE_V30_GENERIC =
|
||||
(VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
|
||||
|
||||
/**
|
||||
* General vCard format with the version 4.0.
|
||||
* @hide vCard 4.0 is not published yet.
|
||||
*/
|
||||
public static final int VCARD_TYPE_V40_GENERIC =
|
||||
(VERSION_40 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_V40_GENERIC_STR = "v40_generic";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
|
||||
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
|
||||
* </p>
|
||||
*/
|
||||
public static final int VCARD_TYPE_V21_EUROPE =
|
||||
(VERSION_21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
|
||||
* </p>
|
||||
* <p>
|
||||
* Not ready yet. Use with caution when you use this.
|
||||
* </p>
|
||||
*/
|
||||
public static final int VCARD_TYPE_V30_EUROPE =
|
||||
(VERSION_30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
|
||||
* </p>
|
||||
* <p>
|
||||
* Not ready yet. Use with caution when you use this.
|
||||
* </p>
|
||||
*/
|
||||
public static final int VCARD_TYPE_V21_JAPANESE =
|
||||
(VERSION_21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
|
||||
* </p>
|
||||
* <p>
|
||||
* Not ready yet. Use with caution when you use this.
|
||||
* </p>
|
||||
*/
|
||||
public static final int VCARD_TYPE_V30_JAPANESE =
|
||||
(VERSION_30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The vCard 2.1 based format which (partially) considers the convention in Japanese
|
||||
* mobile phones, where phonetic names are translated to half-width katakana if
|
||||
* possible, etc. It would be better to use Shift_JIS as a charset for maximum
|
||||
* compatibility.
|
||||
* </p>
|
||||
* @hide Should not be available world wide.
|
||||
*/
|
||||
public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
|
||||
(VERSION_21 | NAME_ORDER_JAPANESE |
|
||||
FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
|
||||
* </p>
|
||||
* <p>
|
||||
* Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
|
||||
* No Android-specific property nor defact property is included. The "Primary" properties
|
||||
* are NOT encoded to Quoted-Printable.
|
||||
* </p>
|
||||
* @hide Should not be available world wide.
|
||||
*/
|
||||
public static final int VCARD_TYPE_DOCOMO =
|
||||
(VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
|
||||
|
||||
/* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
|
||||
|
||||
public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
|
||||
|
||||
private static final Map<String, Integer> sVCardTypeMap;
|
||||
private static final Set<Integer> sJapaneseMobileTypeSet;
|
||||
|
||||
static {
|
||||
sVCardTypeMap = new HashMap<String, Integer>();
|
||||
sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
|
||||
sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
|
||||
sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
|
||||
sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
|
||||
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
|
||||
sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
|
||||
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
|
||||
sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
|
||||
|
||||
sJapaneseMobileTypeSet = new HashSet<Integer>();
|
||||
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
|
||||
sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
|
||||
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
|
||||
sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
|
||||
}
|
||||
|
||||
public static int getVCardTypeFromString(final String vcardTypeString) {
|
||||
final String loweredKey = vcardTypeString.toLowerCase();
|
||||
if (sVCardTypeMap.containsKey(loweredKey)) {
|
||||
return sVCardTypeMap.get(loweredKey);
|
||||
} else if ("default".equalsIgnoreCase(vcardTypeString)) {
|
||||
return VCARD_TYPE_DEFAULT;
|
||||
} else {
|
||||
Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
|
||||
return VCARD_TYPE_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isVersion21(final int vcardType) {
|
||||
return (vcardType & VERSION_MASK) == VERSION_21;
|
||||
}
|
||||
|
||||
public static boolean isVersion30(final int vcardType) {
|
||||
return (vcardType & VERSION_MASK) == VERSION_30;
|
||||
}
|
||||
|
||||
public static boolean isVersion40(final int vcardType) {
|
||||
return (vcardType & VERSION_MASK) == VERSION_40;
|
||||
}
|
||||
|
||||
public static boolean shouldUseQuotedPrintable(final int vcardType) {
|
||||
return !isVersion30(vcardType);
|
||||
}
|
||||
|
||||
public static int getNameOrderType(final int vcardType) {
|
||||
return vcardType & NAME_ORDER_MASK;
|
||||
}
|
||||
|
||||
public static boolean usesAndroidSpecificProperty(final int vcardType) {
|
||||
return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
|
||||
}
|
||||
|
||||
public static boolean usesDefactProperty(final int vcardType) {
|
||||
return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
|
||||
}
|
||||
|
||||
public static boolean showPerformanceLog() {
|
||||
return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
|
||||
}
|
||||
|
||||
public static boolean shouldRefrainQPToNameProperties(final int vcardType) {
|
||||
return (!shouldUseQuotedPrintable(vcardType) ||
|
||||
((vcardType & FLAG_REFRAIN_QP_TO_NAME_PROPERTIES) != 0));
|
||||
}
|
||||
|
||||
public static boolean appendTypeParamName(final int vcardType) {
|
||||
return (isVersion30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the device is Japanese and some Japanese convension is
|
||||
* applied to creating "formatted" something like FORMATTED_ADDRESS.
|
||||
*/
|
||||
public static boolean isJapaneseDevice(final int vcardType) {
|
||||
// TODO: Some mask will be required so that this method wrongly interpret
|
||||
// Japanese"-like" vCard type.
|
||||
// e.g. VCARD_TYPE_V21_JAPANESE_SJIS | FLAG_APPEND_TYPE_PARAMS
|
||||
return sJapaneseMobileTypeSet.contains(vcardType);
|
||||
}
|
||||
|
||||
/* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) {
|
||||
return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0);
|
||||
}
|
||||
|
||||
public static boolean needsToConvertPhoneticString(final int vcardType) {
|
||||
return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
|
||||
}
|
||||
|
||||
public static boolean onlyOneNoteFieldIsAvailable(final int vcardType) {
|
||||
return vcardType == VCARD_TYPE_DOCOMO;
|
||||
}
|
||||
|
||||
public static boolean isDoCoMo(final int vcardType) {
|
||||
return ((vcardType & FLAG_DOCOMO) != 0);
|
||||
}
|
||||
|
||||
private VCardConfig() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
/**
|
||||
* Constants used in both exporter and importer code.
|
||||
*/
|
||||
public class VCardConstants {
|
||||
/* package */ static final String LOG_TAG = "vCard";
|
||||
public static final String VERSION_V21 = "2.1";
|
||||
public static final String VERSION_V30 = "3.0";
|
||||
public static final String VERSION_V40 = "4.0";
|
||||
|
||||
// Valid and supported property names.
|
||||
public static final String PROPERTY_BEGIN = "BEGIN";
|
||||
public static final String PROPERTY_VERSION = "VERSION";
|
||||
public static final String PROPERTY_N = "N";
|
||||
public static final String PROPERTY_FN = "FN";
|
||||
public static final String PROPERTY_ADR = "ADR";
|
||||
public static final String PROPERTY_EMAIL = "EMAIL";
|
||||
public static final String PROPERTY_NOTE = "NOTE";
|
||||
public static final String PROPERTY_ORG = "ORG";
|
||||
public static final String PROPERTY_SOUND = "SOUND"; // Not fully supported.
|
||||
public static final String PROPERTY_TEL = "TEL";
|
||||
public static final String PROPERTY_TITLE = "TITLE";
|
||||
public static final String PROPERTY_ROLE = "ROLE";
|
||||
public static final String PROPERTY_PHOTO = "PHOTO";
|
||||
public static final String PROPERTY_LOGO = "LOGO";
|
||||
public static final String PROPERTY_URL = "URL";
|
||||
public static final String PROPERTY_BDAY = "BDAY"; // Birthday (3.0, 4.0)
|
||||
public static final String PROPERTY_ANNIVERSARY = "ANNIVERSARY"; // Date of marriage (4.0)
|
||||
public static final String PROPERTY_NAME = "NAME"; // (3.0)
|
||||
public static final String PROPERTY_NICKNAME = "NICKNAME"; // (3.0, 4.0)
|
||||
public static final String PROPERTY_SORT_STRING = "SORT-STRING"; // (3.0, 4.0)
|
||||
public static final String PROPERTY_IMPP = "IMPP"; // RFC 4770 (vCard 3.0) and vCard 4.0
|
||||
public static final String PROPERTY_END = "END";
|
||||
|
||||
// Valid property names which are not supported (not appropriately handled) by
|
||||
// importer/exporter. Those properties will be read and discarded silently.
|
||||
public static final String PROPERTY_REV = "REV";
|
||||
public static final String PROPERTY_AGENT = "AGENT"; // (3.0)
|
||||
public static final String PROPERTY_GENDER = "GENDER"; // (4.0)
|
||||
public static final String PROPERTY_XML = "XML"; // (4.0)
|
||||
public static final String PROPERTY_FBURL = "FBURL"; // (4.0)
|
||||
public static final String PROPERTY_PRODID = "PRODID"; // (4.0)
|
||||
public static final String PROPERTY_RELATED = "RELATED"; // (4.0)
|
||||
public static final String PROPERTY_CATEGORIES = "CATEGORIES"; // (4.0)
|
||||
public static final String PROPERTY_CLIENTPIDMAP = "CLIENTPIDMAP"; // (4.0)
|
||||
public static final String PROPERTY_CALURI = "CALURI"; // (4.0)
|
||||
|
||||
|
||||
|
||||
// defact SIP property which had been used till RFC 4770.
|
||||
public static final String PROPERTY_X_SIP = "X-SIP";
|
||||
|
||||
// Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
|
||||
|
||||
// De-fact property values expressing phonetic names.
|
||||
public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
|
||||
public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
|
||||
public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
|
||||
|
||||
// Properties both ContactsStruct and de-fact vCard extensions
|
||||
// Shown in http://en.wikipedia.org/wiki/VCard support are defined here.
|
||||
public static final String PROPERTY_X_AIM = "X-AIM";
|
||||
public static final String PROPERTY_X_MSN = "X-MSN";
|
||||
public static final String PROPERTY_X_YAHOO = "X-YAHOO";
|
||||
public static final String PROPERTY_X_ICQ = "X-ICQ";
|
||||
public static final String PROPERTY_X_JABBER = "X-JABBER";
|
||||
public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
|
||||
public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
|
||||
// Properties only ContactsStruct has. We alse use this.
|
||||
public static final String PROPERTY_X_QQ = "X-QQ";
|
||||
public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
|
||||
|
||||
// Phone number for Skype, available as usual phone.
|
||||
public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
|
||||
|
||||
// Property for Android-specific fields.
|
||||
public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
|
||||
|
||||
// Properties for DoCoMo vCard.
|
||||
public static final String PROPERTY_X_CLASS = "X-CLASS";
|
||||
public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
|
||||
public static final String PROPERTY_X_NO = "X-NO";
|
||||
public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
|
||||
|
||||
public static final String PARAM_TYPE = "TYPE";
|
||||
|
||||
public static final String PARAM_TYPE_HOME = "HOME";
|
||||
public static final String PARAM_TYPE_WORK = "WORK";
|
||||
public static final String PARAM_TYPE_FAX = "FAX";
|
||||
public static final String PARAM_TYPE_CELL = "CELL";
|
||||
public static final String PARAM_TYPE_VOICE = "VOICE";
|
||||
public static final String PARAM_TYPE_INTERNET = "INTERNET";
|
||||
|
||||
public static final String PARAM_VALUE = "VALUE";
|
||||
public static final String PARAM_CHARSET = "CHARSET";
|
||||
public static final String PARAM_ENCODING = "ENCODING";
|
||||
|
||||
// Abbreviation of "prefered" according to vCard 2.1 specification.
|
||||
// We interpret this value as "primary" property during import/export.
|
||||
//
|
||||
// Note: Both vCard specs does not mention anything about the requirement for this parameter,
|
||||
// but there may be some vCard importer which will get confused with more than
|
||||
// one "PREF"s in one property name, while Android accepts them.
|
||||
public static final String PARAM_TYPE_PREF = "PREF";
|
||||
|
||||
// Phone type parameters valid in vCard and known to ContactsContract, but not so common.
|
||||
public static final String PARAM_TYPE_CAR = "CAR";
|
||||
public static final String PARAM_TYPE_ISDN = "ISDN";
|
||||
public static final String PARAM_TYPE_PAGER = "PAGER";
|
||||
public static final String PARAM_TYPE_TLX = "TLX"; // Telex
|
||||
|
||||
// Phone types existing in vCard 2.1 but not known to ContactsContract.
|
||||
public static final String PARAM_TYPE_MODEM = "MODEM";
|
||||
public static final String PARAM_TYPE_MSG = "MSG";
|
||||
public static final String PARAM_TYPE_BBS = "BBS";
|
||||
public static final String PARAM_TYPE_VIDEO = "VIDEO";
|
||||
|
||||
public static final String PARAM_ENCODING_7BIT = "7BIT";
|
||||
public static final String PARAM_ENCODING_8BIT = "8BIT";
|
||||
public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
|
||||
public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
|
||||
public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
|
||||
|
||||
// TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
|
||||
// These types are basically encoded to "X-" parameters when composing vCard.
|
||||
// Parser passes these when "X-" is added to the parameter or not.
|
||||
public static final String PARAM_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
|
||||
public static final String PARAM_PHONE_EXTRA_TYPE_RADIO = "RADIO";
|
||||
public static final String PARAM_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
|
||||
public static final String PARAM_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
|
||||
// vCard composer translates this type to "WORK" + "PREF". Just for parsing.
|
||||
public static final String PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
|
||||
// vCard composer translates this type to "VOICE" Just for parsing.
|
||||
public static final String PARAM_PHONE_EXTRA_TYPE_OTHER = "OTHER";
|
||||
|
||||
// TYPE parameters for postal addresses.
|
||||
public static final String PARAM_ADR_TYPE_PARCEL = "PARCEL";
|
||||
public static final String PARAM_ADR_TYPE_DOM = "DOM";
|
||||
public static final String PARAM_ADR_TYPE_INTL = "INTL";
|
||||
// {@link VCardBuilder} translates this type to "X-OTHER".
|
||||
public static final String PARAM_ADR_EXTRA_TYPE_OTHER = "OTHER";
|
||||
|
||||
public static final String PARAM_LANGUAGE = "LANGUAGE";
|
||||
|
||||
// SORT-AS parameter introduced in vCard 4.0 (as of rev.13)
|
||||
public static final String PARAM_SORT_AS = "SORT-AS";
|
||||
|
||||
// TYPE parameters not officially valid but used in some vCard exporter.
|
||||
// Do not use in composer side.
|
||||
public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
|
||||
|
||||
public interface ImportOnly {
|
||||
public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
|
||||
// Some device emits this "X-" parameter for expressing Google Talk,
|
||||
// which is specifically invalid but should be always properly accepted, and emitted
|
||||
// in some special case (for that device/application).
|
||||
public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
|
||||
}
|
||||
|
||||
//// Mainly for package constants.
|
||||
|
||||
// DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
|
||||
// SORT-STRING invCard 3.0.
|
||||
/* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
|
||||
|
||||
// Used in unit test.
|
||||
public static final int MAX_DATA_COLUMN = 15;
|
||||
|
||||
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
|
||||
static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
|
||||
|
||||
private VCardConstants() {
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.ContactsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* {@link VCardEntryHandler} implementation which commits the entry to ContentResolver.
|
||||
* </P>
|
||||
* <P>
|
||||
* Note:<BR />
|
||||
* Each vCard may contain big photo images encoded by BASE64,
|
||||
* If we store all vCard entries in memory, OutOfMemoryError may be thrown.
|
||||
* Thus, this class push each VCard entry into ContentResolver immediately.
|
||||
* </P>
|
||||
*/
|
||||
public class VCardEntryCommitter implements VCardEntryHandler {
|
||||
public static String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
|
||||
private final ContentResolver mContentResolver;
|
||||
private long mTimeToCommit;
|
||||
// Set the default maximum batch size to 20
|
||||
private int mMaxBatchSize = 20;
|
||||
private ArrayList<ContentProviderOperation> mOperationList;
|
||||
private final ArrayList<Uri> mCreatedUris = new ArrayList<Uri>();
|
||||
|
||||
public VCardEntryCommitter(ContentResolver resolver) {
|
||||
mContentResolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd() {
|
||||
if (mOperationList != null) {
|
||||
mCreatedUris.add(pushIntoContentResolver(mOperationList));
|
||||
}
|
||||
|
||||
if (VCardConfig.showPerformanceLog()) {
|
||||
Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
|
||||
}
|
||||
}
|
||||
|
||||
//because the max batch size is 500 defined in ContactsProvider,so we can enlarge this batch
|
||||
//size to reduce db open/close times. From testing results, we can see performance better
|
||||
//when batch size is more bigger.And also each vcardEntry may have some operation records.
|
||||
//So we can set threshold as 450, batch operations will be executed when threshold reached.
|
||||
//Testing result.
|
||||
//batch size : 100 200 300 400 450 490 20
|
||||
//consume time(10000 contacts): 178s 143s 127s 124s 119s 117s 195s
|
||||
//consume time (1000 contacts): 17.3s 13.9s 12.6s 12.2s 11.8s 11.6s 19.8s
|
||||
public void setMaxBatchSize(int batchSize) {
|
||||
mMaxBatchSize = batchSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryCreated(final VCardEntry vcardEntry) {
|
||||
final long start = System.currentTimeMillis();
|
||||
mOperationList = vcardEntry.constructInsertOperations(mContentResolver, mOperationList);
|
||||
if (mOperationList != null && mOperationList.size() >= mMaxBatchSize) {
|
||||
mCreatedUris.add(pushIntoContentResolver(mOperationList));
|
||||
mOperationList = null;
|
||||
}
|
||||
mTimeToCommit += System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
private Uri pushIntoContentResolver(ArrayList<ContentProviderOperation> operationList) {
|
||||
try {
|
||||
final ContentProviderResult[] results = mContentResolver.applyBatch(
|
||||
ContactsContract.AUTHORITY, operationList);
|
||||
|
||||
// the first result is always the raw_contact. return it's uri so
|
||||
// that it can be found later. do null checking for badly behaving
|
||||
// ContentResolvers
|
||||
return ((results == null || results.length == 0 || results[0] == null)
|
||||
? null : results[0].uri);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
|
||||
return null;
|
||||
} catch (OperationApplicationException e) {
|
||||
Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of created Uris. This list should not be modified by the caller as it is
|
||||
* not a clone.
|
||||
*/
|
||||
public ArrayList<Uri> getCreatedUris() {
|
||||
return mCreatedUris;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
|
||||
* to easily handle each vCard entry.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class understand details inside vCard and translates it to {@link VCardEntry}.
|
||||
* Then the class throw it to {@link VCardEntryHandler} registered via
|
||||
* {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
|
||||
* are able to handle the {@link VCardEntry} object.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you want to know the detail inside vCard, it would be better to implement
|
||||
* {@link VCardInterpreter} directly, instead of relying on this class and
|
||||
* {@link VCardEntry} created by the object.
|
||||
* </p>
|
||||
*/
|
||||
public class VCardEntryConstructor implements VCardInterpreter {
|
||||
private static String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
|
||||
/**
|
||||
* Represents current stack of VCardEntry. Used to support nested vCard (vCard 2.1).
|
||||
*/
|
||||
private final List<VCardEntry> mEntryStack = new ArrayList<VCardEntry>();
|
||||
private VCardEntry mCurrentEntry;
|
||||
|
||||
private final int mVCardType;
|
||||
private final Account mAccount;
|
||||
|
||||
private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
|
||||
|
||||
public VCardEntryConstructor() {
|
||||
this(VCardConfig.VCARD_TYPE_V21_GENERIC, null, null);
|
||||
}
|
||||
|
||||
public VCardEntryConstructor(final int vcardType) {
|
||||
this(vcardType, null, null);
|
||||
}
|
||||
|
||||
public VCardEntryConstructor(final int vcardType, final Account account) {
|
||||
this(vcardType, account, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated targetCharset is not used anymore.
|
||||
* Use {@link #VCardEntryConstructor(int, Account)}
|
||||
*/
|
||||
@Deprecated
|
||||
public VCardEntryConstructor(final int vcardType, final Account account,
|
||||
String targetCharset) {
|
||||
mVCardType = vcardType;
|
||||
mAccount = account;
|
||||
}
|
||||
|
||||
public void addEntryHandler(VCardEntryHandler entryHandler) {
|
||||
mEntryHandlers.add(entryHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVCardStarted() {
|
||||
for (VCardEntryHandler entryHandler : mEntryHandlers) {
|
||||
entryHandler.onStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVCardEnded() {
|
||||
for (VCardEntryHandler entryHandler : mEntryHandlers) {
|
||||
entryHandler.onEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mCurrentEntry = null;
|
||||
mEntryStack.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryStarted() {
|
||||
mCurrentEntry = new VCardEntry(mVCardType, mAccount);
|
||||
mEntryStack.add(mCurrentEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryEnded() {
|
||||
mCurrentEntry.consolidateFields();
|
||||
for (VCardEntryHandler entryHandler : mEntryHandlers) {
|
||||
entryHandler.onEntryCreated(mCurrentEntry);
|
||||
}
|
||||
|
||||
final int size = mEntryStack.size();
|
||||
if (size > 1) {
|
||||
VCardEntry parent = mEntryStack.get(size - 2);
|
||||
parent.addChild(mCurrentEntry);
|
||||
mCurrentEntry = parent;
|
||||
} else {
|
||||
mCurrentEntry = null;
|
||||
}
|
||||
mEntryStack.remove(size - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyCreated(VCardProperty property) {
|
||||
mCurrentEntry.addProperty(property);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
/**
|
||||
* The class which just counts the number of vCard entries in the specified input.
|
||||
*/
|
||||
public class VCardEntryCounter implements VCardInterpreter {
|
||||
private int mCount;
|
||||
|
||||
public int getCount() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVCardStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVCardEnded() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryEnded() {
|
||||
mCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyCreated(VCardProperty property) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The interface called by {@link VCardEntryConstructor}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class is useful when you don't want to know vCard data in detail. If you want to know
|
||||
* it, it would be better to consider using {@link VCardInterpreter}.
|
||||
* </p>
|
||||
*/
|
||||
public interface VCardEntryHandler {
|
||||
/**
|
||||
* Called when the parsing started.
|
||||
*/
|
||||
public void onStart();
|
||||
|
||||
/**
|
||||
* The method called when one vCard entry is created. Children come before their parent in
|
||||
* nested vCard files.
|
||||
*
|
||||
* e.g.
|
||||
* In the following vCard, the entry for "entry2" comes before one for "entry1".
|
||||
* <code>
|
||||
* BEGIN:VCARD
|
||||
* N:entry1
|
||||
* BEGIN:VCARD
|
||||
* N:entry2
|
||||
* END:VCARD
|
||||
* END:VCARD
|
||||
* </code>
|
||||
*/
|
||||
public void onEntryCreated(final VCardEntry entry);
|
||||
|
||||
/**
|
||||
* Called when the parsing ended.
|
||||
* Able to be use this method for showing performance log, etc.
|
||||
*/
|
||||
public void onEnd();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* The interface which should be implemented by the classes which have to analyze each
|
||||
* vCard entry minutely.
|
||||
* </P>
|
||||
* <P>
|
||||
* Here, there are several terms specific to vCard (and this library).
|
||||
* </P>
|
||||
* <P>
|
||||
* The term "entry" is one vCard representation in the input, which should start with "BEGIN:VCARD"
|
||||
* and end with "END:VCARD".
|
||||
* </P>
|
||||
* <P>
|
||||
* The term "property" is one line in vCard entry, which consists of "group", "property name",
|
||||
* "parameter(param) names and values", and "property values".
|
||||
* </P>
|
||||
* <P>
|
||||
* e.g. group1.propName;paramName1=paramValue1;paramName2=paramValue2;propertyValue1;propertyValue2...
|
||||
* </P>
|
||||
*/
|
||||
public interface VCardInterpreter {
|
||||
/**
|
||||
* Called when vCard interpretation started.
|
||||
*/
|
||||
void onVCardStarted();
|
||||
|
||||
/**
|
||||
* Called when vCard interpretation finished.
|
||||
*/
|
||||
void onVCardEnded();
|
||||
|
||||
/**
|
||||
* Called when parsing one vCard entry started.
|
||||
* More specifically, this method is called when "BEGIN:VCARD" is read.
|
||||
*
|
||||
* This may be called before {@link #onEntryEnded()} is called, as vCard 2.1 accepts nested
|
||||
* vCard.
|
||||
*
|
||||
* <code>
|
||||
* BEGIN:VCARD
|
||||
* BEGIN:VCARD
|
||||
* VERSION:2.1
|
||||
* N:test;;;;
|
||||
* END:VCARD
|
||||
* END:VCARD
|
||||
* </code>
|
||||
*/
|
||||
void onEntryStarted();
|
||||
|
||||
/**
|
||||
* Called when parsing one vCard entry ended.
|
||||
* More specifically, this method is called when "END:VCARD" is read.
|
||||
*/
|
||||
void onEntryEnded();
|
||||
|
||||
/**
|
||||
* Called when a property is created.
|
||||
*/
|
||||
void onPropertyCreated(VCardProperty property);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import com.android.vcard.exception.VCardException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public abstract class VCardParser {
|
||||
|
||||
/**
|
||||
* Registers one {@link VCardInterpreter} instance, which receives events along with
|
||||
* vCard parsing.
|
||||
*
|
||||
* @param interpreter
|
||||
*/
|
||||
public abstract void addInterpreter(VCardInterpreter interpreter);
|
||||
|
||||
/**
|
||||
* <p>Parses a whole InputStream as a vCard file and lets registered {@link VCardInterpreter}
|
||||
* instances handle callbacks.</p>
|
||||
*
|
||||
* <p>This method reads a whole InputStream. If you just want to parse one vCard entry inside
|
||||
* a vCard file with multiple entries, try {@link #parseOne(InputStream)}.</p>
|
||||
*
|
||||
* @param is The source to parse.
|
||||
* @throws IOException, VCardException
|
||||
*/
|
||||
public abstract void parse(InputStream is) throws IOException, VCardException;
|
||||
|
||||
/**
|
||||
* <p>Parses the first vCard entry in InputStream and lets registered {@link VCardInterpreter}
|
||||
* instances handle callbacks.</p>
|
||||
*
|
||||
* <p>This method finishes itself when the first entry ended.</p>
|
||||
*
|
||||
* <p>Note that, registered {@link VCardInterpreter} may still see multiple
|
||||
* {@link VCardInterpreter#onEntryStarted()} / {@link VCardInterpreter#onEntryEnded()} calls
|
||||
* even with this method.</p>
|
||||
*
|
||||
* <p>This happens when the first entry contains nested vCards, which is allowed in vCard 2.1.
|
||||
* See the following example.</p>
|
||||
*
|
||||
* <code>
|
||||
* BEGIN:VCARD
|
||||
* N:a
|
||||
* BEGIN:VCARD
|
||||
* N:b
|
||||
* END:VCARD
|
||||
* END:VCARD
|
||||
* </code>
|
||||
*
|
||||
* <p>With this vCard, registered interpreters will grab two
|
||||
* {@link VCardInterpreter#onEntryStarted()} and {@link VCardInterpreter#onEntryEnded()}
|
||||
* calls. Callers should handle the situation by themselves.</p>
|
||||
*
|
||||
* @param is The source to parse.
|
||||
* @throws IOException, VCardException
|
||||
*/
|
||||
public abstract void parseOne(InputStream is) throws IOException, VCardException;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #addInterpreter(VCardInterpreter)} and
|
||||
* {@link #parse(InputStream)}
|
||||
*/
|
||||
@Deprecated
|
||||
public void parse(InputStream is, VCardInterpreter interpreter)
|
||||
throws IOException, VCardException {
|
||||
addInterpreter(interpreter);
|
||||
parse(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
|
||||
* </p>
|
||||
* <p>
|
||||
* Actual cancel is done after parsing the current vcard.
|
||||
* </p>
|
||||
*/
|
||||
public abstract void cancel();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.vcard.exception.VCardException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Basic implementation achieving vCard 3.0 parsing.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class inherits vCard 2.1 implementation since technically they are similar,
|
||||
* while specifically there's logical no relevance between them.
|
||||
* So that developers are not confused with the inheritance,
|
||||
* {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
|
||||
* {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
|
||||
* </p>
|
||||
* @hide
|
||||
*/
|
||||
/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
|
||||
private static final String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
|
||||
private String mPreviousLine;
|
||||
private boolean mEmittedAgentWarning = false;
|
||||
|
||||
public VCardParserImpl_V30() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VCardParserImpl_V30(int vcardType) {
|
||||
super(vcardType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVersion() {
|
||||
return VCardConfig.VERSION_30;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVersionString() {
|
||||
return VCardConstants.VERSION_V30;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String peekLine() throws IOException {
|
||||
if (mPreviousLine != null) {
|
||||
String ret = mPreviousLine;
|
||||
return ret;
|
||||
} else {
|
||||
return mReader.peekLine();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLine() throws IOException {
|
||||
if (mPreviousLine != null) {
|
||||
String ret = mPreviousLine;
|
||||
mPreviousLine = null;
|
||||
return ret;
|
||||
} else {
|
||||
return mReader.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* vCard 3.0 requires that the line with space at the beginning of the line
|
||||
* must be combined with previous line.
|
||||
*/
|
||||
@Override
|
||||
protected String getNonEmptyLine() throws IOException, VCardException {
|
||||
String line;
|
||||
StringBuilder builder = null;
|
||||
while ((line = mReader.readLine()) != null) {
|
||||
// Skip empty lines in order to accomodate implementations that
|
||||
// send line termination variations such as \r\r\n.
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
|
||||
// RFC 2425 describes line continuation as \r\n followed by
|
||||
// a single ' ' or '\t' whitespace character.
|
||||
if (builder == null) {
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
if (mPreviousLine != null) {
|
||||
builder.append(mPreviousLine);
|
||||
mPreviousLine = null;
|
||||
}
|
||||
builder.append(line.substring(1));
|
||||
} else {
|
||||
if (builder != null || mPreviousLine != null) {
|
||||
break;
|
||||
}
|
||||
mPreviousLine = line;
|
||||
}
|
||||
}
|
||||
|
||||
String ret = null;
|
||||
if (builder != null) {
|
||||
ret = builder.toString();
|
||||
} else if (mPreviousLine != null) {
|
||||
ret = mPreviousLine;
|
||||
}
|
||||
mPreviousLine = line;
|
||||
if (ret == null) {
|
||||
throw new VCardException("Reached end of buffer.");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
|
||||
* 1 * (contentline)
|
||||
* ;A vCard object MUST include the VERSION, FN and N types.
|
||||
* [group "."] "END" ":" "VCARD" 1 * CRLF
|
||||
*/
|
||||
@Override
|
||||
protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
|
||||
// TODO: vCard 3.0 supports group.
|
||||
return super.readBeginVCard(allowGarbage);
|
||||
}
|
||||
|
||||
/**
|
||||
* vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
|
||||
*/
|
||||
@Override
|
||||
protected void handleParams(VCardProperty propertyData, final String params)
|
||||
throws VCardException {
|
||||
try {
|
||||
super.handleParams(propertyData, params);
|
||||
} catch (VCardException e) {
|
||||
// maybe IANA type
|
||||
String[] strArray = params.split("=", 2);
|
||||
if (strArray.length == 2) {
|
||||
handleAnyParam(propertyData, strArray[0], strArray[1]);
|
||||
} else {
|
||||
// Must not come here in the current implementation.
|
||||
throw new VCardException(
|
||||
"Unknown params value: " + params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAnyParam(
|
||||
VCardProperty propertyData, final String paramName, final String paramValue) {
|
||||
splitAndPutParam(propertyData, paramName, paramValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleParamWithoutName(VCardProperty property, final String paramValue) {
|
||||
handleType(property, paramValue);
|
||||
}
|
||||
|
||||
/*
|
||||
* vCard 3.0 defines
|
||||
*
|
||||
* param = param-name "=" param-value *("," param-value)
|
||||
* param-name = iana-token / x-name
|
||||
* param-value = ptext / quoted-string
|
||||
* quoted-string = DQUOTE QSAFE-CHAR DQUOTE
|
||||
* QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
|
||||
* ; Any character except CTLs, DQUOTE
|
||||
*
|
||||
* QSAFE-CHAR must not contain DQUOTE, including escaped one (\").
|
||||
*/
|
||||
@Override
|
||||
protected void handleType(VCardProperty property, final String paramValue) {
|
||||
splitAndPutParam(property, VCardConstants.PARAM_TYPE, paramValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits parameter values into pieces in accordance with vCard 3.0 specification and
|
||||
* puts pieces into mInterpreter.
|
||||
*/
|
||||
/*
|
||||
* param-value = ptext / quoted-string
|
||||
* quoted-string = DQUOTE QSAFE-CHAR DQUOTE
|
||||
* QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
|
||||
* ; Any character except CTLs, DQUOTE
|
||||
*
|
||||
* QSAFE-CHAR must not contain DQUOTE, including escaped one (\")
|
||||
*/
|
||||
private void splitAndPutParam(VCardProperty property, String paramName, String paramValue) {
|
||||
// "comma,separated:inside.dquote",pref
|
||||
// -->
|
||||
// - comma,separated:inside.dquote
|
||||
// - pref
|
||||
//
|
||||
// Note: Though there's a code, we don't need to take much care of
|
||||
// wrongly-added quotes like the example above, as they induce
|
||||
// parse errors at the top level (when splitting a line into parts).
|
||||
StringBuilder builder = null; // Delay initialization.
|
||||
boolean insideDquote = false;
|
||||
final int length = paramValue.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
final char ch = paramValue.charAt(i);
|
||||
if (ch == '"') {
|
||||
if (insideDquote) {
|
||||
// End of Dquote.
|
||||
property.addParameter(paramName, encodeParamValue(builder.toString()));
|
||||
builder = null;
|
||||
insideDquote = false;
|
||||
} else {
|
||||
if (builder != null) {
|
||||
if (builder.length() > 0) {
|
||||
// e.g.
|
||||
// pref"quoted"
|
||||
Log.w(LOG_TAG, "Unexpected Dquote inside property.");
|
||||
} else {
|
||||
// e.g.
|
||||
// pref,"quoted"
|
||||
// "quoted",pref
|
||||
property.addParameter(paramName, encodeParamValue(builder.toString()));
|
||||
}
|
||||
}
|
||||
insideDquote = true;
|
||||
}
|
||||
} else if (ch == ',' && !insideDquote) {
|
||||
if (builder == null) {
|
||||
Log.w(LOG_TAG, "Comma is used before actual string comes. (" +
|
||||
paramValue + ")");
|
||||
} else {
|
||||
property.addParameter(paramName, encodeParamValue(builder.toString()));
|
||||
builder = null;
|
||||
}
|
||||
} else {
|
||||
// To stop creating empty StringBuffer at the end of parameter,
|
||||
// we delay creating this object until this point.
|
||||
if (builder == null) {
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
if (insideDquote) {
|
||||
// e.g.
|
||||
// "non-quote-at-end
|
||||
Log.d(LOG_TAG, "Dangling Dquote.");
|
||||
}
|
||||
if (builder != null) {
|
||||
if (builder.length() == 0) {
|
||||
Log.w(LOG_TAG, "Unintended behavior. We must not see empty StringBuilder " +
|
||||
"at the end of parameter value parsing.");
|
||||
} else {
|
||||
property.addParameter(paramName, encodeParamValue(builder.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a param value using UTF-8.
|
||||
*/
|
||||
protected String encodeParamValue(String paramValue) {
|
||||
return VCardUtils.convertStringCharset(
|
||||
paramValue, VCardConfig.DEFAULT_INTERMEDIATE_CHARSET, "UTF-8");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAgent(VCardProperty property) {
|
||||
// The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
|
||||
//
|
||||
// e.g.
|
||||
// AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
|
||||
// TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
|
||||
// ET:jfriday@host.com\nEND:VCARD\n
|
||||
//
|
||||
// TODO: fix this.
|
||||
//
|
||||
// issue:
|
||||
// vCard 3.0 also allows this as an example.
|
||||
//
|
||||
// AGENT;VALUE=uri:
|
||||
// CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
|
||||
//
|
||||
// This is not vCard. Should we support this?
|
||||
//
|
||||
// Just ignore the line for now, since we cannot know how to handle it...
|
||||
if (!mEmittedAgentWarning) {
|
||||
Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
|
||||
mEmittedAgentWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only called from handlePropertyValue(), which has already
|
||||
* read the first line of this property. With v3.0, the getNonEmptyLine()
|
||||
* routine has already concatenated all following continuation lines.
|
||||
* The routine is implemented in the V21 parser to concatenate v2.1 style
|
||||
* data blocks, but is unnecessary here.
|
||||
*/
|
||||
@Override
|
||||
protected String getBase64(final String firstString)
|
||||
throws IOException, VCardException {
|
||||
return firstString;
|
||||
}
|
||||
|
||||
/**
|
||||
* ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
|
||||
* ; \\ encodes \, \n or \N encodes newline
|
||||
* ; \; encodes ;, \, encodes ,
|
||||
*
|
||||
* Note: Apple escapes ':' into '\:' while does not escape '\'
|
||||
*/
|
||||
@Override
|
||||
protected String maybeUnescapeText(final String text) {
|
||||
return unescapeText(text);
|
||||
}
|
||||
|
||||
public static String unescapeText(final String text) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
final int length = text.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch == '\\' && i < length - 1) {
|
||||
final char next_ch = text.charAt(++i);
|
||||
if (next_ch == 'n' || next_ch == 'N') {
|
||||
builder.append("\n");
|
||||
} else {
|
||||
builder.append(next_ch);
|
||||
}
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String maybeUnescapeCharacter(final char ch) {
|
||||
return unescapeCharacter(ch);
|
||||
}
|
||||
|
||||
public static String unescapeCharacter(final char ch) {
|
||||
if (ch == 'n' || ch == 'N') {
|
||||
return "\n";
|
||||
} else {
|
||||
return String.valueOf(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getKnownPropertyNameSet() {
|
||||
return VCardParser_V30.sKnownPropertyNameSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Basic implementation parsing vCard 4.0.
|
||||
* </p>
|
||||
* <p>
|
||||
* vCard 4.0 is not published yet. Also this implementation is premature.
|
||||
* </p>
|
||||
* @hide
|
||||
*/
|
||||
/* package */ class VCardParserImpl_V40 extends VCardParserImpl_V30 {
|
||||
// private static final String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
|
||||
public VCardParserImpl_V40() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VCardParserImpl_V40(final int vcardType) {
|
||||
super(vcardType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVersion() {
|
||||
return VCardConfig.VERSION_40;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVersionString() {
|
||||
return VCardConstants.VERSION_V40;
|
||||
}
|
||||
|
||||
/**
|
||||
* We escape "\N" into new line for safety.
|
||||
*/
|
||||
@Override
|
||||
protected String maybeUnescapeText(final String text) {
|
||||
return unescapeText(text);
|
||||
}
|
||||
|
||||
public static String unescapeText(final String text) {
|
||||
// TODO: more strictly, vCard 4.0 requires different type of unescaping rule
|
||||
// toward each property.
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final int length = text.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch == '\\' && i < length - 1) {
|
||||
final char next_ch = text.charAt(++i);
|
||||
if (next_ch == 'n' || next_ch == 'N') {
|
||||
builder.append("\n");
|
||||
} else {
|
||||
builder.append(next_ch);
|
||||
}
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String unescapeCharacter(final char ch) {
|
||||
if (ch == 'n' || ch == 'N') {
|
||||
return "\n";
|
||||
} else {
|
||||
return String.valueOf(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getKnownPropertyNameSet() {
|
||||
return VCardParser_V40.sKnownPropertyNameSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import com.android.vcard.exception.VCardException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* </p>
|
||||
* vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
|
||||
* </p>
|
||||
* <p>
|
||||
* The spec is written in 1996, and currently various types of "vCard 2.1" exist.
|
||||
* To handle real the world vCard formats appropriately and effectively, this class does not
|
||||
* obey with strict vCard 2.1.
|
||||
* In stead, not only vCard spec but also real world vCard is considered.
|
||||
* </p>
|
||||
* e.g. A lot of devices and softwares let vCard importer/exporter to use
|
||||
* the PNG format to determine the type of image, while it is not allowed in
|
||||
* the original specification. As of 2010, we can see even the FLV format
|
||||
* (possible in Japanese mobile phones).
|
||||
* </p>
|
||||
*/
|
||||
public final class VCardParser_V21 extends VCardParser {
|
||||
/**
|
||||
* A unmodifiable Set storing the property names available in the vCard 2.1 specification.
|
||||
*/
|
||||
/* package */ static final Set<String> sKnownPropertyNameSet =
|
||||
Collections.unmodifiableSet(new HashSet<String>(
|
||||
Arrays.asList("BEGIN", "END", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
|
||||
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
|
||||
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
|
||||
|
||||
/**
|
||||
* A unmodifiable Set storing the types known in vCard 2.1.
|
||||
*/
|
||||
/* package */ static final Set<String> sKnownTypeSet =
|
||||
Collections.unmodifiableSet(new HashSet<String>(
|
||||
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
|
||||
"PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
|
||||
"MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
|
||||
"ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
|
||||
"MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
|
||||
"CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
|
||||
"PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
|
||||
"WAVE", "AIFF", "PCM", "X509", "PGP")));
|
||||
|
||||
/**
|
||||
* A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
|
||||
*/
|
||||
/* package */ static final Set<String> sKnownValueSet =
|
||||
Collections.unmodifiableSet(new HashSet<String>(
|
||||
Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
|
||||
* </p>
|
||||
* <p>
|
||||
* Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
|
||||
* We allow it for safety.
|
||||
* </p>
|
||||
*/
|
||||
/* package */ static final Set<String> sAvailableEncoding =
|
||||
Collections.unmodifiableSet(new HashSet<String>(
|
||||
Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
|
||||
VCardConstants.PARAM_ENCODING_8BIT,
|
||||
VCardConstants.PARAM_ENCODING_QP,
|
||||
VCardConstants.PARAM_ENCODING_BASE64,
|
||||
VCardConstants.PARAM_ENCODING_B)));
|
||||
|
||||
private final VCardParserImpl_V21 mVCardParserImpl;
|
||||
|
||||
public VCardParser_V21() {
|
||||
mVCardParserImpl = new VCardParserImpl_V21();
|
||||
}
|
||||
|
||||
public VCardParser_V21(int vcardType) {
|
||||
mVCardParserImpl = new VCardParserImpl_V21(vcardType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterpreter(VCardInterpreter interpreter) {
|
||||
mVCardParserImpl.addInterpreter(interpreter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(InputStream is) throws IOException, VCardException {
|
||||
mVCardParserImpl.parse(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseOne(InputStream is) throws IOException, VCardException {
|
||||
mVCardParserImpl.parseOne(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
mVCardParserImpl.cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import com.android.vcard.exception.VCardException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* vCard parser for vCard 3.0. See RFC 2426 for more detail.
|
||||
* </p>
|
||||
* <p>
|
||||
* This parser allows vCard format which is not allowed in the RFC, since
|
||||
* we have seen several vCard 3.0 files which don't comply with it.
|
||||
* </p>
|
||||
* <p>
|
||||
* e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
|
||||
* have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
|
||||
* but it is not a must. We silently allow "CHARSET".
|
||||
* </p>
|
||||
*/
|
||||
public class VCardParser_V30 extends VCardParser {
|
||||
/* package */ static final Set<String> sKnownPropertyNameSet =
|
||||
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
||||
"BEGIN", "END", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
|
||||
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
|
||||
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
|
||||
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
|
||||
"SORT-STRING", "CATEGORIES", "PRODID", // 3.0
|
||||
"IMPP"))); // RFC 4770
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
|
||||
* </p>
|
||||
* <p>
|
||||
* Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
|
||||
* </p>
|
||||
* <p>
|
||||
* "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
|
||||
* because the encoding ambiguates how the vCard file to be parsed.
|
||||
* </p>
|
||||
*/
|
||||
/* package */ static final Set<String> sAcceptableEncoding =
|
||||
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
||||
VCardConstants.PARAM_ENCODING_7BIT,
|
||||
VCardConstants.PARAM_ENCODING_8BIT,
|
||||
VCardConstants.PARAM_ENCODING_BASE64,
|
||||
VCardConstants.PARAM_ENCODING_B)));
|
||||
|
||||
private final VCardParserImpl_V30 mVCardParserImpl;
|
||||
|
||||
public VCardParser_V30() {
|
||||
mVCardParserImpl = new VCardParserImpl_V30();
|
||||
}
|
||||
|
||||
public VCardParser_V30(int vcardType) {
|
||||
mVCardParserImpl = new VCardParserImpl_V30(vcardType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterpreter(VCardInterpreter interpreter) {
|
||||
mVCardParserImpl.addInterpreter(interpreter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(InputStream is) throws IOException, VCardException {
|
||||
mVCardParserImpl.parse(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseOne(InputStream is) throws IOException, VCardException {
|
||||
mVCardParserImpl.parseOne(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
mVCardParserImpl.cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import com.android.vcard.exception.VCardException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* vCard parser for vCard 4.0. DO NOT USE IN PRODUCTION.
|
||||
* </p>
|
||||
* <p>
|
||||
* Currently this parser is based on vCard 4.0 specification rev 15 (partly).
|
||||
* Note that some of current implementation lack basic capability required in vCard 4.0.
|
||||
* (e.g. PHOTO has data parameter in rev 15 while this implementation requires "ENCODING=b")
|
||||
* </p>
|
||||
*/
|
||||
public class VCardParser_V40 extends VCardParser {
|
||||
/* package */ static final Set<String> sKnownPropertyNameSet =
|
||||
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
||||
"BEGIN", "END", "VERSION",
|
||||
"SOURCE", "KIND", "FN", "N", "NICKNAME",
|
||||
"PHOTO", "BDAY", "ANNIVERSARY", "GENDER", "ADR", "TEL",
|
||||
"EMAIL", "IMPP", "LANG", "TZ", "GEO", "TITLE", "ROLE",
|
||||
"LOGO", "ORG", "MEMBER", "RELATED", "CATEGORIES",
|
||||
"NOTE", "PRODID", "REV", "SOUND", "UID", "CLIENTPIDMAP",
|
||||
"URL", "KEY", "FBURL", "CALENDRURI", "CALURI", "XML")));
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A unmodifiable Set storing the values for the type "ENCODING", available in vCard 4.0.
|
||||
* </p>
|
||||
*/
|
||||
/* package */ static final Set<String> sAcceptableEncoding =
|
||||
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
||||
VCardConstants.PARAM_ENCODING_8BIT,
|
||||
VCardConstants.PARAM_ENCODING_B)));
|
||||
|
||||
private final VCardParserImpl_V40 mVCardParserImpl;
|
||||
|
||||
public VCardParser_V40() {
|
||||
mVCardParserImpl = new VCardParserImpl_V40();
|
||||
}
|
||||
|
||||
public VCardParser_V40(int vcardType) {
|
||||
mVCardParserImpl = new VCardParserImpl_V40(vcardType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterpreter(VCardInterpreter interpreter) {
|
||||
mVCardParserImpl.addInterpreter(interpreter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(InputStream is) throws IOException, VCardException {
|
||||
mVCardParserImpl.parse(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseOne(InputStream is) throws IOException, VCardException {
|
||||
mVCardParserImpl.parseOne(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
mVCardParserImpl.cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
/**
|
||||
* Callback functionality which can be used with
|
||||
* {@link VCardComposer#setPhoneNumberTranslationCallback(VCardPhoneNumberTranslationCallback)}.
|
||||
* See the doc for the method.
|
||||
*
|
||||
* <p>
|
||||
* TODO: this should be more generic
|
||||
* </p>
|
||||
*
|
||||
* @hide This will change
|
||||
*/
|
||||
public interface VCardPhoneNumberTranslationCallback {
|
||||
/**
|
||||
* Called when a phone number is being handled.
|
||||
* @return formatted phone number.
|
||||
*/
|
||||
public String onValueReceived(String rawValue, int type, String label, boolean isPrimary);
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents vCard's property, or logical "one line" of each vCard entry.
|
||||
*
|
||||
* e.g.
|
||||
* Given a vCard below, objects for <code>N:name</code>, <code>TEL:1111111111</code> are
|
||||
* "property".
|
||||
*
|
||||
* <code>
|
||||
* BEGIN:VCARD
|
||||
* N:name
|
||||
* TEL:1111111111
|
||||
* END:VCARD
|
||||
* </code>
|
||||
*
|
||||
* vCard's property has three elements: name, parameter (or param), and value. Name is the name
|
||||
* of each property. Parameter or param is additional information for the property. Value is one
|
||||
* or multiple values representing the parameter.
|
||||
*
|
||||
* e.g.
|
||||
* <code>N;CHARSET=UTF-8:Joe;Due;M.;Mr.;Ph.D.</code>, has "N" for name, "CHALSET=UTF-8" for param,
|
||||
* and "Joe;Due;M.;Mr.;Ph.D." for value.
|
||||
*
|
||||
* Usually params are represented as "key=value" pair, but not always
|
||||
* (The property <code>TEL;WORK;VOICE:(111) 555-1212</code> has two params without key "TYPE",
|
||||
* which are same as "TYPE=WORK" and "TYPE=VOICE". In vCard 3.0, we can even express them as
|
||||
* "TYPE=WORK,VOICE").
|
||||
*
|
||||
* Sometimes (not always) value can be separated by semi-colon. In the example above "Joe;Due;;;"
|
||||
* should be interpreted as five strings: "Joe" (for family name), "Due" (for given name), "M."
|
||||
* (for middle name), "Mr." (for prefix), and "Ph.D." (for suffix). Whether the value is
|
||||
* separable or not is specified by vCard specs.
|
||||
*/
|
||||
public class VCardProperty {
|
||||
private static final String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
private String mName;
|
||||
private List<String> mGroupList;
|
||||
|
||||
private Map<String, Collection<String>> mParameterMap =
|
||||
new HashMap<String, Collection<String>>();
|
||||
private String mRawValue;
|
||||
|
||||
private List<String> mValueList;
|
||||
private byte[] mByteValue;
|
||||
|
||||
public void setName(String name) {
|
||||
if (mName != null) {
|
||||
Log.w(LOG_TAG, String.format("Property name is re-defined " +
|
||||
"(existing: %s, requested: %s", mName, name));
|
||||
}
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public void addGroup(String group) {
|
||||
if (mGroupList == null) {
|
||||
mGroupList = new ArrayList<String>();
|
||||
}
|
||||
mGroupList.add(group);
|
||||
}
|
||||
|
||||
public void setParameter(final String paramName, final String paramValue) {
|
||||
mParameterMap.clear();
|
||||
addParameter(paramName, paramValue);
|
||||
}
|
||||
|
||||
public void addParameter(final String paramName, final String paramValue) {
|
||||
Collection<String> values;
|
||||
if (!mParameterMap.containsKey(paramName)) {
|
||||
if (paramName.equals("TYPE")) {
|
||||
values = new HashSet<String>();
|
||||
} else {
|
||||
values = new ArrayList<String>();
|
||||
}
|
||||
mParameterMap.put(paramName, values);
|
||||
} else {
|
||||
values = mParameterMap.get(paramName);
|
||||
}
|
||||
values.add(paramValue);
|
||||
}
|
||||
|
||||
public void setRawValue(String rawValue) {
|
||||
mRawValue = rawValue;
|
||||
}
|
||||
|
||||
// TODO: would be much better to have translateRawValue() functionality instead of forcing
|
||||
// VCardParserImpl does this job.
|
||||
|
||||
public void setValues(String... propertyValues) {
|
||||
mValueList = Arrays.asList(propertyValues);
|
||||
}
|
||||
|
||||
public void setValues(List<String> propertyValueList) {
|
||||
mValueList = propertyValueList;
|
||||
}
|
||||
|
||||
public void addValues(String... propertyValues) {
|
||||
if (mValueList == null) {
|
||||
mValueList = Arrays.asList(propertyValues);
|
||||
} else {
|
||||
mValueList.addAll(Arrays.asList(propertyValues));
|
||||
}
|
||||
}
|
||||
|
||||
public void addValues(List<String> propertyValueList) {
|
||||
if (mValueList == null) {
|
||||
mValueList = new ArrayList<String>(propertyValueList);
|
||||
} else {
|
||||
mValueList.addAll(propertyValueList);
|
||||
}
|
||||
}
|
||||
|
||||
public void setByteValue(byte[] byteValue) {
|
||||
mByteValue = byteValue;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public List<String> getGroupList() {
|
||||
return mGroupList;
|
||||
}
|
||||
|
||||
public Map<String, Collection<String>> getParameterMap() {
|
||||
return mParameterMap;
|
||||
}
|
||||
|
||||
public Collection<String> getParameters(String type) {
|
||||
return mParameterMap.get(type);
|
||||
}
|
||||
|
||||
public String getRawValue() {
|
||||
return mRawValue;
|
||||
}
|
||||
|
||||
public List<String> getValueList() {
|
||||
return mValueList;
|
||||
}
|
||||
|
||||
public byte[] getByteValue() {
|
||||
return mByteValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The class which tries to detects the source of a vCard file from its contents.
|
||||
* </p>
|
||||
* <p>
|
||||
* The specification of vCard (including both 2.1 and 3.0) is not so strict as to
|
||||
* guess its format just by reading beginning few lines (usually we can, but in
|
||||
* some most pessimistic case, we cannot until at almost the end of the file).
|
||||
* Also we cannot store all vCard entries in memory, while there's no specification
|
||||
* how big the vCard entry would become after the parse.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class is usually used for the "first scan", in which we can understand which vCard
|
||||
* version is used (and how many entries exist in a file).
|
||||
* </p>
|
||||
*/
|
||||
public class VCardSourceDetector implements VCardInterpreter {
|
||||
private static final String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
|
||||
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
|
||||
"X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
|
||||
"X-ABADR", "X-ABUID"));
|
||||
|
||||
private static Set<String> JAPANESE_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
|
||||
"X-GNO", "X-GN", "X-REDUCTION"));
|
||||
|
||||
private static Set<String> WINDOWS_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
|
||||
"X-MICROSOFT-ASST_TEL", "X-MICROSOFT-ASSISTANT", "X-MICROSOFT-OFFICELOC"));
|
||||
|
||||
// Note: these signes appears before the signs of the other type (e.g. "X-GN").
|
||||
// In other words, Japanese FOMA mobile phones are detected as FOMA, not JAPANESE_MOBILE_PHONES.
|
||||
private static Set<String> FOMA_SIGNS = new HashSet<String>(Arrays.asList(
|
||||
"X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
|
||||
"X-SD-DESCRIPTION"));
|
||||
private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
|
||||
|
||||
/**
|
||||
* Represents that no estimation is available. Users of this class is able to this
|
||||
* constant when you don't want to let a vCard parser rely on estimation for parse type.
|
||||
*/
|
||||
public static final int PARSE_TYPE_UNKNOWN = 0;
|
||||
|
||||
// For Apple's software, which does not mean this type is effective for all its products.
|
||||
// We confirmed they usually use UTF-8, but not sure about vCard type.
|
||||
private static final int PARSE_TYPE_APPLE = 1;
|
||||
// For Japanese mobile phones, which are usually using Shift_JIS as a charset.
|
||||
private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
|
||||
// For some of mobile phones released from DoCoMo.
|
||||
private static final int PARSE_TYPE_DOCOMO_FOMA = 3;
|
||||
// For Japanese Windows Mobile phones. It's version is supposed to be 6.5.
|
||||
private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
|
||||
|
||||
private int mParseType = PARSE_TYPE_UNKNOWN;
|
||||
|
||||
private int mVersion = -1; // -1 == unknown
|
||||
|
||||
// Some mobile phones (like FOMA) tells us the charset of the data.
|
||||
private String mSpecifiedCharset;
|
||||
|
||||
@Override
|
||||
public void onVCardStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVCardEnded() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryEnded() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyCreated(VCardProperty property) {
|
||||
final String propertyName = property.getName();
|
||||
final List<String> valueList = property.getValueList();
|
||||
|
||||
if (propertyName.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)
|
||||
&& valueList.size() > 0) {
|
||||
final String versionString = valueList.get(0);
|
||||
if (versionString.equals(VCardConstants.VERSION_V21)) {
|
||||
mVersion = VCardConfig.VERSION_21;
|
||||
} else if (versionString.equals(VCardConstants.VERSION_V30)) {
|
||||
mVersion = VCardConfig.VERSION_30;
|
||||
} else if (versionString.equals(VCardConstants.VERSION_V40)) {
|
||||
mVersion = VCardConfig.VERSION_40;
|
||||
} else {
|
||||
Log.w(LOG_TAG, "Invalid version string: " + versionString);
|
||||
}
|
||||
} else if (propertyName.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
|
||||
mParseType = PARSE_TYPE_DOCOMO_FOMA;
|
||||
if (valueList.size() > 0) {
|
||||
mSpecifiedCharset = valueList.get(0);
|
||||
}
|
||||
}
|
||||
if (mParseType != PARSE_TYPE_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(propertyName)) {
|
||||
mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
|
||||
} else if (FOMA_SIGNS.contains(propertyName)) {
|
||||
mParseType = PARSE_TYPE_DOCOMO_FOMA;
|
||||
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(propertyName)) {
|
||||
mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
|
||||
} else if (APPLE_SIGNS.contains(propertyName)) {
|
||||
mParseType = PARSE_TYPE_APPLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The available type can be used with vCard parser. You probably need to
|
||||
* use {{@link #getEstimatedCharset()} to understand the charset to be used.
|
||||
*/
|
||||
public int getEstimatedType() {
|
||||
switch (mParseType) {
|
||||
case PARSE_TYPE_DOCOMO_FOMA:
|
||||
return VCardConfig.VCARD_TYPE_DOCOMO;
|
||||
case PARSE_TYPE_MOBILE_PHONE_JP:
|
||||
return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
|
||||
case PARSE_TYPE_APPLE:
|
||||
case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
|
||||
default: {
|
||||
if (mVersion == VCardConfig.VERSION_21) {
|
||||
return VCardConfig.VCARD_TYPE_V21_GENERIC;
|
||||
} else if (mVersion == VCardConfig.VERSION_30) {
|
||||
return VCardConfig.VCARD_TYPE_V30_GENERIC;
|
||||
} else if (mVersion == VCardConfig.VERSION_40) {
|
||||
return VCardConfig.VCARD_TYPE_V40_GENERIC;
|
||||
} else {
|
||||
return VCardConfig.VCARD_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns charset String guessed from the source's properties.
|
||||
* This method must be called after parsing target file(s).
|
||||
* </p>
|
||||
* @return Charset String. Null is returned if guessing the source fails.
|
||||
*/
|
||||
public String getEstimatedCharset() {
|
||||
if (TextUtils.isEmpty(mSpecifiedCharset)) {
|
||||
return mSpecifiedCharset;
|
||||
}
|
||||
switch (mParseType) {
|
||||
case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
|
||||
case PARSE_TYPE_DOCOMO_FOMA:
|
||||
case PARSE_TYPE_MOBILE_PHONE_JP:
|
||||
return "SHIFT_JIS";
|
||||
case PARSE_TYPE_APPLE:
|
||||
return "UTF-8";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,855 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard;
|
||||
|
||||
import android.provider.ContactsContract.CommonDataKinds.Im;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.vcard.exception.VCardException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utilities for VCard handling codes.
|
||||
*/
|
||||
public class VCardUtils {
|
||||
private static final String LOG_TAG = VCardConstants.LOG_TAG;
|
||||
|
||||
/**
|
||||
* See org.apache.commons.codec.DecoderException
|
||||
*/
|
||||
private static class DecoderException extends Exception {
|
||||
public DecoderException(String pMessage) {
|
||||
super(pMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See org.apache.commons.codec.net.QuotedPrintableCodec
|
||||
*/
|
||||
private static class QuotedPrintableCodecPort {
|
||||
private static byte ESCAPE_CHAR = '=';
|
||||
public static final byte[] decodeQuotedPrintable(byte[] bytes)
|
||||
throws DecoderException {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
int b = bytes[i];
|
||||
if (b == ESCAPE_CHAR) {
|
||||
try {
|
||||
int u = Character.digit((char) bytes[++i], 16);
|
||||
int l = Character.digit((char) bytes[++i], 16);
|
||||
if (u == -1 || l == -1) {
|
||||
throw new DecoderException("Invalid quoted-printable encoding");
|
||||
}
|
||||
buffer.write((char) ((u << 4) + l));
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new DecoderException("Invalid quoted-printable encoding");
|
||||
}
|
||||
} else {
|
||||
buffer.write(b);
|
||||
}
|
||||
}
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ported methods which are hidden in {@link PhoneNumberUtils}.
|
||||
*/
|
||||
public static class PhoneNumberUtilsPort {
|
||||
public static String formatNumber(String source, int defaultFormattingType) {
|
||||
final SpannableStringBuilder text = new SpannableStringBuilder(source);
|
||||
PhoneNumberUtils.formatNumber(text, defaultFormattingType);
|
||||
return text.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ported methods which are hidden in {@link TextUtils}.
|
||||
*/
|
||||
public static class TextUtilsPort {
|
||||
public static boolean isPrintableAscii(final char c) {
|
||||
final int asciiFirst = 0x20;
|
||||
final int asciiLast = 0x7E; // included
|
||||
return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
public static boolean isPrintableAsciiOnly(final CharSequence str) {
|
||||
final int len = str.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!isPrintableAscii(str.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
|
||||
// converted to two parameter Strings. These only contain some minor fields valid in both
|
||||
// vCard and current (as of 2009-08-07) Contacts structure.
|
||||
private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
|
||||
private static final Set<String> sPhoneTypesUnknownToContactsSet;
|
||||
private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
|
||||
private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
|
||||
private static final Set<String> sMobilePhoneLabelSet;
|
||||
|
||||
static {
|
||||
sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
|
||||
sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
|
||||
|
||||
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
|
||||
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
|
||||
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
|
||||
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
|
||||
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
|
||||
Phone.TYPE_CALLBACK);
|
||||
sKnownPhoneTypeMap_StoI.put(
|
||||
VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
|
||||
Phone.TYPE_TTY_TDD);
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
|
||||
Phone.TYPE_ASSISTANT);
|
||||
// OTHER (default in Android) should correspond to VOICE (default in vCard).
|
||||
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_VOICE, Phone.TYPE_OTHER);
|
||||
|
||||
sPhoneTypesUnknownToContactsSet = new HashSet<String>();
|
||||
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
|
||||
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
|
||||
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
|
||||
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
|
||||
|
||||
sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
|
||||
VCardConstants.PROPERTY_X_GOOGLE_TALK);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
|
||||
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
|
||||
|
||||
// \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
|
||||
// \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
|
||||
// \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
|
||||
// \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
|
||||
sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
|
||||
"MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
|
||||
"\uFF79\uFF72\uFF80\uFF72"));
|
||||
}
|
||||
|
||||
public static String getPhoneTypeString(Integer type) {
|
||||
return sKnownPhoneTypesMap_ItoS.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Interger when the given types can be parsed as known type. Returns String object
|
||||
* when not, which should be set to label.
|
||||
*/
|
||||
public static Object getPhoneTypeFromStrings(Collection<String> types,
|
||||
String number) {
|
||||
if (number == null) {
|
||||
number = "";
|
||||
}
|
||||
int type = -1;
|
||||
String label = null;
|
||||
boolean isFax = false;
|
||||
boolean hasPref = false;
|
||||
|
||||
if (types != null) {
|
||||
for (final String typeStringOrg : types) {
|
||||
if (typeStringOrg == null) {
|
||||
continue;
|
||||
}
|
||||
final String typeStringUpperCase = typeStringOrg.toUpperCase();
|
||||
if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) {
|
||||
hasPref = true;
|
||||
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_FAX)) {
|
||||
isFax = true;
|
||||
} else {
|
||||
final String labelCandidate;
|
||||
if (typeStringUpperCase.startsWith("X-") && type < 0) {
|
||||
labelCandidate = typeStringOrg.substring(2);
|
||||
} else {
|
||||
labelCandidate = typeStringOrg;
|
||||
}
|
||||
if (labelCandidate.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
// e.g. "home" -> TYPE_HOME
|
||||
final Integer tmp = sKnownPhoneTypeMap_StoI.get(labelCandidate.toUpperCase());
|
||||
if (tmp != null) {
|
||||
final int typeCandidate = tmp;
|
||||
// 1. If a type isn't specified yet, we'll choose the new type candidate.
|
||||
// 2. If the current type is default one (OTHER) or custom one, we'll
|
||||
// prefer more specific types specified in the vCard. Note that OTHER and
|
||||
// the other different types may appear simultaneously here, since vCard
|
||||
// allow to have VOICE and HOME/WORK in one line.
|
||||
// e.g. "TEL;WORK;VOICE:1" -> WORK + OTHER -> Type should be WORK
|
||||
// 3. TYPE_PAGER is prefered when the number contains @ surronded by
|
||||
// a pager number and a domain name.
|
||||
// e.g.
|
||||
// o 1111@domain.com
|
||||
// x @domain.com
|
||||
// x 1111@
|
||||
final int indexOfAt = number.indexOf("@");
|
||||
if ((typeCandidate == Phone.TYPE_PAGER
|
||||
&& 0 < indexOfAt && indexOfAt < number.length() - 1)
|
||||
|| type < 0
|
||||
|| type == Phone.TYPE_CUSTOM
|
||||
|| type == Phone.TYPE_OTHER) {
|
||||
type = tmp;
|
||||
}
|
||||
} else if (type < 0) {
|
||||
type = Phone.TYPE_CUSTOM;
|
||||
label = labelCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type < 0) {
|
||||
if (hasPref) {
|
||||
type = Phone.TYPE_MAIN;
|
||||
} else {
|
||||
// default to TYPE_OTHER
|
||||
type = Phone.TYPE_OTHER;
|
||||
}
|
||||
}
|
||||
if (isFax) {
|
||||
if (type == Phone.TYPE_HOME) {
|
||||
type = Phone.TYPE_FAX_HOME;
|
||||
} else if (type == Phone.TYPE_WORK) {
|
||||
type = Phone.TYPE_FAX_WORK;
|
||||
} else if (type == Phone.TYPE_OTHER) {
|
||||
type = Phone.TYPE_OTHER_FAX;
|
||||
}
|
||||
}
|
||||
if (type == Phone.TYPE_CUSTOM) {
|
||||
return label;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static boolean isMobilePhoneLabel(final String label) {
|
||||
// For backward compatibility.
|
||||
// Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
|
||||
// To support mobile type at that time, this custom label had been used.
|
||||
return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
|
||||
}
|
||||
|
||||
public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
|
||||
return sPhoneTypesUnknownToContactsSet.contains(label);
|
||||
}
|
||||
|
||||
public static String getPropertyNameForIm(final int protocol) {
|
||||
return sKnownImPropNameMap_ItoS.get(protocol);
|
||||
}
|
||||
|
||||
public static String[] sortNameElements(final int nameOrder,
|
||||
final String familyName, final String middleName, final String givenName) {
|
||||
final String[] list = new String[3];
|
||||
final int nameOrderType = VCardConfig.getNameOrderType(nameOrder);
|
||||
switch (nameOrderType) {
|
||||
case VCardConfig.NAME_ORDER_JAPANESE: {
|
||||
if (containsOnlyPrintableAscii(familyName) &&
|
||||
containsOnlyPrintableAscii(givenName)) {
|
||||
list[0] = givenName;
|
||||
list[1] = middleName;
|
||||
list[2] = familyName;
|
||||
} else {
|
||||
list[0] = familyName;
|
||||
list[1] = middleName;
|
||||
list[2] = givenName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VCardConfig.NAME_ORDER_EUROPE: {
|
||||
list[0] = middleName;
|
||||
list[1] = givenName;
|
||||
list[2] = familyName;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
list[0] = givenName;
|
||||
list[1] = middleName;
|
||||
list[2] = familyName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static int getPhoneNumberFormat(final int vcardType) {
|
||||
if (VCardConfig.isJapaneseDevice(vcardType)) {
|
||||
return PhoneNumberUtils.FORMAT_JAPAN;
|
||||
} else {
|
||||
return PhoneNumberUtils.FORMAT_NANP;
|
||||
}
|
||||
}
|
||||
|
||||
public static String constructNameFromElements(final int nameOrder,
|
||||
final String familyName, final String middleName, final String givenName) {
|
||||
return constructNameFromElements(nameOrder, familyName, middleName, givenName,
|
||||
null, null);
|
||||
}
|
||||
|
||||
public static String constructNameFromElements(final int nameOrder,
|
||||
final String familyName, final String middleName, final String givenName,
|
||||
final String prefix, final String suffix) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final String[] nameList = sortNameElements(nameOrder, familyName, middleName, givenName);
|
||||
boolean first = true;
|
||||
if (!TextUtils.isEmpty(prefix)) {
|
||||
first = false;
|
||||
builder.append(prefix);
|
||||
}
|
||||
for (final String namePart : nameList) {
|
||||
if (!TextUtils.isEmpty(namePart)) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
builder.append(' ');
|
||||
}
|
||||
builder.append(namePart);
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(suffix)) {
|
||||
if (!first) {
|
||||
builder.append(' ');
|
||||
}
|
||||
builder.append(suffix);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the given value into pieces using the delimiter ';' inside it.
|
||||
*
|
||||
* Escaped characters in those values are automatically unescaped into original form.
|
||||
*/
|
||||
public static List<String> constructListFromValue(final String value,
|
||||
final int vcardType) {
|
||||
final List<String> list = new ArrayList<String>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
final int length = value.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = value.charAt(i);
|
||||
if (ch == '\\' && i < length - 1) {
|
||||
char nextCh = value.charAt(i + 1);
|
||||
final String unescapedString;
|
||||
if (VCardConfig.isVersion40(vcardType)) {
|
||||
unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh);
|
||||
} else if (VCardConfig.isVersion30(vcardType)) {
|
||||
unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh);
|
||||
} else {
|
||||
if (!VCardConfig.isVersion21(vcardType)) {
|
||||
// Unknown vCard type
|
||||
Log.w(LOG_TAG, "Unknown vCard type");
|
||||
}
|
||||
unescapedString = VCardParserImpl_V21.unescapeCharacter(nextCh);
|
||||
}
|
||||
|
||||
if (unescapedString != null) {
|
||||
builder.append(unescapedString);
|
||||
i++;
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
} else if (ch == ';') {
|
||||
list.add(builder.toString());
|
||||
builder = new StringBuilder();
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
list.add(builder.toString());
|
||||
return list;
|
||||
}
|
||||
|
||||
public static boolean containsOnlyPrintableAscii(final String...values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
return containsOnlyPrintableAscii(Arrays.asList(values));
|
||||
}
|
||||
|
||||
public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
for (final String value : values) {
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
if (!TextUtilsPort.isPrintableAsciiOnly(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This is useful when checking the string should be encoded into quoted-printable
|
||||
* or not, which is required by vCard 2.1.
|
||||
* </p>
|
||||
* <p>
|
||||
* See the definition of "7bit" in vCard 2.1 spec for more information.
|
||||
* </p>
|
||||
*/
|
||||
public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
|
||||
}
|
||||
|
||||
public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
final int asciiFirst = 0x20;
|
||||
final int asciiLast = 0x7E; // included
|
||||
for (final String value : values) {
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
final int length = value.length();
|
||||
for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
|
||||
final int c = value.codePointAt(i);
|
||||
if (!(asciiFirst <= c && c <= asciiLast)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
|
||||
new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
|
||||
* should contain only alphabets, digits, and hyphen.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note: It is already known some devices (wrongly) outputs properties with characters
|
||||
* which should not be in the field. One example is "X-GOOGLE TALK". We accept
|
||||
* such kind of input but must never output it unless the target is very specific
|
||||
* to the device which is able to parse the malformed input.
|
||||
* </p>
|
||||
*/
|
||||
public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
|
||||
}
|
||||
|
||||
public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
final int upperAlphabetFirst = 0x41; // A
|
||||
final int upperAlphabetAfterLast = 0x5b; // [
|
||||
final int lowerAlphabetFirst = 0x61; // a
|
||||
final int lowerAlphabetAfterLast = 0x7b; // {
|
||||
final int digitFirst = 0x30; // 0
|
||||
final int digitAfterLast = 0x3A; // :
|
||||
final int hyphen = '-';
|
||||
for (final String str : values) {
|
||||
if (TextUtils.isEmpty(str)) {
|
||||
continue;
|
||||
}
|
||||
final int length = str.length();
|
||||
for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
|
||||
int codepoint = str.codePointAt(i);
|
||||
if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
|
||||
(upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
|
||||
(digitFirst <= codepoint && codepoint < digitAfterLast) ||
|
||||
(codepoint == hyphen))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean containsOnlyWhiteSpaces(final String...values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
return containsOnlyWhiteSpaces(Arrays.asList(values));
|
||||
}
|
||||
|
||||
public static boolean containsOnlyWhiteSpaces(final Collection<String> values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
for (final String str : values) {
|
||||
if (TextUtils.isEmpty(str)) {
|
||||
continue;
|
||||
}
|
||||
final int length = str.length();
|
||||
for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
|
||||
if (!Character.isWhitespace(str.codePointAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
|
||||
* </p>
|
||||
* <p>
|
||||
* vCard 2.1 specifies:<br />
|
||||
* word = <any printable 7bit us-ascii except []=:., >
|
||||
* </p>
|
||||
*/
|
||||
public static boolean isV21Word(final String value) {
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
final int asciiFirst = 0x20;
|
||||
final int asciiLast = 0x7E; // included
|
||||
final int length = value.length();
|
||||
for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
|
||||
final int c = value.codePointAt(i);
|
||||
if (!(asciiFirst <= c && c <= asciiLast) ||
|
||||
sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final int[] sEscapeIndicatorsV30 = new int[]{
|
||||
':', ';', ',', ' '
|
||||
};
|
||||
|
||||
private static final int[] sEscapeIndicatorsV40 = new int[]{
|
||||
';', ':'
|
||||
};
|
||||
|
||||
/**
|
||||
* <P>
|
||||
* Returns String available as parameter value in vCard 3.0.
|
||||
* </P>
|
||||
* <P>
|
||||
* RFC 2426 requires vCard composer to quote parameter values when it contains
|
||||
* semi-colon, for example (See RFC 2426 for more information).
|
||||
* This method checks whether the given String can be used without quotes.
|
||||
* </P>
|
||||
* <P>
|
||||
* Note: We remove DQUOTE inside the given value silently for now.
|
||||
* </P>
|
||||
*/
|
||||
public static String toStringAsV30ParamValue(String value) {
|
||||
return toStringAsParamValue(value, sEscapeIndicatorsV30);
|
||||
}
|
||||
|
||||
public static String toStringAsV40ParamValue(String value) {
|
||||
return toStringAsParamValue(value, sEscapeIndicatorsV40);
|
||||
}
|
||||
|
||||
private static String toStringAsParamValue(String value, final int[] escapeIndicators) {
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
value = "";
|
||||
}
|
||||
final int asciiFirst = 0x20;
|
||||
final int asciiLast = 0x7E; // included
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final int length = value.length();
|
||||
boolean needQuote = false;
|
||||
for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
|
||||
final int codePoint = value.codePointAt(i);
|
||||
if (codePoint < asciiFirst || codePoint == '"') {
|
||||
// CTL characters and DQUOTE are never accepted. Remove them.
|
||||
continue;
|
||||
}
|
||||
builder.appendCodePoint(codePoint);
|
||||
for (int indicator : escapeIndicators) {
|
||||
if (codePoint == indicator) {
|
||||
needQuote = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String result = builder.toString();
|
||||
return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result))
|
||||
? ""
|
||||
: (needQuote ? ('"' + result + '"')
|
||||
: result));
|
||||
}
|
||||
|
||||
public static String toHalfWidthString(final String orgString) {
|
||||
if (TextUtils.isEmpty(orgString)) {
|
||||
return null;
|
||||
}
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final int length = orgString.length();
|
||||
for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
|
||||
// All Japanese character is able to be expressed by char.
|
||||
// Do not need to use String#codepPointAt().
|
||||
final char ch = orgString.charAt(i);
|
||||
final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
|
||||
if (halfWidthText != null) {
|
||||
builder.append(halfWidthText);
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses the format of input image. Currently just the first few bytes are used.
|
||||
* The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
|
||||
* the guess failed.
|
||||
* @param input Image as byte array.
|
||||
* @return The image type or null when the type cannot be determined.
|
||||
*/
|
||||
public static String guessImageType(final byte[] input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
|
||||
return "GIF";
|
||||
} else if (input.length >= 4 && input[0] == (byte) 0x89
|
||||
&& input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
|
||||
// Note: vCard 2.1 officially does not support PNG, but we may have it and
|
||||
// using X- word like "X-PNG" may not let importers know it is PNG.
|
||||
// So we use the String "PNG" as is...
|
||||
return "PNG";
|
||||
} else if (input.length >= 2 && input[0] == (byte) 0xff
|
||||
&& input[1] == (byte) 0xd8) {
|
||||
return "JPEG";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True when all the given values are null or empty Strings.
|
||||
*/
|
||||
public static boolean areAllEmpty(final String...values) {
|
||||
if (values == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (final String value : values) {
|
||||
if (!TextUtils.isEmpty(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a string looks like it could be an android generated quoted printable.
|
||||
*
|
||||
* Identification of quoted printable is not 100% reliable since it's just ascii. But given
|
||||
* the high number and exact location of generated = signs, there is a high likely-hood that
|
||||
* it would be.
|
||||
*
|
||||
* @return True if it appears like android quoted printable. False otherwise.
|
||||
*/
|
||||
public static boolean appearsLikeAndroidVCardQuotedPrintable(String value) {
|
||||
|
||||
// Quoted printable is always in multiple of 3s. With optional 1 '=' at end.
|
||||
final int remainder = (value.length() % 3);
|
||||
if (value.length() < 2 || (remainder != 1 && remainder != 0)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i += 3) {
|
||||
if (value.charAt(i) != '=') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//// The methods bellow may be used by unit test.
|
||||
|
||||
/**
|
||||
* Unquotes given Quoted-Printable value. value must not be null.
|
||||
*/
|
||||
public static String parseQuotedPrintable(
|
||||
final String value, boolean strictLineBreaking,
|
||||
String sourceCharset, String targetCharset) {
|
||||
// "= " -> " ", "=\t" -> "\t".
|
||||
// Previous code had done this replacement. Keep on the safe side.
|
||||
final String quotedPrintable;
|
||||
{
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final int length = value.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = value.charAt(i);
|
||||
if (ch == '=' && i < length - 1) {
|
||||
char nextCh = value.charAt(i + 1);
|
||||
if (nextCh == ' ' || nextCh == '\t') {
|
||||
builder.append(nextCh);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
builder.append(ch);
|
||||
}
|
||||
quotedPrintable = builder.toString();
|
||||
}
|
||||
|
||||
String[] lines;
|
||||
if (strictLineBreaking) {
|
||||
lines = quotedPrintable.split("\r\n");
|
||||
} else {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
final int length = quotedPrintable.length();
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = quotedPrintable.charAt(i);
|
||||
if (ch == '\n') {
|
||||
list.add(builder.toString());
|
||||
builder = new StringBuilder();
|
||||
} else if (ch == '\r') {
|
||||
list.add(builder.toString());
|
||||
builder = new StringBuilder();
|
||||
if (i < length - 1) {
|
||||
char nextCh = quotedPrintable.charAt(i + 1);
|
||||
if (nextCh == '\n') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
final String lastLine = builder.toString();
|
||||
if (lastLine.length() > 0) {
|
||||
list.add(lastLine);
|
||||
}
|
||||
lines = list.toArray(new String[0]);
|
||||
}
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
if (line.endsWith("=")) {
|
||||
line = line.substring(0, line.length() - 1);
|
||||
}
|
||||
builder.append(line);
|
||||
}
|
||||
|
||||
final String rawString = builder.toString();
|
||||
if (TextUtils.isEmpty(rawString)) {
|
||||
Log.w(LOG_TAG, "Given raw string is empty.");
|
||||
}
|
||||
|
||||
byte[] rawBytes = null;
|
||||
try {
|
||||
rawBytes = rawString.getBytes(sourceCharset);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
|
||||
rawBytes = rawString.getBytes();
|
||||
}
|
||||
|
||||
byte[] decodedBytes = null;
|
||||
try {
|
||||
decodedBytes = QuotedPrintableCodecPort.decodeQuotedPrintable(rawBytes);
|
||||
} catch (DecoderException e) {
|
||||
Log.e(LOG_TAG, "DecoderException is thrown.");
|
||||
decodedBytes = rawBytes;
|
||||
}
|
||||
|
||||
try {
|
||||
return new String(decodedBytes, targetCharset);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
|
||||
return new String(decodedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static final VCardParser getAppropriateParser(int vcardType)
|
||||
throws VCardException {
|
||||
if (VCardConfig.isVersion21(vcardType)) {
|
||||
return new VCardParser_V21();
|
||||
} else if (VCardConfig.isVersion30(vcardType)) {
|
||||
return new VCardParser_V30();
|
||||
} else if (VCardConfig.isVersion40(vcardType)) {
|
||||
return new VCardParser_V40();
|
||||
} else {
|
||||
throw new VCardException("Version is not specified");
|
||||
}
|
||||
}
|
||||
|
||||
public static final String convertStringCharset(
|
||||
String originalString, String sourceCharset, String targetCharset) {
|
||||
if (sourceCharset.equalsIgnoreCase(targetCharset)) {
|
||||
return originalString;
|
||||
}
|
||||
final Charset charset = Charset.forName(sourceCharset);
|
||||
final ByteBuffer byteBuffer = charset.encode(originalString);
|
||||
// byteBuffer.array() "may" return byte array which is larger than
|
||||
// byteBuffer.remaining(). Here, we keep on the safe side.
|
||||
final byte[] bytes = new byte[byteBuffer.remaining()];
|
||||
byteBuffer.get(bytes);
|
||||
try {
|
||||
return new String(bytes, targetCharset);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean
|
||||
|
||||
private VCardUtils() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard.exception;
|
||||
|
||||
public class VCardAgentNotSupportedException extends VCardNotSupportedException {
|
||||
public VCardAgentNotSupportedException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VCardAgentNotSupportedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard.exception;
|
||||
|
||||
public class VCardException extends java.lang.Exception {
|
||||
/**
|
||||
* Constructs a VCardException object
|
||||
*/
|
||||
public VCardException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a VCardException object
|
||||
*
|
||||
* @param message the error message
|
||||
*/
|
||||
public VCardException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.vcard.exception;
|
||||
|
||||
/**
|
||||
* Thrown when the vCard has some line starting with '#'. In the specification,
|
||||
* both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
|
||||
* such lines.
|
||||
*/
|
||||
public class VCardInvalidCommentLineException extends VCardInvalidLineException {
|
||||
public VCardInvalidCommentLineException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VCardInvalidCommentLineException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard.exception;
|
||||
|
||||
/**
|
||||
* Thrown when the vCard has some line starting with '#'. In the specification,
|
||||
* both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
|
||||
* such lines.
|
||||
*/
|
||||
public class VCardInvalidLineException extends VCardException {
|
||||
public VCardInvalidLineException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VCardInvalidLineException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.vcard.exception;
|
||||
|
||||
/**
|
||||
* VCardException thrown when VCard is nested without VCardParser's being notified.
|
||||
*/
|
||||
public class VCardNestedException extends VCardNotSupportedException {
|
||||
public VCardNestedException() {
|
||||
super();
|
||||
}
|
||||
public VCardNestedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard.exception;
|
||||
|
||||
/**
|
||||
* The exception which tells that the input VCard is probably valid from the view of
|
||||
* specification but not supported in the current framework for now.
|
||||
*
|
||||
* This is a kind of a good news from the view of development.
|
||||
* It may be good to ask users to send a report with the VCard example
|
||||
* for the future development.
|
||||
*/
|
||||
public class VCardNotSupportedException extends VCardException {
|
||||
public VCardNotSupportedException() {
|
||||
super();
|
||||
}
|
||||
public VCardNotSupportedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.vcard.exception;
|
||||
|
||||
/**
|
||||
* VCardException used only when the version of the vCard is different.
|
||||
*/
|
||||
public class VCardVersionException extends VCardException {
|
||||
public VCardVersionException() {
|
||||
super();
|
||||
}
|
||||
public VCardVersionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user