Messaging: Vendor chips, colorpicker, photoviewer, and vcard in-tree

Clone no longer depends on LineageOS gitlinks; the libraries used by the app live in this repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-29 06:19:53 +09:00
co-authored by Cursor
parent a6b23f0f3c
commit 49789fc4a5
239 changed files with 35551 additions and 4 deletions
Submodule modules/vcard deleted from f049d8cd3a
+34
View File
@@ -0,0 +1,34 @@
// 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 {
default_applicable_licenses: ["Android-Apache-2.0"],
}
java_library {
name: "com.android.vcard",
// 2.3
sdk_version: "9",
srcs: ["java/**/*.java"],
apex_available: [
"//apex_available:platform",
"com.android.bt",
],
libs: [
"framework-annotations-lib",
],
}
+1
View File
@@ -0,0 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
+3
View File
@@ -0,0 +1,3 @@
# Default code reviewers picked from top 3 or more developers.
# Please update this list if you find better candidates.
johnshao@google.com
+29
View File
@@ -0,0 +1,29 @@
plugins {
id 'com.android.library'
}
android {
namespace 'com.android.vcard'
compileSdk 35
defaultConfig {
minSdk 33
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['java']
res.srcDirs = []
}
}
}
dependencies {
implementation 'androidx.annotation:annotation:1.8.2'
}
@@ -0,0 +1,382 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import androidx.annotation.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
/**
* TextUtils especially for Japanese.
*/
/* package */ class JapaneseUtils {
@VisibleForTesting
static final Map<Character, String> sHalfWidthMap =
new HashMap<Character, String>();
static {
sHalfWidthMap.put('\u3001', "\uFF64");
sHalfWidthMap.put('\u3002', "\uFF61");
sHalfWidthMap.put('\u300C', "\uFF62");
sHalfWidthMap.put('\u300D', "\uFF63");
sHalfWidthMap.put('\u301C', "~");
sHalfWidthMap.put('\u3041', "\uFF67");
sHalfWidthMap.put('\u3042', "\uFF71");
sHalfWidthMap.put('\u3043', "\uFF68");
sHalfWidthMap.put('\u3044', "\uFF72");
sHalfWidthMap.put('\u3045', "\uFF69");
sHalfWidthMap.put('\u3046', "\uFF73");
sHalfWidthMap.put('\u3047', "\uFF6A");
sHalfWidthMap.put('\u3048', "\uFF74");
sHalfWidthMap.put('\u3049', "\uFF6B");
sHalfWidthMap.put('\u304A', "\uFF75");
sHalfWidthMap.put('\u304B', "\uFF76");
sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
sHalfWidthMap.put('\u304D', "\uFF77");
sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
sHalfWidthMap.put('\u304F', "\uFF78");
sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
sHalfWidthMap.put('\u3051', "\uFF79");
sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
sHalfWidthMap.put('\u3053', "\uFF7A");
sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
sHalfWidthMap.put('\u3055', "\uFF7B");
sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
sHalfWidthMap.put('\u3057', "\uFF7C");
sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
sHalfWidthMap.put('\u3059', "\uFF7D");
sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
sHalfWidthMap.put('\u305B', "\uFF7E");
sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
sHalfWidthMap.put('\u305D', "\uFF7F");
sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
sHalfWidthMap.put('\u305F', "\uFF80");
sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
sHalfWidthMap.put('\u3061', "\uFF81");
sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
sHalfWidthMap.put('\u3063', "\uFF6F");
sHalfWidthMap.put('\u3064', "\uFF82");
sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
sHalfWidthMap.put('\u3066', "\uFF83");
sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
sHalfWidthMap.put('\u3068', "\uFF84");
sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
sHalfWidthMap.put('\u306A', "\uFF85");
sHalfWidthMap.put('\u306B', "\uFF86");
sHalfWidthMap.put('\u306C', "\uFF87");
sHalfWidthMap.put('\u306D', "\uFF88");
sHalfWidthMap.put('\u306E', "\uFF89");
sHalfWidthMap.put('\u306F', "\uFF8A");
sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
sHalfWidthMap.put('\u3072', "\uFF8B");
sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
sHalfWidthMap.put('\u3075', "\uFF8C");
sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
sHalfWidthMap.put('\u3078', "\uFF8D");
sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
sHalfWidthMap.put('\u307B', "\uFF8E");
sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
sHalfWidthMap.put('\u307E', "\uFF8F");
sHalfWidthMap.put('\u307F', "\uFF90");
sHalfWidthMap.put('\u3080', "\uFF91");
sHalfWidthMap.put('\u3081', "\uFF92");
sHalfWidthMap.put('\u3082', "\uFF93");
sHalfWidthMap.put('\u3083', "\uFF6C");
sHalfWidthMap.put('\u3084', "\uFF94");
sHalfWidthMap.put('\u3085', "\uFF6D");
sHalfWidthMap.put('\u3086', "\uFF95");
sHalfWidthMap.put('\u3087', "\uFF6E");
sHalfWidthMap.put('\u3088', "\uFF96");
sHalfWidthMap.put('\u3089', "\uFF97");
sHalfWidthMap.put('\u308A', "\uFF98");
sHalfWidthMap.put('\u308B', "\uFF99");
sHalfWidthMap.put('\u308C', "\uFF9A");
sHalfWidthMap.put('\u308D', "\uFF9B");
sHalfWidthMap.put('\u308E', "\uFF9C");
sHalfWidthMap.put('\u308F', "\uFF9C");
sHalfWidthMap.put('\u3090', "\uFF72");
sHalfWidthMap.put('\u3091', "\uFF74");
sHalfWidthMap.put('\u3092', "\uFF66");
sHalfWidthMap.put('\u3093', "\uFF9D");
sHalfWidthMap.put('\u309B', "\uFF9E");
sHalfWidthMap.put('\u309C', "\uFF9F");
sHalfWidthMap.put('\u30A1', "\uFF67");
sHalfWidthMap.put('\u30A2', "\uFF71");
sHalfWidthMap.put('\u30A3', "\uFF68");
sHalfWidthMap.put('\u30A4', "\uFF72");
sHalfWidthMap.put('\u30A5', "\uFF69");
sHalfWidthMap.put('\u30A6', "\uFF73");
sHalfWidthMap.put('\u30A7', "\uFF6A");
sHalfWidthMap.put('\u30A8', "\uFF74");
sHalfWidthMap.put('\u30A9', "\uFF6B");
sHalfWidthMap.put('\u30AA', "\uFF75");
sHalfWidthMap.put('\u30AB', "\uFF76");
sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
sHalfWidthMap.put('\u30AD', "\uFF77");
sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
sHalfWidthMap.put('\u30AF', "\uFF78");
sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
sHalfWidthMap.put('\u30B1', "\uFF79");
sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
sHalfWidthMap.put('\u30B3', "\uFF7A");
sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
sHalfWidthMap.put('\u30B5', "\uFF7B");
sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
sHalfWidthMap.put('\u30B7', "\uFF7C");
sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
sHalfWidthMap.put('\u30B9', "\uFF7D");
sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
sHalfWidthMap.put('\u30BB', "\uFF7E");
sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
sHalfWidthMap.put('\u30BD', "\uFF7F");
sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
sHalfWidthMap.put('\u30BF', "\uFF80");
sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
sHalfWidthMap.put('\u30C1', "\uFF81");
sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
sHalfWidthMap.put('\u30C3', "\uFF6F");
sHalfWidthMap.put('\u30C4', "\uFF82");
sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
sHalfWidthMap.put('\u30C6', "\uFF83");
sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
sHalfWidthMap.put('\u30C8', "\uFF84");
sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
sHalfWidthMap.put('\u30CA', "\uFF85");
sHalfWidthMap.put('\u30CB', "\uFF86");
sHalfWidthMap.put('\u30CC', "\uFF87");
sHalfWidthMap.put('\u30CD', "\uFF88");
sHalfWidthMap.put('\u30CE', "\uFF89");
sHalfWidthMap.put('\u30CF', "\uFF8A");
sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
sHalfWidthMap.put('\u30D2', "\uFF8B");
sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
sHalfWidthMap.put('\u30D5', "\uFF8C");
sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
sHalfWidthMap.put('\u30D8', "\uFF8D");
sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
sHalfWidthMap.put('\u30DB', "\uFF8E");
sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
sHalfWidthMap.put('\u30DE', "\uFF8F");
sHalfWidthMap.put('\u30DF', "\uFF90");
sHalfWidthMap.put('\u30E0', "\uFF91");
sHalfWidthMap.put('\u30E1', "\uFF92");
sHalfWidthMap.put('\u30E2', "\uFF93");
sHalfWidthMap.put('\u30E3', "\uFF6C");
sHalfWidthMap.put('\u30E4', "\uFF94");
sHalfWidthMap.put('\u30E5', "\uFF6D");
sHalfWidthMap.put('\u30E6', "\uFF95");
sHalfWidthMap.put('\u30E7', "\uFF6E");
sHalfWidthMap.put('\u30E8', "\uFF96");
sHalfWidthMap.put('\u30E9', "\uFF97");
sHalfWidthMap.put('\u30EA', "\uFF98");
sHalfWidthMap.put('\u30EB', "\uFF99");
sHalfWidthMap.put('\u30EC', "\uFF9A");
sHalfWidthMap.put('\u30ED', "\uFF9B");
sHalfWidthMap.put('\u30EE', "\uFF9C");
sHalfWidthMap.put('\u30EF', "\uFF9C");
sHalfWidthMap.put('\u30F0', "\uFF72");
sHalfWidthMap.put('\u30F1', "\uFF74");
sHalfWidthMap.put('\u30F2', "\uFF66");
sHalfWidthMap.put('\u30F3', "\uFF9D");
sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
sHalfWidthMap.put('\u30F5', "\uFF76");
sHalfWidthMap.put('\u30F6', "\uFF79");
sHalfWidthMap.put('\u30FB', "\uFF65");
sHalfWidthMap.put('\u30FC', "\uFF70");
sHalfWidthMap.put('\uFF01', "!");
sHalfWidthMap.put('\uFF02', "\"");
sHalfWidthMap.put('\uFF03', "#");
sHalfWidthMap.put('\uFF04', "$");
sHalfWidthMap.put('\uFF05', "%");
sHalfWidthMap.put('\uFF06', "&");
sHalfWidthMap.put('\uFF07', "'");
sHalfWidthMap.put('\uFF08', "(");
sHalfWidthMap.put('\uFF09', ")");
sHalfWidthMap.put('\uFF0A', "*");
sHalfWidthMap.put('\uFF0B', "+");
sHalfWidthMap.put('\uFF0C', ",");
sHalfWidthMap.put('\uFF0D', "-");
sHalfWidthMap.put('\uFF0E', ".");
sHalfWidthMap.put('\uFF0F', "/");
sHalfWidthMap.put('\uFF10', "0");
sHalfWidthMap.put('\uFF11', "1");
sHalfWidthMap.put('\uFF12', "2");
sHalfWidthMap.put('\uFF13', "3");
sHalfWidthMap.put('\uFF14', "4");
sHalfWidthMap.put('\uFF15', "5");
sHalfWidthMap.put('\uFF16', "6");
sHalfWidthMap.put('\uFF17', "7");
sHalfWidthMap.put('\uFF18', "8");
sHalfWidthMap.put('\uFF19', "9");
sHalfWidthMap.put('\uFF1A', ":");
sHalfWidthMap.put('\uFF1B', ";");
sHalfWidthMap.put('\uFF1C', "<");
sHalfWidthMap.put('\uFF1D', "=");
sHalfWidthMap.put('\uFF1E', ">");
sHalfWidthMap.put('\uFF1F', "?");
sHalfWidthMap.put('\uFF20', "@");
sHalfWidthMap.put('\uFF21', "A");
sHalfWidthMap.put('\uFF22', "B");
sHalfWidthMap.put('\uFF23', "C");
sHalfWidthMap.put('\uFF24', "D");
sHalfWidthMap.put('\uFF25', "E");
sHalfWidthMap.put('\uFF26', "F");
sHalfWidthMap.put('\uFF27', "G");
sHalfWidthMap.put('\uFF28', "H");
sHalfWidthMap.put('\uFF29', "I");
sHalfWidthMap.put('\uFF2A', "J");
sHalfWidthMap.put('\uFF2B', "K");
sHalfWidthMap.put('\uFF2C', "L");
sHalfWidthMap.put('\uFF2D', "M");
sHalfWidthMap.put('\uFF2E', "N");
sHalfWidthMap.put('\uFF2F', "O");
sHalfWidthMap.put('\uFF30', "P");
sHalfWidthMap.put('\uFF31', "Q");
sHalfWidthMap.put('\uFF32', "R");
sHalfWidthMap.put('\uFF33', "S");
sHalfWidthMap.put('\uFF34', "T");
sHalfWidthMap.put('\uFF35', "U");
sHalfWidthMap.put('\uFF36', "V");
sHalfWidthMap.put('\uFF37', "W");
sHalfWidthMap.put('\uFF38', "X");
sHalfWidthMap.put('\uFF39', "Y");
sHalfWidthMap.put('\uFF3A', "Z");
sHalfWidthMap.put('\uFF3B', "[");
sHalfWidthMap.put('\uFF3C', "\\");
sHalfWidthMap.put('\uFF3D', "]");
sHalfWidthMap.put('\uFF3E', "^");
sHalfWidthMap.put('\uFF3F', "_");
sHalfWidthMap.put('\uFF41', "a");
sHalfWidthMap.put('\uFF42', "b");
sHalfWidthMap.put('\uFF43', "c");
sHalfWidthMap.put('\uFF44', "d");
sHalfWidthMap.put('\uFF45', "e");
sHalfWidthMap.put('\uFF46', "f");
sHalfWidthMap.put('\uFF47', "g");
sHalfWidthMap.put('\uFF48', "h");
sHalfWidthMap.put('\uFF49', "i");
sHalfWidthMap.put('\uFF4A', "j");
sHalfWidthMap.put('\uFF4B', "k");
sHalfWidthMap.put('\uFF4C', "l");
sHalfWidthMap.put('\uFF4D', "m");
sHalfWidthMap.put('\uFF4E', "n");
sHalfWidthMap.put('\uFF4F', "o");
sHalfWidthMap.put('\uFF50', "p");
sHalfWidthMap.put('\uFF51', "q");
sHalfWidthMap.put('\uFF52', "r");
sHalfWidthMap.put('\uFF53', "s");
sHalfWidthMap.put('\uFF54', "t");
sHalfWidthMap.put('\uFF55', "u");
sHalfWidthMap.put('\uFF56', "v");
sHalfWidthMap.put('\uFF57', "w");
sHalfWidthMap.put('\uFF58', "x");
sHalfWidthMap.put('\uFF59', "y");
sHalfWidthMap.put('\uFF5A', "z");
sHalfWidthMap.put('\uFF5B', "{");
sHalfWidthMap.put('\uFF5C', "|");
sHalfWidthMap.put('\uFF5D', "}");
sHalfWidthMap.put('\uFF5E', "~");
sHalfWidthMap.put('\uFF61', "\uFF61");
sHalfWidthMap.put('\uFF62', "\uFF62");
sHalfWidthMap.put('\uFF63', "\uFF63");
sHalfWidthMap.put('\uFF64', "\uFF64");
sHalfWidthMap.put('\uFF65', "\uFF65");
sHalfWidthMap.put('\uFF66', "\uFF66");
sHalfWidthMap.put('\uFF67', "\uFF67");
sHalfWidthMap.put('\uFF68', "\uFF68");
sHalfWidthMap.put('\uFF69', "\uFF69");
sHalfWidthMap.put('\uFF6A', "\uFF6A");
sHalfWidthMap.put('\uFF6B', "\uFF6B");
sHalfWidthMap.put('\uFF6C', "\uFF6C");
sHalfWidthMap.put('\uFF6D', "\uFF6D");
sHalfWidthMap.put('\uFF6E', "\uFF6E");
sHalfWidthMap.put('\uFF6F', "\uFF6F");
sHalfWidthMap.put('\uFF70', "\uFF70");
sHalfWidthMap.put('\uFF71', "\uFF71");
sHalfWidthMap.put('\uFF72', "\uFF72");
sHalfWidthMap.put('\uFF73', "\uFF73");
sHalfWidthMap.put('\uFF74', "\uFF74");
sHalfWidthMap.put('\uFF75', "\uFF75");
sHalfWidthMap.put('\uFF76', "\uFF76");
sHalfWidthMap.put('\uFF77', "\uFF77");
sHalfWidthMap.put('\uFF78', "\uFF78");
sHalfWidthMap.put('\uFF79', "\uFF79");
sHalfWidthMap.put('\uFF7A', "\uFF7A");
sHalfWidthMap.put('\uFF7B', "\uFF7B");
sHalfWidthMap.put('\uFF7C', "\uFF7C");
sHalfWidthMap.put('\uFF7D', "\uFF7D");
sHalfWidthMap.put('\uFF7E', "\uFF7E");
sHalfWidthMap.put('\uFF7F', "\uFF7F");
sHalfWidthMap.put('\uFF80', "\uFF80");
sHalfWidthMap.put('\uFF81', "\uFF81");
sHalfWidthMap.put('\uFF82', "\uFF82");
sHalfWidthMap.put('\uFF83', "\uFF83");
sHalfWidthMap.put('\uFF84', "\uFF84");
sHalfWidthMap.put('\uFF85', "\uFF85");
sHalfWidthMap.put('\uFF86', "\uFF86");
sHalfWidthMap.put('\uFF87', "\uFF87");
sHalfWidthMap.put('\uFF88', "\uFF88");
sHalfWidthMap.put('\uFF89', "\uFF89");
sHalfWidthMap.put('\uFF8A', "\uFF8A");
sHalfWidthMap.put('\uFF8B', "\uFF8B");
sHalfWidthMap.put('\uFF8C', "\uFF8C");
sHalfWidthMap.put('\uFF8D', "\uFF8D");
sHalfWidthMap.put('\uFF8E', "\uFF8E");
sHalfWidthMap.put('\uFF8F', "\uFF8F");
sHalfWidthMap.put('\uFF90', "\uFF90");
sHalfWidthMap.put('\uFF91', "\uFF91");
sHalfWidthMap.put('\uFF92', "\uFF92");
sHalfWidthMap.put('\uFF93', "\uFF93");
sHalfWidthMap.put('\uFF94', "\uFF94");
sHalfWidthMap.put('\uFF95', "\uFF95");
sHalfWidthMap.put('\uFF96', "\uFF96");
sHalfWidthMap.put('\uFF97', "\uFF97");
sHalfWidthMap.put('\uFF98', "\uFF98");
sHalfWidthMap.put('\uFF99', "\uFF99");
sHalfWidthMap.put('\uFF9A', "\uFF9A");
sHalfWidthMap.put('\uFF9B', "\uFF9B");
sHalfWidthMap.put('\uFF9C', "\uFF9C");
sHalfWidthMap.put('\uFF9D', "\uFF9D");
sHalfWidthMap.put('\uFF9E', "\uFF9E");
sHalfWidthMap.put('\uFF9F', "\uFF9F");
sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
}
/**
* Returns half-width version of that character if possible. Returns null if not possible
* @param ch input character
* @return CharSequence object if the mapping for ch exists. Return null otherwise.
*/
public static String tryGetHalfWidthText(final char ch) {
if (sHalfWidthMap.containsKey(ch)) {
return sHalfWidthMap.get(ch);
} else {
return null;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,721 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.vcard;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
import android.content.Entity.NamedContentValues;
import android.content.EntityIterator;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* The class for composing vCard from Contacts information.
* </p>
* <p>
* Usually, this class should be used like this.
* </p>
* <pre class="prettyprint">VCardComposer composer = null;
* try {
* composer = new VCardComposer(context);
* composer.addHandler(
* composer.new HandlerForOutputStream(outputStream));
* if (!composer.init()) {
* // Do something handling the situation.
* return;
* }
* while (!composer.isAfterLast()) {
* if (mCanceled) {
* // Assume a user may cancel this operation during the export.
* return;
* }
* if (!composer.createOneEntry()) {
* // Do something handling the error situation.
* return;
* }
* }
* } finally {
* if (composer != null) {
* composer.terminate();
* }
* }</pre>
* <p>
* Users have to manually take care of memory efficiency. Even one vCard may contain
* image of non-trivial size for mobile devices.
* </p>
* <p>
* {@link VCardBuilder} is used to build each vCard.
* </p>
*/
public class VCardComposer {
private static final String LOG_TAG = "VCardComposer";
private static final boolean DEBUG = false;
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
public static final String FAILURE_REASON_NO_ENTRY =
"There's no exportable in the database";
public static final String FAILURE_REASON_NOT_INITIALIZED =
"The vCard composer object is not correctly initialized";
/** Should be visible only from developers... (no need to translate, hopefully) */
public static final String FAILURE_REASON_UNSUPPORTED_URI =
"The Uri vCard composer received is not supported by the composer.";
public static final String NO_ERROR = "No error";
// Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
// since usual vCard devices for Japanese devices already use it.
private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8";
private static final Map<Integer, String> sImMap;
static {
sImMap = new HashMap<Integer, String>();
sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
// We don't add Google talk here since it has to be handled separately.
}
private final int mVCardType;
private final ContentResolver mContentResolver;
private final boolean mIsDoCoMo;
/**
* Used only when {@link #mIsDoCoMo} is true. Set to true when the first vCard for DoCoMo
* vCard is emitted.
*/
private boolean mFirstVCardEmittedInDoCoMoCase;
private Cursor mCursor;
private EntityIterator mEntityIterator;
private Method mGetEntityIteratorMethod;
private boolean mCursorSuppliedFromOutside;
private int mIdColumn;
private Uri mContentUriForRawContactsEntity;
private final String mCharset;
private boolean mInitDone;
private String mErrorReason = NO_ERROR;
/**
* Set to false when one of {@link #init()} variants is called, and set to true when
* {@link #terminate()} is called. Initially set to true.
*/
private boolean mTerminateCalled = true;
private RawContactEntitlesInfoCallback mRawContactEntitlesInfoCallback;
private static final String[] sContactsProjection = new String[] {
Contacts._ID,
};
public VCardComposer(Context context) {
this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
}
/**
* The variant which sets charset to null and sets careHandlerErrors to true.
*/
public VCardComposer(Context context, int vcardType) {
this(context, vcardType, null, true);
}
public VCardComposer(Context context, int vcardType, String charset) {
this(context, vcardType, charset, true);
}
/**
* The variant which sets charset to null.
*/
public VCardComposer(final Context context, final int vcardType,
final boolean careHandlerErrors) {
this(context, vcardType, null, careHandlerErrors);
}
/**
* Constructs for supporting call log entry vCard composing.
*
* @param context Context to be used during the composition.
* @param vcardType The type of vCard, typically available via {@link VCardConfig}.
* @param charset The charset to be used. Use null when you don't need the charset.
* @param careHandlerErrors If true, This object returns false everytime
*/
public VCardComposer(final Context context, final int vcardType, String charset,
final boolean careHandlerErrors) {
this(context, context.getContentResolver(), vcardType, charset, careHandlerErrors);
}
/**
* Just for testing for now.
* @param resolver {@link ContentResolver} which used by this object.
* @hide
*/
public VCardComposer(final Context context, ContentResolver resolver,
final int vcardType, String charset, final boolean careHandlerErrors) {
// Not used right now
// mContext = context;
mVCardType = vcardType;
mContentResolver = resolver;
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
final boolean shouldAppendCharsetParam = !(
VCardConfig.isVersion30(vcardType) && UTF_8.equalsIgnoreCase(charset));
if (mIsDoCoMo || shouldAppendCharsetParam) {
if (SHIFT_JIS.equalsIgnoreCase(charset)) {
mCharset = charset;
} else {
/* Log.w(LOG_TAG,
"The charset \"" + charset + "\" is used while "
+ SHIFT_JIS + " is needed to be used."); */
if (TextUtils.isEmpty(charset)) {
mCharset = SHIFT_JIS;
} else {
mCharset = charset;
}
}
} else {
if (TextUtils.isEmpty(charset)) {
mCharset = UTF_8;
} else {
mCharset = charset;
}
}
Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
}
/**
* Initializes this object using default {@link Contacts#CONTENT_URI}.
*
* You can call this method or a variant of this method just once. In other words, you cannot
* reuse this object.
*
* @return Returns true when initialization is successful and all the other
* methods are available. Returns false otherwise.
*/
public boolean init() {
return init(null, null);
}
/**
* Special variant of init(), which accepts a Uri for obtaining {@link RawContactsEntity} from
* {@link ContentResolver} with {@link Contacts#_ID}.
* <code>
* String selection = Data.CONTACT_ID + "=?";
* String[] selectionArgs = new String[] {contactId};
* Cursor cursor = mContentResolver.query(
* contentUriForRawContactsEntity, null, selection, selectionArgs, null)
* </code>
*
* You can call this method or a variant of this method just once. In other words, you cannot
* reuse this object.
*
* @deprecated Use {@link #init(Uri, String[], String, String[], String, Uri)} if you really
* need to change the default Uri.
*/
@Deprecated
public boolean initWithRawContactsEntityUri(Uri contentUriForRawContactsEntity) {
return init(Contacts.CONTENT_URI, sContactsProjection, null, null, null,
contentUriForRawContactsEntity);
}
/**
* Initializes this object using default {@link Contacts#CONTENT_URI} and given selection
* arguments.
*/
public boolean init(final String selection, final String[] selectionArgs) {
return init(Contacts.CONTENT_URI, sContactsProjection, selection, selectionArgs,
null, null);
}
/**
* Note that this is unstable interface, may be deleted in the future.
*/
public boolean init(final Uri contentUri, final String selection,
final String[] selectionArgs, final String sortOrder) {
return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder, null);
}
/**
* @param contentUri Uri for obtaining the list of contactId. Used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param selection selection used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param selectionArgs selectionArgs used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param sortOrder sortOrder used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param contentUriForRawContactsEntity Uri for obtaining entries relevant to each
* contactId.
* Note that this is an unstable interface, may be deleted in the future.
*/
public boolean init(final Uri contentUri, final String selection,
final String[] selectionArgs, final String sortOrder,
final Uri contentUriForRawContactsEntity) {
return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder,
contentUriForRawContactsEntity);
}
/**
* A variant of init(). Currently just for testing. Use other variants for init().
*
* First we'll create {@link Cursor} for the list of contactId.
*
* <code>
* Cursor cursorForId = mContentResolver.query(
* contentUri, projection, selection, selectionArgs, sortOrder);
* </code>
*
* After that, we'll obtain data for each contactId in the list.
*
* <code>
* Cursor cursorForContent = mContentResolver.query(
* contentUriForRawContactsEntity, null,
* Data.CONTACT_ID + "=?", new String[] {contactId}, null)
* </code>
*
* {@link #createOneEntry()} or its variants let the caller obtain each entry from
* <code>cursorForContent</code> above.
*
* @param contentUri Uri for obtaining the list of contactId. Used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param projection projection used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param selection selection used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param selectionArgs selectionArgs used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param sortOrder sortOrder used with
* {@link ContentResolver#query(Uri, String[], String, String[], String)}
* @param contentUriForRawContactsEntity Uri for obtaining entries relevant to each
* contactId.
* @return true when successful
*
* @hide
*/
public boolean init(final Uri contentUri, final String[] projection,
final String selection, final String[] selectionArgs,
final String sortOrder, Uri contentUriForRawContactsEntity) {
if (!ContactsContract.AUTHORITY.equals(contentUri.getAuthority())) {
if (DEBUG) Log.d(LOG_TAG, "Unexpected contentUri: " + contentUri);
mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
return false;
}
if (!initInterFirstPart(contentUriForRawContactsEntity)) {
return false;
}
if (!initInterCursorCreationPart(contentUri, projection, selection, selectionArgs,
sortOrder)) {
return false;
}
if (!initInterMainPart()) {
return false;
}
initEntityIterator();
return initInterLastPart();
}
/**
* Just for testing for now. Do not use.
* Execute before init.
* @hide
*/
public void setGetEntityIteratorMethod(Method getEntityIteratorMethod) {
mGetEntityIteratorMethod = getEntityIteratorMethod;
}
/**
* Just for testing for now. Do not use.
* @hide
*/
public boolean init(Cursor cursor) {
return initWithCallback(cursor, null);
}
/**
* @param cursor Cursor that used to get contact id
* @param rawContactEntitlesInfoCallback Callback that return RawContactEntitlesInfo
* Note that this is an unstable interface, may be deleted in the future.
*
* @return true when successful
*/
public boolean initWithCallback(Cursor cursor,
RawContactEntitlesInfoCallback rawContactEntitlesInfoCallback) {
if (!initInterFirstPart(null)) {
return false;
}
mCursorSuppliedFromOutside = true;
mCursor = cursor;
mRawContactEntitlesInfoCallback = rawContactEntitlesInfoCallback;
if (!initInterMainPart()) {
return false;
}
initEntityIterator();
return initInterLastPart();
}
private boolean initInterFirstPart(Uri contentUriForRawContactsEntity) {
mContentUriForRawContactsEntity =
(contentUriForRawContactsEntity != null ? contentUriForRawContactsEntity :
RawContactsEntity.CONTENT_URI);
if (mInitDone) {
Log.e(LOG_TAG, "init() is already called");
return false;
}
return true;
}
private boolean initInterCursorCreationPart(
final Uri contentUri, final String[] projection,
final String selection, final String[] selectionArgs, final String sortOrder) {
mCursorSuppliedFromOutside = false;
mCursor = mContentResolver.query(
contentUri, projection, selection, selectionArgs, sortOrder);
if (mCursor == null) {
Log.e(LOG_TAG, String.format("Cursor became null unexpectedly"));
mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
return false;
}
return true;
}
private boolean initInterMainPart() {
if (mCursor.getCount() == 0 || !mCursor.moveToFirst()) {
if (DEBUG) {
Log.d(LOG_TAG,
String.format("mCursor has an error (getCount: %d): ", mCursor.getCount()));
}
mErrorReason = FAILURE_REASON_NO_ENTRY;
closeCursorIfAppropriate();
return false;
}
mIdColumn = mCursor.getColumnIndex(Data.CONTACT_ID);
if (mIdColumn < 0) {
mIdColumn = mCursor.getColumnIndex(Contacts._ID);
}
return mIdColumn >= 0;
}
// Testing with 500 Contacts IDs, the export time using an EntityIterator containing all
// Contacts IDs is approximately 120ms (each EntityIterator containing one Contacts ID takes
// about 700ms).
// Initializing an EntityIterator with 500 Contacts IDs takes about 10ms.
private void initEntityIterator() {
StringBuilder selection = new StringBuilder();
StringBuilder contactIds = new StringBuilder();
Uri uri = mContentUriForRawContactsEntity;
if (mRawContactEntitlesInfoCallback != null) {
RawContactEntitlesInfo rawContactEntitlesInfo =
mRawContactEntitlesInfoCallback.getRawContactEntitlesInfo(
mCursor.getLong(mIdColumn));
uri = rawContactEntitlesInfo.rawContactEntitlesUri;
contactIds.append(rawContactEntitlesInfo.contactId);
} else {
do {
contactIds.append(mCursor.getString(mIdColumn));
if (!mCursor.isLast()) {
contactIds.append(",");
} else {
break;
}
} while (mCursor.moveToNext());
// There are some callers that assume that the cursor is at its first position after
// init.
// See b/402311014
mCursor.moveToFirst();
}
selection.append(Data.CONTACT_ID).append(" IN (");
selection.append(contactIds);
selection.append(")");
if (mGetEntityIteratorMethod != null) {
// Please note that this code is executed by unit tests only
try {
mEntityIterator = (EntityIterator) mGetEntityIteratorMethod.invoke(null,
mContentResolver, uri, selection.toString(), null, null);
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "IllegalArgumentException has been thrown: " +
e.getMessage());
} catch (IllegalAccessException e) {
Log.e(LOG_TAG, "IllegalAccessException has been thrown: " +
e.getMessage());
} catch (InvocationTargetException e) {
Log.e(LOG_TAG, "InvocationTargetException has been thrown: ", e);
throw new RuntimeException("InvocationTargetException has been thrown");
}
} else {
mEntityIterator = RawContacts
.newEntityIterator(mContentResolver.query(uri, null,
selection.toString(), null, Data.CONTACT_ID));
}
}
private boolean initInterLastPart() {
mInitDone = true;
mTerminateCalled = false;
return true;
}
/**
* @return a vCard string.
*/
public String createOneEntry() {
if (!mInitDone) {
Log.w(LOG_TAG, "This object is not ready yet.");
return "";
}
if (mIsDoCoMo && !mFirstVCardEmittedInDoCoMoCase) {
mFirstVCardEmittedInDoCoMoCase = true;
// Previously we needed to emit empty data for this specific case, but actually
// this doesn't work now, as resolver doesn't return any data with "-1" contactId.
// TODO: re-introduce or remove this logic. Needs to modify unit test when we
// re-introduce the logic.
// return createOneEntryInternal("-1", getEntityIteratorMethod);
}
return createOneEntryInternal();
}
/**
* Class that store rawContactEntitlesUri and contactId
*/
public static class RawContactEntitlesInfo {
public final Uri rawContactEntitlesUri;
public final long contactId;
public RawContactEntitlesInfo(Uri rawContactEntitlesUri, long contactId) {
this.rawContactEntitlesUri = rawContactEntitlesUri;
this.contactId = contactId;
}
}
/**
* Listener for getting raw contact entitles info
*/
public interface RawContactEntitlesInfoCallback {
/**
* Callback to get RawContactEntitlesInfo from contact id
*
* @param contactId Contact id that you want to process.
* @return RawContactEntitlesInfo that ready to process.
*/
RawContactEntitlesInfo getRawContactEntitlesInfo(long contactId);
}
private String createOneEntryInternal() {
if (!mEntityIterator.hasNext()) {
Log.w(LOG_TAG, "EntityIterator#hasNext() returned false");
return "";
}
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
Entity entity = mEntityIterator.next();
for (NamedContentValues namedContentValues : entity.getSubValues()) {
ContentValues contentValues = namedContentValues.values;
String key = contentValues.getAsString(Data.MIMETYPE);
if (key != null) {
List<ContentValues> contentValuesList = contentValuesListMap.get(key);
if (contentValuesList == null) {
contentValuesList = new ArrayList<ContentValues>();
contentValuesListMap.put(key, contentValuesList);
}
contentValuesList.add(contentValues);
}
}
return buildVCard(contentValuesListMap);
}
private VCardPhoneNumberTranslationCallback mPhoneTranslationCallback;
/**
* <p>
* Set a callback for phone number formatting. It will be called every time when this object
* receives a phone number for printing.
* </p>
* <p>
* When this is set {@link VCardConfig#FLAG_REFRAIN_PHONE_NUMBER_FORMATTING} will be ignored
* and the callback should be responsible for everything about phone number formatting.
* </p>
* <p>
* Caution: This interface will change. Please don't use without any strong reason.
* </p>
*/
public void setPhoneNumberTranslationCallback(VCardPhoneNumberTranslationCallback callback) {
mPhoneTranslationCallback = callback;
}
/**
* Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
* {ContactsContract}. Developers can override this method to customize the output.
*/
public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
if (contentValuesListMap == null) {
Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
return "";
} else {
final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
builder.appendNameProperties(contentValuesListMap.get(
StructuredName.CONTENT_ITEM_TYPE))
.appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE),
mPhoneTranslationCallback)
.appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE))
.appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
.appendSipAddresses(contentValuesListMap.get(SipAddress.CONTENT_ITEM_TYPE));
if ((mVCardType & VCardConfig.FLAG_REFRAIN_NICKNAME_EXPORT) == 0) {
builder.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE));
}
if ((mVCardType & VCardConfig.FLAG_REFRAIN_EMAIL_EXPORT) == 0) {
builder.appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE));
}
if ((mVCardType & VCardConfig.FLAG_REFRAIN_ADDRESS_EXPORT) == 0) {
builder.appendPostals(contentValuesListMap.get(
StructuredPostal.CONTENT_ITEM_TYPE));
}
if ((mVCardType & VCardConfig.FLAG_REFRAIN_ORGANIZATION_EXPORT) == 0) {
builder.appendOrganizations(contentValuesListMap.get(
Organization.CONTENT_ITEM_TYPE));
}
if ((mVCardType & VCardConfig.FLAG_REFRAIN_WEBSITES_EXPORT) == 0) {
builder.appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
}
if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
}
if ((mVCardType & VCardConfig.FLAG_REFRAIN_NOTES_EXPORT) == 0) {
builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE));
}
if ((mVCardType & VCardConfig.FLAG_REFRAIN_EVENTS_EXPORT) == 0) {
builder.appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE));
}
return builder.toString();
}
}
public void terminate() {
closeCursorIfAppropriate();
mTerminateCalled = true;
}
private void closeCursorIfAppropriate() {
if (!mCursorSuppliedFromOutside && mCursor != null) {
try {
mCursor.close();
} catch (SQLiteException e) {
Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
}
mCursor = null;
}
if (mEntityIterator != null) {
try{
mEntityIterator.close();
} catch (SQLiteException e) {
Log.e(LOG_TAG, "SQLiteException on EntityIterator#close(): " + e.getMessage());
}
mEntityIterator = null;
}
}
@Override
protected void finalize() throws Throwable {
try {
if (!mTerminateCalled) {
Log.e(LOG_TAG, "finalized() is called before terminate() being called");
}
} finally {
super.finalize();
}
}
/**
* @return returns the number of available entities. The return value is undefined
* when this object is not ready yet (typically when {{@link #init()} is not called
* or when {@link #terminate()} is already called).
*/
public int getCount() {
if (mCursor == null) {
Log.w(LOG_TAG, "This object is not ready yet.");
return 0;
}
return mCursor.getCount();
}
/**
* @return true when there's no entity to be built. The return value is undefined
* when this object is not ready yet.
*/
public boolean isAfterLast() {
if (mEntityIterator == null) {
Log.w(LOG_TAG, "This object is not ready yet.");
return false;
}
return !mEntityIterator.hasNext();
}
/**
* @return Returns the error reason.
*/
public String getErrorReason() {
return mErrorReason;
}
}
@@ -0,0 +1,554 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* The class representing VCard related configurations. Useful static methods are not in this class
* but in VCardUtils.
*/
public class VCardConfig {
private static final String LOG_TAG = VCardConstants.LOG_TAG;
/* package */ static final int LOG_LEVEL_NONE = 0;
/* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
/* package */ static final int LOG_LEVEL_SHOW_WARNING = 0x2;
/* package */ static final int LOG_LEVEL_VERBOSE =
LOG_LEVEL_PERFORMANCE_MEASUREMENT | LOG_LEVEL_SHOW_WARNING;
/* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
/**
* <p>
* The charset used during import.
* </p>
* <p>
* We cannot determine which charset should be used to interpret lines in vCard,
* while Java requires us to specify it when InputStream is used.
* We need to rely on the mechanism due to some performance reason.
* </p>
* <p>
* In order to avoid "misinterpretation" of charset and lose any data in vCard,
* "ISO-8859-1" is first used for reading the stream.
* When a charset is specified in a property (with "CHARSET=..." parameter),
* the string is decoded to raw bytes and encoded into the specific charset,
* </p>
* <p>
* Unicode specification there's a one to one mapping between each byte in ISO-8859-1
* and a codepoint, and Java specification requires runtime must have the charset.
* Thus, ISO-8859-1 is one effective mapping for intermediate mapping.
* </p>
*/
public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1";
/**
* The charset used when there's no information affbout what charset should be used to
* encode the binary given from vCard.
*/
public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
/**
* Do not use statically like "version == VERSION_V21"
*/
public static final int VERSION_21 = 0;
public static final int VERSION_30 = 1;
public static final int VERSION_40 = 2;
public static final int VERSION_MASK = 3;
public static final int NAME_ORDER_DEFAULT = 0;
public static final int NAME_ORDER_EUROPE = 0x4;
public static final int NAME_ORDER_JAPANESE = 0x8;
private static final int NAME_ORDER_MASK = 0xC;
// 0x10 is reserved for safety
/**
* <p>
* The flag indicating the vCard composer will add some "X-" properties used only in Android
* when the formal vCard specification does not have appropriate fields for that data.
* </p>
* <p>
* For example, Android accepts nickname information while vCard 2.1 does not.
* When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
* instead of just dropping it.
* </p>
* <p>
* vCard parser code automatically parses the field emitted even when this flag is off.
* </p>
*/
private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
/**
* <p>
* The flag indicating the vCard composer will add some "X-" properties seen in the
* vCard data emitted by the other softwares/devices when the formal vCard specification
* does not have appropriate field(s) for that data.
* </p>
* <p>
* One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
* for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
* non-Android devices/softwares. We chose to enable the vCard composer to use those
* defact properties since they are also useful for Android devices.
* </p>
* <p>
* Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
* allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
* in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
* </p>
*/
private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
/**
* <p>
* The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
* mobile careers) should be used. This flag does not include any other information like
* that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
* dialect but the name order should be European", but it is not recommended.
* </p>
*/
private static final int FLAG_DOCOMO = 0x20000000;
/**
* <p>
* The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
* properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
* </p>
* <p>
* We actually cannot define what is the "primary" property. Note that this is NOT defined
* in vCard specification either. Also be aware that it is NOT related to "primary" notion
* used in {@link android.provider.ContactsContract}.
* This notion is just for vCard composition in Android.
* </p>
* <p>
* We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
* do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
* even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
* other properties like "ADR", "ORG", etc.
* <p>
* We are afraid of the case where some vCard importer also forget handling QP presuming QP is
* not used in such fields.
* </p>
* <p>
* This flag is useful when some target importer you are going to focus on does not accept
* such properties with Quoted-Printable encoding.
* </p>
* <p>
* Again, we should not use this flag at all for complying vCard 2.1 spec.
* </p>
* <p>
* In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
* kind of problem (hopefully).
* </p>
* @hide
*/
public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
/**
* <p>
* The flag indicating that phonetic name related fields must be converted to
* appropriate form. Note that "appropriate" is not defined in any vCard specification.
* This is Android-specific.
* </p>
* <p>
* One typical (and currently sole) example where we need this flag is the time when
* we need to emit Japanese phonetic names into vCard entries. The property values
* should be encoded into half-width katakana when the target importer is Japanese mobile
* phones', which are probably not able to parse full-width hiragana/katakana for
* historical reasons, while the vCard importers embedded to softwares for PC should be
* able to parse them as we expect.
* </p>
*/
public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000;
/**
* <p>
* The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
* every time possible. The default behavior does not emit it and is valid in the spec.
* In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
* </p>
* <p>
* Detail:
* How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
* </p>
* <p>
* e.g.
* </p>
* <ol>
* <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
* <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
* <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
* </ol>
* <p>
* If you are targeting to the importer which cannot accept TYPE params without "TYPE="
* strings (which should be rare though), please use this flag.
* </p>
* <p>
* Example usage:
* <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
* </p>
*/
public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
/**
* <p>
* The flag indicating the vCard composer does touch nothing toward phone number Strings
* but leave it as is.
* </p>
* <p>
* The vCard specifications mention nothing toward phone numbers, while some devices
* do (wrongly, but with innevitable reasons).
* For example, there's a possibility Japanese mobile phones are expected to have
* just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones
* should get such characters. To make exported vCard simple for external parsers,
* we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and
* removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)"
* becomes "111-222-3333").
* Unfortunate side effect of that use was some control characters used in the other
* areas may be badly affected by the formatting.
* </p>
* <p>
* This flag disables that formatting, affecting both importer and exporter.
* If the user is aware of some side effects due to the implicit formatting, use this flag.
* </p>
* <p>
* Caution: this flag will be removed in the future, replaced by some richer functionality.
* </p>
*/
public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
/**
* <P>
* The flag asking exporter to refrain image export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x00800000;
/**
* <P>
* The flag asking exporter to refrain events export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_EVENTS_EXPORT = 0x00400000;
/**
* <P>
* The flag asking exporter to refrain addess export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_ADDRESS_EXPORT = 0x00200000;
/**
* <P>
* The flag asking exporter to refrain email export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_EMAIL_EXPORT = 0x00100000;
/**
* <P>
* The flag asking exporter to refrain organization export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_ORGANIZATION_EXPORT = 0x00080000;
/**
* <P>
* The flag asking exporter to refrain notes export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_NOTES_EXPORT = 0x00040000;
/**
* <P>
* The flag asking exporter to refrain phonetic name export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_PHONETIC_NAME_EXPORT = 0x00020000;
/**
* <P>
* The flag asking exporter to refrain websites export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_WEBSITES_EXPORT = 0x00010000;
/**
* <P>
* The flag asking exporter to refrain nickname export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_NICKNAME_EXPORT = 0x00008000;
//// The followings are VCard types available from importer/exporter. ////
/**
* <p>
* The type indicating nothing. Used by {@link VCardSourceDetector} when it
* was not able to guess the exact vCard type.
* </p>
*/
public static final int VCARD_TYPE_UNKNOWN = 0;
/**
* <p>
* Generic vCard format with the vCard 2.1. When composing a vCard entry,
* the US convension will be used toward formatting some values.
* </p>
* <p>
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
* while it should be "Prefix Family Middle Given Suffix" in Japan for example.
* </p>
* <p>
* Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
* outside Android cannot accept it since vCard 2.1 specifically does not allow
* that charset, while we need to use it to support various languages around the world.
* </p>
* <p>
* If you want to use alternative charset, you should notify the charset to the other
* compontent to be used.
* </p>
*/
public static final int VCARD_TYPE_V21_GENERIC =
(VERSION_21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
/**
* <p>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
* </p>
* <p>
* Not fully ready yet. Use with caution when you use this.
* </p>
*/
public static final int VCARD_TYPE_V30_GENERIC =
(VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
/**
* General vCard format with the version 4.0.
* @hide vCard 4.0 is not published yet.
*/
public static final int VCARD_TYPE_V40_GENERIC =
(VERSION_40 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V40_GENERIC_STR = "v40_generic";
/**
* <p>
* General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
* </p>
*/
public static final int VCARD_TYPE_V21_EUROPE =
(VERSION_21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
/**
* <p>
* General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
* </p>
* <p>
* Not ready yet. Use with caution when you use this.
* </p>
*/
public static final int VCARD_TYPE_V30_EUROPE =
(VERSION_30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
/**
* <p>
* The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
* </p>
* <p>
* Not ready yet. Use with caution when you use this.
* </p>
*/
public static final int VCARD_TYPE_V21_JAPANESE =
(VERSION_21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
/**
* <p>
* The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
* </p>
* <p>
* Not ready yet. Use with caution when you use this.
* </p>
*/
public static final int VCARD_TYPE_V30_JAPANESE =
(VERSION_30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8";
/**
* <p>
* The vCard 2.1 based format which (partially) considers the convention in Japanese
* mobile phones, where phonetic names are translated to half-width katakana if
* possible, etc. It would be better to use Shift_JIS as a charset for maximum
* compatibility.
* </p>
* @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
(VERSION_21 | NAME_ORDER_JAPANESE |
FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
/**
* <p>
* The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
* </p>
* <p>
* Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
* No Android-specific property nor defact property is included. The "Primary" properties
* are NOT encoded to Quoted-Printable.
* </p>
* @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_DOCOMO =
(VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
/* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
private static final Map<String, Integer> sVCardTypeMap;
private static final Set<Integer> sJapaneseMobileTypeSet;
static {
sVCardTypeMap = new HashMap<String, Integer>();
sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
sJapaneseMobileTypeSet = new HashSet<Integer>();
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
public static int getVCardTypeFromString(final String vcardTypeString) {
final String loweredKey = vcardTypeString.toLowerCase();
if (sVCardTypeMap.containsKey(loweredKey)) {
return sVCardTypeMap.get(loweredKey);
} else if ("default".equalsIgnoreCase(vcardTypeString)) {
return VCARD_TYPE_DEFAULT;
} else {
Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
return VCARD_TYPE_DEFAULT;
}
}
public static boolean isVersion21(final int vcardType) {
return (vcardType & VERSION_MASK) == VERSION_21;
}
public static boolean isVersion30(final int vcardType) {
return (vcardType & VERSION_MASK) == VERSION_30;
}
public static boolean isVersion40(final int vcardType) {
return (vcardType & VERSION_MASK) == VERSION_40;
}
public static boolean shouldUseQuotedPrintable(final int vcardType) {
return !isVersion30(vcardType);
}
public static int getNameOrderType(final int vcardType) {
return vcardType & NAME_ORDER_MASK;
}
public static boolean usesAndroidSpecificProperty(final int vcardType) {
return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
}
public static boolean usesDefactProperty(final int vcardType) {
return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
}
public static boolean showPerformanceLog() {
return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
}
public static boolean shouldRefrainQPToNameProperties(final int vcardType) {
return (!shouldUseQuotedPrintable(vcardType) ||
((vcardType & FLAG_REFRAIN_QP_TO_NAME_PROPERTIES) != 0));
}
public static boolean appendTypeParamName(final int vcardType) {
return (isVersion30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
}
/**
* @return true if the device is Japanese and some Japanese convension is
* applied to creating "formatted" something like FORMATTED_ADDRESS.
*/
public static boolean isJapaneseDevice(final int vcardType) {
// TODO: Some mask will be required so that this method wrongly interpret
// Japanese"-like" vCard type.
// e.g. VCARD_TYPE_V21_JAPANESE_SJIS | FLAG_APPEND_TYPE_PARAMS
return sJapaneseMobileTypeSet.contains(vcardType);
}
/* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) {
return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0);
}
public static boolean needsToConvertPhoneticString(final int vcardType) {
return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
}
public static boolean onlyOneNoteFieldIsAvailable(final int vcardType) {
return vcardType == VCARD_TYPE_DOCOMO;
}
public static boolean isDoCoMo(final int vcardType) {
return ((vcardType & FLAG_DOCOMO) != 0);
}
private VCardConfig() {
}
}
@@ -0,0 +1,190 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
/**
* Constants used in both exporter and importer code.
*/
public class VCardConstants {
/* package */ static final String LOG_TAG = "vCard";
public static final String VERSION_V21 = "2.1";
public static final String VERSION_V30 = "3.0";
public static final String VERSION_V40 = "4.0";
// Valid and supported property names.
public static final String PROPERTY_BEGIN = "BEGIN";
public static final String PROPERTY_VERSION = "VERSION";
public static final String PROPERTY_N = "N";
public static final String PROPERTY_FN = "FN";
public static final String PROPERTY_ADR = "ADR";
public static final String PROPERTY_EMAIL = "EMAIL";
public static final String PROPERTY_NOTE = "NOTE";
public static final String PROPERTY_ORG = "ORG";
public static final String PROPERTY_SOUND = "SOUND"; // Not fully supported.
public static final String PROPERTY_TEL = "TEL";
public static final String PROPERTY_TITLE = "TITLE";
public static final String PROPERTY_ROLE = "ROLE";
public static final String PROPERTY_PHOTO = "PHOTO";
public static final String PROPERTY_LOGO = "LOGO";
public static final String PROPERTY_URL = "URL";
public static final String PROPERTY_BDAY = "BDAY"; // Birthday (3.0, 4.0)
public static final String PROPERTY_ANNIVERSARY = "ANNIVERSARY"; // Date of marriage (4.0)
public static final String PROPERTY_NAME = "NAME"; // (3.0)
public static final String PROPERTY_NICKNAME = "NICKNAME"; // (3.0, 4.0)
public static final String PROPERTY_SORT_STRING = "SORT-STRING"; // (3.0, 4.0)
public static final String PROPERTY_IMPP = "IMPP"; // RFC 4770 (vCard 3.0) and vCard 4.0
public static final String PROPERTY_END = "END";
// Valid property names which are not supported (not appropriately handled) by
// importer/exporter. Those properties will be read and discarded silently.
public static final String PROPERTY_REV = "REV";
public static final String PROPERTY_AGENT = "AGENT"; // (3.0)
public static final String PROPERTY_GENDER = "GENDER"; // (4.0)
public static final String PROPERTY_XML = "XML"; // (4.0)
public static final String PROPERTY_FBURL = "FBURL"; // (4.0)
public static final String PROPERTY_PRODID = "PRODID"; // (4.0)
public static final String PROPERTY_RELATED = "RELATED"; // (4.0)
public static final String PROPERTY_CATEGORIES = "CATEGORIES"; // (4.0)
public static final String PROPERTY_CLIENTPIDMAP = "CLIENTPIDMAP"; // (4.0)
public static final String PROPERTY_CALURI = "CALURI"; // (4.0)
// defact SIP property which had been used till RFC 4770.
public static final String PROPERTY_X_SIP = "X-SIP";
// Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
// De-fact property values expressing phonetic names.
public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
// Properties both ContactsStruct and de-fact vCard extensions
// Shown in http://en.wikipedia.org/wiki/VCard support are defined here.
public static final String PROPERTY_X_AIM = "X-AIM";
public static final String PROPERTY_X_MSN = "X-MSN";
public static final String PROPERTY_X_YAHOO = "X-YAHOO";
public static final String PROPERTY_X_ICQ = "X-ICQ";
public static final String PROPERTY_X_JABBER = "X-JABBER";
public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
// Properties only ContactsStruct has. We alse use this.
public static final String PROPERTY_X_QQ = "X-QQ";
public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
// Phone number for Skype, available as usual phone.
public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
// Property for Android-specific fields.
public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
// Properties for DoCoMo vCard.
public static final String PROPERTY_X_CLASS = "X-CLASS";
public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
public static final String PROPERTY_X_NO = "X-NO";
public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
public static final String PARAM_TYPE = "TYPE";
public static final String PARAM_TYPE_HOME = "HOME";
public static final String PARAM_TYPE_WORK = "WORK";
public static final String PARAM_TYPE_FAX = "FAX";
public static final String PARAM_TYPE_CELL = "CELL";
public static final String PARAM_TYPE_VOICE = "VOICE";
public static final String PARAM_TYPE_INTERNET = "INTERNET";
public static final String PARAM_VALUE = "VALUE";
public static final String PARAM_CHARSET = "CHARSET";
public static final String PARAM_ENCODING = "ENCODING";
// Abbreviation of "prefered" according to vCard 2.1 specification.
// We interpret this value as "primary" property during import/export.
//
// Note: Both vCard specs does not mention anything about the requirement for this parameter,
// but there may be some vCard importer which will get confused with more than
// one "PREF"s in one property name, while Android accepts them.
public static final String PARAM_TYPE_PREF = "PREF";
// Phone type parameters valid in vCard and known to ContactsContract, but not so common.
public static final String PARAM_TYPE_CAR = "CAR";
public static final String PARAM_TYPE_ISDN = "ISDN";
public static final String PARAM_TYPE_PAGER = "PAGER";
public static final String PARAM_TYPE_TLX = "TLX"; // Telex
// Phone types existing in vCard 2.1 but not known to ContactsContract.
public static final String PARAM_TYPE_MODEM = "MODEM";
public static final String PARAM_TYPE_MSG = "MSG";
public static final String PARAM_TYPE_BBS = "BBS";
public static final String PARAM_TYPE_VIDEO = "VIDEO";
public static final String PARAM_ENCODING_7BIT = "7BIT";
public static final String PARAM_ENCODING_8BIT = "8BIT";
public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
// TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
// These types are basically encoded to "X-" parameters when composing vCard.
// Parser passes these when "X-" is added to the parameter or not.
public static final String PARAM_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
public static final String PARAM_PHONE_EXTRA_TYPE_RADIO = "RADIO";
public static final String PARAM_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
public static final String PARAM_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
// vCard composer translates this type to "WORK" + "PREF". Just for parsing.
public static final String PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
// vCard composer translates this type to "VOICE" Just for parsing.
public static final String PARAM_PHONE_EXTRA_TYPE_OTHER = "OTHER";
// TYPE parameters for postal addresses.
public static final String PARAM_ADR_TYPE_PARCEL = "PARCEL";
public static final String PARAM_ADR_TYPE_DOM = "DOM";
public static final String PARAM_ADR_TYPE_INTL = "INTL";
// {@link VCardBuilder} translates this type to "X-OTHER".
public static final String PARAM_ADR_EXTRA_TYPE_OTHER = "OTHER";
public static final String PARAM_LANGUAGE = "LANGUAGE";
// SORT-AS parameter introduced in vCard 4.0 (as of rev.13)
public static final String PARAM_SORT_AS = "SORT-AS";
// TYPE parameters not officially valid but used in some vCard exporter.
// Do not use in composer side.
public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
public interface ImportOnly {
public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
// Some device emits this "X-" parameter for expressing Google Talk,
// which is specifically invalid but should be always properly accepted, and emitted
// in some special case (for that device/application).
public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
}
//// Mainly for package constants.
// DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
// SORT-STRING invCard 3.0.
/* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
// Used in unit test.
public static final int MAX_DATA_COLUMN = 15;
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
private VCardConstants() {
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,118 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.util.Log;
import java.util.ArrayList;
/**
* <P>
* {@link VCardEntryHandler} implementation which commits the entry to ContentResolver.
* </P>
* <P>
* Note:<BR />
* Each vCard may contain big photo images encoded by BASE64,
* If we store all vCard entries in memory, OutOfMemoryError may be thrown.
* Thus, this class push each VCard entry into ContentResolver immediately.
* </P>
*/
public class VCardEntryCommitter implements VCardEntryHandler {
public static String LOG_TAG = VCardConstants.LOG_TAG;
private final ContentResolver mContentResolver;
private long mTimeToCommit;
// Set the default maximum batch size to 20
private int mMaxBatchSize = 20;
private ArrayList<ContentProviderOperation> mOperationList;
private final ArrayList<Uri> mCreatedUris = new ArrayList<Uri>();
public VCardEntryCommitter(ContentResolver resolver) {
mContentResolver = resolver;
}
@Override
public void onStart() {
}
@Override
public void onEnd() {
if (mOperationList != null) {
mCreatedUris.add(pushIntoContentResolver(mOperationList));
}
if (VCardConfig.showPerformanceLog()) {
Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
}
}
//because the max batch size is 500 defined in ContactsProvider,so we can enlarge this batch
//size to reduce db open/close times. From testing results, we can see performance better
//when batch size is more bigger.And also each vcardEntry may have some operation records.
//So we can set threshold as 450, batch operations will be executed when threshold reached.
//Testing result.
//batch size : 100 200 300 400 450 490 20
//consume time(10000 contacts): 178s 143s 127s 124s 119s 117s 195s
//consume time (1000 contacts): 17.3s 13.9s 12.6s 12.2s 11.8s 11.6s 19.8s
public void setMaxBatchSize(int batchSize) {
mMaxBatchSize = batchSize;
}
@Override
public void onEntryCreated(final VCardEntry vcardEntry) {
final long start = System.currentTimeMillis();
mOperationList = vcardEntry.constructInsertOperations(mContentResolver, mOperationList);
if (mOperationList != null && mOperationList.size() >= mMaxBatchSize) {
mCreatedUris.add(pushIntoContentResolver(mOperationList));
mOperationList = null;
}
mTimeToCommit += System.currentTimeMillis() - start;
}
private Uri pushIntoContentResolver(ArrayList<ContentProviderOperation> operationList) {
try {
final ContentProviderResult[] results = mContentResolver.applyBatch(
ContactsContract.AUTHORITY, operationList);
// the first result is always the raw_contact. return it's uri so
// that it can be found later. do null checking for badly behaving
// ContentResolvers
return ((results == null || results.length == 0 || results[0] == null)
? null : results[0].uri);
} catch (RemoteException e) {
Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
/**
* Returns the list of created Uris. This list should not be modified by the caller as it is
* not a clone.
*/
public ArrayList<Uri> getCreatedUris() {
return mCreatedUris;
}
}
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import android.accounts.Account;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* <p>
* The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
* to easily handle each vCard entry.
* </p>
* <p>
* This class understand details inside vCard and translates it to {@link VCardEntry}.
* Then the class throw it to {@link VCardEntryHandler} registered via
* {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
* are able to handle the {@link VCardEntry} object.
* </p>
* <p>
* If you want to know the detail inside vCard, it would be better to implement
* {@link VCardInterpreter} directly, instead of relying on this class and
* {@link VCardEntry} created by the object.
* </p>
*/
public class VCardEntryConstructor implements VCardInterpreter {
private static String LOG_TAG = VCardConstants.LOG_TAG;
/**
* Represents current stack of VCardEntry. Used to support nested vCard (vCard 2.1).
*/
private final List<VCardEntry> mEntryStack = new ArrayList<VCardEntry>();
private VCardEntry mCurrentEntry;
private final int mVCardType;
private final Account mAccount;
private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
public VCardEntryConstructor() {
this(VCardConfig.VCARD_TYPE_V21_GENERIC, null, null);
}
public VCardEntryConstructor(final int vcardType) {
this(vcardType, null, null);
}
public VCardEntryConstructor(final int vcardType, final Account account) {
this(vcardType, account, null);
}
/**
* @deprecated targetCharset is not used anymore.
* Use {@link #VCardEntryConstructor(int, Account)}
*/
@Deprecated
public VCardEntryConstructor(final int vcardType, final Account account,
String targetCharset) {
mVCardType = vcardType;
mAccount = account;
}
public void addEntryHandler(VCardEntryHandler entryHandler) {
mEntryHandlers.add(entryHandler);
}
@Override
public void onVCardStarted() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onStart();
}
}
@Override
public void onVCardEnded() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEnd();
}
}
public void clear() {
mCurrentEntry = null;
mEntryStack.clear();
}
@Override
public void onEntryStarted() {
mCurrentEntry = new VCardEntry(mVCardType, mAccount);
mEntryStack.add(mCurrentEntry);
}
@Override
public void onEntryEnded() {
mCurrentEntry.consolidateFields();
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEntryCreated(mCurrentEntry);
}
final int size = mEntryStack.size();
if (size > 1) {
VCardEntry parent = mEntryStack.get(size - 2);
parent.addChild(mCurrentEntry);
mCurrentEntry = parent;
} else {
mCurrentEntry = null;
}
mEntryStack.remove(size - 1);
}
@Override
public void onPropertyCreated(VCardProperty property) {
mCurrentEntry.addProperty(property);
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
/**
* The class which just counts the number of vCard entries in the specified input.
*/
public class VCardEntryCounter implements VCardInterpreter {
private int mCount;
public int getCount() {
return mCount;
}
@Override
public void onVCardStarted() {
}
@Override
public void onVCardEnded() {
}
@Override
public void onEntryStarted() {
}
@Override
public void onEntryEnded() {
mCount++;
}
@Override
public void onPropertyCreated(VCardProperty property) {
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
/**
* <p>
* The interface called by {@link VCardEntryConstructor}.
* </p>
* <p>
* This class is useful when you don't want to know vCard data in detail. If you want to know
* it, it would be better to consider using {@link VCardInterpreter}.
* </p>
*/
public interface VCardEntryHandler {
/**
* Called when the parsing started.
*/
public void onStart();
/**
* The method called when one vCard entry is created. Children come before their parent in
* nested vCard files.
*
* e.g.
* In the following vCard, the entry for "entry2" comes before one for "entry1".
* <code>
* BEGIN:VCARD
* N:entry1
* BEGIN:VCARD
* N:entry2
* END:VCARD
* END:VCARD
* </code>
*/
public void onEntryCreated(final VCardEntry entry);
/**
* Called when the parsing ended.
* Able to be use this method for showing performance log, etc.
*/
public void onEnd();
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import java.util.List;
/**
* <P>
* The interface which should be implemented by the classes which have to analyze each
* vCard entry minutely.
* </P>
* <P>
* Here, there are several terms specific to vCard (and this library).
* </P>
* <P>
* The term "entry" is one vCard representation in the input, which should start with "BEGIN:VCARD"
* and end with "END:VCARD".
* </P>
* <P>
* The term "property" is one line in vCard entry, which consists of "group", "property name",
* "parameter(param) names and values", and "property values".
* </P>
* <P>
* e.g. group1.propName;paramName1=paramValue1;paramName2=paramValue2;propertyValue1;propertyValue2...
* </P>
*/
public interface VCardInterpreter {
/**
* Called when vCard interpretation started.
*/
void onVCardStarted();
/**
* Called when vCard interpretation finished.
*/
void onVCardEnded();
/**
* Called when parsing one vCard entry started.
* More specifically, this method is called when "BEGIN:VCARD" is read.
*
* This may be called before {@link #onEntryEnded()} is called, as vCard 2.1 accepts nested
* vCard.
*
* <code>
* BEGIN:VCARD
* BEGIN:VCARD
* VERSION:2.1
* N:test;;;;
* END:VCARD
* END:VCARD
* </code>
*/
void onEntryStarted();
/**
* Called when parsing one vCard entry ended.
* More specifically, this method is called when "END:VCARD" is read.
*/
void onEntryEnded();
/**
* Called when a property is created.
*/
void onPropertyCreated(VCardProperty property);
}
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
public abstract class VCardParser {
/**
* Registers one {@link VCardInterpreter} instance, which receives events along with
* vCard parsing.
*
* @param interpreter
*/
public abstract void addInterpreter(VCardInterpreter interpreter);
/**
* <p>Parses a whole InputStream as a vCard file and lets registered {@link VCardInterpreter}
* instances handle callbacks.</p>
*
* <p>This method reads a whole InputStream. If you just want to parse one vCard entry inside
* a vCard file with multiple entries, try {@link #parseOne(InputStream)}.</p>
*
* @param is The source to parse.
* @throws IOException, VCardException
*/
public abstract void parse(InputStream is) throws IOException, VCardException;
/**
* <p>Parses the first vCard entry in InputStream and lets registered {@link VCardInterpreter}
* instances handle callbacks.</p>
*
* <p>This method finishes itself when the first entry ended.</p>
*
* <p>Note that, registered {@link VCardInterpreter} may still see multiple
* {@link VCardInterpreter#onEntryStarted()} / {@link VCardInterpreter#onEntryEnded()} calls
* even with this method.</p>
*
* <p>This happens when the first entry contains nested vCards, which is allowed in vCard 2.1.
* See the following example.</p>
*
* <code>
* BEGIN:VCARD
* N:a
* BEGIN:VCARD
* N:b
* END:VCARD
* END:VCARD
* </code>
*
* <p>With this vCard, registered interpreters will grab two
* {@link VCardInterpreter#onEntryStarted()} and {@link VCardInterpreter#onEntryEnded()}
* calls. Callers should handle the situation by themselves.</p>
*
* @param is The source to parse.
* @throws IOException, VCardException
*/
public abstract void parseOne(InputStream is) throws IOException, VCardException;
/**
* @deprecated use {@link #addInterpreter(VCardInterpreter)} and
* {@link #parse(InputStream)}
*/
@Deprecated
public void parse(InputStream is, VCardInterpreter interpreter)
throws IOException, VCardException {
addInterpreter(interpreter);
parse(is);
}
/**
* <p>
* Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
* </p>
* <p>
* Actual cancel is done after parsing the current vcard.
* </p>
*/
public abstract void cancel();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,363 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import android.util.Log;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.util.Set;
/**
* <p>
* Basic implementation achieving vCard 3.0 parsing.
* </p>
* <p>
* This class inherits vCard 2.1 implementation since technically they are similar,
* while specifically there's logical no relevance between them.
* So that developers are not confused with the inheritance,
* {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
* {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
* </p>
* @hide
*/
/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
private static final String LOG_TAG = VCardConstants.LOG_TAG;
private String mPreviousLine;
private boolean mEmittedAgentWarning = false;
public VCardParserImpl_V30() {
super();
}
public VCardParserImpl_V30(int vcardType) {
super(vcardType);
}
@Override
protected int getVersion() {
return VCardConfig.VERSION_30;
}
@Override
protected String getVersionString() {
return VCardConstants.VERSION_V30;
}
@Override
protected String peekLine() throws IOException {
if (mPreviousLine != null) {
String ret = mPreviousLine;
return ret;
} else {
return mReader.peekLine();
}
}
@Override
protected String getLine() throws IOException {
if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
} else {
return mReader.readLine();
}
}
/**
* vCard 3.0 requires that the line with space at the beginning of the line
* must be combined with previous line.
*/
@Override
protected String getNonEmptyLine() throws IOException, VCardException {
String line;
StringBuilder builder = null;
while ((line = mReader.readLine()) != null) {
// Skip empty lines in order to accomodate implementations that
// send line termination variations such as \r\r\n.
if (line.length() == 0) {
continue;
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
// RFC 2425 describes line continuation as \r\n followed by
// a single ' ' or '\t' whitespace character.
if (builder == null) {
builder = new StringBuilder();
}
if (mPreviousLine != null) {
builder.append(mPreviousLine);
mPreviousLine = null;
}
builder.append(line.substring(1));
} else {
if (builder != null || mPreviousLine != null) {
break;
}
mPreviousLine = line;
}
}
String ret = null;
if (builder != null) {
ret = builder.toString();
} else if (mPreviousLine != null) {
ret = mPreviousLine;
}
mPreviousLine = line;
if (ret == null) {
throw new VCardException("Reached end of buffer.");
}
return ret;
}
/*
* vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
* 1 * (contentline)
* ;A vCard object MUST include the VERSION, FN and N types.
* [group "."] "END" ":" "VCARD" 1 * CRLF
*/
@Override
protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
return super.readBeginVCard(allowGarbage);
}
/**
* vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
*/
@Override
protected void handleParams(VCardProperty propertyData, final String params)
throws VCardException {
try {
super.handleParams(propertyData, params);
} catch (VCardException e) {
// maybe IANA type
String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
handleAnyParam(propertyData, strArray[0], strArray[1]);
} else {
// Must not come here in the current implementation.
throw new VCardException(
"Unknown params value: " + params);
}
}
}
@Override
protected void handleAnyParam(
VCardProperty propertyData, final String paramName, final String paramValue) {
splitAndPutParam(propertyData, paramName, paramValue);
}
@Override
protected void handleParamWithoutName(VCardProperty property, final String paramValue) {
handleType(property, paramValue);
}
/*
* vCard 3.0 defines
*
* param = param-name "=" param-value *("," param-value)
* param-name = iana-token / x-name
* param-value = ptext / quoted-string
* quoted-string = DQUOTE QSAFE-CHAR DQUOTE
* QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
* ; Any character except CTLs, DQUOTE
*
* QSAFE-CHAR must not contain DQUOTE, including escaped one (\").
*/
@Override
protected void handleType(VCardProperty property, final String paramValue) {
splitAndPutParam(property, VCardConstants.PARAM_TYPE, paramValue);
}
/**
* Splits parameter values into pieces in accordance with vCard 3.0 specification and
* puts pieces into mInterpreter.
*/
/*
* param-value = ptext / quoted-string
* quoted-string = DQUOTE QSAFE-CHAR DQUOTE
* QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
* ; Any character except CTLs, DQUOTE
*
* QSAFE-CHAR must not contain DQUOTE, including escaped one (\")
*/
private void splitAndPutParam(VCardProperty property, String paramName, String paramValue) {
// "comma,separated:inside.dquote",pref
// -->
// - comma,separated:inside.dquote
// - pref
//
// Note: Though there's a code, we don't need to take much care of
// wrongly-added quotes like the example above, as they induce
// parse errors at the top level (when splitting a line into parts).
StringBuilder builder = null; // Delay initialization.
boolean insideDquote = false;
final int length = paramValue.length();
for (int i = 0; i < length; i++) {
final char ch = paramValue.charAt(i);
if (ch == '"') {
if (insideDquote) {
// End of Dquote.
property.addParameter(paramName, encodeParamValue(builder.toString()));
builder = null;
insideDquote = false;
} else {
if (builder != null) {
if (builder.length() > 0) {
// e.g.
// pref"quoted"
Log.w(LOG_TAG, "Unexpected Dquote inside property.");
} else {
// e.g.
// pref,"quoted"
// "quoted",pref
property.addParameter(paramName, encodeParamValue(builder.toString()));
}
}
insideDquote = true;
}
} else if (ch == ',' && !insideDquote) {
if (builder == null) {
Log.w(LOG_TAG, "Comma is used before actual string comes. (" +
paramValue + ")");
} else {
property.addParameter(paramName, encodeParamValue(builder.toString()));
builder = null;
}
} else {
// To stop creating empty StringBuffer at the end of parameter,
// we delay creating this object until this point.
if (builder == null) {
builder = new StringBuilder();
}
builder.append(ch);
}
}
if (insideDquote) {
// e.g.
// "non-quote-at-end
Log.d(LOG_TAG, "Dangling Dquote.");
}
if (builder != null) {
if (builder.length() == 0) {
Log.w(LOG_TAG, "Unintended behavior. We must not see empty StringBuilder " +
"at the end of parameter value parsing.");
} else {
property.addParameter(paramName, encodeParamValue(builder.toString()));
}
}
}
/**
* Encode a param value using UTF-8.
*/
protected String encodeParamValue(String paramValue) {
return VCardUtils.convertStringCharset(
paramValue, VCardConfig.DEFAULT_INTERMEDIATE_CHARSET, "UTF-8");
}
@Override
protected void handleAgent(VCardProperty property) {
// The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
//
// e.g.
// AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
// TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
// ET:jfriday@host.com\nEND:VCARD\n
//
// TODO: fix this.
//
// issue:
// vCard 3.0 also allows this as an example.
//
// AGENT;VALUE=uri:
// CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
//
// This is not vCard. Should we support this?
//
// Just ignore the line for now, since we cannot know how to handle it...
if (!mEmittedAgentWarning) {
Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
mEmittedAgentWarning = true;
}
}
/**
* This is only called from handlePropertyValue(), which has already
* read the first line of this property. With v3.0, the getNonEmptyLine()
* routine has already concatenated all following continuation lines.
* The routine is implemented in the V21 parser to concatenate v2.1 style
* data blocks, but is unnecessary here.
*/
@Override
protected String getBase64(final String firstString)
throws IOException, VCardException {
return firstString;
}
/**
* ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
* ; \\ encodes \, \n or \N encodes newline
* ; \; encodes ;, \, encodes ,
*
* Note: Apple escapes ':' into '\:' while does not escape '\'
*/
@Override
protected String maybeUnescapeText(final String text) {
return unescapeText(text);
}
public static String unescapeText(final String text) {
StringBuilder builder = new StringBuilder();
final int length = text.length();
for (int i = 0; i < length; i++) {
char ch = text.charAt(i);
if (ch == '\\' && i < length - 1) {
final char next_ch = text.charAt(++i);
if (next_ch == 'n' || next_ch == 'N') {
builder.append("\n");
} else {
builder.append(next_ch);
}
} else {
builder.append(ch);
}
}
return builder.toString();
}
@Override
protected String maybeUnescapeCharacter(final char ch) {
return unescapeCharacter(ch);
}
public static String unescapeCharacter(final char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
}
}
@Override
protected Set<String> getKnownPropertyNameSet() {
return VCardParser_V30.sKnownPropertyNameSet;
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import java.util.Set;
/**
* <p>
* Basic implementation parsing vCard 4.0.
* </p>
* <p>
* vCard 4.0 is not published yet. Also this implementation is premature.
* </p>
* @hide
*/
/* package */ class VCardParserImpl_V40 extends VCardParserImpl_V30 {
// private static final String LOG_TAG = VCardConstants.LOG_TAG;
public VCardParserImpl_V40() {
super();
}
public VCardParserImpl_V40(final int vcardType) {
super(vcardType);
}
@Override
protected int getVersion() {
return VCardConfig.VERSION_40;
}
@Override
protected String getVersionString() {
return VCardConstants.VERSION_V40;
}
/**
* We escape "\N" into new line for safety.
*/
@Override
protected String maybeUnescapeText(final String text) {
return unescapeText(text);
}
public static String unescapeText(final String text) {
// TODO: more strictly, vCard 4.0 requires different type of unescaping rule
// toward each property.
final StringBuilder builder = new StringBuilder();
final int length = text.length();
for (int i = 0; i < length; i++) {
char ch = text.charAt(i);
if (ch == '\\' && i < length - 1) {
final char next_ch = text.charAt(++i);
if (next_ch == 'n' || next_ch == 'N') {
builder.append("\n");
} else {
builder.append(next_ch);
}
} else {
builder.append(ch);
}
}
return builder.toString();
}
public static String unescapeCharacter(final char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
}
}
@Override
protected Set<String> getKnownPropertyNameSet() {
return VCardParser_V40.sKnownPropertyNameSet;
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* </p>
* vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
* </p>
* <p>
* The spec is written in 1996, and currently various types of "vCard 2.1" exist.
* To handle real the world vCard formats appropriately and effectively, this class does not
* obey with strict vCard 2.1.
* In stead, not only vCard spec but also real world vCard is considered.
* </p>
* e.g. A lot of devices and softwares let vCard importer/exporter to use
* the PNG format to determine the type of image, while it is not allowed in
* the original specification. As of 2010, we can see even the FLV format
* (possible in Japanese mobile phones).
* </p>
*/
public final class VCardParser_V21 extends VCardParser {
/**
* A unmodifiable Set storing the property names available in the vCard 2.1 specification.
*/
/* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("BEGIN", "END", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
/**
* A unmodifiable Set storing the types known in vCard 2.1.
*/
/* package */ static final Set<String> sKnownTypeSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
"PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
"MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
"ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
"MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
"CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
"PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
"WAVE", "AIFF", "PCM", "X509", "PGP")));
/**
* A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
*/
/* package */ static final Set<String> sKnownValueSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
/**
* <p>
* A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
* </p>
* <p>
* Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
* We allow it for safety.
* </p>
*/
/* package */ static final Set<String> sAvailableEncoding =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
VCardConstants.PARAM_ENCODING_8BIT,
VCardConstants.PARAM_ENCODING_QP,
VCardConstants.PARAM_ENCODING_BASE64,
VCardConstants.PARAM_ENCODING_B)));
private final VCardParserImpl_V21 mVCardParserImpl;
public VCardParser_V21() {
mVCardParserImpl = new VCardParserImpl_V21();
}
public VCardParser_V21(int vcardType) {
mVCardParserImpl = new VCardParserImpl_V21(vcardType);
}
@Override
public void addInterpreter(VCardInterpreter interpreter) {
mVCardParserImpl.addInterpreter(interpreter);
}
@Override
public void parse(InputStream is) throws IOException, VCardException {
mVCardParserImpl.parse(is);
}
@Override
public void parseOne(InputStream is) throws IOException, VCardException {
mVCardParserImpl.parseOne(is);
}
@Override
public void cancel() {
mVCardParserImpl.cancel();
}
}
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* <p>
* vCard parser for vCard 3.0. See RFC 2426 for more detail.
* </p>
* <p>
* This parser allows vCard format which is not allowed in the RFC, since
* we have seen several vCard 3.0 files which don't comply with it.
* </p>
* <p>
* e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
* have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
* but it is not a must. We silently allow "CHARSET".
* </p>
*/
public class VCardParser_V30 extends VCardParser {
/* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "END", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
"SORT-STRING", "CATEGORIES", "PRODID", // 3.0
"IMPP"))); // RFC 4770
/**
* <p>
* A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
* </p>
* <p>
* Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
* </p>
* <p>
* "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
* because the encoding ambiguates how the vCard file to be parsed.
* </p>
*/
/* package */ static final Set<String> sAcceptableEncoding =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
VCardConstants.PARAM_ENCODING_7BIT,
VCardConstants.PARAM_ENCODING_8BIT,
VCardConstants.PARAM_ENCODING_BASE64,
VCardConstants.PARAM_ENCODING_B)));
private final VCardParserImpl_V30 mVCardParserImpl;
public VCardParser_V30() {
mVCardParserImpl = new VCardParserImpl_V30();
}
public VCardParser_V30(int vcardType) {
mVCardParserImpl = new VCardParserImpl_V30(vcardType);
}
@Override
public void addInterpreter(VCardInterpreter interpreter) {
mVCardParserImpl.addInterpreter(interpreter);
}
@Override
public void parse(InputStream is) throws IOException, VCardException {
mVCardParserImpl.parse(is);
}
@Override
public void parseOne(InputStream is) throws IOException, VCardException {
mVCardParserImpl.parseOne(is);
}
@Override
public void cancel() {
mVCardParserImpl.cancel();
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import com.android.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* <p>
* vCard parser for vCard 4.0. DO NOT USE IN PRODUCTION.
* </p>
* <p>
* Currently this parser is based on vCard 4.0 specification rev 15 (partly).
* Note that some of current implementation lack basic capability required in vCard 4.0.
* (e.g. PHOTO has data parameter in rev 15 while this implementation requires "ENCODING=b")
* </p>
*/
public class VCardParser_V40 extends VCardParser {
/* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "END", "VERSION",
"SOURCE", "KIND", "FN", "N", "NICKNAME",
"PHOTO", "BDAY", "ANNIVERSARY", "GENDER", "ADR", "TEL",
"EMAIL", "IMPP", "LANG", "TZ", "GEO", "TITLE", "ROLE",
"LOGO", "ORG", "MEMBER", "RELATED", "CATEGORIES",
"NOTE", "PRODID", "REV", "SOUND", "UID", "CLIENTPIDMAP",
"URL", "KEY", "FBURL", "CALENDRURI", "CALURI", "XML")));
/**
* <p>
* A unmodifiable Set storing the values for the type "ENCODING", available in vCard 4.0.
* </p>
*/
/* package */ static final Set<String> sAcceptableEncoding =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
VCardConstants.PARAM_ENCODING_8BIT,
VCardConstants.PARAM_ENCODING_B)));
private final VCardParserImpl_V40 mVCardParserImpl;
public VCardParser_V40() {
mVCardParserImpl = new VCardParserImpl_V40();
}
public VCardParser_V40(int vcardType) {
mVCardParserImpl = new VCardParserImpl_V40(vcardType);
}
@Override
public void addInterpreter(VCardInterpreter interpreter) {
mVCardParserImpl.addInterpreter(interpreter);
}
@Override
public void parse(InputStream is) throws IOException, VCardException {
mVCardParserImpl.parse(is);
}
@Override
public void parseOne(InputStream is) throws IOException, VCardException {
mVCardParserImpl.parseOne(is);
}
@Override
public void cancel() {
mVCardParserImpl.cancel();
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.vcard;
/**
* Callback functionality which can be used with
* {@link VCardComposer#setPhoneNumberTranslationCallback(VCardPhoneNumberTranslationCallback)}.
* See the doc for the method.
*
* <p>
* TODO: this should be more generic
* </p>
*
* @hide This will change
*/
public interface VCardPhoneNumberTranslationCallback {
/**
* Called when a phone number is being handled.
* @return formatted phone number.
*/
public String onValueReceived(String rawValue, int type, String label, boolean isPrimary);
}
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* Represents vCard's property, or logical "one line" of each vCard entry.
*
* e.g.
* Given a vCard below, objects for <code>N:name</code>, <code>TEL:1111111111</code> are
* "property".
*
* <code>
* BEGIN:VCARD
* N:name
* TEL:1111111111
* END:VCARD
* </code>
*
* vCard's property has three elements: name, parameter (or param), and value. Name is the name
* of each property. Parameter or param is additional information for the property. Value is one
* or multiple values representing the parameter.
*
* e.g.
* <code>N;CHARSET=UTF-8:Joe;Due;M.;Mr.;Ph.D.</code>, has "N" for name, "CHALSET=UTF-8" for param,
* and "Joe;Due;M.;Mr.;Ph.D." for value.
*
* Usually params are represented as "key=value" pair, but not always
* (The property <code>TEL;WORK;VOICE:(111) 555-1212</code> has two params without key "TYPE",
* which are same as "TYPE=WORK" and "TYPE=VOICE". In vCard 3.0, we can even express them as
* "TYPE=WORK,VOICE").
*
* Sometimes (not always) value can be separated by semi-colon. In the example above "Joe;Due;;;"
* should be interpreted as five strings: "Joe" (for family name), "Due" (for given name), "M."
* (for middle name), "Mr." (for prefix), and "Ph.D." (for suffix). Whether the value is
* separable or not is specified by vCard specs.
*/
public class VCardProperty {
private static final String LOG_TAG = VCardConstants.LOG_TAG;
private String mName;
private List<String> mGroupList;
private Map<String, Collection<String>> mParameterMap =
new HashMap<String, Collection<String>>();
private String mRawValue;
private List<String> mValueList;
private byte[] mByteValue;
public void setName(String name) {
if (mName != null) {
Log.w(LOG_TAG, String.format("Property name is re-defined " +
"(existing: %s, requested: %s", mName, name));
}
mName = name;
}
public void addGroup(String group) {
if (mGroupList == null) {
mGroupList = new ArrayList<String>();
}
mGroupList.add(group);
}
public void setParameter(final String paramName, final String paramValue) {
mParameterMap.clear();
addParameter(paramName, paramValue);
}
public void addParameter(final String paramName, final String paramValue) {
Collection<String> values;
if (!mParameterMap.containsKey(paramName)) {
if (paramName.equals("TYPE")) {
values = new HashSet<String>();
} else {
values = new ArrayList<String>();
}
mParameterMap.put(paramName, values);
} else {
values = mParameterMap.get(paramName);
}
values.add(paramValue);
}
public void setRawValue(String rawValue) {
mRawValue = rawValue;
}
// TODO: would be much better to have translateRawValue() functionality instead of forcing
// VCardParserImpl does this job.
public void setValues(String... propertyValues) {
mValueList = Arrays.asList(propertyValues);
}
public void setValues(List<String> propertyValueList) {
mValueList = propertyValueList;
}
public void addValues(String... propertyValues) {
if (mValueList == null) {
mValueList = Arrays.asList(propertyValues);
} else {
mValueList.addAll(Arrays.asList(propertyValues));
}
}
public void addValues(List<String> propertyValueList) {
if (mValueList == null) {
mValueList = new ArrayList<String>(propertyValueList);
} else {
mValueList.addAll(propertyValueList);
}
}
public void setByteValue(byte[] byteValue) {
mByteValue = byteValue;
}
public String getName() {
return mName;
}
public List<String> getGroupList() {
return mGroupList;
}
public Map<String, Collection<String>> getParameterMap() {
return mParameterMap;
}
public Collection<String> getParameters(String type) {
return mParameterMap.get(type);
}
public String getRawValue() {
return mRawValue;
}
public List<String> getValueList() {
return mValueList;
}
public byte[] getByteValue() {
return mByteValue;
}
}
@@ -0,0 +1,186 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import android.text.TextUtils;
import android.util.Log;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* <p>
* The class which tries to detects the source of a vCard file from its contents.
* </p>
* <p>
* The specification of vCard (including both 2.1 and 3.0) is not so strict as to
* guess its format just by reading beginning few lines (usually we can, but in
* some most pessimistic case, we cannot until at almost the end of the file).
* Also we cannot store all vCard entries in memory, while there's no specification
* how big the vCard entry would become after the parse.
* </p>
* <p>
* This class is usually used for the "first scan", in which we can understand which vCard
* version is used (and how many entries exist in a file).
* </p>
*/
public class VCardSourceDetector implements VCardInterpreter {
private static final String LOG_TAG = VCardConstants.LOG_TAG;
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
"X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
"X-ABADR", "X-ABUID"));
private static Set<String> JAPANESE_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
"X-GNO", "X-GN", "X-REDUCTION"));
private static Set<String> WINDOWS_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
"X-MICROSOFT-ASST_TEL", "X-MICROSOFT-ASSISTANT", "X-MICROSOFT-OFFICELOC"));
// Note: these signes appears before the signs of the other type (e.g. "X-GN").
// In other words, Japanese FOMA mobile phones are detected as FOMA, not JAPANESE_MOBILE_PHONES.
private static Set<String> FOMA_SIGNS = new HashSet<String>(Arrays.asList(
"X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
"X-SD-DESCRIPTION"));
private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
/**
* Represents that no estimation is available. Users of this class is able to this
* constant when you don't want to let a vCard parser rely on estimation for parse type.
*/
public static final int PARSE_TYPE_UNKNOWN = 0;
// For Apple's software, which does not mean this type is effective for all its products.
// We confirmed they usually use UTF-8, but not sure about vCard type.
private static final int PARSE_TYPE_APPLE = 1;
// For Japanese mobile phones, which are usually using Shift_JIS as a charset.
private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
// For some of mobile phones released from DoCoMo.
private static final int PARSE_TYPE_DOCOMO_FOMA = 3;
// For Japanese Windows Mobile phones. It's version is supposed to be 6.5.
private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
private int mParseType = PARSE_TYPE_UNKNOWN;
private int mVersion = -1; // -1 == unknown
// Some mobile phones (like FOMA) tells us the charset of the data.
private String mSpecifiedCharset;
@Override
public void onVCardStarted() {
}
@Override
public void onVCardEnded() {
}
@Override
public void onEntryStarted() {
}
@Override
public void onEntryEnded() {
}
@Override
public void onPropertyCreated(VCardProperty property) {
final String propertyName = property.getName();
final List<String> valueList = property.getValueList();
if (propertyName.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)
&& valueList.size() > 0) {
final String versionString = valueList.get(0);
if (versionString.equals(VCardConstants.VERSION_V21)) {
mVersion = VCardConfig.VERSION_21;
} else if (versionString.equals(VCardConstants.VERSION_V30)) {
mVersion = VCardConfig.VERSION_30;
} else if (versionString.equals(VCardConstants.VERSION_V40)) {
mVersion = VCardConfig.VERSION_40;
} else {
Log.w(LOG_TAG, "Invalid version string: " + versionString);
}
} else if (propertyName.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
mParseType = PARSE_TYPE_DOCOMO_FOMA;
if (valueList.size() > 0) {
mSpecifiedCharset = valueList.get(0);
}
}
if (mParseType != PARSE_TYPE_UNKNOWN) {
return;
}
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(propertyName)) {
mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
} else if (FOMA_SIGNS.contains(propertyName)) {
mParseType = PARSE_TYPE_DOCOMO_FOMA;
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(propertyName)) {
mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
} else if (APPLE_SIGNS.contains(propertyName)) {
mParseType = PARSE_TYPE_APPLE;
}
}
/**
* @return The available type can be used with vCard parser. You probably need to
* use {{@link #getEstimatedCharset()} to understand the charset to be used.
*/
public int getEstimatedType() {
switch (mParseType) {
case PARSE_TYPE_DOCOMO_FOMA:
return VCardConfig.VCARD_TYPE_DOCOMO;
case PARSE_TYPE_MOBILE_PHONE_JP:
return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
case PARSE_TYPE_APPLE:
case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
default: {
if (mVersion == VCardConfig.VERSION_21) {
return VCardConfig.VCARD_TYPE_V21_GENERIC;
} else if (mVersion == VCardConfig.VERSION_30) {
return VCardConfig.VCARD_TYPE_V30_GENERIC;
} else if (mVersion == VCardConfig.VERSION_40) {
return VCardConfig.VCARD_TYPE_V40_GENERIC;
} else {
return VCardConfig.VCARD_TYPE_UNKNOWN;
}
}
}
}
/**
* <p>
* Returns charset String guessed from the source's properties.
* This method must be called after parsing target file(s).
* </p>
* @return Charset String. Null is returned if guessing the source fails.
*/
public String getEstimatedCharset() {
if (TextUtils.isEmpty(mSpecifiedCharset)) {
return mSpecifiedCharset;
}
switch (mParseType) {
case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
case PARSE_TYPE_DOCOMO_FOMA:
case PARSE_TYPE_MOBILE_PHONE_JP:
return "SHIFT_JIS";
case PARSE_TYPE_APPLE:
return "UTF-8";
default:
return null;
}
}
}
@@ -0,0 +1,855 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.telephony.PhoneNumberUtils;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
import com.android.vcard.exception.VCardException;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Utilities for VCard handling codes.
*/
public class VCardUtils {
private static final String LOG_TAG = VCardConstants.LOG_TAG;
/**
* See org.apache.commons.codec.DecoderException
*/
private static class DecoderException extends Exception {
public DecoderException(String pMessage) {
super(pMessage);
}
}
/**
* See org.apache.commons.codec.net.QuotedPrintableCodec
*/
private static class QuotedPrintableCodecPort {
private static byte ESCAPE_CHAR = '=';
public static final byte[] decodeQuotedPrintable(byte[] bytes)
throws DecoderException {
if (bytes == null) {
return null;
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (int i = 0; i < bytes.length; i++) {
int b = bytes[i];
if (b == ESCAPE_CHAR) {
try {
int u = Character.digit((char) bytes[++i], 16);
int l = Character.digit((char) bytes[++i], 16);
if (u == -1 || l == -1) {
throw new DecoderException("Invalid quoted-printable encoding");
}
buffer.write((char) ((u << 4) + l));
} catch (ArrayIndexOutOfBoundsException e) {
throw new DecoderException("Invalid quoted-printable encoding");
}
} else {
buffer.write(b);
}
}
return buffer.toByteArray();
}
}
/**
* Ported methods which are hidden in {@link PhoneNumberUtils}.
*/
public static class PhoneNumberUtilsPort {
public static String formatNumber(String source, int defaultFormattingType) {
final SpannableStringBuilder text = new SpannableStringBuilder(source);
PhoneNumberUtils.formatNumber(text, defaultFormattingType);
return text.toString();
}
}
/**
* Ported methods which are hidden in {@link TextUtils}.
*/
public static class TextUtilsPort {
public static boolean isPrintableAscii(final char c) {
final int asciiFirst = 0x20;
final int asciiLast = 0x7E; // included
return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
}
public static boolean isPrintableAsciiOnly(final CharSequence str) {
final int len = str.length();
for (int i = 0; i < len; i++) {
if (!isPrintableAscii(str.charAt(i))) {
return false;
}
}
return true;
}
}
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
// converted to two parameter Strings. These only contain some minor fields valid in both
// vCard and current (as of 2009-08-07) Contacts structure.
private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
private static final Set<String> sPhoneTypesUnknownToContactsSet;
private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
private static final Set<String> sMobilePhoneLabelSet;
static {
sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
Phone.TYPE_CALLBACK);
sKnownPhoneTypeMap_StoI.put(
VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
Phone.TYPE_TTY_TDD);
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
Phone.TYPE_ASSISTANT);
// OTHER (default in Android) should correspond to VOICE (default in vCard).
sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_VOICE, Phone.TYPE_OTHER);
sPhoneTypesUnknownToContactsSet = new HashSet<String>();
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
VCardConstants.PROPERTY_X_GOOGLE_TALK);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
// \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
// \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
// \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
// \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
"MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
"\uFF79\uFF72\uFF80\uFF72"));
}
public static String getPhoneTypeString(Integer type) {
return sKnownPhoneTypesMap_ItoS.get(type);
}
/**
* Returns Interger when the given types can be parsed as known type. Returns String object
* when not, which should be set to label.
*/
public static Object getPhoneTypeFromStrings(Collection<String> types,
String number) {
if (number == null) {
number = "";
}
int type = -1;
String label = null;
boolean isFax = false;
boolean hasPref = false;
if (types != null) {
for (final String typeStringOrg : types) {
if (typeStringOrg == null) {
continue;
}
final String typeStringUpperCase = typeStringOrg.toUpperCase();
if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) {
hasPref = true;
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_FAX)) {
isFax = true;
} else {
final String labelCandidate;
if (typeStringUpperCase.startsWith("X-") && type < 0) {
labelCandidate = typeStringOrg.substring(2);
} else {
labelCandidate = typeStringOrg;
}
if (labelCandidate.length() == 0) {
continue;
}
// e.g. "home" -> TYPE_HOME
final Integer tmp = sKnownPhoneTypeMap_StoI.get(labelCandidate.toUpperCase());
if (tmp != null) {
final int typeCandidate = tmp;
// 1. If a type isn't specified yet, we'll choose the new type candidate.
// 2. If the current type is default one (OTHER) or custom one, we'll
// prefer more specific types specified in the vCard. Note that OTHER and
// the other different types may appear simultaneously here, since vCard
// allow to have VOICE and HOME/WORK in one line.
// e.g. "TEL;WORK;VOICE:1" -> WORK + OTHER -> Type should be WORK
// 3. TYPE_PAGER is prefered when the number contains @ surronded by
// a pager number and a domain name.
// e.g.
// o 1111@domain.com
// x @domain.com
// x 1111@
final int indexOfAt = number.indexOf("@");
if ((typeCandidate == Phone.TYPE_PAGER
&& 0 < indexOfAt && indexOfAt < number.length() - 1)
|| type < 0
|| type == Phone.TYPE_CUSTOM
|| type == Phone.TYPE_OTHER) {
type = tmp;
}
} else if (type < 0) {
type = Phone.TYPE_CUSTOM;
label = labelCandidate;
}
}
}
}
if (type < 0) {
if (hasPref) {
type = Phone.TYPE_MAIN;
} else {
// default to TYPE_OTHER
type = Phone.TYPE_OTHER;
}
}
if (isFax) {
if (type == Phone.TYPE_HOME) {
type = Phone.TYPE_FAX_HOME;
} else if (type == Phone.TYPE_WORK) {
type = Phone.TYPE_FAX_WORK;
} else if (type == Phone.TYPE_OTHER) {
type = Phone.TYPE_OTHER_FAX;
}
}
if (type == Phone.TYPE_CUSTOM) {
return label;
} else {
return type;
}
}
@SuppressWarnings("deprecation")
public static boolean isMobilePhoneLabel(final String label) {
// For backward compatibility.
// Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
// To support mobile type at that time, this custom label had been used.
return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
}
public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
return sPhoneTypesUnknownToContactsSet.contains(label);
}
public static String getPropertyNameForIm(final int protocol) {
return sKnownImPropNameMap_ItoS.get(protocol);
}
public static String[] sortNameElements(final int nameOrder,
final String familyName, final String middleName, final String givenName) {
final String[] list = new String[3];
final int nameOrderType = VCardConfig.getNameOrderType(nameOrder);
switch (nameOrderType) {
case VCardConfig.NAME_ORDER_JAPANESE: {
if (containsOnlyPrintableAscii(familyName) &&
containsOnlyPrintableAscii(givenName)) {
list[0] = givenName;
list[1] = middleName;
list[2] = familyName;
} else {
list[0] = familyName;
list[1] = middleName;
list[2] = givenName;
}
break;
}
case VCardConfig.NAME_ORDER_EUROPE: {
list[0] = middleName;
list[1] = givenName;
list[2] = familyName;
break;
}
default: {
list[0] = givenName;
list[1] = middleName;
list[2] = familyName;
break;
}
}
return list;
}
public static int getPhoneNumberFormat(final int vcardType) {
if (VCardConfig.isJapaneseDevice(vcardType)) {
return PhoneNumberUtils.FORMAT_JAPAN;
} else {
return PhoneNumberUtils.FORMAT_NANP;
}
}
public static String constructNameFromElements(final int nameOrder,
final String familyName, final String middleName, final String givenName) {
return constructNameFromElements(nameOrder, familyName, middleName, givenName,
null, null);
}
public static String constructNameFromElements(final int nameOrder,
final String familyName, final String middleName, final String givenName,
final String prefix, final String suffix) {
final StringBuilder builder = new StringBuilder();
final String[] nameList = sortNameElements(nameOrder, familyName, middleName, givenName);
boolean first = true;
if (!TextUtils.isEmpty(prefix)) {
first = false;
builder.append(prefix);
}
for (final String namePart : nameList) {
if (!TextUtils.isEmpty(namePart)) {
if (first) {
first = false;
} else {
builder.append(' ');
}
builder.append(namePart);
}
}
if (!TextUtils.isEmpty(suffix)) {
if (!first) {
builder.append(' ');
}
builder.append(suffix);
}
return builder.toString();
}
/**
* Splits the given value into pieces using the delimiter ';' inside it.
*
* Escaped characters in those values are automatically unescaped into original form.
*/
public static List<String> constructListFromValue(final String value,
final int vcardType) {
final List<String> list = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
final int length = value.length();
for (int i = 0; i < length; i++) {
char ch = value.charAt(i);
if (ch == '\\' && i < length - 1) {
char nextCh = value.charAt(i + 1);
final String unescapedString;
if (VCardConfig.isVersion40(vcardType)) {
unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh);
} else if (VCardConfig.isVersion30(vcardType)) {
unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh);
} else {
if (!VCardConfig.isVersion21(vcardType)) {
// Unknown vCard type
Log.w(LOG_TAG, "Unknown vCard type");
}
unescapedString = VCardParserImpl_V21.unescapeCharacter(nextCh);
}
if (unescapedString != null) {
builder.append(unescapedString);
i++;
} else {
builder.append(ch);
}
} else if (ch == ';') {
list.add(builder.toString());
builder = new StringBuilder();
} else {
builder.append(ch);
}
}
list.add(builder.toString());
return list;
}
public static boolean containsOnlyPrintableAscii(final String...values) {
if (values == null) {
return true;
}
return containsOnlyPrintableAscii(Arrays.asList(values));
}
public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
if (values == null) {
return true;
}
for (final String value : values) {
if (TextUtils.isEmpty(value)) {
continue;
}
if (!TextUtilsPort.isPrintableAsciiOnly(value)) {
return false;
}
}
return true;
}
/**
* <p>
* This is useful when checking the string should be encoded into quoted-printable
* or not, which is required by vCard 2.1.
* </p>
* <p>
* See the definition of "7bit" in vCard 2.1 spec for more information.
* </p>
*/
public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
if (values == null) {
return true;
}
return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
}
public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
if (values == null) {
return true;
}
final int asciiFirst = 0x20;
final int asciiLast = 0x7E; // included
for (final String value : values) {
if (TextUtils.isEmpty(value)) {
continue;
}
final int length = value.length();
for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
final int c = value.codePointAt(i);
if (!(asciiFirst <= c && c <= asciiLast)) {
return false;
}
}
}
return true;
}
private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
/**
* <p>
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen.
* </p>
* <p>
* Note: It is already known some devices (wrongly) outputs properties with characters
* which should not be in the field. One example is "X-GOOGLE TALK". We accept
* such kind of input but must never output it unless the target is very specific
* to the device which is able to parse the malformed input.
* </p>
*/
public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
if (values == null) {
return true;
}
return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
}
public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
if (values == null) {
return true;
}
final int upperAlphabetFirst = 0x41; // A
final int upperAlphabetAfterLast = 0x5b; // [
final int lowerAlphabetFirst = 0x61; // a
final int lowerAlphabetAfterLast = 0x7b; // {
final int digitFirst = 0x30; // 0
final int digitAfterLast = 0x3A; // :
final int hyphen = '-';
for (final String str : values) {
if (TextUtils.isEmpty(str)) {
continue;
}
final int length = str.length();
for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
int codepoint = str.codePointAt(i);
if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
(upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
(digitFirst <= codepoint && codepoint < digitAfterLast) ||
(codepoint == hyphen))) {
return false;
}
}
}
return true;
}
public static boolean containsOnlyWhiteSpaces(final String...values) {
if (values == null) {
return true;
}
return containsOnlyWhiteSpaces(Arrays.asList(values));
}
public static boolean containsOnlyWhiteSpaces(final Collection<String> values) {
if (values == null) {
return true;
}
for (final String str : values) {
if (TextUtils.isEmpty(str)) {
continue;
}
final int length = str.length();
for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
if (!Character.isWhitespace(str.codePointAt(i))) {
return false;
}
}
}
return true;
}
/**
* <p>
* Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
* </p>
* <p>
* vCard 2.1 specifies:<br />
* word = &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;
}
private static final int[] sEscapeIndicatorsV30 = new int[]{
':', ';', ',', ' '
};
private static final int[] sEscapeIndicatorsV40 = new int[]{
';', ':'
};
/**
* <P>
* Returns String available as parameter value in vCard 3.0.
* </P>
* <P>
* RFC 2426 requires vCard composer to quote parameter values when it contains
* semi-colon, for example (See RFC 2426 for more information).
* This method checks whether the given String can be used without quotes.
* </P>
* <P>
* Note: We remove DQUOTE inside the given value silently for now.
* </P>
*/
public static String toStringAsV30ParamValue(String value) {
return toStringAsParamValue(value, sEscapeIndicatorsV30);
}
public static String toStringAsV40ParamValue(String value) {
return toStringAsParamValue(value, sEscapeIndicatorsV40);
}
private static String toStringAsParamValue(String value, final int[] escapeIndicators) {
if (TextUtils.isEmpty(value)) {
value = "";
}
final int asciiFirst = 0x20;
final int asciiLast = 0x7E; // included
final StringBuilder builder = new StringBuilder();
final int length = value.length();
boolean needQuote = false;
for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
final int codePoint = value.codePointAt(i);
if (codePoint < asciiFirst || codePoint == '"') {
// CTL characters and DQUOTE are never accepted. Remove them.
continue;
}
builder.appendCodePoint(codePoint);
for (int indicator : escapeIndicators) {
if (codePoint == indicator) {
needQuote = true;
break;
}
}
}
final String result = builder.toString();
return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result))
? ""
: (needQuote ? ('"' + result + '"')
: result));
}
public static String toHalfWidthString(final String orgString) {
if (TextUtils.isEmpty(orgString)) {
return null;
}
final StringBuilder builder = new StringBuilder();
final int length = orgString.length();
for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
// All Japanese character is able to be expressed by char.
// Do not need to use String#codepPointAt().
final char ch = orgString.charAt(i);
final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
if (halfWidthText != null) {
builder.append(halfWidthText);
} else {
builder.append(ch);
}
}
return builder.toString();
}
/**
* Guesses the format of input image. Currently just the first few bytes are used.
* The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
* the guess failed.
* @param input Image as byte array.
* @return The image type or null when the type cannot be determined.
*/
public static String guessImageType(final byte[] input) {
if (input == null) {
return null;
}
if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
return "GIF";
} else if (input.length >= 4 && input[0] == (byte) 0x89
&& input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
// Note: vCard 2.1 officially does not support PNG, but we may have it and
// using X- word like "X-PNG" may not let importers know it is PNG.
// So we use the String "PNG" as is...
return "PNG";
} else if (input.length >= 2 && input[0] == (byte) 0xff
&& input[1] == (byte) 0xd8) {
return "JPEG";
} else {
return null;
}
}
/**
* @return True when all the given values are null or empty Strings.
*/
public static boolean areAllEmpty(final String...values) {
if (values == null) {
return true;
}
for (final String value : values) {
if (!TextUtils.isEmpty(value)) {
return false;
}
}
return true;
}
/**
* Checks to see if a string looks like it could be an android generated quoted printable.
*
* Identification of quoted printable is not 100% reliable since it's just ascii. But given
* the high number and exact location of generated = signs, there is a high likely-hood that
* it would be.
*
* @return True if it appears like android quoted printable. False otherwise.
*/
public static boolean appearsLikeAndroidVCardQuotedPrintable(String value) {
// Quoted printable is always in multiple of 3s. With optional 1 '=' at end.
final int remainder = (value.length() % 3);
if (value.length() < 2 || (remainder != 1 && remainder != 0)) {
return false;
}
for (int i = 0; i < value.length(); i += 3) {
if (value.charAt(i) != '=') {
return false;
}
}
return true;
}
//// The methods bellow may be used by unit test.
/**
* Unquotes given Quoted-Printable value. value must not be null.
*/
public static String parseQuotedPrintable(
final String value, boolean strictLineBreaking,
String sourceCharset, String targetCharset) {
// "= " -> " ", "=\t" -> "\t".
// Previous code had done this replacement. Keep on the safe side.
final String quotedPrintable;
{
final StringBuilder builder = new StringBuilder();
final int length = value.length();
for (int i = 0; i < length; i++) {
char ch = value.charAt(i);
if (ch == '=' && i < length - 1) {
char nextCh = value.charAt(i + 1);
if (nextCh == ' ' || nextCh == '\t') {
builder.append(nextCh);
i++;
continue;
}
}
builder.append(ch);
}
quotedPrintable = builder.toString();
}
String[] lines;
if (strictLineBreaking) {
lines = quotedPrintable.split("\r\n");
} else {
StringBuilder builder = new StringBuilder();
final int length = quotedPrintable.length();
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < length; i++) {
char ch = quotedPrintable.charAt(i);
if (ch == '\n') {
list.add(builder.toString());
builder = new StringBuilder();
} else if (ch == '\r') {
list.add(builder.toString());
builder = new StringBuilder();
if (i < length - 1) {
char nextCh = quotedPrintable.charAt(i + 1);
if (nextCh == '\n') {
i++;
}
}
} else {
builder.append(ch);
}
}
final String lastLine = builder.toString();
if (lastLine.length() > 0) {
list.add(lastLine);
}
lines = list.toArray(new String[0]);
}
final StringBuilder builder = new StringBuilder();
for (String line : lines) {
if (line.endsWith("=")) {
line = line.substring(0, line.length() - 1);
}
builder.append(line);
}
final String rawString = builder.toString();
if (TextUtils.isEmpty(rawString)) {
Log.w(LOG_TAG, "Given raw string is empty.");
}
byte[] rawBytes = null;
try {
rawBytes = rawString.getBytes(sourceCharset);
} catch (UnsupportedEncodingException e) {
Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
rawBytes = rawString.getBytes();
}
byte[] decodedBytes = null;
try {
decodedBytes = QuotedPrintableCodecPort.decodeQuotedPrintable(rawBytes);
} catch (DecoderException e) {
Log.e(LOG_TAG, "DecoderException is thrown.");
decodedBytes = rawBytes;
}
try {
return new String(decodedBytes, targetCharset);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return new String(decodedBytes);
}
}
public static final VCardParser getAppropriateParser(int vcardType)
throws VCardException {
if (VCardConfig.isVersion21(vcardType)) {
return new VCardParser_V21();
} else if (VCardConfig.isVersion30(vcardType)) {
return new VCardParser_V30();
} else if (VCardConfig.isVersion40(vcardType)) {
return new VCardParser_V40();
} else {
throw new VCardException("Version is not specified");
}
}
public static final String convertStringCharset(
String originalString, String sourceCharset, String targetCharset) {
if (sourceCharset.equalsIgnoreCase(targetCharset)) {
return originalString;
}
final Charset charset = Charset.forName(sourceCharset);
final ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
final byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
return new String(bytes, targetCharset);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return null;
}
}
// TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean
private VCardUtils() {
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.exception;
public class VCardAgentNotSupportedException extends VCardNotSupportedException {
public VCardAgentNotSupportedException() {
super();
}
public VCardAgentNotSupportedException(String message) {
super(message);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.exception;
public class VCardException extends java.lang.Exception {
/**
* Constructs a VCardException object
*/
public VCardException() {
super();
}
/**
* Constructs a VCardException object
*
* @param message the error message
*/
public VCardException(String message) {
super(message);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.exception;
/**
* Thrown when the vCard has some line starting with '#'. In the specification,
* both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
* such lines.
*/
public class VCardInvalidCommentLineException extends VCardInvalidLineException {
public VCardInvalidCommentLineException() {
super();
}
public VCardInvalidCommentLineException(final String message) {
super(message);
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.exception;
/**
* Thrown when the vCard has some line starting with '#'. In the specification,
* both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
* such lines.
*/
public class VCardInvalidLineException extends VCardException {
public VCardInvalidLineException() {
super();
}
public VCardInvalidLineException(final String message) {
super(message);
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.exception;
/**
* VCardException thrown when VCard is nested without VCardParser's being notified.
*/
public class VCardNestedException extends VCardNotSupportedException {
public VCardNestedException() {
super();
}
public VCardNestedException(String message) {
super(message);
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.exception;
/**
* The exception which tells that the input VCard is probably valid from the view of
* specification but not supported in the current framework for now.
*
* This is a kind of a good news from the view of development.
* It may be good to ask users to send a report with the VCard example
* for the future development.
*/
public class VCardNotSupportedException extends VCardException {
public VCardNotSupportedException() {
super();
}
public VCardNotSupportedException(String message) {
super(message);
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.exception;
/**
* VCardException used only when the version of the vCard is different.
*/
public class VCardVersionException extends VCardException {
public VCardVersionException() {
super();
}
public VCardVersionException(String message) {
super(message);
}
}
+45
View File
@@ -0,0 +1,45 @@
// 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 {
default_team: "trendy_team_pixel_connectivity_bt",
default_applicable_licenses: ["Android-Apache-2.0"],
}
android_test {
name: "AndroidVCardTests",
certificate: "platform",
platform_apis: true,
test_suites: ["device-tests"],
srcs: ["src/**/*.java"],
libs: [
"android.test.runner.stubs.system",
"android.test.base.stubs.system",
"android.test.mock.stubs.system",
],
static_libs: [
"com.android.vcard",
"junit",
"androidx.test.ext.truth",
"androidx.test.rules",
"mockito-target",
"truth",
],
jacoco: {
include_filter: ["com.android.vcard.*"],
exclude_filter: ["com.android.vcard.tests.*"],
},
}
+32
View File
@@ -0,0 +1,32 @@
<?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.
-->
<!--
Run test with:
adb shell am instrument -w com.android.vcard.tests/android.test.InstrumentationTestRunner
-->
<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>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.vcard.tests"
android:label="TestRunner letting users run each test including new one" />
</manifest>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<configuration description="TestRunner letting users run each test including new one.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="AndroidVCardTests.apk" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="AndroidVCardTests" />
<test class="com.android.tradefed.testtype.InstrumentationTest" >
<option name="package" value="com.android.vcard.tests" />
<option name="runner" value="android.test.InstrumentationTestRunner" />
<option name="hidden-api-checks" value="false"/>
</test>
</configuration>
@@ -0,0 +1,10 @@
BEGIN:VCARD
VERSION:2.1
N:bogus
URL:http://bogus.com/
ADR;HOME:;;Grindelberg 999;Hamburg;;99999;Deutschland
ADR;HOME:;;Hermann v. Brevern\
9999999\
Packstation 999;Hamburg;;99999;Deutschland
BDAY;VALUE=DATE:20081203
END:VCARD
@@ -0,0 +1,6 @@
BEGIN:VCARD
X-ANDROID-CUSTOM:custom_mime1;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15
X-ANDROID-CUSTOM:custom_mime2;a;b;c;d;e;f;g;h;i;j;k;l;m;n;o;p
X-ANDROID-CUSTOM:custom_mime3
X-ANDROID-CUSTOM:custom_mime4;z
END:VCARD
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:2.1
N:;A\;B\\;C\\\;;D;\:E;\\\\;
FN:A;B\C\;D:E\\
END:VCARD
@@ -0,0 +1,78 @@
BEGIN:VCARD
VERSION:2.1
N:name
FN:fullname
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
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
END:VCARD
@@ -0,0 +1,44 @@
BEGIN:VCARD
VERSION:2.1
FN:boogie
N:boogie
PHOTO;ENCODING=BASE64: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
fGh4i/V4q4YNEfoiwAAAABJRU5ErkJggg==
TEL;TYPE=WORK:+5555555
TEL;TYPE=CELL:+5555556
EMAIL;TYPE=INTERNET:forrestgump@walladalla.com
END:VCARD
@@ -0,0 +1,107 @@
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;FAX:(404) 555-1212P*#55
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
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
X-ATTRIBUTE:Some String
BDAY:19800101
GEO:35.6563854,139.6994233
URL:http://www.example.com/
REV:20080424T195243Z
END:VCARD
+5
View File
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:2.1
X-ANDROID-CUSTOM:vnd.android.cursor.item/nickname;Nick;1;;;;;;;;;;;;;
X-GOOGLE-TALK:hhh@gmail.com
END:VCARD
@@ -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
@@ -0,0 +1,7 @@
BEGIN:VCARD
VERSION:2.1
N:;Omega;;;
EMAIL;INTERNET:"Omega"
<omega@example.com>
FN:Omega
END:VCARD
@@ -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
@@ -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
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:2.1
FN;CHARSET=SHIFT_JIS:ˆÉ”\ Œ\ˆê ˆÉ—\
N;CHARSET=SHIFT_JIS:ˆÉ”\;Œ\ˆê;ˆÉ—\;;
END:VCARD
@@ -0,0 +1,7 @@
BEGIN:VCARD
VERSION:2.1
N:name
FN:fullname
PHOTO;ENCODING=BASE64;TYPE=JPEG:qgEPAAIAAAAHAAAAugEQAAIAAAAG:ASDF==
END:VCARD
@@ -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
+13
View File
@@ -0,0 +1,13 @@
BEGIN:VCARD
VERSION:2.1
N:parent
BEGIN:VCARD
VERSION:2.1
N:nest1
END:VCARD
BEGIN:VCARD
VERSION:2.1
N:nest2
END:VCARD
TEL:1
END:VCARD
@@ -0,0 +1,16 @@
Content-Type: text/x-vcard
CHARSET=utf-8
profile=vCard
BEGIN:VCARD
VERSION:2.1
N:parent
BEGIN:VCARD
VERSION:2.1
N:nest1
END:VCARD
BEGIN:VCARD
VERSION:2.1
N:nest2
END:VCARD
TEL:1
END:VCARD
@@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
FN:Normal Guy
ORG:Company;Organization;Devision;Room;Sheet No.
TITLE:Excellent Janitor
END:VCARD
@@ -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
@@ -0,0 +1,3 @@
BEGIN:VCARD
N:Ando;Roid;
END:VCARD
@@ -0,0 +1,3 @@
BEGIN:VCARD
FN:Ando Roid
END:VCARD
@@ -0,0 +1,4 @@
BEGIN:VCARD
N:Ando;Roid;
FN:Ando Roid
END:VCARD
+6
View File
@@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
FN:Android
X-SIP:888
X-SIP:sip:90-180-360
END:VCARD
@@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
FN:Nice Guy
TITLE:Cool Title
ORG:Marverous;Perfect;Great;Good;Bad;Poor
END:VCARD
@@ -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
@@ -0,0 +1,42 @@
BEGIN:VCARD
VERSION:2.1
N:Ando;Roid;
PHOTO;ENCODING=BASE64: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
fGh4i/V4q4YNEfoiwAAAABJRU5ErkJggg==
X-ANDROID-CUSTOM:vnd.android.cursor.item/contact_event;1999-07-10;1;;;;;;;;;;;;;
BDAY:1975-08-20
END:VCARD
@@ -0,0 +1,9 @@
BEGIN:VCARD
VERSION:2.1
N:Ando;Roid;
ADR;X-custom:pobox;street
TEL;X-CuStoMpRop:1
TEL;custompropertywithoutx:2
EMAIL;X-cUstomPrOperty:email@example.com
EMAIL;CUSTOMPROPERTYWITHOUTX:email2@example.com
END:VCARD
@@ -0,0 +1,8 @@
BEGIN:VCARD
VERSION:3.0
N:Familyname;Givenname;;;
FN:Givenname Familyname
ADR;TYPE=WORK:;;1010 Technology Pkwy\, Silicon City\, Somecountry;;;;
ADR;TYPE=X-Other:;;123 Main St\, Anytown\, Anywhere;;;;
ADR;TYPE=X-School:;;112358 Academic Lane\, College Town\, Someplace Great;;;;
END:VCARD
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:3.0
N;TYPE=PREF,HOME:F;G;M;;
TEL;TYPE="COMMA,SEPARATED:INSIDE.DQUOTE",PREF:1
END:VCARD
@@ -0,0 +1,107 @@
BEGIN:VCARD
VERSION:3.0
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;FAX:(404) 555-1212P*#55
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
@@ -0,0 +1,104 @@
BEGIN:VCARD␍
VERSION:3.0␍
N:Smith;Bob;;;␍
FN:Bob Smith␍
ORG:Smith Auto Repair;␍
item1.EMAIL;type=INTERNET;type=pref:bob@smithauto.com␍
TEL;type=CELL;type=VOICE;type=pref:(650) 555-1212␍
item2.URL;type=pref:www.smithauto.com␍
item2.X-ABLabel:_$!<HomePage>!$_␍
PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4gxYSUNDX1BST0ZJTEUA␍
AQEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1␍
JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA␍
AAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8A␍
AAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5k␍
AAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAAB␍
RtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAE␍
PAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQ␍
AAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2␍
LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWF␍
laIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUA␍
AAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFk␍
lFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAA␍
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC␍
5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAA␍
AC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAA␍
AAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGlu␍
IElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbi␍
BJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4A␍
EM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAA␍
AAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZ␍
AB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAK␍
QAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgB␍
PgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDA␍
IUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMh␍
Ay0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH␍
4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYG␍
JwY3BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICw␍
gfCDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpU␍
CmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDP␍
MNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P␍
7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIx␍
NDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbW␍
FvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGu␍
wbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4f␍
aR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHy␍
RNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClr␍
KZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLy␍
QvWi+RL8cv/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1␍
TTWHNcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qj␍
voPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1␍
QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSj␍
dKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS␍
MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVl␍
qmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GND␍
Y5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK␍
9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52␍
m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqI␍
EKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuW␍
i/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflw␍
qXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopaj␍
BqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq␍
+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7wh␍
vJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6yb␍
nKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY11zX␍
4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vkc+T85YTmDe␍
aW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ␍
9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t////4QBMRXhpZgAATU0AKgAAAA␍
gAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAMqADAAQAAAABAAAA␍
MgAAAAD/2wBDAAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ␍
0JCgsLDAwMBwkNDg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsL␍
CwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAAyADIDASIAAhEBAxEB/8QAHw␍
AAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQR␍
BRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRk␍
dISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKz␍
tLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQ␍
EBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJB␍
UQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1␍
RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3␍
uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9/K␍
4L9oT9oLR/2efBo1PxGr3V3csYrGyjYCS6kAyeT91RwWbnGRwSQD1eqeMtI0PVbax1rVNOs769␍
IFvbz3KRyz5OBsQnLcgjgV8u/tsfBi9+LXxcink8a+B9GtdPsUggstV1XyJ4ySXdzHsON2V5zy␍
FFfHca53i8qyurUyqKniLqK1Xut9Wm7aLVJ7u2ljKtNxi+Xc8u+Inxa+K/gvwnpeqw3UvhPwx4␍
qnm1HS7XTpk2xhyHZQeZFU7t4UkD5jgDoM34S/HL4weNvGVtonw88Vaxfanfq6xxXU6SJgKWYk␍
zAhcAE56+nart7+y3qmpWFpa6l8U/hvcWtgCltDL4id47cNyRGpTCg98Ypuk/sq6joGoxXuhfF␍
H4a2V5Ad0U9v4haKWM9PlZUBHUjj1r+Y54fiGWMp1YyrqkuW6VeKlZJc6i01GKvflXLaKsraHm␍
2qXvrb1Po39jv9o2y15T8PfFGm2/h/xJ4eD2ywRybor3y2IkZSST5m4MzAk7slgTzj3+vgj4e/␍
s36j4O+JOj67afEr4aSXljqEdySmvFpZjvG5clPmLgsvPXdX27rPxG8P+HdftdK1/W9JstTvsf␍
Z7Se7SOabPA2oTk5PA9e1f0D4c55ja+Wyo5vFQdJqMW3H3ov4E+XTmVraJX00vc78PNuNpm1RR␍
RX6YdB+cX7dF5NP+1B4oaaV2a3MCxEtzEBBGQF9MEk/U079tO5k1D4yWs967SzTaDp0ju3JZjD␍
kk/U1D+3Ef+MnvF3+/D/6Tx0/9sf8A5K3Yf9i9pv8A6Ir+JeIpN1M8Tf8AzEx/9Krnjz+36/5n␍
lG0ego2D0H5UtFfntjnLnhtAfEumcD/j8h7f9NFrv/2zLuWf9pzxi8sjs8V0io27lAsMe0A9sd␍
q4Hw1/yMum/wDX3D/6MWu+/bR/5Oc8af8AXzH/AOk8VfRUdMgxH/X6l/6brmi/hv1X6n6PaDM1␍
xoVlJMxZ3gRmJ6klRk0U3w3n/hHbD/r2j/8AQRRX960XenH0R7iPNfin+xn4J+L/AI/TxH4sgv␍
1vSEFxHb3Hlw3oTgeauCc4AXKkZAr5O/4KDwJbftLXkcCKkcem2iqqjAUBCAAK/QiuH+LvwN8I␍
/Eq3m1HxzoGn6lf2dq6wzyofMUAEgZBGQCSQDnGa/N+OuAKOe5dWpZbGFKrOcak5NW53FS3aTd␍
/eb23v3bOetQU4tR0Z+YdFdz+zJ4YsPGvx68K6V4ptY73T767KTwSZ2yqI3bBxz1Ar03/goJ8I␍
fDfwk8ReGIfhxpFtpMV7bXDzrCG/eFWjCkkk9AT+Z9a/lTC8MYjF5JiM9jOPsqU1BrXmbfLqtL␍
W95de55apNwc+x4N4a/wCRl03P/P3D/wCjFr9GPiJ+yB4G+KXxCj8S+LtPnk1DKGdI7hkhvNgA␍
XzUHXgAcYyAAc15l+wd8C/CHib4OaT4k8QaBYXuuQ307JdzKXZSknyHBOOMDHHGK+nK/ofws4C␍
pUMqlXzOMKtPEezqRi1flspNN3Vr+9bS/XV3PQw1C0Ly1vqIqhVAUAAcADtRS0V+4nYFVtYUNp␍
N0GAIMLgg9/lNFFZ1vgl6MD82/2POP2mvBX/AF+t/wCiZK9o/wCCpqKNd8FMANxgvATjkgNBj+␍
Z/Oiiv5EyP/k3eZ/8AX6H50jyof7vL1/yPWf8AgnuAP2YNJI6m6u8+/wC/evbKKK/pbgf/AJJ7␍
L/8ArzT/APSUejR/hx9Aooor6k0P/9k=␍
END:VCARD␍
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:3.0
FN;CHARSET=SHIFT_JIS:ˆÉ”\ Œ\ˆê ˆÉ—\
N;CHARSET=SHIFT_JIS:ˆÉ”\;Œ\ˆê;ˆÉ—\;;
END:VCARD
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:3.0
N:F;G;M;;
TEL;TYPE="费":1
END:VCARD
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:3.0
N:F;G;M;;
TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
END:VCARD
@@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:3.0
FN:Pause Wait
N:Pause;Wait;;;
TEL:p1234p5678w9
END:VCARD
@@ -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
+5
View File
@@ -0,0 +1,5 @@
BEGIN:VCARD
VERSION:3.0
FN:Android
IMPP;TYPE=personal:sip:android@android.example.com
END:VCARD
+7
View File
@@ -0,0 +1,7 @@
BEGIN:VCARD
VERSION:4.0
FN:安藤 ロイド
N:安藤;ロイド;;;
TEL;TYPE=HOME:tel:1
TEL;TYPE=HOME:sip:example@example.com
END:VCARD
@@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:4.0
FN:安藤 ロイド
N;SORT-AS="あんどう;ろいど":安藤;ロイド;;;
ORG;TYPE=WORK;SORT-AS="ぐーぐる;けんさくぶもん":グーグル;検索部門
END:VCARD
@@ -0,0 +1,31 @@
/*
* Copyright 2022 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.JapaneseUtils;
import junit.framework.TestCase;
public class JapaneseUtilsTest extends TestCase {
static final char TEST_CONTAINED_CHAR = '\uFFE5';
static final char TEST_UNCONTAINED_CHAR = '\uFFF5';
public void testTryGetHalfWidthText() {
assertNull(JapaneseUtils.tryGetHalfWidthText(TEST_UNCONTAINED_CHAR));
assertEquals(JapaneseUtils.tryGetHalfWidthText(TEST_CONTAINED_CHAR), "\u005C\u005C");
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2012 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;
import com.android.vcard.VCardBuilder;
import com.android.vcard.VCardConfig;
import com.google.android.collect.Lists;
import junit.framework.TestCase;
import java.util.ArrayList;
/**
* Unit test for VCardBuilder.
*/
public class VCardBuilderTest extends TestCase {
public void testVCardNameFieldFromDisplayName() {
final ArrayList<ContentValues> contentList = Lists.newArrayList();
final ContentValues values = new ContentValues();
values.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "ने");
contentList.add(values);
final VCardBuilder builder = new VCardBuilder(VCardConfig.VCARD_TYPE_DEFAULT);
builder.appendNameProperties(contentList);
final String actual = builder.toString();
final String expectedCommon = ";CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:" +
"=E0=A4=A8=E0=A5=87";
final String expectedName = "N" + expectedCommon + ";;;;";
final String expectedFullName = "FN" + expectedCommon;
assertTrue("Actual value:\n" + actual + " expected to contain\n" + expectedName +
"\nbut does not.", actual.contains(expectedName));
assertTrue("Actual value:\n" + actual + " expected to contain\n" + expectedFullName +
"\nbut does not.", actual.contains(expectedFullName));
}
}
@@ -0,0 +1,436 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.tests;
import com.android.vcard.VCardConfig;
import com.android.vcard.VCardConstants;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntry.AndroidCustomData;
import com.android.vcard.VCardEntry.AnniversaryData;
import com.android.vcard.VCardEntry.BirthdayData;
import com.android.vcard.VCardEntry.EmailData;
import com.android.vcard.VCardEntry.EntryElement;
import com.android.vcard.VCardEntry.EntryLabel;
import com.android.vcard.VCardEntry.ImData;
import com.android.vcard.VCardEntry.NameData;
import com.android.vcard.VCardEntry.NicknameData;
import com.android.vcard.VCardEntry.NoteData;
import com.android.vcard.VCardEntry.OrganizationData;
import com.android.vcard.VCardEntry.PhoneData;
import com.android.vcard.VCardEntry.PhotoData;
import com.android.vcard.VCardEntry.PostalData;
import com.android.vcard.VCardEntry.SipData;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntryHandler;
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardProperty;
import android.accounts.Account;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.test.AndroidTestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class VCardEntryTests extends AndroidTestCase {
private class MockVCardEntryHandler implements VCardEntryHandler {
private List<VCardEntry> mEntries = new ArrayList<VCardEntry>();
private boolean mOnStartCalled;
private boolean mOnEndCalled;
@Override
public void onStart() {
assertFalse(mOnStartCalled);
mOnStartCalled = true;
}
@Override
public void onEntryCreated(VCardEntry entry) {
assertTrue(mOnStartCalled);
assertFalse(mOnEndCalled);
mEntries.add(entry);
}
@Override
public void onEnd() {
assertTrue(mOnStartCalled);
assertFalse(mOnEndCalled);
mOnEndCalled = true;
}
public List<VCardEntry> getEntries() {
return mEntries;
}
}
/**
* Tests VCardEntry and related clasess can handle nested classes given
* {@link VCardInterpreter} is called appropriately.
*
* This test manually calls VCardInterpreter's callback mechanism and checks
* {@link VCardEntryConstructor} constructs {@link VCardEntry} per given calls.
*
* Intended vCard is as follows:
* <code>
* BEGIN:VCARD
* N:test1
* BEGIN:VCARD
* N:test2
* END:VCARD
* TEL:1
* END:VCARD
* </code>
*/
public void testNestHandling() {
VCardEntryConstructor entryConstructor = new VCardEntryConstructor();
MockVCardEntryHandler entryHandler = new MockVCardEntryHandler();
entryConstructor.addEntryHandler(entryHandler);
entryConstructor.onVCardStarted();
entryConstructor.onEntryStarted();
VCardProperty property = new VCardProperty();
property.setName(VCardConstants.PROPERTY_N);
property.setValues("test1");
entryConstructor.onPropertyCreated(property);
entryConstructor.onEntryStarted(); // begin nest
property = new VCardProperty();
property.setName(VCardConstants.PROPERTY_N);
property.setValues("test2");
entryConstructor.onPropertyCreated(property);
entryConstructor.onEntryEnded(); // end nest
property = new VCardProperty();
property.setName(VCardConstants.PROPERTY_TEL);
property.setValues("1");
entryConstructor.onPropertyCreated(property);
entryConstructor.onEntryEnded();
entryConstructor.onVCardEnded();
List<VCardEntry> entries = entryHandler.getEntries();
assertEquals(2, entries.size());
VCardEntry parent = entries.get(1);
VCardEntry child = entries.get(0);
assertEquals("test1", parent.getDisplayName());
assertEquals("test2", child.getDisplayName());
List<VCardEntry.PhoneData> phoneList = parent.getPhoneList();
assertNotNull(phoneList);
assertEquals(1, phoneList.size());
assertEquals("1", phoneList.get(0).getNumber());
}
private class MockEntryElementIterator implements VCardEntry.EntryElementIterator {
private boolean mStartCalled;
private boolean mEndCalled;
private EntryLabel mLabel;
private final Map<EntryLabel, EntryElement> mExpectedElements =
new HashMap<EntryLabel, EntryElement>();
public void addExpectedElement(EntryElement elem) {
mExpectedElements.put(elem.getEntryLabel(), elem);
}
@Override
public void onIterationStarted() {
assertFalse(mStartCalled);
assertFalse(mEndCalled);
assertNull(mLabel);
mStartCalled = true;
}
@Override
public void onIterationEnded() {
assertTrue(mStartCalled);
assertFalse(mEndCalled);
assertNull(mLabel);
assertTrue("Expected Elements remaining: " +
Arrays.toString(mExpectedElements.values().toArray()),
mExpectedElements.isEmpty());
}
@Override
public void onElementGroupStarted(EntryLabel label) {
assertTrue(mStartCalled);
assertFalse(mEndCalled);
assertNull(mLabel);
mLabel = label;
}
@Override
public void onElementGroupEnded() {
assertTrue(mStartCalled);
assertFalse(mEndCalled);
assertNotNull(mLabel);
mLabel = null;
}
@Override
public boolean onElement(EntryElement elem) {
EntryElement expectedElem = mExpectedElements.remove(elem.getEntryLabel());
assertNotNull("Unexpected elem: " + elem.toString(), expectedElem);
assertEquals(expectedElem, elem);
return true;
}
}
/**
* Tests every element in VCardEntry is iterated by
* {@link VCardEntry#iterateAllData(com.android.vcard.VCardEntry.EntryElementIterator)}.
*/
public void testEntryElementIterator() {
VCardEntry entry = new VCardEntry();
MockEntryElementIterator iterator = new MockEntryElementIterator();
VCardProperty property = new VCardProperty();
property.setName("N");
property.setValues("family", "given", "middle", "prefix", "suffix");
entry.addProperty(property);
NameData nameData = new NameData();
nameData.setFamily("family");
nameData.setGiven("given");
nameData.setMiddle("middle");
nameData.setPrefix("prefix");
nameData.setSuffix("suffix");
iterator.addExpectedElement(nameData);
property = new VCardProperty();
property.setName("TEL");
property.setParameter("TYPE", "HOME");
property.setValues("1");
entry.addProperty(property);
PhoneData phoneData = new PhoneData("1", Phone.TYPE_HOME, null, false);
iterator.addExpectedElement(phoneData);
property = new VCardProperty();
property.setName("EMAIL");
property.setParameter("TYPE", "WORK");
property.setValues("email");
entry.addProperty(property);
EmailData emailData = new EmailData("email", Email.TYPE_WORK, null, false);
iterator.addExpectedElement(emailData);
property = new VCardProperty();
property.setName("ADR");
property.setParameter("TYPE", "HOME");
property.setValues(null, null, "street");
entry.addProperty(property);
PostalData postalData = new PostalData(null, null, "street", null, null, null,
null, StructuredPostal.TYPE_HOME, null, false,
VCardConfig.VCARD_TYPE_DEFAULT);
iterator.addExpectedElement(postalData);
property = new VCardProperty();
property.setName("ORG");
property.setValues("organization", "depertment");
entry.addProperty(property);
OrganizationData organizationData = new OrganizationData(
"organization", "depertment", null, null, Organization.TYPE_WORK, false);
iterator.addExpectedElement(organizationData);
property = new VCardProperty();
property.setName("X-GOOGLE-TALK");
property.setParameter("TYPE", "WORK");
property.setValues("googletalk");
entry.addProperty(property);
ImData imData = new ImData(
Im.PROTOCOL_GOOGLE_TALK, null, "googletalk", Im.TYPE_WORK, false);
iterator.addExpectedElement(imData);
property = new VCardProperty();
property.setName("PHOTO");
property.setParameter("TYPE", "PNG");
byte[] photoBytes = new byte[] {1};
property.setByteValue(photoBytes);
entry.addProperty(property);
PhotoData photoData = new PhotoData("PNG", photoBytes, false);
iterator.addExpectedElement(photoData);
property = new VCardProperty();
property.setName("X-SIP");
property.setValues("sipdata");
entry.addProperty(property);
SipData sipData = new SipData("sip:sipdata", SipAddress.TYPE_OTHER, null, false);
iterator.addExpectedElement(sipData);
property = new VCardProperty();
property.setName("NICKNAME");
property.setValues("nickname");
entry.addProperty(property);
NicknameData nicknameData = new NicknameData("nickname");
iterator.addExpectedElement(nicknameData);
property = new VCardProperty();
property.setName("NOTE");
property.setValues("note");
entry.addProperty(property);
NoteData noteData = new NoteData("note");
iterator.addExpectedElement(noteData);
property = new VCardProperty();
property.setName("BDAY");
property.setValues("birthday");
entry.addProperty(property);
BirthdayData birthdayData = new BirthdayData("birthday");
iterator.addExpectedElement(birthdayData);
property = new VCardProperty();
property.setName("ANNIVERSARY");
property.setValues("anniversary");
entry.addProperty(property);
AnniversaryData anniversaryData = new AnniversaryData("anniversary");
iterator.addExpectedElement(anniversaryData);
property = new VCardProperty();
property.setName("X-ANDROID-CUSTOM");
property.setValues("mime;value");
entry.addProperty(property);
AndroidCustomData androidCustom = new AndroidCustomData("mime", Arrays.asList("value"));
iterator.addExpectedElement(androidCustom);
entry.iterateAllData(iterator);
}
public void testToString() {
VCardEntry entry = new VCardEntry();
VCardProperty property = new VCardProperty();
property.setName("N");
property.setValues("Family", "Given", "Middle", "Prefix", "Suffix");
entry.addProperty(property);
entry.consolidateFields();
String result = entry.toString();
assertNotNull(result);
assertTrue(result.contains(String.valueOf(entry.hashCode())));
assertTrue(result.contains(VCardEntry.EntryLabel.NAME.toString()));
assertTrue(result.contains("Family"));
assertTrue(result.contains("Given"));
assertTrue(result.contains("Middle"));
assertTrue(result.contains("Prefix"));
assertTrue(result.contains("Suffix"));
}
/**
* Tests that VCardEntry emits correct insert operation for name field.
*/
public void testConstructInsertOperationsInsertName() {
VCardEntry entry = new VCardEntry();
VCardProperty property = new VCardProperty();
property.setName("N");
property.setValues("Family", "Given", "Middle", "Prefix", "Suffix");
entry.addProperty(property);
entry.consolidateFields();
NameData nameData = entry.getNameData();
assertEquals("Family", nameData.getFamily());
assertEquals("Given", nameData.getGiven());
assertEquals("Middle", nameData.getMiddle());
assertEquals("Prefix", nameData.getPrefix());
assertEquals("Suffix", nameData.getSuffix());
ContentResolver resolver = getContext().getContentResolver();
ArrayList<ContentProviderOperation> operationList =
new ArrayList<ContentProviderOperation>();
entry.constructInsertOperations(resolver, operationList);
// Need too many details for testing these. Just check basics.
// TODO: introduce nice-to-test mechanism here.
assertEquals(2, operationList.size());
assertEquals(ContentProviderOperation.TYPE_INSERT, operationList.get(0).getType());
assertEquals(ContentProviderOperation.TYPE_INSERT, operationList.get(1).getType());
}
/**
* Tests that VCardEntry refrains from emitting unnecessary insert operation.
*/
public void testConstructInsertOperationsEmptyData() {
VCardEntry entry = new VCardEntry();
ContentResolver resolver = getContext().getContentResolver();
ArrayList<ContentProviderOperation> operationList =
new ArrayList<ContentProviderOperation>();
entry.constructInsertOperations(resolver, operationList);
assertEquals(0, operationList.size());
}
/**
* Tests that VCardEntry can add an account via the constructor
*/
public void testConstructor_withAccount_canGetAccount() {
Account account = new Account("test-type", "test-name");
VCardEntry entry = new VCardEntry(VCardConfig.VCARD_TYPE_V21_GENERIC, account);
assertEquals(entry.getAccount(), account);
}
/**
* Tests that VCardEntry can add a null account via the constructor
*/
public void testConstructor_withNullAccount_canGetNullAccount() {
VCardEntry entry = new VCardEntry(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
assertEquals(entry.getAccount(), null);
}
/**
* Tests that VCardEntry can add an account via the setter, updating a null account to a value
*/
public void testSetAccount_fromNullToValue_canGetAccount() {
Account account = new Account("test-type", "test-name");
VCardEntry entry = new VCardEntry();
assertEquals(entry.getAccount(), null);
entry.setAccount(account);
assertEquals(entry.getAccount(), account);
}
/**
* Tests that VCardEntry can add an account via the setter, changing one account to another
*/
public void testSetAccount_fromToNewValue_canGetAccount() {
Account account1 = new Account("test-type-1", "test-name-1");
VCardEntry entry = new VCardEntry(VCardConfig.VCARD_TYPE_V21_GENERIC, account1);
assertEquals(entry.getAccount(), account1);
Account account2 = new Account("test-type-2", "test-name-2");
entry.setAccount(account2);
assertEquals(entry.getAccount(), account2);
}
/**
* Tests that VCardEntry can add an account via the setter, changing one account to another
*/
public void testSetAccount_setValueTwice_canGetAccount() {
VCardEntry entry = new VCardEntry();
assertEquals(entry.getAccount(), null);
Account account1 = new Account("test-type-1", "test-name-1");
entry.setAccount(account1);
assertEquals(entry.getAccount(), account1);
Account account2 = new Account("test-type-2", "test-name-2");
entry.setAccount(account2);
assertEquals(entry.getAccount(), account2);
}
// TODO: add bunch of test for constructInsertOperations..
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.tests;
import com.android.vcard.tests.testutils.VCardTestsBase;
/**
* Test cases specific for nested vCard.
*/
public class VCardImporterNestTests extends VCardTestsBase {
public void testSimpleNest() {
mVerifier.initForImportTest(V21, R.raw.v21_nest);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", "nest1");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", "nest2");
// Parent comes last.
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", "parent")
.addExpectedNodeWithOrder("TEL", "1");
}
public void testSimpleNestWithMime() {
mVerifier.initForImportTest(V21, R.raw.v21_nest_with_mime);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", "nest1");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", "nest2");
// Parent comes last.
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", "parent")
.addExpectedNodeWithOrder("TEL", "1");
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,440 @@
/*
* 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.VCardConstants;
import com.android.vcard.tests.testutils.ContactEntry;
import com.android.vcard.tests.testutils.ContentValuesBuilder;
import com.android.vcard.tests.testutils.PropertyNodesVerifierElem;
import com.android.vcard.tests.testutils.VCardTestsBase;
import com.android.vcard.tests.testutils.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.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null);
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.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
mContentValuesForSJis) :
(VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null));
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.isVersion21(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.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
mContentValuesForSJis) :
(VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 :
mContentValuesForUtf8));
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", "", "", "", "", "", ""),
new TypeSet("X-" + VCardConstants.PARAM_ADR_EXTRA_TYPE_OTHER));
}
/**
* 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);
}
}
@@ -0,0 +1,242 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vcard.tests;
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.VCardProperty;
import com.android.vcard.exception.VCardException;
import android.test.AndroidTestCase;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Tests for basic {@link VCardParser} capability (e.g. if it calls callbacks
* in {@link VCardInterpreter}}.
*/
public class VCardParserTests extends AndroidTestCase {
private enum Order {
START,
END,
START_ENTRY,
END_ENTRY,
PROPERTY_CREATED,
}
private class MockVCardInterpreter implements VCardInterpreter {
private final List<Order> mHistory = new ArrayList<Order>();
private final List<Order> mExpectedOrder = new ArrayList<Order>();
public MockVCardInterpreter addExpectedOrder(Order order) {
mExpectedOrder.add(order);
return this;
}
private void inspectOrder(Order order) {
mHistory.add(order);
final Order top = mExpectedOrder.get(0);
assertEquals(top, order);
mExpectedOrder.remove(0);
}
public void verify() {
assertTrue(String.format("Remaining: " + Arrays.toString(mExpectedOrder.toArray())),
mExpectedOrder.isEmpty());
}
@Override
public void onVCardStarted() {
inspectOrder(Order.START);
}
@Override
public void onVCardEnded() {
inspectOrder(Order.END);
}
@Override
public void onEntryStarted() {
inspectOrder(Order.START_ENTRY);
}
@Override
public void onEntryEnded() {
inspectOrder(Order.END_ENTRY);
}
@Override
public void onPropertyCreated(VCardProperty property) {
inspectOrder(Order.PROPERTY_CREATED);
}
}
public void testSimple() throws IOException, VCardException {
InputStream inputStream = getContext().getResources().openRawResource(R.raw.v21_simple_1);
try {
VCardParser parser = new VCardParser_V21();
MockVCardInterpreter interpreter = new MockVCardInterpreter();
interpreter.addExpectedOrder(Order.START)
.addExpectedOrder(Order.START_ENTRY)
.addExpectedOrder(Order.PROPERTY_CREATED)
.addExpectedOrder(Order.END_ENTRY)
.addExpectedOrder(Order.END);
parser.addInterpreter(interpreter);
parser.parse(inputStream);
interpreter.verify();
} finally {
inputStream.close();
}
}
public void testNest() throws IOException, VCardException {
InputStream inputStream = getContext().getResources().openRawResource(R.raw.v21_nest);
try {
VCardParser parser = new VCardParser_V21();
MockVCardInterpreter interpreter = new MockVCardInterpreter();
interpreter.addExpectedOrder(Order.START)
.addExpectedOrder(Order.START_ENTRY)
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.START_ENTRY) // First nested vCard begins
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.END_ENTRY) // First nested vCard ends
.addExpectedOrder(Order.START_ENTRY) // Second nested vCard begins
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.END_ENTRY) // Second nested vCard ends
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.END_ENTRY)
.addExpectedOrder(Order.END);
parser.addInterpreter(interpreter);
parser.parse(inputStream);
interpreter.verify();
} finally {
inputStream.close();
}
}
/**
* Test vCard containing v3.0 line continuations and with non-standard
* line terminators \r\r\n coming from iOS 6.1.3. Tests to make sure the
* parser correctly skips the extra line terminators and still
* successfully concatenates the multi-line block.
*/
public void testIosMultiline() throws IOException, VCardException {
InputStream inputStream = getContext().getResources().openRawResource(R.raw.v30_ios_613_multiline);
try {
VCardParser parser = new VCardParser_V30();
MockVCardInterpreter interpreter = new MockVCardInterpreter();
interpreter.addExpectedOrder(Order.START)
.addExpectedOrder(Order.START_ENTRY)
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.PROPERTY_CREATED) // For FN
.addExpectedOrder(Order.PROPERTY_CREATED) // For ORG
.addExpectedOrder(Order.PROPERTY_CREATED) // For item1
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For item2
.addExpectedOrder(Order.PROPERTY_CREATED) // For item3
.addExpectedOrder(Order.PROPERTY_CREATED) // For PHOTO
.addExpectedOrder(Order.END_ENTRY)
.addExpectedOrder(Order.END);
parser.addInterpreter(interpreter);
parser.parse(inputStream);
interpreter.verify();
} finally {
inputStream.close();
}
}
/**
* Tests if {@link VCardParser#parse(InputStream)} parses the whole vCard file and
* {@link VCardParser#parseOne(InputStream)} parses just first entry of a vCard file
* with multiple vCard entries.
*/
public void testParseOne() throws IOException, VCardException {
InputStream inputStream =
getContext().getResources().openRawResource(R.raw.v21_multiple_entry);
try {
VCardParser parser = new VCardParser_V21();
MockVCardInterpreter interpreter = new MockVCardInterpreter();
interpreter.addExpectedOrder(Order.START)
.addExpectedOrder(Order.START_ENTRY)
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.PROPERTY_CREATED) // For SOUND
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.END_ENTRY)
.addExpectedOrder(Order.START_ENTRY)
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.PROPERTY_CREATED) // For SOUND
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.END_ENTRY)
.addExpectedOrder(Order.START_ENTRY)
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.PROPERTY_CREATED) // For SOUND
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.END_ENTRY)
.addExpectedOrder(Order.END);
parser.addInterpreter(interpreter);
// Use parse() to check we
parser.parse(inputStream);
interpreter.verify();
} finally {
inputStream.close();
}
inputStream = getContext().getResources().openRawResource(R.raw.v21_multiple_entry);
try {
VCardParser parser = new VCardParser_V21();
MockVCardInterpreter interpreter = new MockVCardInterpreter();
interpreter.addExpectedOrder(Order.START)
.addExpectedOrder(Order.START_ENTRY)
.addExpectedOrder(Order.PROPERTY_CREATED) // For VERSION
.addExpectedOrder(Order.PROPERTY_CREATED) // For N
.addExpectedOrder(Order.PROPERTY_CREATED) // For SOUND
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.PROPERTY_CREATED) // For TEL
.addExpectedOrder(Order.END_ENTRY)
.addExpectedOrder(Order.END);
parser.addInterpreter(interpreter);
parser.parseOne(inputStream);
interpreter.verify();
} finally {
inputStream.close();
}
}
}
@@ -0,0 +1,98 @@
/*
* 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;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.test.AndroidTestCase;
import com.android.vcard.VCardConfig;
import com.android.vcard.tests.testutils.VCardVerifier;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import java.util.Arrays;
/**
* Tests confirming utilities for vCard tests work fine.
*
* Now that the foundation classes for vCard test cases became too complicated to
* rely on without testing itself.
*/
public class VCardTestUtilsTests extends AndroidTestCase {
public void testShouldFailAtPropertyNodeVerification() {
boolean failureDetected = false;
try {
final VCardVerifier verifier = new VCardVerifier(this);
verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash);
verifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;--", // wrong
Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
.addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
verifier.verify();
} catch (AssertionFailedError e) {
failureDetected = true;
}
if (!failureDetected) {
TestCase.fail("Test case that should fail actually succeeded.");
}
}
public void testShouldFailAtContentValueVerification() {
boolean failureDetected = false;
try {
final VCardVerifier verifier = new VCardVerifier(this);
verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash);
verifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.GIVEN_NAME, "A;B\\")
.put(StructuredName.MIDDLE_NAME, "C\\;")
.put(StructuredName.PREFIX, "D")
.put(StructuredName.SUFFIX, ":E");
// DISPLAY_NAME is missing.
verifier.verify();
} catch (AssertionFailedError e) {
failureDetected = true;
}
if (!failureDetected) {
TestCase.fail("Test case that should fail actually succeeded.");
}
}
public void testShouldFailAtLineVerification() {
boolean failureDetected = false;
try {
final VCardVerifier verifier = new VCardVerifier(this);
verifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_GENERIC);
verifier.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}>]");
verifier.addLineVerifierElem()
.addExpected("TEL:1") // wrong
.addExpected("FN:[<{Unescaped:Asciis}>]");
verifier.verify();
} catch (AssertionFailedError e) {
failureDetected = true;
}
if (!failureDetected) {
TestCase.fail("Test case that should fail actually succeeded.");
}
}
}
@@ -0,0 +1,144 @@
/*
* 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.text.TextUtils;
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)));
}
}
public void testToStringAvailableAsV30ParamValue() {
// Smoke tests.
assertEquals("HOME", VCardUtils.toStringAsV30ParamValue("HOME"));
assertEquals("TEL", VCardUtils.toStringAsV30ParamValue("TEL"));
assertEquals("PAGER", VCardUtils.toStringAsV30ParamValue("PAGER"));
assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue("")));
assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(null)));
assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(" \t")));
// non-Ascii must be allowed
assertEquals("\u4E8B\u52D9\u6240",
VCardUtils.toStringAsV30ParamValue("\u4E8B\u52D9\u6240"));
// Reported as bug report.
assertEquals("\u8D39", VCardUtils.toStringAsV30ParamValue("\u8D39"));
assertEquals("\"comma,separated\"",
VCardUtils.toStringAsV30ParamValue("comma,separated"));
assertEquals("\"colon:aware\"",
VCardUtils.toStringAsV30ParamValue("colon:aware"));
// CTL characters.
assertEquals("CTLExample",
VCardUtils.toStringAsV30ParamValue("CTL\u0001Example"));
assertTrue(TextUtils.isEmpty(
VCardUtils.toStringAsV30ParamValue("\u0001\u0002\u0003")));
// DQUOTE must be removed.
assertEquals("quoted",
VCardUtils.toStringAsV30ParamValue("\"quoted\""));
// DQUOTE must be removed basically, but we should detect a space, which
// require us to use DQUOTE again.
// Right-side has one more illegal dquote to test quote-handle code thoroughly.
assertEquals("\"Already quoted\"",
VCardUtils.toStringAsV30ParamValue("\"Already quoted\"\""));
}
public void testAppearsLikeAndroidVCardQuotedPrintableByLength() {
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("="));
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=1"));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12"));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12="));
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12=1"));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12=12"));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12=12="));
}
public void testAppearsLikeAndroidVCardQuotedPrintableByContent() {
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("==="));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("===="));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("======="));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12=12="));
assertTrue(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12=34="));
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=123"));
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=123="));
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("_12="));
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12_"));
assertFalse(VCardUtils.appearsLikeAndroidVCardQuotedPrintable("=12=34_56="));
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2022 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.exception.VCardAgentNotSupportedException;
import junit.framework.TestCase;
public class VCardAgentNotSupportedExceptionTest extends TestCase {
static final String TEST_MESSAGE = "message";
public void testExceptionWithoutMessage() {
VCardAgentNotSupportedException exception = new VCardAgentNotSupportedException();
assertNull(exception.getMessage());
}
public void testExceptionWithMessage() {
VCardAgentNotSupportedException exception = new VCardAgentNotSupportedException(
TEST_MESSAGE);
assertEquals(exception.getMessage(), TEST_MESSAGE);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2022 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.exception.VCardException;
import junit.framework.TestCase;
public class VCardExceptionTest extends TestCase {
static final String TEST_MESSAGE = "message";
public void testExceptionWithoutMessage() {
VCardException exception = new VCardException();
assertNull(exception.getMessage());
}
public void testExceptionWithMessage() {
VCardException exception = new VCardException(TEST_MESSAGE);
assertEquals(exception.getMessage(), TEST_MESSAGE);
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2022 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.exception.VCardInvalidCommentLineException;
import junit.framework.TestCase;
public class VCardInvalidCommentLineExceptionTest extends TestCase {
static final String TEST_MESSAGE = "message";
public void testExceptionWithoutMessage() {
VCardInvalidCommentLineException exception = new VCardInvalidCommentLineException();
assertNull(exception.getMessage());
}
public void testExceptionWithMessage() {
VCardInvalidCommentLineException exception = new VCardInvalidCommentLineException(
TEST_MESSAGE);
assertEquals(exception.getMessage(), TEST_MESSAGE);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2022 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.exception.VCardInvalidLineException;
import junit.framework.TestCase;
public class VCardInvalidLineExceptionTest extends TestCase {
static final String TEST_MESSAGE = "message";
public void testExceptionWithoutMessage() {
VCardInvalidLineException exception = new VCardInvalidLineException();
assertNull(exception.getMessage());
}
public void testExceptionWithMessage() {
VCardInvalidLineException exception = new VCardInvalidLineException(TEST_MESSAGE);
assertEquals(exception.getMessage(), TEST_MESSAGE);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2022 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.exception.VCardNestedException;
import junit.framework.TestCase;
public class VCardNestedExceptionTest extends TestCase {
static final String TEST_MESSAGE = "message";
public void testExceptionWithoutMessage() {
VCardNestedException exception = new VCardNestedException();
assertNull(exception.getMessage());
}
public void testExceptionWithMessage() {
VCardNestedException exception = new VCardNestedException(TEST_MESSAGE);
assertEquals(exception.getMessage(), TEST_MESSAGE);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2022 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.exception.VCardNotSupportedException;
import junit.framework.TestCase;
public class VCardNotSupportedExceptionTest extends TestCase {
static final String TEST_MESSAGE = "message";
public void testExceptionWithoutMessage() {
VCardNotSupportedException exception = new VCardNotSupportedException();
assertNull(exception.getMessage());
}
public void testExceptionWithMessage() {
VCardNotSupportedException exception = new VCardNotSupportedException(TEST_MESSAGE);
assertEquals(exception.getMessage(), TEST_MESSAGE);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2022 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.exception.VCardVersionException;
import junit.framework.TestCase;
public class VCardVersionExceptionTest extends TestCase {
static final String TEST_MESSAGE = "message";
public void testExceptionWithoutMessage() {
VCardVersionException exception = new VCardVersionException();
assertNull(exception.getMessage());
}
public void testExceptionWithMessage() {
VCardVersionException exception = new VCardVersionException(TEST_MESSAGE);
assertEquals(exception.getMessage(), TEST_MESSAGE);
}
}
@@ -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.testutils;
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;
}
}
@@ -0,0 +1,82 @@
/*
* 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.testutils;
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;
}
}
@@ -0,0 +1,58 @@
/*
* 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.testutils;
import android.test.AndroidTestCase;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryHandler;
import java.util.ArrayList;
import java.util.List;
public class ContentValuesVerifier implements VCardEntryHandler {
private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
new ArrayList<ContentValuesVerifierElem>();
private int mIndex;
public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
ContentValuesVerifierElem elem = new ContentValuesVerifierElem(androidTestCase);
mContentValuesVerifierElemList.add(elem);
return elem;
}
@Override
public void onStart() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingStart();
}
}
@Override
public void onEntryCreated(VCardEntry entry) {
AndroidTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size());
mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry);
mIndex++;
}
@Override
public void onEnd() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingEnd();
elem.verifyResolver();
}
}
}
@@ -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.testutils;
import android.content.ContentValues;
import android.provider.ContactsContract.Data;
import android.test.AndroidTestCase;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryCommitter;
import com.android.vcard.VCardEntryHandler;
public class ContentValuesVerifierElem {
private final ImportTestResolver mResolver;
private final VCardEntryHandler mHandler;
public ContentValuesVerifierElem(AndroidTestCase 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 verifyResolver() {
mResolver.verify();
}
public void onParsingStart() {
mHandler.onStart();
}
public void onEntryCreated(VCardEntry entry) {
mHandler.onEntryCreated(entry);
}
public void onParsingEnd() {
mHandler.onEnd();
}
}
@@ -0,0 +1,196 @@
/*
* 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.testutils;
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.AndroidTestCase;
import android.test.mock.MockContentProvider;
import android.test.mock.MockCursor;
import android.util.Log;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ExportTestProvider extends MockContentProvider {
final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
private static class MockEntityIterator implements EntityIterator {
private final List<Entity> mEntityList;
private Iterator<Entity> mIterator;
public MockEntityIterator() {
mEntityList = new ArrayList<Entity>();
mIterator = mEntityList.iterator();
}
public void add(List<ContentValues> contentValuesList) {
Entity entity = new Entity(new ContentValues());
for (ContentValues contentValues : contentValuesList) {
entity.addSubValue(Data.CONTENT_URI, contentValues);
}
mEntityList.add(entity);
}
@Override
public boolean hasNext() {
return mIterator.hasNext();
}
@Override
public Entity next() {
return mIterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
@Override
public void reset() {
mIterator = mEntityList.iterator();
}
@Override
public void close() {
}
}
public ExportTestProvider(AndroidTestCase androidTestCase) {
}
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) {
TestCase.assertTrue(uri != null);
TestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
final String authority = uri.getAuthority();
TestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
TestCase.assertTrue(selection != null);
TestCase.assertTrue((selection.contains(Data.CONTACT_ID + " IN ")));
TestCase.assertNull(selectionArgs);
MockEntityIterator iterator = new MockEntityIterator();
for (ContactEntry contactEntry : mContactEntryList) {
iterator.add(contactEntry.getList());
}
iterator.reset();
return iterator;
}
@Override
public Cursor query(Uri uri,String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
TestCase.assertTrue(VCardVerifier.CONTACTS_TEST_CONTENT_URI.equals(uri));
// In this test, following arguments are not supported.
TestCase.assertNull(selection);
TestCase.assertNull(selectionArgs);
TestCase.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() - 1) {
mCurrentPosition++;
return true;
} else {
return false;
}
}
@Override
public boolean isBeforeFirst() {
return mCurrentPosition < 0;
}
@Override
public boolean isLast() {
return mCurrentPosition == mContactEntryList.size() - 1;
}
@Override
public boolean isAfterLast() {
return mCurrentPosition >= mContactEntryList.size();
}
@Override
public int getColumnIndex(String columnName) {
TestCase.assertEquals(Data.CONTACT_ID, columnName);
return 0;
}
@Override
public int getInt(int columnIndex) {
TestCase.assertEquals(0, columnIndex);
TestCase.assertTrue(mCurrentPosition >= 0
&& mCurrentPosition < mContactEntryList.size());
return mCurrentPosition;
}
@Override
public long getLong(int columnIndex) {
return getInt(columnIndex);
}
@Override
public String getString(int columnIndex) {
return String.valueOf(getInt(columnIndex));
}
@Override
public void close() {
}
};
}
}
@@ -0,0 +1,37 @@
/*
* 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.testutils;
import android.provider.ContactsContract.RawContacts;
import android.test.AndroidTestCase;
import android.test.mock.MockContentResolver;
public class ExportTestResolver extends MockContentResolver {
private final ExportTestProvider mProvider;
public ExportTestResolver(AndroidTestCase androidTestCase) {
mProvider = new ExportTestProvider(androidTestCase);
addProvider(VCardVerifier.VCARD_TEST_AUTHORITY, mProvider);
addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
}
public ContactEntry addInputContactEntry() {
return mProvider.buildInputEntry();
}
public ExportTestProvider getProvider() {
return mProvider;
}
}
@@ -0,0 +1,241 @@
/*
* 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.testutils;
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.test.mock.MockContentProvider;
import android.text.TextUtils;
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.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public class ImportTestProvider extends MockContentProvider {
private final Map<String, Collection<ContentValues>>
mMimeTypeToExpectedContentValues = new HashMap<String, Collection<ContentValues>>();
public void addExpectedContentValues(ContentValues expectedContentValues) {
final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
final Collection<ContentValues> contentValuesCollection;
if (mMimeTypeToExpectedContentValues.containsKey(mimeType)) {
contentValuesCollection = mMimeTypeToExpectedContentValues.get(mimeType);
} else {
contentValuesCollection = new ArrayList<ContentValues>();
mMimeTypeToExpectedContentValues.put(mimeType, contentValuesCollection);
}
contentValuesCollection.add(expectedContentValues);
}
@Override
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations) {
if (operations == null) {
TestCase.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)) {
TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
} else if (uri.equals(Data.CONTENT_URI)) {
final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
if (!mMimeTypeToExpectedContentValues.containsKey(mimeType)) {
TestCase.fail("Unregistered MimeType " + 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()) {
TestCase.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)) {
TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
checked = true;
break;
}
}
if (!checked) {
final StringBuilder builder = new StringBuilder();
builder.append("\n");
builder.append("Unexpected: ");
builder.append(convertToEasilyReadableString(actualContentValues));
builder.append("\n");
builder.append("Expected : ");
for (ContentValues expectedContentValues : contentValuesCollection) {
builder.append(convertToEasilyReadableString(expectedContentValues));
}
TestCase.fail(builder.toString());
}
} else {
TestCase.fail("Unexpected Uri has come: " + uri);
}
} // for (int i = 0; i < size; i++) {
return fakeResultArray;
}
/**
* Checks all expected ContentValues are consumed during import.
*/
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();
TestCase.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 {
TestCase.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(
final ContentValues expected, final 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;
}
// Type mismatch usuall happens as importer doesn't care the type of each value.
// For example, variable type might be Integer when importing the type of TEL,
// while variable type would be String when importing the type of RELATION.
final Object actualValue = actual.get(key);
if (value instanceof byte[]) {
if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
byte[] e = (byte[])value;
byte[] a = (byte[])actualValue;
return false;
}
} else if (!value.equals(actualValue) &&
!value.toString().equals(actualValue.toString())) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,56 @@
/*
* 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.testutils;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.provider.ContactsContract.RawContacts;
import android.test.AndroidTestCase;
import android.test.mock.MockContentResolver;
import java.util.ArrayList;
public class ImportTestResolver extends MockContentResolver {
private final ImportTestProvider mProvider;
public ImportTestResolver(AndroidTestCase androidTestCase) {
mProvider = new ImportTestProvider();
}
@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);
}
}
}

Some files were not shown because too many files have changed in this diff Show More