Make vCard code a separated static library.

- Move the library to a separate directory in framewokr/base, and rename its package from
  android.pim.vcard to com.android.vcard.
- Move all tests for the library under the directory.
- Confirm all tests for vCard are successful.

It would be better for us to have this directory somewhere else (like external/).
But I'll submit this here now and move it to the right place as soon as possible.
From the view of build mechanism, we can do that immediately.

BUG: 2689523
Change-Id: I435e10571b7160bfcc029bed7c37aaac1c6fd69a
This commit is contained in:
Daisuke Miyakawa 2010-05-14 11:17:46 -07:00 committed by Alex Ray
parent 9b0be73f2a
commit e6cbafd07d
65 changed files with 13491 additions and 0 deletions

28
vcard/Android.mk Normal file
View file

@ -0,0 +1,28 @@
# 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := com.android.vcard
LOCAL_SRC_FILES := $(call all-java-files-under, java)
# Use google-common instead of android-common for using hidden code in telephony library.
# Use ext for using Quoted-Printable codec.
LOCAL_JAVA_LIBRARIES := google-common ext
include $(BUILD_STATIC_JAVA_LIBRARY)
# Build the test package.
include $(call all-makefiles-under, $(LOCAL_PATH))

View file

@ -0,0 +1,379 @@
/*
* 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.HashMap;
import java.util.Map;
/**
* TextUtils especially for Japanese.
*/
/* package */ class JapaneseUtils {
static private 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

View file

@ -0,0 +1,677 @@
/*
* 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.EntityIterator;
import android.content.Entity.NamedContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
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.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.text.TextUtils;
import android.util.CharsetUtils;
import android.util.Log;
import com.android.vcard.exception.VCardException;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.UnsupportedCharsetException;
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";
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";
public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
// 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";
/**
* Special URI for testing.
*/
public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
public static final Uri VCARD_TEST_AUTHORITY_URI =
Uri.parse("content://" + VCARD_TEST_AUTHORITY);
public static final Uri CONTACTS_TEST_CONTENT_URI =
Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");
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.
}
public static interface OneEntryHandler {
public boolean onInit(Context context);
public boolean onEntryCreated(String vcard);
public void onTerminate();
}
/**
* <p>
* An useful handler for emitting vCard String to an OutputStream object one by one.
* </p>
* <p>
* The input OutputStream object is closed() on {@link #onTerminate()}.
* Must not close the stream outside this class.
* </p>
*/
public final class HandlerForOutputStream implements OneEntryHandler {
@SuppressWarnings("hiding")
private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
private boolean mOnTerminateIsCalled = false;
private final OutputStream mOutputStream; // mWriter will close this.
private Writer mWriter;
/**
* Input stream will be closed on the detruction of this object.
*/
public HandlerForOutputStream(final OutputStream outputStream) {
mOutputStream = outputStream;
}
public boolean onInit(final Context context) {
try {
mWriter = new BufferedWriter(new OutputStreamWriter(
mOutputStream, mCharset));
} catch (UnsupportedEncodingException e1) {
Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
mErrorReason = "Encoding is not supported (usually this does not happen!): "
+ mCharset;
return false;
}
if (mIsDoCoMo) {
try {
// Create one empty entry.
mWriter.write(createOneEntryInternal("-1", null));
} catch (VCardException e) {
Log.e(LOG_TAG, "VCardException has been thrown during on Init(): " +
e.getMessage());
return false;
} catch (IOException e) {
Log.e(LOG_TAG,
"IOException occurred during exportOneContactData: "
+ e.getMessage());
mErrorReason = "IOException occurred: " + e.getMessage();
return false;
}
}
return true;
}
public boolean onEntryCreated(String vcard) {
try {
mWriter.write(vcard);
} catch (IOException e) {
Log.e(LOG_TAG,
"IOException occurred during exportOneContactData: "
+ e.getMessage());
mErrorReason = "IOException occurred: " + e.getMessage();
return false;
}
return true;
}
public void onTerminate() {
mOnTerminateIsCalled = true;
if (mWriter != null) {
try {
// Flush and sync the data so that a user is able to pull
// the SDCard just after
// the export.
mWriter.flush();
if (mOutputStream != null
&& mOutputStream instanceof FileOutputStream) {
((FileOutputStream) mOutputStream).getFD().sync();
}
} catch (IOException e) {
Log.d(LOG_TAG,
"IOException during closing the output stream: "
+ e.getMessage());
} finally {
closeOutputStream();
}
}
}
public void closeOutputStream() {
try {
mWriter.close();
} catch (IOException e) {
Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
}
}
@Override
public void finalize() {
if (!mOnTerminateIsCalled) {
onTerminate();
}
}
}
private final Context mContext;
private final int mVCardType;
private final boolean mCareHandlerErrors;
private final ContentResolver mContentResolver;
private final boolean mIsDoCoMo;
private Cursor mCursor;
private int mIdColumn;
private final String mCharset;
private boolean mTerminateIsCalled;
private final List<OneEntryHandler> mHandlerList;
private String mErrorReason = NO_ERROR;
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);
}
/**
* Construct 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
* a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
* If false, this ignores those errors.
*/
public VCardComposer(final Context context, final int vcardType, String charset,
final boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
mCareHandlerErrors = careHandlerErrors;
mContentResolver = context.getContentResolver();
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
final boolean shouldAppendCharsetParam = !(
VCardConfig.isV30(vcardType) && UTF_8.equalsIgnoreCase(charset));
if (mIsDoCoMo || shouldAppendCharsetParam) {
if (SHIFT_JIS.equalsIgnoreCase(charset)) {
if (mIsDoCoMo) {
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG,
"DoCoMo-specific SHIFT_JIS was not found. "
+ "Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
} else {
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG,
"Career-specific SHIFT_JIS was not found. "
+ "Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
}
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 {
try {
charset = CharsetUtils.charsetForVendor(charset).name();
} catch (UnsupportedCharsetException e) {
Log.i(LOG_TAG,
"Career-specific \"" + charset + "\" was not found (as usual). "
+ "Use it as is.");
}
mCharset = charset;
}
}
} else {
if (TextUtils.isEmpty(charset)) {
mCharset = UTF_8;
} else {
try {
charset = CharsetUtils.charsetForVendor(charset).name();
} catch (UnsupportedCharsetException e) {
Log.i(LOG_TAG,
"Career-specific \"" + charset + "\" was not found (as usual). "
+ "Use it as is.");
}
mCharset = charset;
}
}
Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
}
/**
* Must be called before {@link #init()}.
*/
public void addHandler(OneEntryHandler handler) {
if (handler != null) {
mHandlerList.add(handler);
}
}
/**
* @return Returns true when initialization is successful and all the other
* methods are available. Returns false otherwise.
*/
public boolean init() {
return init(null, null);
}
public boolean init(final String selection, final String[] selectionArgs) {
return init(Contacts.CONTENT_URI, selection, selectionArgs, 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) {
if (contentUri == null) {
return false;
}
if (mCareHandlerErrors) {
final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
for (OneEntryHandler handler : mHandlerList) {
if (!handler.onInit(mContext)) {
for (OneEntryHandler finished : finishedList) {
finished.onTerminate();
}
return false;
}
}
} else {
// Just ignore the false returned from onInit().
for (OneEntryHandler handler : mHandlerList) {
handler.onInit(mContext);
}
}
final String[] projection;
if (Contacts.CONTENT_URI.equals(contentUri) ||
CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
projection = sContactsProjection;
} else {
mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
return false;
}
mCursor = mContentResolver.query(
contentUri, projection, selection, selectionArgs, sortOrder);
if (mCursor == null) {
mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
return false;
}
if (getCount() == 0 || !mCursor.moveToFirst()) {
try {
mCursor.close();
} catch (SQLiteException e) {
Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
} finally {
mCursor = null;
mErrorReason = FAILURE_REASON_NO_ENTRY;
}
return false;
}
mIdColumn = mCursor.getColumnIndex(Contacts._ID);
return true;
}
public boolean createOneEntry() {
return createOneEntry(null);
}
/**
* @param getEntityIteratorMethod For Dependency Injection.
* @hide just for testing.
*/
public boolean createOneEntry(Method getEntityIteratorMethod) {
if (mCursor == null || mCursor.isAfterLast()) {
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
}
final String vcard;
try {
if (mIdColumn >= 0) {
vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
getEntityIteratorMethod);
} else {
Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
return true;
}
} catch (VCardException e) {
Log.e(LOG_TAG, "VCardException has been thrown: " + e.getMessage());
return false;
} catch (OutOfMemoryError error) {
// Maybe some data (e.g. photo) is too big to have in memory. But it
// should be rare.
Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry.");
System.gc();
// TODO: should tell users what happened?
return true;
} finally {
mCursor.moveToNext();
}
// This function does not care the OutOfMemoryError on the handler side :-P
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
for (OneEntryHandler handler : mHandlerList) {
if (!handler.onEntryCreated(vcard)) {
return false;
}
}
} else {
for (OneEntryHandler handler : mHandlerList) {
handler.onEntryCreated(vcard);
}
}
return true;
}
private String createOneEntryInternal(final String contactId,
final Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
// The resolver may return the entity iterator with no data. It is possible.
// e.g. If all the data in the contact of the given contact id are not exportable ones,
// they are hidden from the view of this method, though contact id itself exists.
EntityIterator entityIterator = null;
try {
final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
// .appendQueryParameter("for_export_only", "1")
.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
.build();
final String selection = Data.CONTACT_ID + "=?";
final String[] selectionArgs = new String[] {contactId};
if (getEntityIteratorMethod != null) {
// Please note that this branch is executed by unit tests only
try {
entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
mContentResolver, uri, selection, selectionArgs, 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: ");
StackTraceElement[] stackTraceElements = e.getCause().getStackTrace();
for (StackTraceElement element : stackTraceElements) {
Log.e(LOG_TAG, " at " + element.toString());
}
throw new VCardException("InvocationTargetException has been thrown: " +
e.getCause().getMessage());
}
} else {
entityIterator = RawContacts.newEntityIterator(mContentResolver.query(
uri, null, selection, selectionArgs, null));
}
if (entityIterator == null) {
Log.e(LOG_TAG, "EntityIterator is null");
return "";
}
if (!entityIterator.hasNext()) {
Log.w(LOG_TAG, "Data does not exist. contactId: " + contactId);
return "";
}
while (entityIterator.hasNext()) {
Entity entity = entityIterator.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);
}
}
}
} finally {
if (entityIterator != null) {
entityIterator.close();
}
}
return buildVCard(contentValuesListMap);
}
/**
* 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))
.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
.appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
.appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
.appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
.appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
.appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
.appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
.appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
.appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
return builder.toString();
}
}
public void terminate() {
for (OneEntryHandler handler : mHandlerList) {
handler.onTerminate();
}
if (mCursor != null) {
try {
mCursor.close();
} catch (SQLiteException e) {
Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
}
mCursor = null;
}
mTerminateIsCalled = true;
}
@Override
public void finalize() {
if (!mTerminateIsCalled) {
Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
terminate();
}
}
/**
* @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 (mCursor == null) {
Log.w(LOG_TAG, "This object is not ready yet.");
return false;
}
return mCursor.isAfterLast();
}
/**
* @return Returns the error reason.
*/
public String getErrorReason() {
return mErrorReason;
}
}

View file

@ -0,0 +1,478 @@
/*
* 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 = "VCardConfig";
/* 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 a given vCard file
* at first, while we have to decode sime encoded data (e.g. BASE64) to binary.
* In order to avoid "misinterpretation" of charset as much as possible,
* "ISO-8859-1" (a.k.a Latin-1) is first used for reading a stream.
* When charset is specified in a property (with "CHARSET=..." parameter),
* the string is decoded to raw bytes and encoded into the specific charset,
* assuming "ISO-8859-1" is able to map "all" 8bit characters to some unicode,
* and it has 1 to 1 mapping in all 8bit characters.
* If the assumption is not correct, this setting will cause some bug.
* </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";
public static final int FLAG_V21 = 0;
public static final int FLAG_V30 = 1;
// 0x2 is reserved for the future use ...
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>
*/
public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
/**
* <p>
* For importer only. Ignored in exporter.
* </p>
* <p>
* The flag indicating the parser should handle a nested vCard, in which vCard clause starts
* in another vCard clause. Here's a typical example.
* </p>
* <pre class="prettyprint">BEGIN:VCARD
* BEGIN:VCARD
* VERSION:2.1
* ...
* END:VCARD
* END:VCARD</pre>
* <p>
* The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries,
* while some mobile devices emit nested ones as primary data to be imported.
* </p>
* <p>
* This flag forces a vCard parser to torelate such a nest and understand its content.
* </p>
*/
public static final int FLAG_TORELATE_NEST = 0x01000000;
//// 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 =
(FLAG_V21 | 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 =
(FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_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 =
(FLAG_V21 | 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 =
(FLAG_V30 | 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 =
(FLAG_V21 | 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 =
(FLAG_V30 | 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 =
(FLAG_V21 | 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 isV30(final int vcardType) {
return ((vcardType & FLAG_V30) != 0);
}
public static boolean shouldUseQuotedPrintable(final int vcardType) {
return !isV30(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 (isV30(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() {
}
}

View file

@ -0,0 +1,160 @@
/*
* 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 {
public static final String VERSION_V21 = "2.1";
public static final String VERSION_V30 = "3.0";
// The property names valid both in vCard 2.1 and 3.0.
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
public static final String PROPERTY_END = "END";
// Valid property names not supported (not appropriately handled) by our vCard importer now.
public static final String PROPERTY_REV = "REV";
public static final String PROPERTY_AGENT = "AGENT";
// Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
public static final String PROPERTY_NAME = "NAME";
public static final String PROPERTY_NICKNAME = "NICKNAME";
public static final String PROPERTY_SORT_STRING = "SORT-STRING";
// 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 in Eclair 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";
// 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";
// 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";
/* package */ 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

View file

@ -0,0 +1,68 @@
/*
* 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.net.Uri;
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 = "VCardEntryComitter";
private final ContentResolver mContentResolver;
private long mTimeToCommit;
private ArrayList<Uri> mCreatedUris = new ArrayList<Uri>();
public VCardEntryCommitter(ContentResolver resolver) {
mContentResolver = resolver;
}
public void onStart() {
}
public void onEnd() {
if (VCardConfig.showPerformanceLog()) {
Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
}
}
public void onEntryCreated(final VCardEntry vcardEntry) {
long start = System.currentTimeMillis();
mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver));
mTimeToCommit += System.currentTimeMillis() - start;
}
/**
* 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;
}
}

View file

@ -0,0 +1,240 @@
/*
* 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.CharsetUtils;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
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 = "VCardEntryConstructor";
private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
private VCardEntry mCurrentVCardEntry;
private String mParamType;
// The charset using which {@link VCardInterpreter} parses the text.
// Each String is first decoded into binary stream with this charset, and encoded back
// to "target charset", which may be explicitly specified by the vCard with "CHARSET"
// property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
private final String mSourceCharset;
private final boolean mStrictLineBreaking;
private final int mVCardType;
private final Account mAccount;
// For measuring performance.
private long mTimePushIntoContentResolver;
private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
public VCardEntryConstructor() {
this(VCardConfig.VCARD_TYPE_V21_GENERIC, null, null, false);
}
public VCardEntryConstructor(final int vcardType) {
this(vcardType, null, null, false);
}
public VCardEntryConstructor(final int vcardType, final Account account) {
this(vcardType, account, null, false);
}
public VCardEntryConstructor(final int vcardType, final Account account,
final String inputCharset) {
this(vcardType, account, inputCharset, false);
}
/**
* @hide
*/
public VCardEntryConstructor(final int vcardType, final Account account,
final String inputCharset, final boolean strictLineBreakParsing) {
if (inputCharset != null) {
mSourceCharset = inputCharset;
} else {
mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
}
mStrictLineBreaking = strictLineBreakParsing;
mVCardType = vcardType;
mAccount = account;
}
public void addEntryHandler(VCardEntryHandler entryHandler) {
mEntryHandlers.add(entryHandler);
}
public void start() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onStart();
}
}
public void end() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEnd();
}
}
public void clear() {
mCurrentVCardEntry = null;
mCurrentProperty = new VCardEntry.Property();
}
public void startEntry() {
if (mCurrentVCardEntry != null) {
Log.e(LOG_TAG, "Nested VCard code is not supported now.");
}
mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
}
public void endEntry() {
mCurrentVCardEntry.consolidateFields();
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEntryCreated(mCurrentVCardEntry);
}
mCurrentVCardEntry = null;
}
public void startProperty() {
mCurrentProperty.clear();
}
public void endProperty() {
mCurrentVCardEntry.addProperty(mCurrentProperty);
}
public void propertyName(String name) {
mCurrentProperty.setPropertyName(name);
}
public void propertyGroup(String group) {
}
public void propertyParamType(String type) {
if (mParamType != null) {
Log.e(LOG_TAG, "propertyParamType() is called more than once " +
"before propertyParamValue() is called");
}
mParamType = type;
}
public void propertyParamValue(String value) {
if (mParamType == null) {
// From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
mParamType = "TYPE";
}
mCurrentProperty.addParameter(mParamType, value);
mParamType = null;
}
private static String encodeToSystemCharset(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 {
String ret = new String(bytes, targetCharset);
return ret;
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return null;
}
}
private String handleOneValue(String value,
String sourceCharset, String targetCharset, String encoding) {
if (value == null) {
Log.w(LOG_TAG, "Null is given.");
value = "";
}
if (encoding != null) {
if (encoding.equals("BASE64") || encoding.equals("B")) {
mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
return VCardUtils.parseQuotedPrintable(
value, mStrictLineBreaking, sourceCharset, targetCharset);
}
Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
}
// Just translate the charset of a given String from inputCharset to a system one.
return encodeToSystemCharset(value, sourceCharset, targetCharset);
}
public void propertyValues(List<String> values) {
if (values == null || values.isEmpty()) {
return;
}
final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
final String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null);
String targetCharset = CharsetUtils.nameForDefaultVendor(
((charsetCollection != null) ? charsetCollection.iterator().next() : null));
if (TextUtils.isEmpty(targetCharset)) {
targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
for (final String value : values) {
mCurrentProperty.addToPropertyValueList(
handleOneValue(value, mSourceCharset, targetCharset, encoding));
}
}
/**
* @hide
*/
public void showPerformanceInfo() {
Log.d(LOG_TAG, "time for insert ContactStruct to database: " +
mTimePushIntoContentResolver + " ms");
}
}

View file

@ -0,0 +1,63 @@
/*
* 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;
/**
* 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;
}
public void start() {
}
public void end() {
}
public void startEntry() {
}
public void endEntry() {
mCount++;
}
public void startProperty() {
}
public void endProperty() {
}
public void propertyGroup(String group) {
}
public void propertyName(String name) {
}
public void propertyParamType(String type) {
}
public void propertyParamValue(String value) {
}
public void propertyValues(List<String> values) {
}
}

View file

@ -0,0 +1,43 @@
/*
* 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 successfully created
*/
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();
}

View file

@ -0,0 +1,102 @@
/*
* 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 start();
/**
* Called when vCard interpretation finished.
*/
void end();
/**
* Called when parsing one vCard entry started.
* More specifically, this method is called when "BEGIN:VCARD" is read.
*/
void startEntry();
/**
* Called when parsing one vCard entry ended.
* More specifically, this method is called when "END:VCARD" is read.
* Note that {@link #startEntry()} may be called since
* vCard (especially 2.1) allows nested vCard.
*/
void endEntry();
/**
* Called when reading one property started.
*/
void startProperty();
/**
* Called when reading one property ended.
*/
void endProperty();
/**
* @param group A group name. This method may be called more than once or may not be
* called at all, depending on how many gruoups are appended to the property.
*/
void propertyGroup(String group);
/**
* @param name A property name like "N", "FN", "ADR", etc.
*/
void propertyName(String name);
/**
* @param type A parameter name like "ENCODING", "CHARSET", etc.
*/
void propertyParamType(String type);
/**
* @param value A parameter value. This method may be called without
* {@link #propertyParamType(String)} being called (when the vCard is vCard 2.1).
*/
void propertyParamValue(String value);
/**
* @param values List of property values. The size of values would be 1 unless
* coressponding property name is "N", "ADR", or "ORG".
*/
void propertyValues(List<String> values);
}

View file

@ -0,0 +1,102 @@
/*
* 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.Collection;
import java.util.List;
/**
* The {@link VCardInterpreter} implementation which aggregates more than one
* {@link VCardInterpreter} objects and make a user object treat them as one
* {@link VCardInterpreter} object.
*/
public final class VCardInterpreterCollection implements VCardInterpreter {
private final Collection<VCardInterpreter> mInterpreterCollection;
public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
mInterpreterCollection = interpreterCollection;
}
public Collection<VCardInterpreter> getCollection() {
return mInterpreterCollection;
}
public void start() {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.start();
}
}
public void end() {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.end();
}
}
public void startEntry() {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.startEntry();
}
}
public void endEntry() {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.endEntry();
}
}
public void startProperty() {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.startProperty();
}
}
public void endProperty() {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.endProperty();
}
}
public void propertyGroup(String group) {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyGroup(group);
}
}
public void propertyName(String name) {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyName(name);
}
}
public void propertyParamType(String type) {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyParamType(type);
}
}
public void propertyParamValue(String value) {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyParamValue(value);
}
}
public void propertyValues(List<String> values) {
for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyValues(values);
}
}
}

View file

@ -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;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
public interface VCardParser {
/**
* <p>
* Parses the given stream and send the vCard data into VCardBuilderBase object.
* </p>.
* <p>
* Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
* local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
* formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
* In some exreme case, it is allowed for vCard to have different charsets in one vCard.
* </p>
* <p>
* We recommend you use {@link VCardSourceDetector} and detect which kind of source the
* vCard comes from and explicitly specify a charset using the result.
* </p>
*
* @param is The source to parse.
* @param interepreter A {@link VCardInterpreter} object which used to construct data.
* @throws IOException, VCardException
*/
public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException;
/**
* <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();
}

View file

@ -0,0 +1,968 @@
/*
* 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.text.TextUtils;
import android.util.Log;
import com.android.vcard.exception.VCardAgentNotSupportedException;
import com.android.vcard.exception.VCardException;
import com.android.vcard.exception.VCardInvalidCommentLineException;
import com.android.vcard.exception.VCardInvalidLineException;
import com.android.vcard.exception.VCardNestedException;
import com.android.vcard.exception.VCardVersionException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/**
* <p>
* Basic implementation achieving vCard parsing. Based on vCard 2.1,
* </p>
* @hide
*/
/* package */ class VCardParserImpl_V21 {
private static final String LOG_TAG = "VCardParserImpl_V21";
private static final class CustomBufferedReader extends BufferedReader {
private long mTime;
public CustomBufferedReader(Reader in) {
super(in);
}
@Override
public String readLine() throws IOException {
long start = System.currentTimeMillis();
String ret = super.readLine();
long end = System.currentTimeMillis();
mTime += end - start;
return ret;
}
public long getTotalmillisecond() {
return mTime;
}
}
private static final String sDefaultEncoding = "8BIT";
protected boolean mCanceled;
protected VCardInterpreter mInterpreter;
protected final String mImportCharset;
/**
* <p>
* The encoding type for deconding byte streams. This member variable is
* reset to a default encoding every time when a new item comes.
* </p>
* <p>
* "Encoding" in vCard is different from "Charset". It is mainly used for
* addresses, notes, images. "7BIT", "8BIT", "BASE64", and
* "QUOTED-PRINTABLE" are known examples.
* </p>
*/
protected String mCurrentEncoding;
/**
* <p>
* The reader object to be used internally.
* </p>
* <p>
* Developers should not directly read a line from this object. Use
* getLine() unless there some reason.
* </p>
*/
protected BufferedReader mReader;
/**
* <p>
* Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
* specification, but happens to be seen in real world vCard.
* </p>
*/
protected final Set<String> mUnknownTypeSet = new HashSet<String>();
/**
* <p>
* Set for storing unkonwn VALUE attributes, which is not acceptable in
* vCard specification, but happens to be seen in real world vCard.
* </p>
*/
protected final Set<String> mUnknownValueSet = new HashSet<String>();
// In some cases, vCard is nested. Currently, we only consider the most
// interior vCard data.
// See v21_foma_1.vcf in test directory for more information.
// TODO: Don't ignore by using count, but read all of information outside vCard.
private int mNestCount;
// Used only for parsing END:VCARD.
private String mPreviousLine;
// For measuring performance.
private long mTimeTotal;
private long mTimeReadStartRecord;
private long mTimeReadEndRecord;
private long mTimeStartProperty;
private long mTimeEndProperty;
private long mTimeParseItems;
private long mTimeParseLineAndHandleGroup;
private long mTimeParsePropertyValues;
private long mTimeParseAdrOrgN;
private long mTimeHandleMiscPropertyValue;
private long mTimeHandleQuotedPrintable;
private long mTimeHandleBase64;
public VCardParserImpl_V21() {
this(VCardConfig.VCARD_TYPE_DEFAULT, null);
}
public VCardParserImpl_V21(int vcardType) {
this(vcardType, null);
}
public VCardParserImpl_V21(int vcardType, String importCharset) {
if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) {
mNestCount = 1;
}
mImportCharset = (!TextUtils.isEmpty(importCharset) ? importCharset :
VCardConfig.DEFAULT_INTERMEDIATE_CHARSET);
}
/**
* <p>
* Parses the file at the given position.
* </p>
*/
// <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
protected void parseVCardFile() throws IOException, VCardException {
boolean readingFirstFile = true;
while (true) {
if (mCanceled) {
break;
}
if (!parseOneVCard(readingFirstFile)) {
break;
}
readingFirstFile = false;
}
if (mNestCount > 0) {
boolean useCache = true;
for (int i = 0; i < mNestCount; i++) {
readEndVCard(useCache, true);
useCache = false;
}
}
}
/**
* @return true when a given property name is a valid property name.
*/
protected boolean isValidPropertyName(final String propertyName) {
if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
propertyName.startsWith("X-"))
&& !mUnknownTypeSet.contains(propertyName)) {
mUnknownTypeSet.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
}
return true;
}
/**
* @return String. It may be null, or its length may be 0
* @throws IOException
*/
protected String getLine() throws IOException {
return mReader.readLine();
}
/**
* @return String with it's length > 0
* @throws IOException
* @throws VCardException when the stream reached end of line
*/
protected String getNonEmptyLine() throws IOException, VCardException {
String line;
while (true) {
line = getLine();
if (line == null) {
throw new VCardException("Reached end of buffer.");
} else if (line.trim().length() > 0) {
return line;
}
}
}
/*
* vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
* items *CRLF
* "END" [ws] ":" [ws] "VCARD"
*/
private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
boolean allowGarbage = false;
if (firstRead) {
if (mNestCount > 0) {
for (int i = 0; i < mNestCount; i++) {
if (!readBeginVCard(allowGarbage)) {
return false;
}
allowGarbage = true;
}
}
}
if (!readBeginVCard(allowGarbage)) {
return false;
}
long start;
if (mInterpreter != null) {
start = System.currentTimeMillis();
mInterpreter.startEntry();
mTimeReadStartRecord += System.currentTimeMillis() - start;
}
start = System.currentTimeMillis();
parseItems();
mTimeParseItems += System.currentTimeMillis() - start;
readEndVCard(true, false);
if (mInterpreter != null) {
start = System.currentTimeMillis();
mInterpreter.endEntry();
mTimeReadEndRecord += System.currentTimeMillis() - start;
}
return true;
}
/**
* @return True when successful. False when reaching the end of line
* @throws IOException
* @throws VCardException
*/
protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
String line;
do {
while (true) {
line = getLine();
if (line == null) {
return false;
} else if (line.trim().length() > 0) {
break;
}
}
String[] strArray = line.split(":", 2);
int length = strArray.length;
// Though vCard 2.1/3.0 specification does not allow lower cases,
// vCard file emitted by some external vCard expoter have such
// invalid Strings.
// So we allow it.
// e.g. BEGIN:vCard
if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
&& strArray[1].trim().equalsIgnoreCase("VCARD")) {
return true;
} else if (!allowGarbage) {
if (mNestCount > 0) {
mPreviousLine = line;
return false;
} else {
throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
+ "(Instead, \"" + line + "\" came)");
}
}
} while (allowGarbage);
throw new VCardException("Reached where must not be reached.");
}
/**
* <p>
* The arguments useCache and allowGarbase are usually true and false
* accordingly when this function is called outside this function itself.
* </p>
*
* @param useCache When true, line is obtained from mPreviousline.
* Otherwise, getLine() is used.
* @param allowGarbage When true, ignore non "END:VCARD" line.
* @throws IOException
* @throws VCardException
*/
protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException,
VCardException {
String line;
do {
if (useCache) {
// Though vCard specification does not allow lower cases,
// some data may have them, so we allow it.
line = mPreviousLine;
} else {
while (true) {
line = getLine();
if (line == null) {
throw new VCardException("Expected END:VCARD was not found.");
} else if (line.trim().length() > 0) {
break;
}
}
}
String[] strArray = line.split(":", 2);
if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END")
&& strArray[1].trim().equalsIgnoreCase("VCARD")) {
return;
} else if (!allowGarbage) {
throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
}
useCache = false;
} while (allowGarbage);
}
/*
* items = *CRLF item / item
*/
protected void parseItems() throws IOException, VCardException {
boolean ended = false;
if (mInterpreter != null) {
long start = System.currentTimeMillis();
mInterpreter.startProperty();
mTimeStartProperty += System.currentTimeMillis() - start;
}
ended = parseItem();
if (mInterpreter != null && !ended) {
long start = System.currentTimeMillis();
mInterpreter.endProperty();
mTimeEndProperty += System.currentTimeMillis() - start;
}
while (!ended) {
// follow VCARD ,it wont reach endProperty
if (mInterpreter != null) {
long start = System.currentTimeMillis();
mInterpreter.startProperty();
mTimeStartProperty += System.currentTimeMillis() - start;
}
try {
ended = parseItem();
} catch (VCardInvalidCommentLineException e) {
Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
ended = false;
}
if (mInterpreter != null && !ended) {
long start = System.currentTimeMillis();
mInterpreter.endProperty();
mTimeEndProperty += System.currentTimeMillis() - start;
}
}
}
/*
* item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
* [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
* CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
* "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mCurrentEncoding = sDefaultEncoding;
final String line = getNonEmptyLine();
long start = System.currentTimeMillis();
String[] propertyNameAndValue = separateLineAndHandleGroup(line);
if (propertyNameAndValue == null) {
return true;
}
if (propertyNameAndValue.length != 2) {
throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
}
String propertyName = propertyNameAndValue[0].toUpperCase();
String propertyValue = propertyNameAndValue[1];
mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) {
start = System.currentTimeMillis();
handleMultiplePropertyValue(propertyName, propertyValue);
mTimeParseAdrOrgN += System.currentTimeMillis() - start;
return false;
} else if (propertyName.equals("AGENT")) {
handleAgent(propertyValue);
return false;
} else if (isValidPropertyName(propertyName)) {
if (propertyName.equals("BEGIN")) {
if (propertyValue.equals("VCARD")) {
throw new VCardNestedException("This vCard has nested vCard data in it.");
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
} else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) {
throw new VCardVersionException("Incompatible version: " + propertyValue + " != "
+ getVersionString());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
mTimeParsePropertyValues += System.currentTimeMillis() - start;
return false;
}
throw new VCardException("Unknown property name: \"" + propertyName + "\"");
}
// For performance reason, the states for group and property name are merged into one.
static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
static private final int STATE_PARAMS = 1;
// vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
static private final int STATE_PARAMS_IN_DQUOTE = 2;
protected String[] separateLineAndHandleGroup(String line) throws VCardException {
final String[] propertyNameAndValue = new String[2];
final int length = line.length();
if (length > 0 && line.charAt(0) == '#') {
throw new VCardInvalidCommentLineException();
}
int state = STATE_GROUP_OR_PROPERTY_NAME;
int nameIndex = 0;
// This loop is developed so that we don't have to take care of bottle neck here.
// Refactor carefully when you need to do so.
for (int i = 0; i < length; i++) {
final char ch = line.charAt(i);
switch (state) {
case STATE_GROUP_OR_PROPERTY_NAME: {
if (ch == ':') { // End of a property name.
final String propertyName = line.substring(nameIndex, i);
if (propertyName.equalsIgnoreCase("END")) {
mPreviousLine = line;
return null;
}
if (mInterpreter != null) {
mInterpreter.propertyName(propertyName);
}
propertyNameAndValue[0] = propertyName;
if (i < length - 1) {
propertyNameAndValue[1] = line.substring(i + 1);
} else {
propertyNameAndValue[1] = "";
}
return propertyNameAndValue;
} else if (ch == '.') { // Each group is followed by the dot.
final String groupName = line.substring(nameIndex, i);
if (groupName.length() == 0) {
Log.w(LOG_TAG, "Empty group found. Ignoring.");
} else if (mInterpreter != null) {
mInterpreter.propertyGroup(groupName);
}
nameIndex = i + 1; // Next should be another group or a property name.
} else if (ch == ';') { // End of property name and beginneng of parameters.
final String propertyName = line.substring(nameIndex, i);
if (propertyName.equalsIgnoreCase("END")) {
mPreviousLine = line;
return null;
}
if (mInterpreter != null) {
mInterpreter.propertyName(propertyName);
}
propertyNameAndValue[0] = propertyName;
nameIndex = i + 1;
state = STATE_PARAMS; // Start parameter parsing.
}
break;
}
case STATE_PARAMS: {
if (ch == '"') {
if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
"Silently allow it");
}
state = STATE_PARAMS_IN_DQUOTE;
} else if (ch == ';') { // Starts another param.
handleParams(line.substring(nameIndex, i));
nameIndex = i + 1;
} else if (ch == ':') { // End of param and beginenning of values.
handleParams(line.substring(nameIndex, i));
if (i < length - 1) {
propertyNameAndValue[1] = line.substring(i + 1);
} else {
propertyNameAndValue[1] = "";
}
return propertyNameAndValue;
}
break;
}
case STATE_PARAMS_IN_DQUOTE: {
if (ch == '"') {
if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
"Silently allow it");
}
state = STATE_PARAMS;
}
break;
}
}
}
throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
}
/*
* params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
* param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
* pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
* [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
* [ws] word / knowntype
*/
protected void handleParams(String params) throws VCardException {
final String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
final String paramName = strArray[0].trim().toUpperCase();
String paramValue = strArray[1].trim();
if (paramName.equals("TYPE")) {
handleType(paramValue);
} else if (paramName.equals("VALUE")) {
handleValue(paramValue);
} else if (paramName.equals("ENCODING")) {
handleEncoding(paramValue);
} else if (paramName.equals("CHARSET")) {
handleCharset(paramValue);
} else if (paramName.equals("LANGUAGE")) {
handleLanguage(paramValue);
} else if (paramName.startsWith("X-")) {
handleAnyParam(paramName, paramValue);
} else {
throw new VCardException("Unknown type \"" + paramName + "\"");
}
} else {
handleParamWithoutName(strArray[0]);
}
}
/**
* vCard 3.0 parser implementation may throw VCardException.
*/
@SuppressWarnings("unused")
protected void handleParamWithoutName(final String paramValue) throws VCardException {
handleType(paramValue);
}
/*
* ptypeval = knowntype / "X-" word
*/
protected void handleType(final String ptypeval) {
if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
|| ptypeval.startsWith("X-"))
&& !mUnknownTypeSet.contains(ptypeval)) {
mUnknownTypeSet.add(ptypeval);
Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
}
if (mInterpreter != null) {
mInterpreter.propertyParamType("TYPE");
mInterpreter.propertyParamValue(ptypeval);
}
}
/*
* pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
*/
protected void handleValue(final String pvalueval) {
if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
|| pvalueval.startsWith("X-")
|| mUnknownValueSet.contains(pvalueval))) {
mUnknownValueSet.add(pvalueval);
Log.w(LOG_TAG, String.format(
"The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
}
if (mInterpreter != null) {
mInterpreter.propertyParamType("VALUE");
mInterpreter.propertyParamValue(pvalueval);
}
}
/*
* pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
*/
protected void handleEncoding(String pencodingval) throws VCardException {
if (getAvailableEncodingSet().contains(pencodingval) ||
pencodingval.startsWith("X-")) {
if (mInterpreter != null) {
mInterpreter.propertyParamType("ENCODING");
mInterpreter.propertyParamValue(pencodingval);
}
mCurrentEncoding = pencodingval;
} else {
throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
}
}
/**
* <p>
* vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
* but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
* We allow any charset.
* </p>
*/
protected void handleCharset(String charsetval) {
if (mInterpreter != null) {
mInterpreter.propertyParamType("CHARSET");
mInterpreter.propertyParamValue(charsetval);
}
}
/**
* See also Section 7.1 of RFC 1521
*/
protected void handleLanguage(String langval) throws VCardException {
String[] strArray = langval.split("-");
if (strArray.length != 2) {
throw new VCardException("Invalid Language: \"" + langval + "\"");
}
String tmp = strArray[0];
int length = tmp.length();
for (int i = 0; i < length; i++) {
if (!isAsciiLetter(tmp.charAt(i))) {
throw new VCardException("Invalid Language: \"" + langval + "\"");
}
}
tmp = strArray[1];
length = tmp.length();
for (int i = 0; i < length; i++) {
if (!isAsciiLetter(tmp.charAt(i))) {
throw new VCardException("Invalid Language: \"" + langval + "\"");
}
}
if (mInterpreter != null) {
mInterpreter.propertyParamType("LANGUAGE");
mInterpreter.propertyParamValue(langval);
}
}
private boolean isAsciiLetter(char ch) {
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return true;
}
return false;
}
/**
* Mainly for "X-" type. This accepts any kind of type without check.
*/
protected void handleAnyParam(String paramName, String paramValue) {
if (mInterpreter != null) {
mInterpreter.propertyParamType(paramName);
mInterpreter.propertyParamValue(paramValue);
}
}
protected void handlePropertyValue(String propertyName, String propertyValue)
throws IOException, VCardException {
final String upperEncoding = mCurrentEncoding.toUpperCase();
if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
final long start = System.currentTimeMillis();
final String result = getQuotedPrintable(propertyValue);
if (mInterpreter != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
mInterpreter.propertyValues(v);
}
mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
} else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
|| upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
final long start = System.currentTimeMillis();
// It is very rare, but some BASE64 data may be so big that
// OutOfMemoryError occurs. To ignore such cases, use try-catch.
try {
final String result = getBase64(propertyValue);
if (mInterpreter != null) {
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add(result);
mInterpreter.propertyValues(arrayList);
}
} catch (OutOfMemoryError error) {
Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
if (mInterpreter != null) {
mInterpreter.propertyValues(null);
}
}
mTimeHandleBase64 += System.currentTimeMillis() - start;
} else {
if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
upperEncoding.startsWith("X-"))) {
Log.w(LOG_TAG,
String.format("The encoding \"%s\" is unsupported by vCard %s",
mCurrentEncoding, getVersionString()));
}
final long start = System.currentTimeMillis();
if (mInterpreter != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(maybeUnescapeText(propertyValue));
mInterpreter.propertyValues(v);
}
mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
}
}
/**
* <p>
* Parses and returns Quoted-Printable.
* </p>
*
* @param firstString The string following a parameter name and attributes.
* Example: "string" in
* "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
* @return whole Quoted-Printable string, including a given argument and
* following lines. Excludes the last empty line following to Quoted
* Printable lines.
* @throws IOException
* @throws VCardException
*/
private String getQuotedPrintable(String firstString) throws IOException, VCardException {
// Specifically, there may be some padding between = and CRLF.
// See the following:
//
// qp-line := *(qp-segment transport-padding CRLF)
// qp-part transport-padding
// qp-segment := qp-section *(SPACE / TAB) "="
// ; Maximum length of 76 characters
//
// e.g. (from RFC 2045)
// Now's the time =
// for all folk to come=
// to the aid of their country.
if (firstString.trim().endsWith("=")) {
// remove "transport-padding"
int pos = firstString.length() - 1;
while (firstString.charAt(pos) != '=') {
}
StringBuilder builder = new StringBuilder();
builder.append(firstString.substring(0, pos + 1));
builder.append("\r\n");
String line;
while (true) {
line = getLine();
if (line == null) {
throw new VCardException("File ended during parsing a Quoted-Printable String");
}
if (line.trim().endsWith("=")) {
// remove "transport-padding"
pos = line.length() - 1;
while (line.charAt(pos) != '=') {
}
builder.append(line.substring(0, pos + 1));
builder.append("\r\n");
} else {
builder.append(line);
break;
}
}
return builder.toString();
} else {
return firstString;
}
}
protected String getBase64(String firstString) throws IOException, VCardException {
StringBuilder builder = new StringBuilder();
builder.append(firstString);
while (true) {
String line = getLine();
if (line == null) {
throw new VCardException("File ended during parsing BASE64 binary");
}
if (line.length() == 0) {
break;
}
builder.append(line);
}
return builder.toString();
}
/**
* <p>
* Mainly for "ADR", "ORG", and "N"
* </p>
*/
/*
* addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr,
* Street, Locality, Region, Postal Code, Country Name orgparts =
* *(strnosemi ";") strnosemi ; First is Organization Name, remainder are
* Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family,
* Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III,
* Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a
* semicolon in this string, it must be escaped ; with a "\" character. We
* do not care the number of "strnosemi" here. We are not sure whether we
* should add "\" CRLF to each value. We exclude them for now.
*/
protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
throws IOException, VCardException {
// vCard 2.1 does not allow QUOTED-PRINTABLE here, but some
// softwares/devices
// emit such data.
if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
propertyValue = getQuotedPrintable(propertyValue);
}
if (mInterpreter != null) {
mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
(getVersion() == VCardConfig.FLAG_V30)));
}
}
/*
* vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
* error toward the AGENT property.
* // TODO: Support AGENT property.
* item =
* ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
* ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
*/
protected void handleAgent(final String propertyValue) throws VCardException {
if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
// Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
return;
} else {
throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
}
}
/**
* For vCard 3.0.
*/
protected String maybeUnescapeText(final String text) {
return text;
}
/**
* Returns unescaped String if the character should be unescaped. Return
* null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
* while "\x" should not be.
*/
protected String maybeUnescapeCharacter(final char ch) {
return unescapeCharacter(ch);
}
/* package */ static String unescapeCharacter(final char ch) {
// Original vCard 2.1 specification does not allow transformation
// "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
// implementation of
// this class allowed them, so keep it as is.
if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
return String.valueOf(ch);
} else {
return null;
}
}
private void showPerformanceInfo() {
Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
if (mReader instanceof CustomBufferedReader) {
Log.d(LOG_TAG, "Total readLine time: "
+ ((CustomBufferedReader) mReader).getTotalmillisecond() + " ms");
}
Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord
+ " ms");
Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms");
Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup
+ " ms");
Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue
+ " ms");
Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms");
Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
}
/**
* @return {@link VCardConfig#FLAG_V21}
*/
protected int getVersion() {
return VCardConfig.FLAG_V21;
}
/**
* @return {@link VCardConfig#FLAG_V30}
*/
protected String getVersionString() {
return VCardConstants.VERSION_V21;
}
protected Set<String> getKnownPropertyNameSet() {
return VCardParser_V21.sKnownPropertyNameSet;
}
protected Set<String> getKnownTypeSet() {
return VCardParser_V21.sKnownTypeSet;
}
protected Set<String> getKnownValueSet() {
return VCardParser_V21.sKnownValueSet;
}
protected Set<String> getAvailableEncodingSet() {
return VCardParser_V21.sAvailableEncoding;
}
protected String getDefaultEncoding() {
return sDefaultEncoding;
}
public void parse(InputStream is, VCardInterpreter interpreter)
throws IOException, VCardException {
if (is == null) {
throw new NullPointerException("InputStream must not be null.");
}
final InputStreamReader tmpReader = new InputStreamReader(is, mImportCharset);
if (VCardConfig.showPerformanceLog()) {
mReader = new CustomBufferedReader(tmpReader);
} else {
mReader = new BufferedReader(tmpReader);
}
mInterpreter = interpreter;
final long start = System.currentTimeMillis();
if (mInterpreter != null) {
mInterpreter.start();
}
parseVCardFile();
if (mInterpreter != null) {
mInterpreter.end();
}
mTimeTotal += System.currentTimeMillis() - start;
if (VCardConfig.showPerformanceLog()) {
showPerformanceInfo();
}
}
public final void cancel() {
mCanceled = true;
}
}

View file

@ -0,0 +1,317 @@
/*
* 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 = "VCardParserImpl_V30";
private String mPreviousLine;
private boolean mEmittedAgentWarning = false;
public VCardParserImpl_V30() {
super();
}
public VCardParserImpl_V30(int vcardType) {
super(vcardType, null);
}
public VCardParserImpl_V30(int vcardType, String importCharset) {
super(vcardType, importCharset);
}
@Override
protected int getVersion() {
return VCardConfig.FLAG_V30;
}
@Override
protected String getVersionString() {
return VCardConstants.VERSION_V30;
}
@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 (true) {
line = mReader.readLine();
if (line == null) {
if (builder != null) {
return builder.toString();
} else if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
}
throw new VCardException("Reached end of buffer.");
} else if (line.length() == 0) {
if (builder != null) {
return builder.toString();
} else if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
}
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
if (builder != null) {
// See Section 5.8.1 of RFC 2425 (MIME-DIR document).
// Following is the excerpts from it.
//
// DESCRIPTION:This is a long description that exists on a long line.
//
// Can be represented as:
//
// DESCRIPTION:This is a long description
// that exists on a long line.
//
// It could also be represented as:
//
// DESCRIPTION:This is a long descrip
// tion that exists o
// n a long line.
builder.append(line.substring(1));
} else if (mPreviousLine != null) {
builder = new StringBuilder();
builder.append(mPreviousLine);
mPreviousLine = null;
builder.append(line.substring(1));
} else {
throw new VCardException("Space exists at the beginning of the line");
}
} else {
if (mPreviousLine == null) {
mPreviousLine = line;
if (builder != null) {
return builder.toString();
}
} else {
String ret = mPreviousLine;
mPreviousLine = line;
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);
}
@Override
protected void readEndVCard(boolean useCache, boolean allowGarbage)
throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
super.readEndVCard(useCache, allowGarbage);
}
/**
* vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
*/
@Override
protected void handleParams(final String params) throws VCardException {
try {
super.handleParams(params);
} catch (VCardException e) {
// maybe IANA type
String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
handleAnyParam(strArray[0], strArray[1]);
} else {
// Must not come here in the current implementation.
throw new VCardException(
"Unknown params value: " + params);
}
}
}
@Override
protected void handleAnyParam(final String paramName, final String paramValue) {
super.handleAnyParam(paramName, paramValue);
}
@Override
protected void handleParamWithoutName(final String paramValue) throws VCardException {
super.handleParamWithoutName(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
*/
@Override
protected void handleType(final String ptypevalues) {
String[] ptypeArray = ptypevalues.split(",");
mInterpreter.propertyParamType("TYPE");
for (String value : ptypeArray) {
int length = value.length();
if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
mInterpreter.propertyParamValue(value.substring(1, value.length() - 1));
} else {
mInterpreter.propertyParamValue(value);
}
}
}
@Override
protected void handleAgent(final String propertyValue) {
// 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;
}
}
/**
* vCard 3.0 does not require two CRLF at the last of BASE64 data.
* It only requires that data should be MIME-encoded.
*/
@Override
protected String getBase64(final String firstString)
throws IOException, VCardException {
final StringBuilder builder = new StringBuilder();
builder.append(firstString);
while (true) {
final String line = getLine();
if (line == null) {
throw new VCardException("File ended during parsing BASE64 binary");
}
if (line.length() == 0) {
break;
} else if (!line.startsWith(" ") && !line.startsWith("\t")) {
mPreviousLine = line;
break;
}
builder.append(line);
}
return builder.toString();
}
/**
* 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;
}
}

View file

@ -0,0 +1,113 @@
/*
* 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 implements 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", "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);
}
public VCardParser_V21(int parseType, String inputCharset) {
mVCardParserImpl = new VCardParserImpl_V21(parseType, null);
}
public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
mVCardParserImpl.parse(is, interepreter);
}
public void cancel() {
mVCardParserImpl.cancel();
}
}

View file

@ -0,0 +1,91 @@
/*
* 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 implements VCardParser {
/* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "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
/**
* <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);
}
public VCardParser_V30(int vcardType, String importCharset) {
mVCardParserImpl = new VCardParserImpl_V30(vcardType, importCharset);
}
public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
mVCardParserImpl.parse(is, interepreter);
}
public void cancel() {
mVCardParserImpl.cancel();
}
}

View file

@ -0,0 +1,172 @@
/*
* 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 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 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";
// TODO: Should replace this with types in VCardConfig
private 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, which use nested vCard.
private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
// For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
private int mParseType = 0; // Not sure.
// Some mobile phones (like FOMA) tells us the charset of the data.
private boolean mNeedParseSpecifiedCharset;
private String mSpecifiedCharset;
public void start() {
}
public void end() {
}
public void startEntry() {
}
public void startProperty() {
mNeedParseSpecifiedCharset = false;
}
public void endProperty() {
}
public void endEntry() {
}
public void propertyGroup(String group) {
}
public void propertyName(String name) {
if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
// Probably Shift_JIS is used, but we should double confirm.
mNeedParseSpecifiedCharset = true;
return;
}
if (mParseType != PARSE_TYPE_UNKNOWN) {
return;
}
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
} else if (FOMA_SIGNS.contains(name)) {
mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
} else if (APPLE_SIGNS.contains(name)) {
mParseType = PARSE_TYPE_APPLE;
}
}
public void propertyParamType(String type) {
}
public void propertyParamValue(String value) {
}
public void propertyValues(List<String> values) {
if (mNeedParseSpecifiedCharset && values.size() > 0) {
mSpecifiedCharset = values.get(0);
}
}
/**
* @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_TORELATE_NEST:
return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
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:
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_TORELATE_NEST:
case PARSE_TYPE_MOBILE_PHONE_JP:
return "SHIFT_JIS";
case PARSE_TYPE_APPLE:
return "UTF-8";
default:
return null;
}
}
}

View file

@ -0,0 +1,658 @@
/*
* 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 org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.net.QuotedPrintableCodec;
import android.content.ContentProviderOperation;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
import java.io.UnsupportedEncodingException;
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 = "VCardUtils";
// 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);
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 (String typeString : types) {
if (typeString == null) {
continue;
}
typeString = typeString.toUpperCase();
if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
hasPref = true;
} else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
isFax = true;
} else {
if (typeString.startsWith("X-") && type < 0) {
typeString = typeString.substring(2);
}
if (typeString.length() == 0) {
continue;
}
final Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
if (tmp != null) {
final int typeCandidate = tmp;
// 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 = tmp;
}
} else if (type < 0) {
type = Phone.TYPE_CUSTOM;
label = typeString;
}
}
}
}
if (type < 0) {
if (hasPref) {
type = Phone.TYPE_MAIN;
} else {
// default to TYPE_HOME
type = Phone.TYPE_HOME;
}
}
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 vcardType,
final String familyName, final String middleName, final String givenName) {
final String[] list = new String[3];
final int nameOrderType = VCardConfig.getNameOrderType(vcardType);
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;
}
}
/**
* <p>
* Inserts postal data into the builder object.
* </p>
* <p>
* Note that the data structure of ContactsContract is different from that defined in vCard.
* So some conversion may be performed in this method.
* </p>
*/
public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
final ContentProviderOperation.Builder builder,
final VCardEntry.PostalData postalData) {
builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
builder.withValue(StructuredPostal.TYPE, postalData.type);
if (postalData.type == StructuredPostal.TYPE_CUSTOM) {
builder.withValue(StructuredPostal.LABEL, postalData.label);
}
final String streetString;
if (TextUtils.isEmpty(postalData.street)) {
if (TextUtils.isEmpty(postalData.extendedAddress)) {
streetString = null;
} else {
streetString = postalData.extendedAddress;
}
} else {
if (TextUtils.isEmpty(postalData.extendedAddress)) {
streetString = postalData.street;
} else {
streetString = postalData.street + " " + postalData.extendedAddress;
}
}
builder.withValue(StructuredPostal.POBOX, postalData.pobox);
builder.withValue(StructuredPostal.STREET, streetString);
builder.withValue(StructuredPostal.CITY, postalData.localty);
builder.withValue(StructuredPostal.REGION, postalData.region);
builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
builder.withValue(StructuredPostal.COUNTRY, postalData.country);
builder.withValue(StructuredPostal.FORMATTED_ADDRESS,
postalData.getFormattedAddress(vcardType));
if (postalData.isPrimary) {
builder.withValue(Data.IS_PRIMARY, 1);
}
}
public static String constructNameFromElements(final int vcardType,
final String familyName, final String middleName, final String givenName) {
return constructNameFromElements(vcardType, familyName, middleName, givenName,
null, null);
}
public static String constructNameFromElements(final int vcardType,
final String familyName, final String middleName, final String givenName,
final String prefix, final String suffix) {
final StringBuilder builder = new StringBuilder();
final String[] nameList = sortNameElements(vcardType, 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();
}
public static List<String> constructListFromValue(final String value,
final boolean isV30) {
final List<String> list = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
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 =
(isV30 ? VCardParserImpl_V30.unescapeCharacter(nextCh) :
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 (!TextUtils.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;
}
/**
* <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 = &lt;any printable 7bit us-ascii except []=:., &gt;
* </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;
}
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;
}
//// The methods bellow may be used by unit test.
/**
* @hide
*/
public static String parseQuotedPrintable(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 = QuotedPrintableCodec.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);
}
}
private VCardUtils() {
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

25
vcard/tests/Android.mk Normal file
View file

@ -0,0 +1,25 @@
# 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CERTIFICATE := platform
LOCAL_MODULE_TAGS := tests
LOCAL_PACKAGE_NAME := AndroidVCardTests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := android.test.runner google-common
LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
include $(BUILD_PACKAGE)

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.vcard.tests"
android:sharedUserId="com.android.uid.test">
<application>
<uses-library android:name="android.test.runner" />
</application>
<!-- Run tests with "runtest common" -->
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.vcard.tests"
android:label="Android vCard Library Tests" />
</manifest>

View file

@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:2.1
N:;A\;B\\;C\\\;;D;\:E;\\\\;
FN:A;B\C\;D:E\\
END:VCARD

View file

@ -0,0 +1,106 @@
BEGIN:VCARD
VERSION:2.1
N:Gump;Forrest;Hoge;Pos;Tao
FN:Joe Due
ORG:Gump Shrimp Co.;Sales Dept.\;Manager;Fish keeper
ROLE:Fish Cake Keeper!
X-CLASS:PUBLIC
TITLE:Shrimp Man
TEL;WORK;VOICE:(111) 555-1212
TEL;HOME;VOICE:(404) 555-1212
TEL;CELL:0311111111
TEL;VIDEO:0322222222
TEL;VOICE:0333333333
ADR;WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
LABEL;WORK;ENCODING=QUOTED-PRINTABLE:100 Waters Edge=0D=0ABaytown, LA 30314=0D=0AUnited States of America
ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A=
Baytown, LA 30314=0D=0A=
United States of America
EMAIL;PREF;INTERNET:forrestgump@walladalla.com
EMAIL;CELL:cell@example.com
NOTE:The following note is the example from RFC 2045.
NOTE;ENCODING=QUOTED-PRINTABLE:Now's the time =
for all folk to come=
to the aid of their country.
PHOTO;ENCODING=BASE64;TYPE=JPEG:
/9j/4QoPRXhpZgAATU0AKgAAAAgADQEOAAIAAAAPAAAAqgEPAAIAAAAHAAAAugEQAAIAAAAG
AAAAwgESAAMAAAABAAEAAAEaAAUAAAABAAAAyAEbAAUAAAABAAAA0AEoAAMAAAABAAIAAAEx
AAIAAAAOAAAA2AEyAAIAAAAUAAAA5gITAAMAAAABAAEAAIKYAAIAAAAOAAAA+odpAAQAAAAB
AAABhMSlAAcAAAB8AAABCAAABB4yMDA4MTAyOTEzNTUzMQAARG9Db01vAABEOTA1aQAAAABI
AAAAAQAAAEgAAAABRDkwNWkgVmVyMS4wMAAyMDA4OjEwOjI5IDEzOjU1OjQ3ACAgICAgICAg
ICAgICAAUHJpbnRJTQAwMzAwAAAABgABABQAFAACAQAAAAADAAAANAEABQAAAAEBAQAAAAEQ
gAAAAAAAEQkAACcQAAAPCwAAJxAAAAWXAAAnEAAACLAAACcQAAAcAQAAJxAAAAJeAAAnEAAA
AIsAACcQAAADywAAJxAAABvlAAAnEAAogpoABQAAAAEAAANqgp0ABQAAAAEAAANyiCIAAwAA
AAEAAgAAkAAABwAAAAQwMjIwkAMAAgAAABQAAAN6kAQAAgAAABQAAAOOkQEABwAAAAQBAgMA
kQIABQAAAAEAAAOikgEACgAAAAEAAAOqkgIABQAAAAEAAAOykgQACgAAAAEAAAO6kgUABQAA
AAEAAAPCkgcAAwAAAAEAAgAAkggAAwAAAAEAAAAAkgkAAwAAAAEAAAAAkgoABQAAAAEAAAPK
knwABwAAAAEAAAAAkoYABwAAABYAAAPSoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIAAwAA
AAEAYAAAoAMAAwAAAAEASAAAoAUABAAAAAEAAAQAog4ABQAAAAEAAAPoog8ABQAAAAEAAAPw
ohAAAwAAAAEAAgAAohcAAwAAAAEAAgAAowAABwAAAAEDAAAAowEABwAAAAEBAAAApAEAAwAA
AAEAAAAApAIAAwAAAAEAAAAApAMAAwAAAAEAAAAApAQABQAAAAEAAAP4pAUAAwAAAAEAHQAA
pAYAAwAAAAEAAAAApAcAAwAAAAEAAAAApAgAAwAAAAEAAAAApAkAAwAAAAEAAAAApAoAAwAA
AAEAAAAApAwAAwAAAAEAAgAAAAAAAAAAAFMAACcQAAABXgAAAGQyMDA4OjEwOjI5IDEzOjU1
OjMxADIwMDg6MTA6MjkgMTM6NTU6NDcAAAApiAAAGwAAAAKyAAAAZAAAAV4AAABkAAAAAAAA
AGQAAAAlAAAACgAADpIAAAPoAAAAAAAAAAAyMDA4MTAyOTEzNTUzMQAAICoAAAAKAAAq4gAA
AAoAAAAAAAAAAQACAAEAAgAAAARSOTgAAAIABwAAAAQwMTAwAAAAAAAGAQMAAwAAAAEABgAA
ARoABQAAAAEAAARsARsABQAAAAEAAAR0ASgAAwAAAAEAAgAAAgEABAAAAAEAAAR8AgIABAAA
AAEAAAWLAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAIQAIBYYHBgUIBwaHCQiICYwUDQwLCww
YkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxgEiJCQwKjBeNDRe
xoRwhMbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbG
/8AAEQgAeACgAwEhAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAA
AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK
FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG
h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl
5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQH
BQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka
JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz
9PX29/j5+v/aAAwDAQACEQMRAD8AFFSqKkZIoqRVpgSKKeBTEOApwFADsUYpgIRSEUANIppF
ICNhUTCgCMio2FICJhULCgC0oqVaAJFFSqKBkgFOApiHCnCgB2KMUCENJQA0imEUDGMKiYUA
RtUbUgIWqJhQBZSpFoAlWpVoGPFPFMQ7tSK2ODQA4yKO9HmKe9FxAzDHFIOlAAaYaAGNUTUD
ImqNqQETVE1AE6VKKAJFNSqaAHg08GmANIFFQM5Y5qJMBuT60ZNQIcrkVYSQMKuLGKaaasQx
qiagZE1RtSAjaomoAkQ1KpoAlU1IpoAkU07OBTArO+5qkV12Y71lfUBmaKkCRSuznrTFba2a
oCwGyM0E1qIjY1GxoGRNUZNICNqiagByGplNAEimpFNMB4YDvSucpxSYEIU04KazsAu1qArU
WELtPpTSposBNETt5pxNaoCNjUbGgCNjUZoGRtUTUgFU1KpoAkBqQHigCFnO7rUqOdlZp6gA
c+tODn1pXAXzD60eYfWncQvmNSGQ07gOMhCVEJGz1ptgS5yKYxqwGE1GxoAiamGkMapqVTQB
Kpp+eKAICfmqWM/Kaz6gANOBqQFzRmmAuaTNACsfkqMHmm9wJs8U0mtRDGNRsaAI2phpDI1N
SqaAJFNSA8UCISfmqSM/Kaz6jAHmnA1ICg0uaAFzSZpgKx+SmDrTe4E2eKaTWoiMmmMaAIzT
DSGRKakU0ASKaeDTERseakjPyms+oxAacDUgOBpc0gFzSZpgOY/KKYv3qrqIlpprQBjGoyaA
GGmmkMgU1IppgPBqQGgQu0Gn4wvFKwEQpwNZDHZpc0ALmigRKBleaQKBWtgA001QDGqM0gGm
mGkMrqakBoAepp4NMRIDTwaAE2A008GokgHxjd1pzKFpW0uAg5NSBBTirgOpDWgDTTTQAw0w
0gGGmmgZWBp4pASKaeDTEOBp4NADwajbrUyBEkXWnSUdAGr1qeiAMSkNWAhphoAaaYaQDDTT
SGVRTwaYDxTwaBDwaeDQA4GlK5oauIeo20pGaLaAKqgU6hKwBSGmAhphoAaaYaQxhpppDKgN
PFMB4p4oEPFOBpgPBp4NAhwpwoAWloAKSgBDTTQMYaYaQDTTTSGA/9n/2wCEAAoHBwgHBgoI
CAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9
PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7
Ozs7Ozs7Ozs7Ozs7O//AABEIAEgAYAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAA
AQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNC
scEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp
anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS
09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
CQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVi
ctEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4
eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY
2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AJ7SLgcVr20I4rNFGvbQAAHFaEUX
SrQi5HCMdKk8oY6VSJYx4hjpVaWMelAFC4iGDxWPdR8mkxmRdxjBrEvI+tZjN20xtHNbNqAc
UIDXg6Cr0WKtCY8XKQOyzOB3FKNWsyceZ+lS6sY6NkjvPSdwImBHUmmy4q076oCjOODWPdgc
0MpGPdAYNYl4o5rNjNKzkyorZtXxihAa1vIDip7m9Frb7/4jwKcnyxbEzN3ieJppZsyZ4U1H
urzZau4mWVlNrGk0UuWPVa1YroXEIkHfrXZh5W90RWncAHmsi6bJNdQ0ZNw3BrGuiMGs2Mks
puBzWzbzdOaEBeOpR2oUtkk9hTru7iuo4m8wgemKyqTi04sBsfkEf68j8KlUQZz9o/SuZRj3
JYriAji4/Sp7W6htbV2aXcu70ramoxle4gN7HcIXjbis+4k5NdaaauhmVcv1rHuW61DGiG1m
6c1s20/TmgAv5vmj57VKk3+ixnPc1xVV70h9CVJuOtSrL71hFgxzScUkkn+iY/2q1i9xDrGT
9y31pJ5Otd1L+GhMy7mTrWXO2SapjRn28vTmta3nxjmgGOvJd2w1Kkv+ipz/ABGuOoveYdCe
ObjrU6y5rlsA8ycUksn+ij/eNaw6iJLNsW59zTJn6816FP4EJmbO+Saz5m602UjIgk4HNadv
LwKaBl+MpIMOMipp490SCJeF7CoqQvF2JuRqWQ4YEGrSiQJuKnHrXByMpki73GFBNXIoh9n2
SrnnOK6MPTbd3sSwIVF2qMCqkzHmuy1lYRnTHrVGWpZaMKB+BWlbycYoQM0IZDxzV+GU8c1a
IYy5Y+dnHatAsfsAHfArmS1mPoh1gT8x9qtk1rQX7tCe5DIapzGtGBQm71SlqGWjnIH6Vowt
zmhAy/E3vV6F6tEMuxlWIyAfrVxCCAO1VZEEyYA4AApxNGwyJ+lVJRUsaKMw61SlFQzRAP/Z
X-ATTRIBUTE:Some String
BDAY:19800101
GEO:35.6563854,139.6994233
URL:http://www.example.com/
REV:20080424T195243Z
END:VCARD

View file

@ -0,0 +1,10 @@
BEGIN:vCard
VERSION:2.1
UID:357
N:;Conference Call
FN:Conference Call
# This line must be ignored.
NOTE;ENCODING=QUOTED-PRINTABLE:This is an (sharp ->=
#<- sharp) example. This message must NOT be ignored.
# This line must be ignored too.
END:vCard

View file

@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
N;CHARSET=SHIFT_JIS:安藤ロイド;;;;
SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:アンドウロイド;;;;
TEL;PREF;VOICE:0300000000
END:VCARD

View file

@ -0,0 +1,10 @@
BEGIN:VCARD
VERSION:2.1
FN;CHARSET=SHIFT_JIS:安藤 ロイド 1
N;CHARSET=SHIFT_JIS:安藤;ロイド1;;;
SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:アンドウ;ロイド1;;;
ADR;HOME;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:;=93=8C=8B=9E=93=73=
=8F=61=92=4A=8B=E6=8D=F7=8B=75=92=AC26-1=83=5A=83=8B=83=8A=83=41=83=93=
=83=5E=83=8F=81=5B6=8A=4B;;;;150-8512;
NOTE;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:=83=81=83=82
END:VCARD

View file

@ -0,0 +1,33 @@
BEGIN:VCARD
VERSION:2.1
N;CHARSET=SHIFT_JIS:安藤ロイド3;;;;
SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:アンドウロイド3;;;;
TEL;X-NEC-SECRET:9
TEL;X-NEC-HOTEL:10
TEL;X-NEC-SCHOOL:11
TEL;HOME;FAX:12
END:VCARD
BEGIN:VCARD
VERSION:2.1
N;CHARSET=SHIFT_JIS:安藤ロイド4;;;;
SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:アンドウロイド4;;;;
TEL;MODEM:13
TEL;PAGER:14
TEL;X-NEC-FAMILY:15
TEL;X-NEC-GIRL:16
END:VCARD
BEGIN:VCARD
VERSION:2.1
N;CHARSET=SHIFT_JIS:安藤ロイド5;;;;
SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:アンドウロイド5;;;;
TEL;X-NEC-BOY:17
TEL;X-NEC-FRIEND:18
TEL;X-NEC-PHS:19
TEL;X-NEC-RESTAURANT:20
END:VCARD

View file

@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
FN:Normal Guy
ORG:Company;Organization;Devision;Room;Sheet No.
TITLE:Excellent Janitor
END:VCARD

View file

@ -0,0 +1,15 @@
BEGIN:VCARD
VERSION:2.1
FN:Smith
TEL;HOME:1
TEL;WORK;PREF:2
TEL;ISDN:3
EMAIL;PREF;HOME:test@example.com
EMAIL;CELL;PREF:test2@examination.com
ORG:Company
TITLE:Engineer
ORG:Mystery
TITLE:Blogger
ORG:Poetry
TITLE:Poet
END:VCARD

View file

@ -0,0 +1,3 @@
BEGIN:VCARD
N:Ando;Roid;
END:VCARD

View file

@ -0,0 +1,3 @@
BEGIN:VCARD
FN:Ando Roid
END:VCARD

View file

@ -0,0 +1,4 @@
BEGIN:VCARD
N:Ando;Roid;
FN:Ando Roid
END:VCARD

View file

@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
FN:Nice Guy
TITLE:Cool Title
ORG:Marverous;Perfect;Great;Good;Bad;Poor
END:VCARD

View file

@ -0,0 +1,10 @@
BEGIN:VCARD
VERSION:2.1
N:Example;;;;
FN:Example
ANNIVERSARY;VALUE=DATE:20091010
AGENT:Invalid line which must be handled correctly.
X-CLASS:PUBLIC
X-REDUCTION:
X-NO:
END:VCARD

View file

@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:3.0
N:F;G;M;;
TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
END:VCARD

View file

@ -0,0 +1,13 @@
BEGIN:VCARD
VERSION:3.0
FN:And Roid
N:And;Roid;;;
ORG:Open;Handset; Alliance
SORT-STRING:android
TEL;TYPE=PREF;TYPE=VOICE:0300000000
CLASS:PUBLIC
X-GNO:0
X-GN:group0
X-REDUCTION:0
REV:20081031T065854Z
END:VCARD

View file

@ -0,0 +1,971 @@
/*
* 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.tests;
import android.content.ContentValues;
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.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import com.android.vcard.VCardConfig;
import com.android.vcard.tests.test_utils.ContactEntry;
import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem;
import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem.TypeSet;
import java.util.Arrays;
/**
* Tests for the code related to vCard exporter, inculding vCard composer.
* This test class depends on vCard importer code, so if tests for vCard importer fail,
* the result of this class will not be reliable.
*/
public class VCardExporterTests extends VCardTestsBase {
private static final byte[] sPhotoByteArray =
VCardImporterTests.sPhotoByteArrayForComplicatedCase;
public void testSimpleV21() {
mVerifier.initForExportTest(V21);
mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Ando")
.put(StructuredName.GIVEN_NAME, "Roid");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "Roid Ando")
.addExpectedNode("N", "Ando;Roid;;;",
Arrays.asList("Ando", "Roid", "", "", ""));
}
private void testStructuredNameBasic(int vcardType) {
final boolean isV30 = VCardConfig.isV30(vcardType);
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
.put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix")
.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
.addExpectedNodeWithOrder("FN",
"AppropriatePrefix AppropriateGivenName "
+ "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
if (isV30) {
elem.addExpectedNode("SORT-STRING",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ "AppropriatePhoneticFamily");
}
}
public void testStructuredNameBasicV21() {
testStructuredNameBasic(V21);
}
public void testStructuredNameBasicV30() {
testStructuredNameBasic(V30);
}
/**
* Test that only "primary" StructuredName is emitted, so that our vCard file
* will not confuse the external importer, assuming there may be some importer
* which presume that there's only one property toward each of "N", "FN", etc.
* Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
*/
private void testStructuredNameUsePrimaryCommon(int vcardType) {
final boolean isV30 = (vcardType == V30);
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
.put(StructuredName.PREFIX, "DoNotEmitPrefix1")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
// With "IS_PRIMARY=1". This is what we should use.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
.put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix")
.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
.put(StructuredName.IS_PRIMARY, 1);
// With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
.put(StructuredName.PREFIX, "DoNotEmitPrefix2")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
.put(StructuredName.IS_PRIMARY, 1);
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
.addExpectedNodeWithOrder("FN",
"AppropriatePrefix AppropriateGivenName "
+ "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
if (isV30) {
elem.addExpectedNode("SORT-STRING",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ "AppropriatePhoneticFamily");
}
}
public void testStructuredNameUsePrimaryV21() {
testStructuredNameUsePrimaryCommon(V21);
}
public void testStructuredNameUsePrimaryV30() {
testStructuredNameUsePrimaryCommon(V30);
}
/**
* Tests that only "super primary" StructuredName is emitted.
* See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
*/
private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
final boolean isV30 = (vcardType == V30);
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
.put(StructuredName.PREFIX, "DoNotEmitPrefix1")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
// With "IS_PRIMARY=1", but we should ignore this time.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
.put(StructuredName.PREFIX, "DoNotEmitPrefix2")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
.put(StructuredName.IS_PRIMARY, 1);
// With "IS_SUPER_PRIMARY=1". This is what we should use.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
.put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix")
.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
.put(StructuredName.IS_SUPER_PRIMARY, 1);
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3")
.put(StructuredName.PREFIX, "DoNotEmitPrefix3")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix3")
.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily3")
.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven3")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
.put(StructuredName.IS_PRIMARY, 1);
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
.addExpectedNodeWithOrder("FN",
"AppropriatePrefix AppropriateGivenName "
+ "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
if (isV30) {
elem.addExpectedNode("SORT-STRING",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle"
+ " AppropriatePhoneticFamily");
}
}
public void testStructuredNameUseSuperPrimaryV21() {
testStructuredNameUseSuperPrimaryCommon(V21);
}
public void testStructuredNameUseSuperPrimaryV30() {
testStructuredNameUseSuperPrimaryCommon(V30);
}
public void testNickNameV30() {
mVerifier.initForExportTest(V30);
mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "Nicky");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNodeWithOrder("NICKNAME", "Nicky");
}
private void testPhoneBasicCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "1")
.put(Phone.TYPE, Phone.TYPE_HOME);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "1", new TypeSet("HOME"));
}
public void testPhoneBasicV21() {
testPhoneBasicCommon(V21);
}
public void testPhoneBasicV30() {
testPhoneBasicCommon(V30);
}
public void testPhoneRefrainFormatting() {
mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING);
mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "1234567890(abcdefghijklmnopqrstuvwxyz)")
.put(Phone.TYPE, Phone.TYPE_HOME);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "1234567890(abcdefghijklmnopqrstuvwxyz)",
new TypeSet("HOME"));
}
/**
* Tests that vCard composer emits corresponding type param which we expect.
*/
private void testPhoneVariousTypeSupport(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "10")
.put(Phone.TYPE, Phone.TYPE_HOME);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "20")
.put(Phone.TYPE, Phone.TYPE_WORK);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "30")
.put(Phone.TYPE, Phone.TYPE_FAX_HOME);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "40")
.put(Phone.TYPE, Phone.TYPE_FAX_WORK);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "50")
.put(Phone.TYPE, Phone.TYPE_MOBILE);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "60")
.put(Phone.TYPE, Phone.TYPE_PAGER);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "70")
.put(Phone.TYPE, Phone.TYPE_OTHER);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "80")
.put(Phone.TYPE, Phone.TYPE_CAR);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "90")
.put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "100")
.put(Phone.TYPE, Phone.TYPE_ISDN);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "110")
.put(Phone.TYPE, Phone.TYPE_MAIN);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "120")
.put(Phone.TYPE, Phone.TYPE_OTHER_FAX);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "130")
.put(Phone.TYPE, Phone.TYPE_TELEX);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "140")
.put(Phone.TYPE, Phone.TYPE_WORK_MOBILE);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "150")
.put(Phone.TYPE, Phone.TYPE_WORK_PAGER);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "160")
.put(Phone.TYPE, Phone.TYPE_MMS);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "10", new TypeSet("HOME"))
.addExpectedNode("TEL", "20", new TypeSet("WORK"))
.addExpectedNode("TEL", "30", new TypeSet("HOME", "FAX"))
.addExpectedNode("TEL", "40", new TypeSet("WORK", "FAX"))
.addExpectedNode("TEL", "50", new TypeSet("CELL"))
.addExpectedNode("TEL", "60", new TypeSet("PAGER"))
.addExpectedNode("TEL", "70", new TypeSet("VOICE"))
.addExpectedNode("TEL", "80", new TypeSet("CAR"))
.addExpectedNode("TEL", "90", new TypeSet("WORK", "PREF"))
.addExpectedNode("TEL", "100", new TypeSet("ISDN"))
.addExpectedNode("TEL", "110", new TypeSet("PREF"))
.addExpectedNode("TEL", "120", new TypeSet("FAX"))
.addExpectedNode("TEL", "130", new TypeSet("TLX"))
.addExpectedNode("TEL", "140", new TypeSet("WORK", "CELL"))
.addExpectedNode("TEL", "150", new TypeSet("WORK", "PAGER"))
.addExpectedNode("TEL", "160", new TypeSet("MSG"));
}
public void testPhoneVariousTypeSupportV21() {
testPhoneVariousTypeSupport(V21);
}
public void testPhoneVariousTypeSupportV30() {
testPhoneVariousTypeSupport(V30);
}
/**
* Tests that "PREF"s are emitted appropriately.
*/
private void testPhonePrefHandlingCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "1")
.put(Phone.TYPE, Phone.TYPE_HOME);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "2")
.put(Phone.TYPE, Phone.TYPE_WORK)
.put(Phone.IS_PRIMARY, 1);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "3")
.put(Phone.TYPE, Phone.TYPE_FAX_HOME)
.put(Phone.IS_PRIMARY, 1);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "4")
.put(Phone.TYPE, Phone.TYPE_FAX_WORK);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "4", new TypeSet("WORK", "FAX"))
.addExpectedNode("TEL", "3", new TypeSet("HOME", "FAX", "PREF"))
.addExpectedNode("TEL", "2", new TypeSet("WORK", "PREF"))
.addExpectedNode("TEL", "1", new TypeSet("HOME"));
}
public void testPhonePrefHandlingV21() {
testPhonePrefHandlingCommon(V21);
}
public void testPhonePrefHandlingV30() {
testPhonePrefHandlingCommon(V30);
}
private void testMiscPhoneTypeHandling(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "1")
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "Modem");
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "2")
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "MSG");
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "3")
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "BBS");
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "4")
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "VIDEO");
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "5")
.put(Phone.TYPE, Phone.TYPE_CUSTOM);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "6")
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "_AUTO_CELL"); // The old indicator for the type mobile.
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "7")
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "\u643A\u5E2F"); // Mobile phone in Japanese Kanji
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "8")
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "invalid");
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
elem.addExpectedNode("TEL", "1", new TypeSet("MODEM"))
.addExpectedNode("TEL", "2", new TypeSet("MSG"))
.addExpectedNode("TEL", "3", new TypeSet("BBS"))
.addExpectedNode("TEL", "4", new TypeSet("VIDEO"))
.addExpectedNode("TEL", "5", new TypeSet("VOICE"))
.addExpectedNode("TEL", "6", new TypeSet("CELL"))
.addExpectedNode("TEL", "7", new TypeSet("CELL"))
.addExpectedNode("TEL", "8", new TypeSet("X-invalid"));
}
public void testPhoneTypeHandlingV21() {
testMiscPhoneTypeHandling(V21);
}
public void testPhoneTypeHandlingV30() {
testMiscPhoneTypeHandling(V30);
}
private void testEmailBasicCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "sample@example.com");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("EMAIL", "sample@example.com");
}
public void testEmailBasicV21() {
testEmailBasicCommon(V21);
}
public void testEmailBasicV30() {
testEmailBasicCommon(V30);
}
private void testEmailVariousTypeSupportCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "type_home@example.com")
.put(Email.TYPE, Email.TYPE_HOME);
entry.addContentValues(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "type_work@example.com")
.put(Email.TYPE, Email.TYPE_WORK);
entry.addContentValues(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "type_mobile@example.com")
.put(Email.TYPE, Email.TYPE_MOBILE);
entry.addContentValues(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "type_other@example.com")
.put(Email.TYPE, Email.TYPE_OTHER);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME"))
.addExpectedNode("EMAIL", "type_work@example.com", new TypeSet("WORK"))
.addExpectedNode("EMAIL", "type_mobile@example.com", new TypeSet("CELL"))
.addExpectedNode("EMAIL", "type_other@example.com");
}
public void testEmailVariousTypeSupportV21() {
testEmailVariousTypeSupportCommon(V21);
}
public void testEmailVariousTypeSupportV30() {
testEmailVariousTypeSupportCommon(V30);
}
private void testEmailPrefHandlingCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "type_home@example.com")
.put(Email.TYPE, Email.TYPE_HOME)
.put(Email.IS_PRIMARY, 1);
entry.addContentValues(Email.CONTENT_ITEM_TYPE)
.put(Email.DATA, "type_notype@example.com")
.put(Email.IS_PRIMARY, 1);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("EMAIL", "type_notype@example.com", new TypeSet("PREF"))
.addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF"));
}
public void testEmailPrefHandlingV21() {
testEmailPrefHandlingCommon(V21);
}
public void testEmailPrefHandlingV30() {
testEmailPrefHandlingCommon(V30);
}
private void testPostalAddressCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "Pobox")
.put(StructuredPostal.NEIGHBORHOOD, "Neighborhood")
.put(StructuredPostal.STREET, "Street")
.put(StructuredPostal.CITY, "City")
.put(StructuredPostal.REGION, "Region")
.put(StructuredPostal.POSTCODE, "100")
.put(StructuredPostal.COUNTRY, "Country")
.put(StructuredPostal.FORMATTED_ADDRESS, "Formatted Address")
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK);
// adr-value = 0*6(text-value ";") text-value
// ; PO Box, Extended Address, Street, Locality, Region, Postal Code,
// ; Country Name
//
// The NEIGHBORHOOD field is appended after the CITY field.
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("ADR",
Arrays.asList("Pobox", "", "Street", "City Neighborhood",
"Region", "100", "Country"), new TypeSet("WORK"));
}
public void testPostalAddressV21() {
testPostalAddressCommon(V21);
}
public void testPostalAddressV30() {
testPostalAddressCommon(V30);
}
private void testPostalAddressNonNeighborhood(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.CITY, "City");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("ADR",
Arrays.asList("", "", "", "City", "", "", ""), new TypeSet("HOME"));
}
public void testPostalAddressNonNeighborhoodV21() {
testPostalAddressNonNeighborhood(V21);
}
public void testPostalAddressNonNeighborhoodV30() {
testPostalAddressNonNeighborhood(V30);
}
private void testPostalAddressNonCity(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.NEIGHBORHOOD, "Neighborhood");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("ADR",
Arrays.asList("", "", "", "Neighborhood", "", "", ""), new TypeSet("HOME"));
}
public void testPostalAddressNonCityV21() {
testPostalAddressNonCity(V21);
}
public void testPostalAddressNonCityV30() {
testPostalAddressNonCity(V30);
}
private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.REGION, "") // Must be ignored.
.put(StructuredPostal.FORMATTED_ADDRESS,
"Formatted address CA 123-334 United Statue");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;",
Arrays.asList("", "Formatted address CA 123-334 United Statue",
"", "", "", "", ""), new TypeSet("HOME"));
}
public void testPostalOnlyWithFormattedAddressV21() {
testPostalOnlyWithFormattedAddressCommon(V21);
}
public void testPostalOnlyWithFormattedAddressV30() {
testPostalOnlyWithFormattedAddressCommon(V30);
}
/**
* Tests that the vCard composer honors formatted data when it is available
* even when it is partial.
*/
private void testPostalWithBothStructuredAndFormattedCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "Pobox")
.put(StructuredPostal.COUNTRY, "Country")
.put(StructuredPostal.FORMATTED_ADDRESS,
"Formatted address CA 123-334 United Statue"); // Should be ignored
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("ADR", "Pobox;;;;;;Country",
Arrays.asList("Pobox", "", "", "", "", "", "Country"),
new TypeSet("HOME"));
}
public void testPostalWithBothStructuredAndFormattedV21() {
testPostalWithBothStructuredAndFormattedCommon(V21);
}
public void testPostalWithBothStructuredAndFormattedV30() {
testPostalWithBothStructuredAndFormattedCommon(V30);
}
private void testOrganizationCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
.put(Organization.COMPANY, "CompanyX")
.put(Organization.DEPARTMENT, "DepartmentY")
.put(Organization.TITLE, "TitleZ")
.put(Organization.JOB_DESCRIPTION, "Description Rambda") // Ignored.
.put(Organization.OFFICE_LOCATION, "Mountain View") // Ignored.
.put(Organization.PHONETIC_NAME, "PhoneticName!") // Ignored
.put(Organization.SYMBOL, "(^o^)/~~"); // Ignore him (her).
entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
.putNull(Organization.COMPANY)
.put(Organization.DEPARTMENT, "DepartmentXX")
.putNull(Organization.TITLE);
entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
.put(Organization.COMPANY, "CompanyXYZ")
.putNull(Organization.DEPARTMENT)
.put(Organization.TITLE, "TitleXYZYX");
// Currently we do not use group but depend on the order.
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNodeWithOrder("ORG", "CompanyX;DepartmentY",
Arrays.asList("CompanyX", "DepartmentY"))
.addExpectedNodeWithOrder("TITLE", "TitleZ")
.addExpectedNodeWithOrder("ORG", "DepartmentXX")
.addExpectedNodeWithOrder("ORG", "CompanyXYZ")
.addExpectedNodeWithOrder("TITLE", "TitleXYZYX");
}
public void testOrganizationV21() {
testOrganizationCommon(V21);
}
public void testOrganizationV30() {
testOrganizationCommon(V30);
}
private void testImVariousTypeSupportCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_AIM)
.put(Im.DATA, "aim");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_MSN)
.put(Im.DATA, "msn");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_YAHOO)
.put(Im.DATA, "yahoo");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_SKYPE)
.put(Im.DATA, "skype");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_QQ)
.put(Im.DATA, "qq");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
.put(Im.DATA, "google talk");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_ICQ)
.put(Im.DATA, "icq");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_JABBER)
.put(Im.DATA, "jabber");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING)
.put(Im.DATA, "netmeeting");
// No determined way to express unknown type...
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("X-JABBER", "jabber")
.addExpectedNode("X-ICQ", "icq")
.addExpectedNode("X-GOOGLE-TALK", "google talk")
.addExpectedNode("X-QQ", "qq")
.addExpectedNode("X-SKYPE-USERNAME", "skype")
.addExpectedNode("X-YAHOO", "yahoo")
.addExpectedNode("X-MSN", "msn")
.addExpectedNode("X-NETMEETING", "netmeeting")
.addExpectedNode("X-AIM", "aim");
}
public void testImBasiV21() {
testImVariousTypeSupportCommon(V21);
}
public void testImBasicV30() {
testImVariousTypeSupportCommon(V30);
}
private void testImPrefHandlingCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_AIM)
.put(Im.DATA, "aim1");
entry.addContentValues(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_AIM)
.put(Im.DATA, "aim2")
.put(Im.TYPE, Im.TYPE_HOME)
.put(Im.IS_PRIMARY, 1);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("X-AIM", "aim1")
.addExpectedNode("X-AIM", "aim2", new TypeSet("HOME", "PREF"));
}
public void testImPrefHandlingV21() {
testImPrefHandlingCommon(V21);
}
public void testImPrefHandlingV30() {
testImPrefHandlingCommon(V30);
}
private void testWebsiteCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Website.CONTENT_ITEM_TYPE)
.put(Website.URL, "http://website.example.android.com/index.html")
.put(Website.TYPE, Website.TYPE_BLOG);
entry.addContentValues(Website.CONTENT_ITEM_TYPE)
.put(Website.URL, "ftp://ftp.example.android.com/index.html")
.put(Website.TYPE, Website.TYPE_FTP);
// We drop TYPE information since vCard (especially 3.0) does not allow us to emit it.
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("URL", "ftp://ftp.example.android.com/index.html")
.addExpectedNode("URL", "http://website.example.android.com/index.html");
}
public void testWebsiteV21() {
testWebsiteCommon(V21);
}
public void testWebsiteV30() {
testWebsiteCommon(V30);
}
private String getAndroidPropValue(final String mimeType, String value, Integer type) {
return getAndroidPropValue(mimeType, value, type, null);
}
private String getAndroidPropValue(final String mimeType, String value,
Integer type, String label) {
return (mimeType + ";" + value + ";"
+ (type != null ? type : "") + ";"
+ (label != null ? label : "") + ";;;;;;;;;;;;");
}
private void testEventCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Event.CONTENT_ITEM_TYPE)
.put(Event.TYPE, Event.TYPE_ANNIVERSARY)
.put(Event.START_DATE, "1982-06-16");
entry.addContentValues(Event.CONTENT_ITEM_TYPE)
.put(Event.TYPE, Event.TYPE_BIRTHDAY)
.put(Event.START_DATE, "2008-10-22");
entry.addContentValues(Event.CONTENT_ITEM_TYPE)
.put(Event.TYPE, Event.TYPE_OTHER)
.put(Event.START_DATE, "2018-03-12");
entry.addContentValues(Event.CONTENT_ITEM_TYPE)
.put(Event.TYPE, Event.TYPE_CUSTOM)
.put(Event.LABEL, "The last day")
.put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed.");
entry.addContentValues(Event.CONTENT_ITEM_TYPE)
.put(Event.TYPE, Event.TYPE_BIRTHDAY)
.put(Event.START_DATE, "2009-05-19"); // Should be ignored.
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("BDAY", "2008-10-22")
.addExpectedNode("X-ANDROID-CUSTOM",
getAndroidPropValue(
Event.CONTENT_ITEM_TYPE, "1982-06-16", Event.TYPE_ANNIVERSARY))
.addExpectedNode("X-ANDROID-CUSTOM",
getAndroidPropValue(
Event.CONTENT_ITEM_TYPE, "2018-03-12", Event.TYPE_OTHER))
.addExpectedNode("X-ANDROID-CUSTOM",
getAndroidPropValue(
Event.CONTENT_ITEM_TYPE,
"When the Tower of Hanoi with 64 rings is completed.",
Event.TYPE_CUSTOM, "The last day"));
}
public void testEventV21() {
testEventCommon(V21);
}
public void testEventV30() {
testEventCommon(V30);
}
private void testNoteCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note1");
entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note2")
.put(Note.IS_PRIMARY, 1); // Just ignored.
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNodeWithOrder("NOTE", "note1")
.addExpectedNodeWithOrder("NOTE", "note2");
}
public void testNoteV21() {
testNoteCommon(V21);
}
public void testNoteV30() {
testNoteCommon(V30);
}
private void testPhotoCommon(int vcardType) {
final boolean isV30 = vcardType == V30;
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "PhotoTest");
entry.addContentValues(Photo.CONTENT_ITEM_TYPE)
.put(Photo.PHOTO, sPhotoByteArray);
ContentValues contentValuesForPhoto = new ContentValues();
contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "PhotoTest")
.addExpectedNode("N", "PhotoTest;;;;",
Arrays.asList("PhotoTest", "", "", "", ""))
.addExpectedNodeWithOrder("PHOTO", null, null, sPhotoByteArray,
contentValuesForPhoto, new TypeSet("JPEG"), null);
}
public void testPhotoV21() {
testPhotoCommon(V21);
}
public void testPhotoV30() {
testPhotoCommon(V30);
}
private void testRelationCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE)
.put(Relation.TYPE, Relation.TYPE_MOTHER)
.put(Relation.NAME, "Ms. Mother");
mVerifier.addContentValuesVerifierElem().addExpected(Relation.CONTENT_ITEM_TYPE)
.put(Relation.TYPE, Relation.TYPE_MOTHER)
.put(Relation.NAME, "Ms. Mother");
}
public void testRelationV21() {
testRelationCommon(V21);
}
public void testRelationV30() {
testRelationCommon(V30);
}
public void testV30HandleEscape() {
mVerifier.initForExportTest(V30);
mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\\")
.put(StructuredName.GIVEN_NAME, ";")
.put(StructuredName.MIDDLE_NAME, ",")
.put(StructuredName.PREFIX, "\n")
.put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]");
// Verifies the vCard String correctly escapes each character which must be escaped.
mVerifier.addLineVerifierElem()
.addExpected("N:\\\\;\\;;\\,;\\n;")
.addExpected("FN:[<{Unescaped:Asciis}>]");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "[<{Unescaped:Asciis}>]")
.addExpectedNode("N", Arrays.asList("\\", ";", ",", "\n", ""));
}
/**
* There's no "NICKNAME" property in vCard 2.1, while there is in vCard 3.0.
* We use Android-specific "X-ANDROID-CUSTOM" property.
* This test verifies the functionality.
*/
public void testNickNameV21() {
mVerifier.initForExportTest(V21);
mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "Nicky");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("X-ANDROID-CUSTOM",
Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;");
mVerifier.addContentValuesVerifierElem().addExpected(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "Nicky");
}
public void testTolerateBrokenPhoneNumberEntryV21() {
mVerifier.initForExportTest(V21);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.TYPE, Phone.TYPE_HOME)
.put(Phone.NUMBER, "111-222-3333 (Miami)\n444-5555-666 (Tokyo);"
+ "777-888-9999 (Chicago);111-222-3333 (Miami)");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "111-222-3333", new TypeSet("HOME"))
.addExpectedNode("TEL", "444-555-5666", new TypeSet("HOME"))
.addExpectedNode("TEL", "777-888-9999", new TypeSet("HOME"));
}
private void testPickUpNonEmptyContentValuesCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "family1"); // Not primary. Should be ignored.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.IS_PRIMARY, 1)
.put(StructuredName.FAMILY_NAME, "family2"); // This entry is what we want.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.IS_PRIMARY, 1)
.put(StructuredName.FAMILY_NAME, "family3");
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "family4");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("N", Arrays.asList("family2", "", "", "", ""))
.addExpectedNode("FN", "family2");
}
public void testPickUpNonEmptyContentValuesV21() {
testPickUpNonEmptyContentValuesCommon(V21);
}
public void testPickUpNonEmptyContentValuesV30() {
testPickUpNonEmptyContentValuesCommon(V30);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
/*
* 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.tests;
import android.content.ContentValues;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import com.android.vcard.VCardConfig;
import com.android.vcard.tests.test_utils.ContactEntry;
import com.android.vcard.tests.test_utils.ContentValuesBuilder;
import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem;
import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem.TypeSet;
import java.util.Arrays;
public class VCardJapanizationTests extends VCardTestsBase {
private void testNameUtf8Common(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
.put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
.put(StructuredName.MIDDLE_NAME, "B")
.put(StructuredName.PREFIX, "Dr.")
.put(StructuredName.SUFFIX, "Ph.D");
ContentValues contentValues =
(VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
contentValues)
.addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
Arrays.asList(
"\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
null, contentValues, null, null);
}
public void testNameUtf8V21() {
testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE);
}
public void testNameUtf8V30() {
testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE);
}
public void testNameShiftJis() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
.put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
.put(StructuredName.MIDDLE_NAME, "B")
.put(StructuredName.PREFIX, "Dr.")
.put(StructuredName.SUFFIX, "Ph.D");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
mContentValuesForSJis)
.addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
Arrays.asList(
"\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
null, mContentValuesForSJis, null, null);
}
/**
* DoCoMo phones require all name elements should be in "family name" field.
*/
public void testNameDoCoMo() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
.put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
.put(StructuredName.MIDDLE_NAME, "B")
.put(StructuredName.PREFIX, "Dr.")
.put(StructuredName.SUFFIX, "Ph.D");
final String fullName = "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D";
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("N", fullName + ";;;;",
Arrays.asList(fullName, "", "", "", ""),
null, mContentValuesForSJis, null, null)
.addExpectedNode("FN", fullName, mContentValuesForSJis)
.addExpectedNode("SOUND", ";;;;", new TypeSet("X-IRMC-N"))
.addExpectedNode("TEL", "", new TypeSet("HOME"))
.addExpectedNode("EMAIL", "", new TypeSet("HOME"))
.addExpectedNode("ADR", "", new TypeSet("HOME"))
.addExpectedNode("X-CLASS", "PUBLIC")
.addExpectedNode("X-REDUCTION", "")
.addExpectedNode("X-NO", "")
.addExpectedNode("X-DCM-HMN-MODE", "");
}
private void testPhoneticNameCommon(int vcardType, String charset) {
mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
final ContentValues contentValues =
("SHIFT_JIS".equalsIgnoreCase(charset) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
mContentValuesForQPAndSJis) :
(VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060",
contentValues)
.addExpectedNode("X-PHONETIC-MIDDLE-NAME",
"\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0",
contentValues)
.addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046",
contentValues);
if (VCardConfig.isV30(vcardType)) {
elem.addExpectedNode("SORT-STRING",
"\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046",
contentValues);
}
ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE);
builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046")
.put(StructuredName.DISPLAY_NAME,
"\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 " +
"\u305F\u308D\u3046");
}
public void testPhoneticNameForJapaneseV21Utf8() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, null);
}
public void testPhoneticNameForJapaneseV21Sjis() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
}
public void testPhoneticNameForJapaneseV30Utf8() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null);
}
public void testPhoneticNameForJapaneseV30SJis() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
}
public void testPhoneticNameForMobileV21_1() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("SOUND",
"\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
"\uFF80\uFF9B\uFF73;;;;",
mContentValuesForSJis, new TypeSet("X-IRMC-N"));
ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE);
builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
.put(StructuredName.PHONETIC_MIDDLE_NAME,
"\uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91")
.put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
.put(StructuredName.DISPLAY_NAME,
"\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
"\uFF80\uFF9B\uFF73");
}
public void testPhoneticNameForMobileV21_2() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("SOUND", "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73;;;;",
mContentValuesForSJis, new TypeSet("X-IRMC-N"));
ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE);
builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
.put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
.put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
}
private void testPostalAddressWithJapaneseCommon(int vcardType, String charset) {
mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
.put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
.put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
.put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
.put(StructuredPostal.POSTCODE, "494-1313")
.put(StructuredPostal.COUNTRY, "\u65E5\u672C")
.put(StructuredPostal.FORMATTED_ADDRESS,
"\u3053\u3093\u306A\u3068\u3053\u308D\u3092\u898B"
+ "\u308B\u306A\u3093\u3066\u6687\u4EBA\u3067\u3059\u304B\uFF1F")
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
ContentValues contentValues = ("UTF-8".equalsIgnoreCase(charset) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
mContentValuesForQPAndSJis) :
(VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
mContentValuesForQPAndUtf8));
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
// LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is
// same as that in vCard 3.0, which can be changed in the future.
elem.addExpectedNode("ADR", Arrays.asList("\u79C1\u66F8\u7BB107",
"", "\u96DB\u898B\u6CA2\u6751", "\u9E7F\u9AA8\u5E02", "\u00D7\u00D7\u770C",
"494-1313", "\u65E5\u672C"),
contentValues);
mVerifier.addContentValuesVerifierElem().addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
.put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
.put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
.put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
.put(StructuredPostal.POSTCODE, "494-1313")
.put(StructuredPostal.COUNTRY, "\u65E5\u672C")
.put(StructuredPostal.FORMATTED_ADDRESS,
"\u65E5\u672C 494-1313 \u00D7\u00D7\u770C \u9E7F\u9AA8\u5E02 " +
"\u96DB\u898B\u6CA2\u6751 " + "\u79C1\u66F8\u7BB107")
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
}
public void testPostalAddresswithJapaneseV21() {
testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
}
/**
* Verifies that only one address field is emitted toward DoCoMo phones.
* Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
*/
public void testPostalAdrressForDoCoMo_1() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
.put(StructuredPostal.POBOX, "1");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
.put(StructuredPostal.POBOX, "2");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
.put(StructuredPostal.POBOX, "3");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "custom")
.put(StructuredPostal.POBOX, "4");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "", new TypeSet("HOME"))
.addExpectedNode("EMAIL", "", new TypeSet("HOME"))
.addExpectedNode("X-CLASS", "PUBLIC")
.addExpectedNode("X-REDUCTION", "")
.addExpectedNode("X-NO", "")
.addExpectedNode("X-DCM-HMN-MODE", "")
.addExpectedNode("ADR",
Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
}
public void testPostalAdrressForDoCoMo_2() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
.put(StructuredPostal.POBOX, "1");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
.put(StructuredPostal.POBOX, "2");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "custom")
.put(StructuredPostal.POBOX, "3");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "", new TypeSet("HOME"))
.addExpectedNode("EMAIL", "", new TypeSet("HOME"))
.addExpectedNode("X-CLASS", "PUBLIC")
.addExpectedNode("X-REDUCTION", "")
.addExpectedNode("X-NO", "")
.addExpectedNode("X-DCM-HMN-MODE", "")
.addExpectedNode("ADR",
Arrays.asList("2", "", "", "", "", "", ""), new TypeSet("WORK"));
}
public void testPostalAdrressForDoCoMo_3() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "custom1")
.put(StructuredPostal.POBOX, "1");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
.put(StructuredPostal.POBOX, "2");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "custom2")
.put(StructuredPostal.POBOX, "3");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "", new TypeSet("HOME"))
.addExpectedNode("EMAIL", "", new TypeSet("HOME"))
.addExpectedNode("X-CLASS", "PUBLIC")
.addExpectedNode("X-REDUCTION", "")
.addExpectedNode("X-NO", "")
.addExpectedNode("X-DCM-HMN-MODE", "")
.addExpectedNode("ADR", Arrays.asList("2", "", "", "", "", "", ""));
}
/**
* Verifies the vCard exporter tolerates null TYPE.
*/
public void testPostalAdrressForDoCoMo_4() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "1");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
.put(StructuredPostal.POBOX, "2");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
.put(StructuredPostal.POBOX, "3");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
.put(StructuredPostal.POBOX, "4");
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "5");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "", new TypeSet("HOME"))
.addExpectedNode("EMAIL", "", new TypeSet("HOME"))
.addExpectedNode("X-CLASS", "PUBLIC")
.addExpectedNode("X-REDUCTION", "")
.addExpectedNode("X-NO", "")
.addExpectedNode("X-DCM-HMN-MODE", "")
.addExpectedNode("ADR",
Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
}
private void testJapanesePhoneNumberCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "0312341234")
.put(Phone.TYPE, Phone.TYPE_HOME);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "09012341234")
.put(Phone.TYPE, Phone.TYPE_MOBILE);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
.addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
}
public void testJapanesePhoneNumberV21_1() {
testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE);
}
public void testJapanesePhoneNumberV30() {
testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE);
}
public void testJapanesePhoneNumberDoCoMo() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "0312341234")
.put(Phone.TYPE, Phone.TYPE_HOME);
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "09012341234")
.put(Phone.TYPE, Phone.TYPE_MOBILE);
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("EMAIL", "", new TypeSet("HOME"))
.addExpectedNode("X-CLASS", "PUBLIC")
.addExpectedNode("X-REDUCTION", "")
.addExpectedNode("X-NO", "")
.addExpectedNode("X-DCM-HMN-MODE", "")
.addExpectedNode("ADR", "", new TypeSet("HOME"))
.addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
.addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
}
public void testNoteDoCoMo() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note1");
entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note2");
entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note3");
// More than one note fields must be aggregated into one note.
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "", new TypeSet("HOME"))
.addExpectedNode("EMAIL", "", new TypeSet("HOME"))
.addExpectedNode("X-CLASS", "PUBLIC")
.addExpectedNode("X-REDUCTION", "")
.addExpectedNode("X-NO", "")
.addExpectedNode("X-DCM-HMN-MODE", "")
.addExpectedNode("ADR", "", new TypeSet("HOME"))
.addExpectedNode("NOTE", "note1\nnote2\nnote3", mContentValuesForQP);
}
public void testAndroidCustomV21() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC);
mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("X-ANDROID-CUSTOM",
Arrays.asList(Nickname.CONTENT_ITEM_TYPE,
"\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC",
"", "", "", "", "", "", "", "", "", "", "", "", "", ""),
mContentValuesForQPAndUtf8);
}
}

View file

@ -0,0 +1,85 @@
/*
* 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.tests;
import android.content.ContentValues;
import android.test.AndroidTestCase;
import com.android.vcard.VCardConfig;
import com.android.vcard.tests.test_utils.VCardVerifier;
/**
* BaseClass for vCard unit tests with utility classes.
* Please do not add each unit test here.
*/
/* package */ class VCardTestsBase extends AndroidTestCase {
public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC;
public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC;
// Do not modify these during tests.
protected final ContentValues mContentValuesForQP;
protected final ContentValues mContentValuesForSJis;
protected final ContentValues mContentValuesForUtf8;
protected final ContentValues mContentValuesForQPAndSJis;
protected final ContentValues mContentValuesForQPAndUtf8;
protected final ContentValues mContentValuesForBase64V21;
protected final ContentValues mContentValuesForBase64V30;
protected VCardVerifier mVerifier;
private boolean mSkipVerification;
public VCardTestsBase() {
super();
// Not using constants in vCard code since it may be wrong.
mContentValuesForQP = new ContentValues();
mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
mContentValuesForSJis = new ContentValues();
mContentValuesForSJis.put("CHARSET", "SHIFT_JIS");
mContentValuesForUtf8 = new ContentValues();
mContentValuesForUtf8.put("CHARSET", "UTF-8");
mContentValuesForQPAndSJis = new ContentValues();
mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE");
mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS");
mContentValuesForQPAndUtf8 = new ContentValues();
mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE");
mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8");
mContentValuesForBase64V21 = new ContentValues();
mContentValuesForBase64V21.put("ENCODING", "BASE64");
mContentValuesForBase64V30 = new ContentValues();
mContentValuesForBase64V30.put("ENCODING", "b");
}
@Override
public void testAndroidTestCaseSetupProperly() {
super.testAndroidTestCaseSetupProperly();
mSkipVerification = true;
}
@Override
public void setUp() throws Exception{
super.setUp();
mVerifier = new VCardVerifier(this);
mSkipVerification = false;
}
@Override
public void tearDown() throws Exception {
super.tearDown();
if (!mSkipVerification) {
mVerifier.verify();
}
}
}

View file

@ -0,0 +1,84 @@
/*
* 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.tests;
import com.android.vcard.VCardUtils;
import junit.framework.TestCase;
import java.util.List;
public class VCardUtilsTests extends TestCase {
public void testContainsOnlyPrintableAscii() {
assertTrue(VCardUtils.containsOnlyPrintableAscii((String)null));
assertTrue(VCardUtils.containsOnlyPrintableAscii((String[])null));
assertTrue(VCardUtils.containsOnlyPrintableAscii((List<String>)null));
assertTrue(VCardUtils.containsOnlyPrintableAscii(""));
assertTrue(VCardUtils.containsOnlyPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
assertTrue(VCardUtils.containsOnlyPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
StringBuilder builder = new StringBuilder();
for (int i = 0x20; i < 0x7F; i++) {
builder.append((char)i);
}
assertTrue(VCardUtils.containsOnlyPrintableAscii(builder.toString()));
assertTrue(VCardUtils.containsOnlyPrintableAscii("\r\n"));
assertFalse(VCardUtils.containsOnlyPrintableAscii("\u0019"));
assertFalse(VCardUtils.containsOnlyPrintableAscii("\u007F"));
}
public void testContainsOnlyNonCrLfPrintableAscii() {
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String)null));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String[])null));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((List<String>)null));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(""));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
StringBuilder builder = new StringBuilder();
for (int i = 0x20; i < 0x7F; i++) {
builder.append((char)i);
}
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(builder.toString()));
assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u0019"));
assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u007F"));
assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\r"));
assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\n"));
}
public void testContainsOnlyAlphaDigitHyphen() {
assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String)null));
assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String[])null));
assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((List<String>)null));
assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen(""));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("0123456789-"));
for (int i = 0; i < 0x30; i++) {
if (i == 0x2D) { // -
continue;
}
assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
}
for (int i = 0x3A; i < 0x41; i++) {
assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
}
for (int i = 0x5B; i < 0x61; i++) {
assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
}
for (int i = 0x7B; i < 0x100; i++) {
assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
}
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.tests.test_utils;
import android.content.ContentValues;
import android.provider.ContactsContract.Data;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* The class representing one contact, which should contain multiple ContentValues like
* StructuredName, Email, etc.
* </p>
*/
public final class ContactEntry {
private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
public ContentValuesBuilder addContentValues(String mimeType) {
ContentValues contentValues = new ContentValues();
contentValues.put(Data.MIMETYPE, mimeType);
mContentValuesList.add(contentValues);
return new ContentValuesBuilder(contentValues);
}
public List<ContentValues> getList() {
return mContentValuesList;
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.tests.test_utils;
import android.content.ContentValues;
/**
* ContentValues-like class which enables users to chain put() methods and restricts
* the other methods.
*/
public class ContentValuesBuilder {
private final ContentValues mContentValues;
public ContentValuesBuilder(final ContentValues contentValues) {
mContentValues = contentValues;
}
public ContentValuesBuilder put(String key, String value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, Byte value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, Short value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, Integer value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, Long value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, Float value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, Double value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, Boolean value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder put(String key, byte[] value) {
mContentValues.put(key, value);
return this;
}
public ContentValuesBuilder putNull(String key) {
mContentValues.putNull(key);
return this;
}
}

View file

@ -0,0 +1,102 @@
/*
* 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.tests.test_utils;
import android.test.AndroidTestCase;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntryHandler;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class ContentValuesVerifier implements VCardEntryHandler {
private AndroidTestCase mTestCase;
private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
new ArrayList<ContentValuesVerifierElem>();
private int mIndex;
public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
mTestCase = androidTestCase;
ContentValuesVerifierElem importVerifier = new ContentValuesVerifierElem(androidTestCase);
mContentValuesVerifierElemList.add(importVerifier);
return importVerifier;
}
public void verify(int resId, int vCardType) throws IOException, VCardException {
verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
}
public void verify(int resId, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
verify(mTestCase.getContext().getResources().openRawResource(resId),
vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
verify(is, vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
VCardEntryConstructor builder =
new VCardEntryConstructor(vCardType, null, null, false);
builder.addEntryHandler(this);
try {
vCardParser.parse(is, builder);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
public void onStart() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingStart();
}
}
public void onEntryCreated(VCardEntry entry) {
mTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size());
mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry);
mIndex++;
}
public void onEnd() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingEnd();
elem.verifyResolver();
}
}
}

View file

@ -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.tests.test_utils;
import android.content.ContentValues;
import android.provider.ContactsContract.Data;
import android.test.AndroidTestCase;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryCommitter;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntryHandler;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
public class ContentValuesVerifierElem {
private final AndroidTestCase mTestCase;
private final ImportTestResolver mResolver;
private final VCardEntryHandler mHandler;
public ContentValuesVerifierElem(AndroidTestCase androidTestCase) {
mTestCase = androidTestCase;
mResolver = new ImportTestResolver(androidTestCase);
mHandler = new VCardEntryCommitter(mResolver);
}
public ContentValuesBuilder addExpected(String mimeType) {
ContentValues contentValues = new ContentValues();
contentValues.put(Data.MIMETYPE, mimeType);
mResolver.addExpectedContentValues(contentValues);
return new ContentValuesBuilder(contentValues);
}
public void verify(int resId, int vCardType)
throws IOException, VCardException {
verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
}
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
VCardEntryConstructor builder =
new VCardEntryConstructor(vCardType, null, null, false);
builder.addEntryHandler(mHandler);
try {
vCardParser.parse(is, builder);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
verifyResolver();
}
public void verifyResolver() {
mResolver.verify();
}
public void onParsingStart() {
mHandler.onStart();
}
public void onEntryCreated(VCardEntry entry) {
mHandler.onEntryCreated(entry);
}
public void onParsingEnd() {
mHandler.onEnd();
}
}

View file

@ -0,0 +1,176 @@
/*
* 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.tests.test_utils;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Entity;
import android.content.EntityIterator;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.test.mock.MockContentProvider;
import android.test.mock.MockCursor;
import com.android.vcard.VCardComposer;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/* package */ class ExportTestProvider extends MockContentProvider {
final private TestCase mTestCase;
final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
private static class MockEntityIterator implements EntityIterator {
List<Entity> mEntityList;
Iterator<Entity> mIterator;
public MockEntityIterator(List<ContentValues> contentValuesList) {
mEntityList = new ArrayList<Entity>();
Entity entity = new Entity(new ContentValues());
for (ContentValues contentValues : contentValuesList) {
entity.addSubValue(Data.CONTENT_URI, contentValues);
}
mEntityList.add(entity);
mIterator = mEntityList.iterator();
}
public boolean hasNext() {
return mIterator.hasNext();
}
public Entity next() {
return mIterator.next();
}
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
public void reset() {
mIterator = mEntityList.iterator();
}
public void close() {
}
}
public ExportTestProvider(TestCase testCase) {
mTestCase = testCase;
}
public ContactEntry buildInputEntry() {
ContactEntry contactEntry = new ContactEntry();
mContactEntryList.add(contactEntry);
return contactEntry;
}
/**
* <p>
* An old method which had existed but was removed from ContentResolver.
* </p>
* <p>
* We still keep using this method since we don't have a propeer way to know
* which value in the ContentValue corresponds to the entry in Contacts database.
* </p>
*/
public EntityIterator queryEntities(Uri uri,
String selection, String[] selectionArgs, String sortOrder) {
mTestCase.assertTrue(uri != null);
mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
final String authority = uri.getAuthority();
mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
mTestCase.assertEquals(1, selectionArgs.length);
final int id = Integer.parseInt(selectionArgs[0]);
mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size());
return new MockEntityIterator(mContactEntryList.get(id).getList());
}
@Override
public Cursor query(Uri uri,String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
// In this test, following arguments are not supported.
mTestCase.assertNull(selection);
mTestCase.assertNull(selectionArgs);
mTestCase.assertNull(sortOrder);
return new MockCursor() {
int mCurrentPosition = -1;
@Override
public int getCount() {
return mContactEntryList.size();
}
@Override
public boolean moveToFirst() {
mCurrentPosition = 0;
return true;
}
@Override
public boolean moveToNext() {
if (mCurrentPosition < mContactEntryList.size()) {
mCurrentPosition++;
return true;
} else {
return false;
}
}
@Override
public boolean isBeforeFirst() {
return mCurrentPosition < 0;
}
@Override
public boolean isAfterLast() {
return mCurrentPosition >= mContactEntryList.size();
}
@Override
public int getColumnIndex(String columnName) {
mTestCase.assertEquals(Contacts._ID, columnName);
return 0;
}
@Override
public int getInt(int columnIndex) {
mTestCase.assertEquals(0, columnIndex);
mTestCase.assertTrue(mCurrentPosition >= 0
&& mCurrentPosition < mContactEntryList.size());
return mCurrentPosition;
}
@Override
public String getString(int columnIndex) {
return String.valueOf(getInt(columnIndex));
}
@Override
public void close() {
}
};
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.tests.test_utils;
import android.provider.ContactsContract.RawContacts;
import android.test.mock.MockContentResolver;
import com.android.vcard.VCardComposer;
import junit.framework.TestCase;
/* package */ class ExportTestResolver extends MockContentResolver {
private final ExportTestProvider mProvider;
public ExportTestResolver(TestCase testCase) {
mProvider = new ExportTestProvider(testCase);
addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
}
public ContactEntry addInputContactEntry() {
return mProvider.buildInputEntry();
}
public ExportTestProvider getProvider() {
return mProvider;
}
}

View file

@ -0,0 +1,274 @@
/*
* 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.tests.test_utils;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.net.Uri;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
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.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.test.mock.MockContentProvider;
import android.text.TextUtils;
import android.util.Log;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry;
/* package */ class ImportTestProvider extends MockContentProvider {
private static final Set<String> sKnownMimeTypeSet =
new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE,
Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE,
Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE,
Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE,
Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
GroupMembership.CONTENT_ITEM_TYPE));
final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
private final TestCase mTestCase;
public ImportTestProvider(TestCase testCase) {
mTestCase = testCase;
mMimeTypeToExpectedContentValues =
new HashMap<String, Collection<ContentValues>>();
for (String acceptanbleMimeType : sKnownMimeTypeSet) {
// Do not use HashSet since the current implementation changes the content of
// ContentValues after the insertion, which make the result of hashCode()
// changes...
mMimeTypeToExpectedContentValues.put(
acceptanbleMimeType, new ArrayList<ContentValues>());
}
}
public void addExpectedContentValues(ContentValues expectedContentValues) {
final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
if (!sKnownMimeTypeSet.contains(mimeType)) {
mTestCase.fail(String.format(
"Unknow MimeType %s in the test code. Test code should be broken.",
mimeType));
}
final Collection<ContentValues> contentValuesCollection =
mMimeTypeToExpectedContentValues.get(mimeType);
contentValuesCollection.add(expectedContentValues);
}
@Override
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations) {
if (operations == null) {
mTestCase.fail("There is no operation.");
}
final int size = operations.size();
ContentProviderResult[] fakeResultArray = new ContentProviderResult[size];
for (int i = 0; i < size; i++) {
Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i));
fakeResultArray[i] = new ContentProviderResult(uri);
}
for (int i = 0; i < size; i++) {
ContentProviderOperation operation = operations.get(i);
ContentValues contentValues = operation.resolveValueBackReferences(
fakeResultArray, i);
}
for (int i = 0; i < size; i++) {
ContentProviderOperation operation = operations.get(i);
ContentValues actualContentValues = operation.resolveValueBackReferences(
fakeResultArray, i);
final Uri uri = operation.getUri();
if (uri.equals(RawContacts.CONTENT_URI)) {
mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
} else if (uri.equals(Data.CONTENT_URI)) {
final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
if (!sKnownMimeTypeSet.contains(mimeType)) {
mTestCase.fail(String.format(
"Unknown MimeType %s. Probably added after developing this test",
mimeType));
}
// Remove data meaningless in this unit tests.
// Specifically, Data.DATA1 - DATA7 are set to null or empty String
// regardless of the input, but it may change depending on how
// resolver-related code handles it.
// Here, we ignore these implementation-dependent specs and
// just check whether vCard importer correctly inserts rellevent data.
Set<String> keyToBeRemoved = new HashSet<String>();
for (Entry<String, Object> entry : actualContentValues.valueSet()) {
Object value = entry.getValue();
if (value == null || TextUtils.isEmpty(value.toString())) {
keyToBeRemoved.add(entry.getKey());
}
}
for (String key: keyToBeRemoved) {
actualContentValues.remove(key);
}
/* for testing
Log.d("@@@",
String.format("MimeType: %s, data: %s",
mimeType, actualContentValues.toString())); */
// Remove RAW_CONTACT_ID entry just for safety, since we do not care
// how resolver-related code handles the entry in this unit test,
if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) {
actualContentValues.remove(Data.RAW_CONTACT_ID);
}
final Collection<ContentValues> contentValuesCollection =
mMimeTypeToExpectedContentValues.get(mimeType);
if (contentValuesCollection.isEmpty()) {
mTestCase.fail("ContentValues for MimeType " + mimeType
+ " is not expected at all (" + actualContentValues + ")");
}
boolean checked = false;
for (ContentValues expectedContentValues : contentValuesCollection) {
/*for testing
Log.d("@@@", "expected: "
+ convertToEasilyReadableString(expectedContentValues));
Log.d("@@@", "actual : "
+ convertToEasilyReadableString(actualContentValues));*/
if (equalsForContentValues(expectedContentValues,
actualContentValues)) {
mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
checked = true;
break;
}
}
if (!checked) {
final StringBuilder builder = new StringBuilder();
builder.append("Unexpected: ");
builder.append(convertToEasilyReadableString(actualContentValues));
builder.append("\nExpected: ");
for (ContentValues expectedContentValues : contentValuesCollection) {
builder.append(convertToEasilyReadableString(expectedContentValues));
}
mTestCase.fail(builder.toString());
}
} else {
mTestCase.fail("Unexpected Uri has come: " + uri);
}
} // for (int i = 0; i < size; i++) {
return fakeResultArray;
}
public void verify() {
StringBuilder builder = new StringBuilder();
for (Collection<ContentValues> contentValuesCollection :
mMimeTypeToExpectedContentValues.values()) {
for (ContentValues expectedContentValues: contentValuesCollection) {
builder.append(convertToEasilyReadableString(expectedContentValues));
builder.append("\n");
}
}
if (builder.length() > 0) {
final String failMsg =
"There is(are) remaining expected ContentValues instance(s): \n"
+ builder.toString();
mTestCase.fail(failMsg);
}
}
/**
* Utility method to print ContentValues whose content is printed with sorted keys.
*/
private String convertToEasilyReadableString(ContentValues contentValues) {
if (contentValues == null) {
return "null";
}
String mimeTypeValue = "";
SortedMap<String, String> sortedMap = new TreeMap<String, String>();
for (Entry<String, Object> entry : contentValues.valueSet()) {
final String key = entry.getKey();
final Object value = entry.getValue();
final String valueString = (value != null ? value.toString() : null);
if (Data.MIMETYPE.equals(key)) {
mimeTypeValue = valueString;
} else {
mTestCase.assertNotNull(key);
sortedMap.put(key, valueString);
}
}
StringBuilder builder = new StringBuilder();
builder.append(Data.MIMETYPE);
builder.append('=');
builder.append(mimeTypeValue);
for (Entry<String, String> entry : sortedMap.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
builder.append(' ');
builder.append(key);
builder.append("=\"");
builder.append(value);
builder.append('"');
}
return builder.toString();
}
private static boolean equalsForContentValues(
ContentValues expected, ContentValues actual) {
if (expected == actual) {
return true;
} else if (expected == null || actual == null || expected.size() != actual.size()) {
return false;
}
for (Entry<String, Object> entry : expected.valueSet()) {
final String key = entry.getKey();
final Object value = entry.getValue();
if (!actual.containsKey(key)) {
return false;
}
if (value instanceof byte[]) {
Object actualValue = actual.get(key);
if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
byte[] e = (byte[])value;
byte[] a = (byte[])actualValue;
Log.d("@@@", "expected (len: " + e.length + "): " + Arrays.toString(e));
Log.d("@@@", "actual (len: " + a.length + "): " + Arrays.toString(a));
return false;
}
} else if (!value.equals(actual.get(key))) {
Log.d("@@@", "different.");
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,57 @@
/*
* 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.tests.test_utils;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.provider.ContactsContract.RawContacts;
import android.test.mock.MockContentResolver;
import junit.framework.TestCase;
import java.util.ArrayList;
/* package */ class ImportTestResolver extends MockContentResolver {
private final ImportTestProvider mProvider;
public ImportTestResolver(TestCase testCase) {
mProvider = new ImportTestProvider(testCase);
}
@Override
public ContentProviderResult[] applyBatch(String authority,
ArrayList<ContentProviderOperation> operations) {
equalsString(authority, RawContacts.CONTENT_URI.toString());
return mProvider.applyBatch(operations);
}
public void addExpectedContentValues(ContentValues expectedContentValues) {
mProvider.addExpectedContentValues(expectedContentValues);
}
public void verify() {
mProvider.verify();
}
private static boolean equalsString(String a, String b) {
if (a == null || a.length() == 0) {
return b == null || b.length() == 0;
} else {
return a.equals(b);
}
}
}

View file

@ -0,0 +1,66 @@
/*
* 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.tests.test_utils;
import com.android.vcard.VCardComposer;
import android.content.Context;
import junit.framework.TestCase;
import java.util.ArrayList;
public class LineVerifier implements VCardComposer.OneEntryHandler {
private final TestCase mTestCase;
private final ArrayList<LineVerifierElem> mLineVerifierElemList;
private int mVCardType;
private int index;
public LineVerifier(TestCase testCase, int vcardType) {
mTestCase = testCase;
mLineVerifierElemList = new ArrayList<LineVerifierElem>();
mVCardType = vcardType;
}
public LineVerifierElem addLineVerifierElem() {
LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType);
mLineVerifierElemList.add(lineVerifier);
return lineVerifier;
}
public void verify(String vcard) {
if (index >= mLineVerifierElemList.size()) {
mTestCase.fail("Insufficient number of LineVerifier (" + index + ")");
}
LineVerifierElem lineVerifier = mLineVerifierElemList.get(index);
lineVerifier.verify(vcard);
index++;
}
public boolean onEntryCreated(String vcard) {
verify(vcard);
return true;
}
public boolean onInit(Context context) {
return true;
}
public void onTerminate() {
}
}

View file

@ -0,0 +1,106 @@
/*
* 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.tests.test_utils;
import android.text.TextUtils;
import com.android.vcard.VCardConfig;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.List;
public class LineVerifierElem {
private final TestCase mTestCase;
private final List<String> mExpectedLineList = new ArrayList<String>();
private final boolean mIsV30;
public LineVerifierElem(TestCase testCase, int vcardType) {
mTestCase = testCase;
mIsV30 = VCardConfig.isV30(vcardType);
}
public LineVerifierElem addExpected(final String line) {
if (!TextUtils.isEmpty(line)) {
mExpectedLineList.add(line);
}
return this;
}
public void verify(final String vcard) {
final String[] lineArray = vcard.split("\\r?\\n");
final int length = lineArray.length;
boolean beginExists = false;
boolean endExists = false;
boolean versionExists = false;
for (int i = 0; i < length; i++) {
final String line = lineArray[i];
if (TextUtils.isEmpty(line)) {
continue;
}
if ("BEGIN:VCARD".equalsIgnoreCase(line)) {
if (beginExists) {
mTestCase.fail("Multiple \"BEGIN:VCARD\" line found");
} else {
beginExists = true;
continue;
}
} else if ("END:VCARD".equalsIgnoreCase(line)) {
if (endExists) {
mTestCase.fail("Multiple \"END:VCARD\" line found");
} else {
endExists = true;
continue;
}
} else if ((mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) {
if (versionExists) {
mTestCase.fail("Multiple VERSION line + found");
} else {
versionExists = true;
continue;
}
}
if (!beginExists) {
mTestCase.fail("Property other than BEGIN came before BEGIN property: "
+ line);
} else if (endExists) {
mTestCase.fail("Property other than END came after END property: "
+ line);
}
final int index = mExpectedLineList.indexOf(line);
if (index >= 0) {
mExpectedLineList.remove(index);
} else {
mTestCase.fail("Unexpected line: " + line);
}
}
if (!mExpectedLineList.isEmpty()) {
StringBuffer buffer = new StringBuffer();
for (String expectedLine : mExpectedLineList) {
buffer.append(expectedLine);
buffer.append("\n");
}
mTestCase.fail("Expected line(s) not found:" + buffer.toString());
}
}
}

View file

@ -0,0 +1,205 @@
/*
* 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.tests.test_utils;
import android.content.ContentValues;
import com.android.vcard.VCardEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* <p>
* The class representing one property (e.g. "N;ENCODING=UTF-8:family:given:middle:prefix:suffix").
* </p>
* <p>
* Previously used in main vCard handling code but now exists only for testing.
* </p>
* <p>
* Especially useful for testing parser code (VCardParser), since all properties can be
* checked via this class unlike {@link VCardEntry}, which only emits the result of
* interpretation of the content of each vCard. We cannot know whether vCard parser or
* {@link VCardEntry} is wrong without this class.
* </p>
*/
public class PropertyNode {
public String propName;
public String propValue;
public List<String> propValue_vector;
/** Store value as byte[],after decode.
* Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc.
*/
public byte[] propValue_bytes;
/**
* param store: key=paramType, value=paramValue
* Note that currently PropertyNode class does not support multiple param-values
* defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
* one String value like "A,B", not ["A", "B"]...
* TODO: fix this.
*/
public ContentValues paramMap;
/** Only for TYPE=??? param store. */
public Set<String> paramMap_TYPE;
/** Store group values. Used only in VCard. */
public Set<String> propGroupSet;
public PropertyNode() {
propName = "";
propValue = "";
propValue_vector = new ArrayList<String>();
paramMap = new ContentValues();
paramMap_TYPE = new HashSet<String>();
propGroupSet = new HashSet<String>();
}
public PropertyNode(
String propName, String propValue, List<String> propValue_vector,
byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE,
Set<String> propGroupSet) {
if (propName != null) {
this.propName = propName;
} else {
this.propName = "";
}
if (propValue != null) {
this.propValue = propValue;
} else {
this.propValue = "";
}
if (propValue_vector != null) {
this.propValue_vector = propValue_vector;
} else {
this.propValue_vector = new ArrayList<String>();
}
this.propValue_bytes = propValue_bytes;
if (paramMap != null) {
this.paramMap = paramMap;
} else {
this.paramMap = new ContentValues();
}
if (paramMap_TYPE != null) {
this.paramMap_TYPE = paramMap_TYPE;
} else {
this.paramMap_TYPE = new HashSet<String>();
}
if (propGroupSet != null) {
this.propGroupSet = propGroupSet;
} else {
this.propGroupSet = new HashSet<String>();
}
}
@Override
public int hashCode() {
// vCard may contain more than one same line in one entry, while HashSet or any other
// library which utilize hashCode() does not honor that, so intentionally throw an
// Exception.
throw new UnsupportedOperationException(
"PropertyNode does not provide hashCode() implementation intentionally.");
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PropertyNode)) {
return false;
}
PropertyNode node = (PropertyNode)obj;
if (propName == null || !propName.equals(node.propName)) {
return false;
} else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
return false;
} else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
return false;
} else if (!propGroupSet.equals(node.propGroupSet)) {
return false;
}
if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) {
return true;
} else {
if (!propValue.equals(node.propValue)) {
return false;
}
// The value in propValue_vector is not decoded even if it should be
// decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector
// is 1, the encoded value is stored in propValue, so we do not have to
// check it.
return (propValue_vector.equals(node.propValue_vector) ||
propValue_vector.size() == 1 ||
node.propValue_vector.size() == 1);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("propName: ");
builder.append(propName);
builder.append(", paramMap: ");
builder.append(paramMap.toString());
builder.append(", paramMap_TYPE: [");
boolean first = true;
for (String elem : paramMap_TYPE) {
if (first) {
first = false;
} else {
builder.append(", ");
}
builder.append('"');
builder.append(elem);
builder.append('"');
}
builder.append("]");
if (!propGroupSet.isEmpty()) {
builder.append(", propGroupSet: [");
first = true;
for (String elem : propGroupSet) {
if (first) {
first = false;
} else {
builder.append(", ");
}
builder.append('"');
builder.append(elem);
builder.append('"');
}
builder.append("]");
}
if (propValue_vector != null && propValue_vector.size() > 1) {
builder.append(", propValue_vector size: ");
builder.append(propValue_vector.size());
}
if (propValue_bytes != null) {
builder.append(", propValue_bytes size: ");
builder.append(propValue_bytes.length);
}
builder.append(", propValue: \"");
builder.append(propValue);
builder.append("\"");
return builder.toString();
}
}

View file

@ -0,0 +1,91 @@
/*
* 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.tests.test_utils;
import android.test.AndroidTestCase;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class PropertyNodesVerifier extends VNodeBuilder {
private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
private final AndroidTestCase mAndroidTestCase;
private int mIndex;
public PropertyNodesVerifier(AndroidTestCase testCase) {
super();
mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
mAndroidTestCase = testCase;
}
public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
mPropertyNodesVerifierElemList.add(elem);
return elem;
}
public void verify(int resId, int vCardType)
throws IOException, VCardException {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
}
public void verify(int resId, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
vCardParser = new VCardParser_V30();
} else {
vCardParser = new VCardParser_V21();
}
verify(is, vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
try {
vCardParser.parse(is, this);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
@Override
public void endEntry() {
super.endEntry();
mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
mIndex++;
}
}

View file

@ -0,0 +1,317 @@
/*
* 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.tests.test_utils;
import android.content.ContentValues;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Utility class which verifies input VNode.
*
* This class first checks whether each propertyNode in the VNode is in the
* "ordered expected property list".
* If the node does not exist in the "ordered list", the class refers to
* "unorderd expected property set" and checks the node is expected somewhere.
*/
public class PropertyNodesVerifierElem {
public static class TypeSet extends HashSet<String> {
public TypeSet(String ... array) {
super(Arrays.asList(array));
}
}
public static class GroupSet extends HashSet<String> {
public GroupSet(String ... array) {
super(Arrays.asList(array));
}
}
private final HashMap<String, List<PropertyNode>> mOrderedNodeMap;
// Intentionally use ArrayList instead of Set, assuming there may be more than one
// exactly same objects.
private final ArrayList<PropertyNode> mUnorderedNodeList;
private final TestCase mTestCase;
public PropertyNodesVerifierElem(TestCase testCase) {
mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
mUnorderedNodeList = new ArrayList<PropertyNode>();
mTestCase = testCase;
}
// WithOrder
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue) {
return addExpectedNodeWithOrder(propName, propValue, null, null, null, null, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(
String propName, String propValue, ContentValues contentValues) {
return addExpectedNodeWithOrder(propName, propValue, null,
null, contentValues, null, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(
String propName, List<String> propValueList, ContentValues contentValues) {
return addExpectedNodeWithOrder(propName, null, propValueList,
null, contentValues, null, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(
String propName, String propValue, List<String> propValueList) {
return addExpectedNodeWithOrder(propName, propValue, propValueList, null,
null, null, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(
String propName, List<String> propValueList) {
final String propValue = concatinateListWithSemiColon(propValueList);
return addExpectedNodeWithOrder(propName, propValue.toString(), propValueList,
null, null, null, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
TypeSet paramMap_TYPE) {
return addExpectedNodeWithOrder(propName, propValue, null,
null, null, paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName,
List<String> propValueList, TypeSet paramMap_TYPE) {
return addExpectedNodeWithOrder(propName, null, propValueList, null, null,
paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
ContentValues paramMap, TypeSet paramMap_TYPE) {
return addExpectedNodeWithOrder(propName, propValue, null, null,
paramMap, paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
List<String> propValueList, TypeSet paramMap_TYPE) {
return addExpectedNodeWithOrder(propName, propValue, propValueList, null, null,
paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
List<String> propValueList, byte[] propValue_bytes,
ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
if (propValue == null && propValueList != null) {
propValue = concatinateListWithSemiColon(propValueList);
}
PropertyNode propertyNode = new PropertyNode(propName,
propValue, propValueList, propValue_bytes,
paramMap, paramMap_TYPE, propGroupSet);
List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
if (expectedNodeList == null) {
expectedNodeList = new ArrayList<PropertyNode>();
mOrderedNodeMap.put(propName, expectedNodeList);
}
expectedNodeList.add(propertyNode);
return this;
}
// WithoutOrder
public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue) {
return addExpectedNode(propName, propValue, null, null, null, null, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
ContentValues contentValues) {
return addExpectedNode(propName, propValue, null, null, contentValues, null, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName,
List<String> propValueList, ContentValues contentValues) {
return addExpectedNode(propName, null,
propValueList, null, contentValues, null, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
List<String> propValueList) {
return addExpectedNode(propName, propValue, propValueList, null, null, null, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName,
List<String> propValueList) {
return addExpectedNode(propName, null, propValueList,
null, null, null, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
TypeSet paramMap_TYPE) {
return addExpectedNode(propName, propValue, null, null, null, paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName,
List<String> propValueList, TypeSet paramMap_TYPE) {
final String propValue = concatinateListWithSemiColon(propValueList);
return addExpectedNode(propName, propValue, propValueList, null, null,
paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
List<String> propValueList, TypeSet paramMap_TYPE) {
return addExpectedNode(propName, propValue, propValueList, null, null,
paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
ContentValues paramMap, TypeSet paramMap_TYPE) {
return addExpectedNode(propName, propValue, null, null,
paramMap, paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
List<String> propValueList, byte[] propValue_bytes,
ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
if (propValue == null && propValueList != null) {
propValue = concatinateListWithSemiColon(propValueList);
}
mUnorderedNodeList.add(new PropertyNode(propName, propValue,
propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet));
return this;
}
public void verify(VNode vnode) {
for (PropertyNode actualNode : vnode.propList) {
verifyNode(actualNode.propName, actualNode);
}
if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) {
List<String> expectedProps = new ArrayList<String>();
for (List<PropertyNode> nodes : mOrderedNodeMap.values()) {
for (PropertyNode node : nodes) {
if (!expectedProps.contains(node.propName)) {
expectedProps.add(node.propName);
}
}
}
for (PropertyNode node : mUnorderedNodeList) {
if (!expectedProps.contains(node.propName)) {
expectedProps.add(node.propName);
}
}
mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+ " was not found.");
}
}
private void verifyNode(final String propName, final PropertyNode actualNode) {
List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
if (size > 0) {
for (int i = 0; i < size; i++) {
PropertyNode expectedNode = expectedNodeList.get(i);
List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>();
if (expectedNode.propName.equals(propName)) {
if (expectedNode.equals(actualNode)) {
expectedNodeList.remove(i);
if (expectedNodeList.size() == 0) {
mOrderedNodeMap.remove(propName);
}
return;
} else {
expectedButDifferentValueList.add(expectedNode);
}
}
// "actualNode" is not in ordered expected list.
// Try looking over unordered expected list.
if (tryFoundExpectedNodeFromUnorderedList(actualNode,
expectedButDifferentValueList)) {
return;
}
if (!expectedButDifferentValueList.isEmpty()) {
// Same propName exists but with different value(s).
failWithExpectedNodeList(propName, actualNode,
expectedButDifferentValueList);
} else {
// There's no expected node with same propName.
mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
}
}
} else {
List<PropertyNode> expectedButDifferentValueList =
new ArrayList<PropertyNode>();
if (tryFoundExpectedNodeFromUnorderedList(actualNode, expectedButDifferentValueList)) {
return;
} else {
if (!expectedButDifferentValueList.isEmpty()) {
// Same propName exists but with different value(s).
failWithExpectedNodeList(propName, actualNode,
expectedButDifferentValueList);
} else {
// There's no expected node with same propName.
mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
}
}
}
}
private String concatinateListWithSemiColon(List<String> array) {
StringBuffer buffer = new StringBuffer();
boolean first = true;
for (String propValueElem : array) {
if (first) {
first = false;
} else {
buffer.append(';');
}
buffer.append(propValueElem);
}
return buffer.toString();
}
private boolean tryFoundExpectedNodeFromUnorderedList(PropertyNode actualNode,
List<PropertyNode> expectedButDifferentValueList) {
final String propName = actualNode.propName;
int unorderedListSize = mUnorderedNodeList.size();
for (int i = 0; i < unorderedListSize; i++) {
PropertyNode unorderedExpectedNode = mUnorderedNodeList.get(i);
if (unorderedExpectedNode.propName.equals(propName)) {
if (unorderedExpectedNode.equals(actualNode)) {
mUnorderedNodeList.remove(i);
return true;
}
expectedButDifferentValueList.add(unorderedExpectedNode);
}
}
return false;
}
private void failWithExpectedNodeList(String propName, PropertyNode actualNode,
List<PropertyNode> expectedNodeList) {
StringBuilder builder = new StringBuilder();
for (PropertyNode expectedNode : expectedNodeList) {
builder.append("expected: ");
builder.append(expectedNode.toString());
builder.append("\n");
}
mTestCase.fail("Property \"" + propName + "\" has wrong value.\n"
+ builder.toString()
+ " actual: " + actualNode.toString());
}
}

View file

@ -0,0 +1,339 @@
/*
* 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.tests.test_utils;
import android.content.ContentResolver;
import android.content.Context;
import android.content.EntityIterator;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.test.mock.MockContext;
import android.text.TextUtils;
import android.util.Log;
import com.android.vcard.VCardComposer;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardInterpreterCollection;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* <p>
* The class lets users checks that given expected vCard data are same as given actual vCard data.
* Able to verify both vCard importer/exporter.
* </p>
* <p>
* First a user has to initialize the object by calling either
* {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}.
* "Round trip test" (import -> export -> import, or export -> import -> export) is not supported.
* </p>
*/
public class VCardVerifier {
private static final String LOG_TAG = "VCardVerifier";
private static class CustomMockContext extends MockContext {
final ContentResolver mResolver;
public CustomMockContext(ContentResolver resolver) {
mResolver = resolver;
}
@Override
public ContentResolver getContentResolver() {
return mResolver;
}
}
private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
public boolean onInit(Context context) {
return true;
}
public boolean onEntryCreated(String vcard) {
verifyOneVCard(vcard);
return true;
}
public void onTerminate() {
}
}
private final AndroidTestCase mTestCase;
private final VCardVerifierInternal mVCardVerifierInternal;
private int mVCardType;
private boolean mIsV30;
private boolean mIsDoCoMo;
// Only one of them must be non-empty.
private ExportTestResolver mExportTestResolver;
private InputStream mInputStream;
// To allow duplication, use list instead of set.
// When null, we don't need to do the verification.
private PropertyNodesVerifier mPropertyNodesVerifier;
private LineVerifier mLineVerifier;
private ContentValuesVerifier mContentValuesVerifier;
private boolean mInitialized;
private boolean mVerified = false;
private String mCharset;
// Called by VCardTestsBase
public VCardVerifier(AndroidTestCase testCase) {
mTestCase = testCase;
mVCardVerifierInternal = new VCardVerifierInternal();
mExportTestResolver = null;
mInputStream = null;
mInitialized = false;
mVerified = false;
}
// Should be called at the beginning of each import test.
public void initForImportTest(int vcardType, int resId) {
if (mInitialized) {
mTestCase.fail("Already initialized");
}
mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
setInputResourceId(resId);
mInitialized = true;
}
// Should be called at the beginning of each export test.
public void initForExportTest(int vcardType) {
initForExportTest(vcardType, "UTF-8");
}
public void initForExportTest(int vcardType, String charset) {
if (mInitialized) {
mTestCase.fail("Already initialized");
}
mExportTestResolver = new ExportTestResolver(mTestCase);
mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mInitialized = true;
if (TextUtils.isEmpty(charset)) {
mCharset = "UTF-8";
} else {
mCharset = charset;
}
}
private void setInputResourceId(int resId) {
InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
if (inputStream == null) {
mTestCase.fail("Wrong resId: " + resId);
}
setInputStream(inputStream);
}
private void setInputStream(InputStream inputStream) {
if (mExportTestResolver != null) {
mTestCase.fail("addInputEntry() is called.");
} else if (mInputStream != null) {
mTestCase.fail("InputStream is already set");
}
mInputStream = inputStream;
}
public ContactEntry addInputEntry() {
if (!mInitialized) {
mTestCase.fail("Not initialized");
}
if (mInputStream != null) {
mTestCase.fail("setInputStream is called");
}
return mExportTestResolver.addInputContactEntry();
}
public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
if (!mInitialized) {
mTestCase.fail("Not initialized");
}
if (mPropertyNodesVerifier == null) {
mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase);
}
PropertyNodesVerifierElem elem =
mPropertyNodesVerifier.addPropertyNodesVerifierElem();
elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
return elem;
}
public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
if (!mInitialized) {
mTestCase.fail("Not initialized");
}
PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
if (mIsV30) {
elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", "");
} else if (mIsDoCoMo) {
elem.addExpectedNodeWithOrder("N", "");
}
return elem;
}
public LineVerifierElem addLineVerifierElem() {
if (!mInitialized) {
mTestCase.fail("Not initialized");
}
if (mLineVerifier == null) {
mLineVerifier = new LineVerifier(mTestCase, mVCardType);
}
return mLineVerifier.addLineVerifierElem();
}
public ContentValuesVerifierElem addContentValuesVerifierElem() {
if (!mInitialized) {
mTestCase.fail("Not initialized");
}
if (mContentValuesVerifier == null) {
mContentValuesVerifier = new ContentValuesVerifier();
}
return mContentValuesVerifier.addElem(mTestCase);
}
private void verifyOneVCard(final String vcard) {
Log.d(LOG_TAG, vcard);
final VCardInterpreter builder;
if (mContentValuesVerifier != null) {
final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
final VCardEntryConstructor vcardDataBuilder =
new VCardEntryConstructor(mVCardType);
vcardDataBuilder.addEntryHandler(mContentValuesVerifier);
if (mPropertyNodesVerifier != null) {
builder = new VCardInterpreterCollection(Arrays.asList(
mPropertyNodesVerifier, vcardDataBuilder));
} else {
builder = vnodeBuilder;
}
} else {
if (mPropertyNodesVerifier != null) {
builder = mPropertyNodesVerifier;
} else {
return;
}
}
InputStream is = null;
try {
// Note: we must not specify charset toward vCard parsers. This code checks whether
// those parsers are able to encode given binary without any extra information for
// charset.
final VCardParser parser = (mIsV30 ?
new VCardParser_V30(mVCardType) : new VCardParser_V21(mVCardType));
is = new ByteArrayInputStream(vcard.getBytes(mCharset));
parser.parse(is, builder);
} catch (IOException e) {
mTestCase.fail("Unexpected IOException: " + e.getMessage());
} catch (VCardException e) {
mTestCase.fail("Unexpected VCardException: " + e.getMessage());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
mTestCase.fail("Unexpected IOException: " + e.getMessage());
}
}
}
}
public void verify() {
if (!mInitialized) {
mTestCase.fail("Not initialized.");
}
if (mVerified) {
mTestCase.fail("verify() was called twice.");
}
if (mInputStream != null) {
try {
verifyForImportTest();
} catch (IOException e) {
mTestCase.fail("IOException was thrown: " + e.getMessage());
} catch (VCardException e) {
mTestCase.fail("VCardException was thrown: " + e.getMessage());
}
} else if (mExportTestResolver != null){
verifyForExportTest();
} else {
mTestCase.fail("No input is determined");
}
mVerified = true;
}
private void verifyForImportTest() throws IOException, VCardException {
if (mLineVerifier != null) {
mTestCase.fail("Not supported now.");
}
if (mContentValuesVerifier != null) {
mContentValuesVerifier.verify(mInputStream, mVCardType);
}
}
public static EntityIterator mockGetEntityIteratorMethod(
final ContentResolver resolver,
final Uri uri, final String selection,
final String[] selectionArgs, final String sortOrder) {
if (ExportTestResolver.class.equals(resolver.getClass())) {
return ((ExportTestResolver)resolver).getProvider().queryEntities(
uri, selection, selectionArgs, sortOrder);
}
Log.e(LOG_TAG, "Unexpected provider given.");
return null;
}
private Method getMockGetEntityIteratorMethod()
throws SecurityException, NoSuchMethodException {
return this.getClass().getMethod("mockGetEntityIteratorMethod",
ContentResolver.class, Uri.class, String.class, String[].class, String.class);
}
private void verifyForExportTest() {
final VCardComposer composer =
new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset);
composer.addHandler(mLineVerifier);
composer.addHandler(mVCardVerifierInternal);
if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
mTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
}
mTestCase.assertFalse(composer.isAfterLast());
try {
while (!composer.isAfterLast()) {
try {
final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
mTestCase.assertNotNull(mockGetEntityIteratorMethod);
mTestCase.assertTrue(composer.createOneEntry(mockGetEntityIteratorMethod));
} catch (Exception e) {
e.printStackTrace();
mTestCase.fail();
}
}
} finally {
composer.terminate();
}
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.tests.test_utils;
import java.util.ArrayList;
/**
* Previously used in main vCard handling code but now exists only for testing.
*/
public class VNode {
public String VName;
public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>();
/** 0:parse over. 1:parsing. */
public int parseStatus = 1;
}

View file

@ -0,0 +1,247 @@
/*
* 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.tests.test_utils;
import android.content.ContentValues;
import android.util.Base64;
import android.util.CharsetUtils;
import android.util.Log;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardUtils;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* The class storing the parse result to custom datastruct:
* {@link VNode}, and {@link PropertyNode}.
* Maybe several vcard instance, so use vNodeList to store.
* </p>
* <p>
* This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
* </p>
*/
/* package */ class VNodeBuilder implements VCardInterpreter {
static private String LOG_TAG = "VNodeBuilder";
public List<VNode> vNodeList = new ArrayList<VNode>();
private int mNodeListPos = 0;
private VNode mCurrentVNode;
private PropertyNode mCurrentPropNode;
private String mCurrentParamType;
/**
* The charset using which VParser parses the text.
*/
private String mSourceCharset;
/**
* The charset with which byte array is encoded to String.
*/
private String mTargetCharset;
private boolean mStrictLineBreakParsing;
public VNodeBuilder() {
this(VCardConfig.DEFAULT_INTERMEDIATE_CHARSET, VCardConfig.DEFAULT_IMPORT_CHARSET, false);
}
public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
this(null, targetCharset, strictLineBreakParsing);
}
/**
* @hide sourceCharset is temporal.
*/
public VNodeBuilder(String sourceCharset, String targetCharset,
boolean strictLineBreakParsing) {
if (sourceCharset != null) {
mSourceCharset = sourceCharset;
} else {
mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
}
if (targetCharset != null) {
mTargetCharset = targetCharset;
} else {
mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
mStrictLineBreakParsing = strictLineBreakParsing;
}
public void start() {
}
public void end() {
}
// Note: I guess that this code assumes the Record may nest like this:
// START:VPOS
// ...
// START:VPOS2
// ...
// END:VPOS2
// ...
// END:VPOS
//
// However the following code has a bug.
// When error occurs after calling startRecord(), the entry which is probably
// the cause of the error remains to be in vNodeList, while endRecord() is not called.
//
// I leave this code as is since I'm not familiar with vcalendar specification.
// But I believe we should refactor this code in the future.
// Until this, the last entry has to be removed when some error occurs.
public void startEntry() {
VNode vnode = new VNode();
vnode.parseStatus = 1;
vnode.VName = "VCARD";
// I feel this should be done in endRecord(), but it cannot be done because of
// the reason above.
vNodeList.add(vnode);
mNodeListPos = vNodeList.size() - 1;
mCurrentVNode = vNodeList.get(mNodeListPos);
}
public void endEntry() {
VNode endNode = vNodeList.get(mNodeListPos);
endNode.parseStatus = 0;
while(mNodeListPos > 0){
mNodeListPos--;
if((vNodeList.get(mNodeListPos)).parseStatus == 1)
break;
}
mCurrentVNode = vNodeList.get(mNodeListPos);
}
public void startProperty() {
mCurrentPropNode = new PropertyNode();
}
public void endProperty() {
mCurrentVNode.propList.add(mCurrentPropNode);
}
public void propertyName(String name) {
mCurrentPropNode.propName = name;
}
public void propertyGroup(String group) {
mCurrentPropNode.propGroupSet.add(group);
}
public void propertyParamType(String type) {
mCurrentParamType = type;
}
public void propertyParamValue(String value) {
if (mCurrentParamType == null ||
mCurrentParamType.equalsIgnoreCase("TYPE")) {
mCurrentPropNode.paramMap_TYPE.add(value);
} else {
mCurrentPropNode.paramMap.put(mCurrentParamType, value);
}
mCurrentParamType = null;
}
private String encodeString(String originalString, String targetCharset) {
if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
return originalString;
}
Charset charset = Charset.forName(mSourceCharset);
ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
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;
}
}
private String handleOneValue(String value, String targetCharset, String encoding) {
if (encoding != null) {
encoding = encoding.toUpperCase();
if (encoding.equals("BASE64") || encoding.equals("B")) {
// Assume BASE64 is used only when the number of values is 1.
mCurrentPropNode.propValue_bytes = Base64.decode(value.getBytes(), Base64.NO_WRAP);
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
return VCardUtils.parseQuotedPrintable(
value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
}
// Unknown encoding. Fall back to default.
}
return encodeString(value, targetCharset);
}
public void propertyValues(List<String> values) {
if (values == null || values.size() == 0) {
mCurrentPropNode.propValue_bytes = null;
mCurrentPropNode.propValue_vector.clear();
mCurrentPropNode.propValue_vector.add("");
mCurrentPropNode.propValue = "";
return;
}
ContentValues paramMap = mCurrentPropNode.paramMap;
String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET"));
String encoding = paramMap.getAsString("ENCODING");
if (targetCharset == null || targetCharset.length() == 0) {
targetCharset = mTargetCharset;
}
for (String value : values) {
mCurrentPropNode.propValue_vector.add(
handleOneValue(value, targetCharset, encoding));
}
mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector);
}
private String listToString(List<String> list){
int size = list.size();
if (size > 1) {
StringBuilder typeListB = new StringBuilder();
for (String type : list) {
typeListB.append(type).append(";");
}
int len = typeListB.length();
if (len > 0 && typeListB.charAt(len - 1) == ';') {
return typeListB.substring(0, len - 1);
}
return typeListB.toString();
} else if (size == 1) {
return list.get(0);
} else {
return "";
}
}
public String getResult(){
throw new RuntimeException("Not supported");
}
}