Merge "DOC CHANGE: Clean up the concepts topic testing_android.jd"
diff --git a/camera/tests/CameraServiceTest/Android.mk b/camera/tests/CameraServiceTest/Android.mk
index 9bb190a..cf4e42f 100644
--- a/camera/tests/CameraServiceTest/Android.mk
+++ b/camera/tests/CameraServiceTest/Android.mk
@@ -21,4 +21,6 @@
libcamera_client \
libsurfaceflinger_client
-include $(BUILD_EXECUTABLE)
+# Disable it because the ISurface interface may change, and before we have a
+# chance to fix this test, we don't want to break normal builds.
+#include $(BUILD_EXECUTABLE)
diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h
index a1a02e0..b859e78 100644
--- a/include/private/surfaceflinger/SharedBufferStack.h
+++ b/include/private/surfaceflinger/SharedBufferStack.h
@@ -55,12 +55,6 @@
*
*/
-// When changing these values, the COMPILE_TIME_ASSERT at the end of this
-// file need to be updated.
-const unsigned int NUM_LAYERS_MAX = 31;
-const unsigned int NUM_BUFFER_MAX = 16;
-const unsigned int NUM_DISPLAY_MAX = 4;
-
// ----------------------------------------------------------------------------
class Region;
@@ -82,6 +76,13 @@
friend class SharedBufferServer;
public:
+ // When changing these values, the COMPILE_TIME_ASSERT at the end of this
+ // file need to be updated.
+ static const unsigned int NUM_LAYERS_MAX = 31;
+ static const unsigned int NUM_BUFFER_MAX = 16;
+ static const unsigned int NUM_BUFFER_MIN = 2;
+ static const unsigned int NUM_DISPLAY_MAX = 4;
+
struct Statistics { // 4 longs
typedef int32_t usecs_t;
usecs_t totalTime;
@@ -147,7 +148,7 @@
// FIXME: this should be replaced by a lock-less primitive
Mutex lock;
Condition cv;
- SharedBufferStack surfaces[ NUM_LAYERS_MAX ];
+ SharedBufferStack surfaces[ SharedBufferStack::NUM_LAYERS_MAX ];
};
// ============================================================================
@@ -155,10 +156,9 @@
class SharedBufferBase
{
public:
- SharedBufferBase(SharedClient* sharedClient, int surface, int num,
+ SharedBufferBase(SharedClient* sharedClient, int surface,
int32_t identity);
~SharedBufferBase();
- uint32_t getIdentity();
status_t getStatus() const;
size_t getFrontBuffer() const;
String8 dump(char const* prefix) const;
@@ -166,9 +166,7 @@
protected:
SharedClient* const mSharedClient;
SharedBufferStack* const mSharedStack;
- int mNumBuffers;
const int mIdentity;
- int32_t computeTail() const;
friend struct Update;
friend struct QueueUpdate;
@@ -217,8 +215,16 @@
bool needNewBuffer(int buffer) const;
status_t setDirtyRegion(int buffer, const Region& reg);
status_t setCrop(int buffer, const Rect& reg);
- status_t setBufferCount(int bufferCount);
+
+ class SetBufferCountCallback {
+ friend class SharedBufferClient;
+ virtual status_t operator()(int bufferCount) const = 0;
+ protected:
+ virtual ~SetBufferCountCallback() { }
+ };
+ status_t setBufferCount(int bufferCount, const SetBufferCountCallback& ipc);
+
private:
friend struct Condition;
friend struct DequeueCondition;
@@ -249,11 +255,16 @@
inline const char* name() const { return "LockCondition"; }
};
+ int32_t computeTail() const;
+
+ mutable RWLock mLock;
+ int mNumBuffers;
+
int32_t tail;
int32_t undoDequeueTail;
int32_t queued_head;
// statistics...
- nsecs_t mDequeueTime[NUM_BUFFER_MAX];
+ nsecs_t mDequeueTime[SharedBufferStack::NUM_BUFFER_MAX];
};
// ----------------------------------------------------------------------------
@@ -267,7 +278,8 @@
ssize_t retireAndLock();
status_t unlock(int buffer);
void setStatus(status_t status);
- status_t reallocate();
+ status_t reallocateAll();
+ status_t reallocateAllExcept(int buffer);
status_t assertReallocate(int buffer);
int32_t getQueuedCount() const;
Region getDirtyRegion(int buffer) const;
@@ -287,9 +299,11 @@
size_t mCapacity;
uint32_t mList;
public:
- BufferList(size_t c = NUM_BUFFER_MAX) : mCapacity(c), mList(0) { }
+ BufferList(size_t c = SharedBufferStack::NUM_BUFFER_MAX)
+ : mCapacity(c), mList(0) { }
status_t add(int value);
status_t remove(int value);
+ uint32_t getMask() const { return mList; }
class const_iterator {
friend class BufferList;
@@ -324,6 +338,9 @@
}
};
+ // this protects mNumBuffers and mBufferList
+ mutable RWLock mLock;
+ int mNumBuffers;
BufferList mBufferList;
struct UnlockUpdate : public UpdateBase {
@@ -373,7 +390,7 @@
uint8_t connected;
uint8_t reserved[3];
uint32_t pad[7];
- display_cblk_t displays[NUM_DISPLAY_MAX];
+ display_cblk_t displays[SharedBufferStack::NUM_DISPLAY_MAX];
};
// ---------------------------------------------------------------------------
diff --git a/include/surfaceflinger/ISurface.h b/include/surfaceflinger/ISurface.h
index 9476686..18e7950 100644
--- a/include/surfaceflinger/ISurface.h
+++ b/include/surfaceflinger/ISurface.h
@@ -53,7 +53,8 @@
public:
DECLARE_META_INTERFACE(Surface);
- virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage) = 0;
+ virtual sp<GraphicBuffer> requestBuffer(int bufferIdx,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage) = 0;
virtual status_t setBufferCount(int bufferCount) = 0;
class BufferHeap {
diff --git a/include/surfaceflinger/Surface.h b/include/surfaceflinger/Surface.h
index 973780f..e561fb9 100644
--- a/include/surfaceflinger/Surface.h
+++ b/include/surfaceflinger/Surface.h
@@ -36,6 +36,7 @@
// ---------------------------------------------------------------------------
+class GraphicBuffer;
class GraphicBufferMapper;
class IOMX;
class Rect;
@@ -212,30 +213,55 @@
int dispatch_connect(va_list args);
int dispatch_disconnect(va_list args);
int dispatch_crop(va_list args);
+ int dispatch_set_buffer_count(va_list args);
+ int dispatch_set_buffers_geometry(va_list args);
void setUsage(uint32_t reqUsage);
int connect(int api);
int disconnect(int api);
int crop(Rect const* rect);
int setBufferCount(int bufferCount);
+ int setBuffersGeometry(int w, int h, int format);
/*
* private stuff...
*/
void init();
status_t validate() const;
- sp<SurfaceComposerClient> getClient() const;
+ status_t initCheck() const;
sp<ISurface> getISurface() const;
inline const GraphicBufferMapper& getBufferMapper() const { return mBufferMapper; }
inline GraphicBufferMapper& getBufferMapper() { return mBufferMapper; }
- status_t getBufferLocked(int index, int usage);
+ status_t getBufferLocked(int index,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
int getBufferIndex(const sp<GraphicBuffer>& buffer) const;
- uint32_t getUsage() const;
- int getConnectedApi() const;
+ int getConnectedApi() const;
+ bool needNewBuffer(int bufIdx,
+ uint32_t *pWidth, uint32_t *pHeight,
+ uint32_t *pFormat, uint32_t *pUsage) const;
+
+ class BufferInfo {
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mFormat;
+ uint32_t mUsage;
+ mutable uint32_t mDirty;
+ enum {
+ GEOMETRY = 0x01
+ };
+ public:
+ BufferInfo();
+ void set(uint32_t w, uint32_t h, uint32_t format);
+ void set(uint32_t usage);
+ void get(uint32_t *pWidth, uint32_t *pHeight,
+ uint32_t *pFormat, uint32_t *pUsage) const;
+ bool validateBuffer(const sp<GraphicBuffer>& buffer) const;
+ };
+
// constants
sp<SurfaceComposerClient> mClient;
sp<ISurface> mSurface;
@@ -245,16 +271,16 @@
uint32_t mFlags;
GraphicBufferMapper& mBufferMapper;
SharedBufferClient* mSharedBufferClient;
+ status_t mInitCheck;
// protected by mSurfaceLock
Rect mSwapRectangle;
- uint32_t mUsage;
int mConnected;
Rect mNextBufferCrop;
+ BufferInfo mBufferInfo;
// protected by mSurfaceLock. These are also used from lock/unlock
// but in that case, they must be called form the same thread.
- sp<GraphicBuffer> mBuffers[2];
mutable Region mDirtyRegion;
// must be used from the lock/unlock thread
@@ -263,6 +289,9 @@
mutable Region mOldDirtyRegion;
bool mReserved;
+ // only used from dequeueBuffer()
+ Vector< sp<GraphicBuffer> > mBuffers;
+
// query() must be called from dequeueBuffer() thread
uint32_t mWidth;
uint32_t mHeight;
diff --git a/include/surfaceflinger/SurfaceComposerClient.h b/include/surfaceflinger/SurfaceComposerClient.h
index 9d0f0cb..8396038 100644
--- a/include/surfaceflinger/SurfaceComposerClient.h
+++ b/include/surfaceflinger/SurfaceComposerClient.h
@@ -123,13 +123,6 @@
status_t linkToComposerDeath(const sp<IBinder::DeathRecipient>& recipient,
void* cookie = NULL, uint32_t flags = 0);
-private:
- friend class Surface;
- friend class SurfaceControl;
-
- SurfaceComposerClient(const sp<ISurfaceComposer>& sm,
- const sp<IBinder>& conn);
-
status_t hide(SurfaceID id);
status_t show(SurfaceID id, int32_t layer = -1);
status_t freeze(SurfaceID id);
@@ -142,17 +135,21 @@
status_t setMatrix(SurfaceID id, float dsdx, float dtdx, float dsdy, float dtdy);
status_t setPosition(SurfaceID id, int32_t x, int32_t y);
status_t setSize(SurfaceID id, uint32_t w, uint32_t h);
-
void signalServer();
-
status_t destroySurface(SurfaceID sid);
- void _init(const sp<ISurfaceComposer>& sm,
+ SharedClient* getSharedClient() const;
+
+private:
+ SurfaceComposerClient(const sp<ISurfaceComposer>& sm,
+ const sp<IBinder>& conn);
+
+ void init(const sp<ISurfaceComposer>& sm,
const sp<ISurfaceFlingerClient>& conn);
- inline layer_state_t* _get_state_l(SurfaceID id);
- layer_state_t* _lockLayerState(SurfaceID id);
- inline void _unlockLayerState();
+ inline layer_state_t* get_state_l(SurfaceID id);
+ layer_state_t* lockLayerState(SurfaceID id);
+ inline void unlockLayerState();
mutable Mutex mLock;
layer_state_t* mPrebuiltLayerState;
diff --git a/include/ui/egl/android_natives.h b/include/ui/egl/android_natives.h
index 49bfa2b..171f3df 100644
--- a/include/ui/egl/android_natives.h
+++ b/include/ui/egl/android_natives.h
@@ -81,6 +81,8 @@
NATIVE_WINDOW_CONNECT,
NATIVE_WINDOW_DISCONNECT,
NATIVE_WINDOW_SET_CROP,
+ NATIVE_WINDOW_SET_BUFFER_COUNT,
+ NATIVE_WINDOW_SET_BUFFERS_GEOMETRY,
};
/* parameter for NATIVE_WINDOW_[DIS]CONNECT */
@@ -190,6 +192,8 @@
* NATIVE_WINDOW_CONNECT
* NATIVE_WINDOW_DISCONNECT
* NATIVE_WINDOW_SET_CROP
+ * NATIVE_WINDOW_SET_BUFFER_COUNT
+ * NATIVE_WINDOW_SET_BUFFERS_GEOMETRY
*
*/
@@ -201,8 +205,9 @@
/*
- * native_window_set_usage() sets the intended usage flags for the next
- * buffers acquired with (*lockBuffer)() and on.
+ * native_window_set_usage(..., usage)
+ * Sets the intended usage flags for the next buffers
+ * acquired with (*lockBuffer)() and on.
* By default (if this function is never called), a usage of
* GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE
* is assumed.
@@ -217,8 +222,8 @@
}
/*
- * native_window_connect(..., NATIVE_WINDOW_API_EGL) must be called
- * by EGL when the window is made current.
+ * native_window_connect(..., NATIVE_WINDOW_API_EGL)
+ * Must be called by EGL when the window is made current.
* Returns -EINVAL if for some reason the window cannot be connected, which
* can happen if it's connected to some other API.
*/
@@ -229,8 +234,8 @@
}
/*
- * native_window_disconnect(..., NATIVE_WINDOW_API_EGL) must be called
- * by EGL when the window is made not current.
+ * native_window_disconnect(..., NATIVE_WINDOW_API_EGL)
+ * Must be called by EGL when the window is made not current.
* An error is returned if for instance the window wasn't connected in the
* first place.
*/
@@ -241,8 +246,8 @@
}
/*
- * native_window_set_crop(..., crop) sets which region of the next queued
- * buffers needs to be considered.
+ * native_window_set_crop(..., crop)
+ * Sets which region of the next queued buffers needs to be considered.
* A buffer's crop region is scaled to match the surface's size.
*
* The specified crop region applies to all buffers queued after it is called.
@@ -259,6 +264,36 @@
return window->perform(window, NATIVE_WINDOW_SET_CROP, crop);
}
+/*
+ * native_window_set_buffer_count(..., count)
+ * Sets the number of buffers associated with this native window.
+ */
+static inline int native_window_set_buffer_count(
+ android_native_window_t* window,
+ size_t bufferCount)
+{
+ return window->perform(window, NATIVE_WINDOW_SET_BUFFER_COUNT, bufferCount);
+}
+
+/*
+ * native_window_set_buffers_geometry(..., int w, int h, int format)
+ * All buffers dequeued after this call will have the geometry specified.
+ * In particular, all buffers will have a fixed-size, independent form the
+ * native-window size. They will be appropriately scaled to the window-size
+ * upon composition.
+ *
+ * If all parameters are 0, the normal behavior is restored. That is,
+ * dequeued buffers following this call will be sized to the window's size.
+ *
+ */
+static inline int native_window_set_buffers_geometry(
+ android_native_window_t* window,
+ int w, int h, int format)
+{
+ return window->perform(window, NATIVE_WINDOW_SET_BUFFERS_GEOMETRY,
+ w, h, format);
+}
+
// ---------------------------------------------------------------------------
/* FIXME: this is legacy for pixmaps */
diff --git a/include/utils/threads.h b/include/utils/threads.h
index 5ac0c5e..1bcfaed 100644
--- a/include/utils/threads.h
+++ b/include/utils/threads.h
@@ -295,6 +295,96 @@
/*****************************************************************************/
+#if defined(HAVE_PTHREADS)
+
+/*
+ * Simple mutex class. The implementation is system-dependent.
+ *
+ * The mutex must be unlocked by the thread that locked it. They are not
+ * recursive, i.e. the same thread can't lock it multiple times.
+ */
+class RWLock {
+public:
+ enum {
+ PRIVATE = 0,
+ SHARED = 1
+ };
+
+ RWLock();
+ RWLock(const char* name);
+ RWLock(int type, const char* name = NULL);
+ ~RWLock();
+
+ status_t readLock();
+ status_t tryReadLock();
+ status_t writeLock();
+ status_t tryWriteLock();
+ void unlock();
+
+ class AutoRLock {
+ public:
+ inline AutoRLock(RWLock& rwlock) : mLock(rwlock) { mLock.readLock(); }
+ inline ~AutoRLock() { mLock.unlock(); }
+ private:
+ RWLock& mLock;
+ };
+
+ class AutoWLock {
+ public:
+ inline AutoWLock(RWLock& rwlock) : mLock(rwlock) { mLock.writeLock(); }
+ inline ~AutoWLock() { mLock.unlock(); }
+ private:
+ RWLock& mLock;
+ };
+
+private:
+ // A RWLock cannot be copied
+ RWLock(const RWLock&);
+ RWLock& operator = (const RWLock&);
+
+ pthread_rwlock_t mRWLock;
+};
+
+inline RWLock::RWLock() {
+ pthread_rwlock_init(&mRWLock, NULL);
+}
+inline RWLock::RWLock(const char* name) {
+ pthread_rwlock_init(&mRWLock, NULL);
+}
+inline RWLock::RWLock(int type, const char* name) {
+ if (type == SHARED) {
+ pthread_rwlockattr_t attr;
+ pthread_rwlockattr_init(&attr);
+ pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+ pthread_rwlock_init(&mRWLock, &attr);
+ pthread_rwlockattr_destroy(&attr);
+ } else {
+ pthread_rwlock_init(&mRWLock, NULL);
+ }
+}
+inline RWLock::~RWLock() {
+ pthread_rwlock_destroy(&mRWLock);
+}
+inline status_t RWLock::readLock() {
+ return -pthread_rwlock_rdlock(&mRWLock);
+}
+inline status_t RWLock::tryReadLock() {
+ return -pthread_rwlock_tryrdlock(&mRWLock);
+}
+inline status_t RWLock::writeLock() {
+ return -pthread_rwlock_wrlock(&mRWLock);
+}
+inline status_t RWLock::tryWriteLock() {
+ return -pthread_rwlock_trywrlock(&mRWLock);
+}
+inline void RWLock::unlock() {
+ pthread_rwlock_unlock(&mRWLock);
+}
+
+#endif // HAVE_PTHREADS
+
+/*****************************************************************************/
+
/*
* Condition variable class. The implementation is system-dependent.
*
diff --git a/libs/audioflinger/AudioPolicyManagerBase.cpp b/libs/audioflinger/AudioPolicyManagerBase.cpp
index 381a958..549d661 100644
--- a/libs/audioflinger/AudioPolicyManagerBase.cpp
+++ b/libs/audioflinger/AudioPolicyManagerBase.cpp
@@ -902,7 +902,8 @@
#ifdef AUDIO_POLICY_TEST
Thread(false),
#endif //AUDIO_POLICY_TEST
- mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false)
+ mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false),
+ mLastVoiceVolume(-1.0f)
{
mpClientInterface = clientInterface;
@@ -1713,29 +1714,38 @@
}
float volume = computeVolume(stream, index, output, device);
- // do not set volume if the float value did not change
- if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || force) {
+ // We actually change the volume if:
+ // - the float value returned by computeVolume() changed
+ // - the force flag is set
+ if (volume != mOutputs.valueFor(output)->mCurVolume[stream] ||
+ force) {
mOutputs.valueFor(output)->mCurVolume[stream] = volume;
LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs);
if (stream == AudioSystem::VOICE_CALL ||
stream == AudioSystem::DTMF ||
stream == AudioSystem::BLUETOOTH_SCO) {
- float voiceVolume = -1.0;
// offset value to reflect actual hardware volume that never reaches 0
// 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java)
volume = 0.01 + 0.99 * volume;
- if (stream == AudioSystem::VOICE_CALL) {
- voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
- } else if (stream == AudioSystem::BLUETOOTH_SCO) {
- voiceVolume = 1.0;
- }
- if (voiceVolume >= 0 && output == mHardwareOutput) {
- mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
- }
}
mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs);
}
+ if (stream == AudioSystem::VOICE_CALL ||
+ stream == AudioSystem::BLUETOOTH_SCO) {
+ float voiceVolume;
+ // Force voice volume to max for bluetooth SCO as volume is managed by the headset
+ if (stream == AudioSystem::VOICE_CALL) {
+ voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
+ } else {
+ voiceVolume = 1.0;
+ }
+ if (voiceVolume != mLastVoiceVolume && output == mHardwareOutput) {
+ mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
+ mLastVoiceVolume = voiceVolume;
+ }
+ }
+
return NO_ERROR;
}
diff --git a/libs/surfaceflinger/Barrier.h b/libs/surfaceflinger/Barrier.h
index e2bcf6a..6f8507e 100644
--- a/libs/surfaceflinger/Barrier.h
+++ b/libs/surfaceflinger/Barrier.h
@@ -29,10 +29,6 @@
inline Barrier() : state(CLOSED) { }
inline ~Barrier() { }
void open() {
- // gcc memory barrier, this makes sure all memory writes
- // have been issued by gcc. On an SMP system we'd need a real
- // h/w barrier.
- asm volatile ("":::"memory");
Mutex::Autolock _l(lock);
state = OPENED;
cv.broadcast();
diff --git a/libs/surfaceflinger/Layer.cpp b/libs/surfaceflinger/Layer.cpp
index 1fe997d..3fbb4d3 100644
--- a/libs/surfaceflinger/Layer.cpp
+++ b/libs/surfaceflinger/Layer.cpp
@@ -55,12 +55,13 @@
mNeedsBlending(true),
mNeedsDithering(false),
mTextureManager(mFlags),
- mBufferManager(mTextureManager)
+ mBufferManager(mTextureManager),
+ mWidth(0), mHeight(0), mFixedSize(false)
{
// no OpenGL operation is possible here, since we might not be
// in the OpenGL thread.
lcblk = new SharedBufferServer(
- client->ctrlblk, i, mBufferManager.getBufferCount(),
+ client->ctrlblk, i, mBufferManager.getDefaultBufferCount(),
getIdentity());
mBufferManager.setActiveBufferIndex( lcblk->getFrontBuffer() );
@@ -68,7 +69,10 @@
Layer::~Layer()
{
- destroy();
+ // FIXME: must be called from the main UI thread
+ EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay());
+ mBufferManager.destroy(dpy);
+
// the actual buffers will be destroyed here
delete lcblk;
}
@@ -81,17 +85,6 @@
lcblk->setStatus(NO_INIT);
}
-void Layer::destroy()
-{
- EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay());
- mBufferManager.destroy(dpy);
-
- mSurface.clear();
-
- Mutex::Autolock _l(mLock);
- mWidth = mHeight = 0;
-}
-
sp<LayerBaseClient::Surface> Layer::createSurface() const
{
return mSurface;
@@ -99,9 +92,17 @@
status_t Layer::ditch()
{
+ // NOTE: Called from the main UI thread
+
// the layer is not on screen anymore. free as much resources as possible
mFreezeLock.clear();
- destroy();
+
+ EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay());
+ mBufferManager.destroy(dpy);
+ mSurface.clear();
+
+ Mutex::Autolock _l(mLock);
+ mWidth = mHeight = 0;
return NO_ERROR;
}
@@ -208,10 +209,23 @@
drawWithOpenGL(clip, tex);
}
+bool Layer::needsFiltering() const
+{
+ if (!(mFlags & DisplayHardware::SLOW_CONFIG)) {
+ // NOTE: there is a race here, because mFixedSize is updated in a
+ // binder transaction. however, it doesn't really matter since it is
+ // evaluated each time we draw. To be perfectly correct, this flag
+ // would have to be associated with a buffer.
+ if (mFixedSize)
+ return true;
+ }
+ return LayerBase::needsFiltering();
+}
+
status_t Layer::setBufferCount(int bufferCount)
{
- // this ensures our client doesn't go away while we're accessing
+ // Ensures our client doesn't go away while we're accessing
// the shared area.
sp<Client> ourClient(client.promote());
if (ourClient == 0) {
@@ -219,23 +233,26 @@
return DEAD_OBJECT;
}
- status_t err;
-
- // FIXME: resize() below is NOT thread-safe, we need to synchronize
- // the users of lcblk in our process (ie: retire), and we assume the
- // client is not mucking with the SharedStack, which is only enforced
- // by construction, therefore we need to protect ourselves against
- // buggy and malicious client (as always)
-
- err = lcblk->resize(bufferCount);
+ // NOTE: lcblk->resize() is protected by an internal lock
+ status_t err = lcblk->resize(bufferCount);
+ if (err == NO_ERROR)
+ mBufferManager.resize(bufferCount);
return err;
}
-sp<GraphicBuffer> Layer::requestBuffer(int index, int usage)
+sp<GraphicBuffer> Layer::requestBuffer(int index,
+ uint32_t reqWidth, uint32_t reqHeight, uint32_t reqFormat,
+ uint32_t usage)
{
sp<GraphicBuffer> buffer;
+ if ((reqWidth | reqHeight | reqFormat) < 0)
+ return buffer;
+
+ if ((!reqWidth && reqHeight) || (reqWidth && !reqHeight))
+ return buffer;
+
// this ensures our client doesn't go away while we're accessing
// the shared area.
sp<Client> ourClient(client.promote());
@@ -248,7 +265,7 @@
* This is called from the client's Surface::dequeue(). This can happen
* at any time, especially while we're in the middle of using the
* buffer 'index' as our front buffer.
- *
+ *
* Make sure the buffer we're resizing is not the front buffer and has been
* dequeued. Once this condition is asserted, we are guaranteed that this
* buffer cannot become the front buffer under our feet, since we're called
@@ -261,23 +278,33 @@
return buffer;
}
- uint32_t w, h;
+ uint32_t w, h, f;
{ // scope for the lock
Mutex::Autolock _l(mLock);
- w = mWidth;
- h = mHeight;
+ const bool fixedSizeChanged = mFixedSize != (reqWidth && reqHeight);
+ const bool formatChanged = mReqFormat != reqFormat;
+ mReqWidth = reqWidth;
+ mReqHeight = reqHeight;
+ mReqFormat = reqFormat;
+ mFixedSize = reqWidth && reqHeight;
+ w = reqWidth ? reqWidth : mWidth;
+ h = reqHeight ? reqHeight : mHeight;
+ f = reqFormat ? reqFormat : mFormat;
buffer = mBufferManager.detachBuffer(index);
+ if (fixedSizeChanged || formatChanged) {
+ lcblk->reallocateAllExcept(index);
+ }
}
const uint32_t effectiveUsage = getEffectiveUsage(usage);
if (buffer!=0 && buffer->getStrongCount() == 1) {
- err = buffer->reallocate(w, h, mFormat, effectiveUsage);
+ err = buffer->reallocate(w, h, f, effectiveUsage);
} else {
// here we have to reallocate a new buffer because we could have a
// client in our process with a reference to it (eg: status bar),
// and we can't release the handle under its feet.
buffer.clear();
- buffer = new GraphicBuffer(w, h, mFormat, effectiveUsage);
+ buffer = new GraphicBuffer(w, h, f, effectiveUsage);
err = buffer->initCheck();
}
@@ -293,12 +320,7 @@
if (err == NO_ERROR && buffer->handle != 0) {
Mutex::Autolock _l(mLock);
- if (mWidth && mHeight) {
- mBufferManager.attachBuffer(index, buffer);
- } else {
- // oops we got killed while we were allocating the buffer
- buffer.clear();
- }
+ mBufferManager.attachBuffer(index, buffer);
}
return buffer;
}
@@ -335,39 +357,46 @@
const Layer::State& front(drawingState());
const Layer::State& temp(currentState());
- if ((front.requested_w != temp.requested_w) ||
- (front.requested_h != temp.requested_h)) {
+ const bool sizeChanged = (front.requested_w != temp.requested_w) ||
+ (front.requested_h != temp.requested_h);
+
+ if (sizeChanged) {
// the size changed, we need to ask our client to request a new buffer
LOGD_IF(DEBUG_RESIZE,
- "resize (layer=%p), requested (%dx%d), drawing (%d,%d)",
- this,
- int(temp.requested_w), int(temp.requested_h),
- int(front.requested_w), int(front.requested_h));
+ "resize (layer=%p), requested (%dx%d), drawing (%d,%d)",
+ this,
+ int(temp.requested_w), int(temp.requested_h),
+ int(front.requested_w), int(front.requested_h));
- // we're being resized and there is a freeze display request,
- // acquire a freeze lock, so that the screen stays put
- // until we've redrawn at the new size; this is to avoid
- // glitches upon orientation changes.
- if (mFlinger->hasFreezeRequest()) {
- // if the surface is hidden, don't try to acquire the
- // freeze lock, since hidden surfaces may never redraw
- if (!(front.flags & ISurfaceComposer::eLayerHidden)) {
- mFreezeLock = mFlinger->getFreezeLock();
+ if (!isFixedSize()) {
+ // we're being resized and there is a freeze display request,
+ // acquire a freeze lock, so that the screen stays put
+ // until we've redrawn at the new size; this is to avoid
+ // glitches upon orientation changes.
+ if (mFlinger->hasFreezeRequest()) {
+ // if the surface is hidden, don't try to acquire the
+ // freeze lock, since hidden surfaces may never redraw
+ if (!(front.flags & ISurfaceComposer::eLayerHidden)) {
+ mFreezeLock = mFlinger->getFreezeLock();
+ }
}
+
+ // this will make sure LayerBase::doTransaction doesn't update
+ // the drawing state's size
+ Layer::State& editDraw(mDrawingState);
+ editDraw.requested_w = temp.requested_w;
+ editDraw.requested_h = temp.requested_h;
+
+ // record the new size, form this point on, when the client request
+ // a buffer, it'll get the new size.
+ setBufferSize(temp.requested_w, temp.requested_h);
+
+ // all buffers need reallocation
+ lcblk->reallocateAll();
+ } else {
+ // record the new size
+ setBufferSize(temp.requested_w, temp.requested_h);
}
-
- // this will make sure LayerBase::doTransaction doesn't update
- // the drawing state's size
- Layer::State& editDraw(mDrawingState);
- editDraw.requested_w = temp.requested_w;
- editDraw.requested_h = temp.requested_h;
-
- // record the new size, form this point on, when the client request a
- // buffer, it'll get the new size.
- setDrawingSize(temp.requested_w, temp.requested_h);
-
- // all buffers need reallocation
- lcblk->reallocate();
}
if (temp.sequence != front.sequence) {
@@ -381,12 +410,17 @@
return LayerBase::doTransaction(flags);
}
-void Layer::setDrawingSize(uint32_t w, uint32_t h) {
+void Layer::setBufferSize(uint32_t w, uint32_t h) {
Mutex::Autolock _l(mLock);
mWidth = w;
mHeight = h;
}
+bool Layer::isFixedSize() const {
+ Mutex::Autolock _l(mLock);
+ return mFixedSize;
+}
+
// ----------------------------------------------------------------------------
// pageflip handling...
// ----------------------------------------------------------------------------
@@ -544,12 +578,20 @@
// ---------------------------------------------------------------------------
Layer::BufferManager::BufferManager(TextureManager& tm)
- : mTextureManager(tm), mActiveBuffer(0), mFailover(false)
+ : mNumBuffers(NUM_BUFFERS), mTextureManager(tm),
+ mActiveBuffer(0), mFailover(false)
{
}
-size_t Layer::BufferManager::getBufferCount() const {
- return NUM_BUFFERS;
+Layer::BufferManager::~BufferManager()
+{
+}
+
+status_t Layer::BufferManager::resize(size_t size)
+{
+ Mutex::Autolock _l(mLock);
+ mNumBuffers = size;
+ return NO_ERROR;
}
// only for debugging
@@ -568,51 +610,55 @@
}
Texture Layer::BufferManager::getActiveTexture() const {
- return mFailover ? mFailoverTexture : mBufferData[mActiveBuffer].texture;
+ Texture res;
+ if (mFailover) {
+ res = mFailoverTexture;
+ } else {
+ static_cast<Image&>(res) = mBufferData[mActiveBuffer].texture;
+ }
+ return res;
}
sp<GraphicBuffer> Layer::BufferManager::getActiveBuffer() const {
+ const size_t activeBuffer = mActiveBuffer;
+ BufferData const * const buffers = mBufferData;
Mutex::Autolock _l(mLock);
- return mBufferData[mActiveBuffer].buffer;
+ return buffers[activeBuffer].buffer;
}
sp<GraphicBuffer> Layer::BufferManager::detachBuffer(size_t index)
{
+ BufferData* const buffers = mBufferData;
sp<GraphicBuffer> buffer;
Mutex::Autolock _l(mLock);
- buffer = mBufferData[index].buffer;
- mBufferData[index].buffer = 0;
+ buffer = buffers[index].buffer;
+ buffers[index].buffer = 0;
return buffer;
}
status_t Layer::BufferManager::attachBuffer(size_t index,
const sp<GraphicBuffer>& buffer)
{
+ BufferData* const buffers = mBufferData;
Mutex::Autolock _l(mLock);
- mBufferData[index].buffer = buffer;
- mBufferData[index].texture.dirty = true;
- return NO_ERROR;
-}
-
-status_t Layer::BufferManager::destroyTexture(Texture* tex, EGLDisplay dpy)
-{
- if (tex->name != -1U) {
- glDeleteTextures(1, &tex->name);
- tex->name = -1U;
- }
- if (tex->image != EGL_NO_IMAGE_KHR) {
- eglDestroyImageKHR(dpy, tex->image);
- tex->image = EGL_NO_IMAGE_KHR;
- }
+ buffers[index].buffer = buffer;
+ buffers[index].texture.dirty = true;
return NO_ERROR;
}
status_t Layer::BufferManager::destroy(EGLDisplay dpy)
{
- Mutex::Autolock _l(mLock);
- for (size_t i=0 ; i<NUM_BUFFERS ; i++) {
- destroyTexture(&mBufferData[i].texture, dpy);
- mBufferData[i].buffer = 0;
+ BufferData* const buffers = mBufferData;
+ size_t num;
+ { // scope for the lock
+ Mutex::Autolock _l(mLock);
+ num = mNumBuffers;
+ for (size_t i=0 ; i<num ; i++) {
+ buffers[i].buffer = 0;
+ }
+ }
+ for (size_t i=0 ; i<num ; i++) {
+ destroyTexture(&buffers[i].texture, dpy);
}
destroyTexture(&mFailoverTexture, dpy);
return NO_ERROR;
@@ -622,7 +668,7 @@
const sp<GraphicBuffer>& buffer)
{
size_t index = mActiveBuffer;
- Texture& texture(mBufferData[index].texture);
+ Image& texture(mBufferData[index].texture);
status_t err = mTextureManager.initEglImage(&texture, dpy, buffer);
// if EGLImage fails, we switch to regular texture mode, and we
// free all resources associated with using EGLImages.
@@ -631,7 +677,8 @@
destroyTexture(&mFailoverTexture, dpy);
} else {
mFailover = true;
- for (size_t i=0 ; i<NUM_BUFFERS ; i++) {
+ const size_t num = mNumBuffers;
+ for (size_t i=0 ; i<num ; i++) {
destroyTexture(&mBufferData[i].texture, dpy);
}
}
@@ -644,6 +691,19 @@
return mTextureManager.loadTexture(&mFailoverTexture, dirty, t);
}
+status_t Layer::BufferManager::destroyTexture(Image* tex, EGLDisplay dpy)
+{
+ if (tex->name != -1U) {
+ glDeleteTextures(1, &tex->name);
+ tex->name = -1U;
+ }
+ if (tex->image != EGL_NO_IMAGE_KHR) {
+ eglDestroyImageKHR(dpy, tex->image);
+ tex->image = EGL_NO_IMAGE_KHR;
+ }
+ return NO_ERROR;
+}
+
// ---------------------------------------------------------------------------
Layer::SurfaceLayer::SurfaceLayer(const sp<SurfaceFlinger>& flinger,
@@ -656,12 +716,18 @@
{
}
-sp<GraphicBuffer> Layer::SurfaceLayer::requestBuffer(int index, int usage)
+sp<GraphicBuffer> Layer::SurfaceLayer::requestBuffer(int index,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage)
{
sp<GraphicBuffer> buffer;
sp<Layer> owner(getOwner());
if (owner != 0) {
- buffer = owner->requestBuffer(index, usage);
+ /*
+ * requestBuffer() cannot be called from the main thread
+ * as it could cause a dead-lock, since it may have to wait
+ * on conditions updated my the main thread.
+ */
+ buffer = owner->requestBuffer(index, w, h, format, usage);
}
return buffer;
}
@@ -671,6 +737,11 @@
status_t err = DEAD_OBJECT;
sp<Layer> owner(getOwner());
if (owner != 0) {
+ /*
+ * setBufferCount() cannot be called from the main thread
+ * as it could cause a dead-lock, since it may have to wait
+ * on conditions updated my the main thread.
+ */
err = owner->setBufferCount(bufferCount);
}
return err;
diff --git a/libs/surfaceflinger/Layer.h b/libs/surfaceflinger/Layer.h
index 80fbd6a..59603a5 100644
--- a/libs/surfaceflinger/Layer.h
+++ b/libs/surfaceflinger/Layer.h
@@ -57,7 +57,8 @@
status_t setBuffers(uint32_t w, uint32_t h,
PixelFormat format, uint32_t flags=0);
- void setDrawingSize(uint32_t w, uint32_t h);
+ void setBufferSize(uint32_t w, uint32_t h);
+ bool isFixedSize() const;
virtual void onDraw(const Region& clip) const;
virtual uint32_t doTransaction(uint32_t transactionFlags);
@@ -66,6 +67,7 @@
virtual void finishPageFlip();
virtual bool needsBlending() const { return mNeedsBlending; }
virtual bool needsDithering() const { return mNeedsDithering; }
+ virtual bool needsFiltering() const;
virtual bool isSecure() const { return mSecure; }
virtual sp<Surface> createSurface() const;
virtual status_t ditch();
@@ -88,9 +90,9 @@
uint32_t getEffectiveUsage(uint32_t usage) const;
- sp<GraphicBuffer> requestBuffer(int index, int usage);
+ sp<GraphicBuffer> requestBuffer(int bufferIdx,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
status_t setBufferCount(int bufferCount);
- void destroy();
class SurfaceLayer : public LayerBaseClient::Surface {
public:
@@ -98,7 +100,8 @@
SurfaceID id, const sp<Layer>& owner);
~SurfaceLayer();
private:
- virtual sp<GraphicBuffer> requestBuffer(int index, int usage);
+ virtual sp<GraphicBuffer> requestBuffer(int bufferIdx,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
virtual status_t setBufferCount(int bufferCount);
sp<Layer> getOwner() const {
return static_cast<Layer*>(Surface::getOwner().get());
@@ -120,25 +123,32 @@
static const size_t NUM_BUFFERS = 2;
struct BufferData {
sp<GraphicBuffer> buffer;
- Texture texture;
+ Image texture;
};
+ // this lock protect mBufferData[].buffer but since there
+ // is very little contention, we have only one like for
+ // the whole array, we also use it to protect mNumBuffers.
mutable Mutex mLock;
- BufferData mBufferData[NUM_BUFFERS];
+ BufferData mBufferData[SharedBufferStack::NUM_BUFFER_MAX];
+ size_t mNumBuffers;
Texture mFailoverTexture;
TextureManager& mTextureManager;
ssize_t mActiveBuffer;
bool mFailover;
- static status_t destroyTexture(Texture* tex, EGLDisplay dpy);
+ static status_t destroyTexture(Image* tex, EGLDisplay dpy);
public:
+ static size_t getDefaultBufferCount() { return NUM_BUFFERS; }
BufferManager(TextureManager& tm);
-
- size_t getBufferCount() const;
+ ~BufferManager();
// detach/attach buffer from/to given index
sp<GraphicBuffer> detachBuffer(size_t index);
status_t attachBuffer(size_t index, const sp<GraphicBuffer>& buffer);
+ // resize the number of active buffers
+ status_t resize(size_t size);
+
// ----------------------------------------------
// must be called from GL thread
@@ -170,9 +180,15 @@
TextureManager mTextureManager;
BufferManager mBufferManager;
+ // this lock protects mWidth and mHeight which are accessed from
+ // the main thread and requestBuffer's binder transaction thread.
mutable Mutex mLock;
uint32_t mWidth;
uint32_t mHeight;
+ uint32_t mReqWidth;
+ uint32_t mReqHeight;
+ uint32_t mReqFormat;
+ bool mFixedSize;
};
// ---------------------------------------------------------------------------
diff --git a/libs/surfaceflinger/LayerBase.cpp b/libs/surfaceflinger/LayerBase.cpp
index 63b9520..76733a9 100644
--- a/libs/surfaceflinger/LayerBase.cpp
+++ b/libs/surfaceflinger/LayerBase.cpp
@@ -42,8 +42,7 @@
LayerBase::LayerBase(SurfaceFlinger* flinger, DisplayID display)
: dpy(display), contentDirty(false),
mFlinger(flinger),
- mTransformed(false),
- mUseLinearFiltering(false),
+ mNeedsFiltering(false),
mOrientation(0),
mLeft(0), mTop(0),
mTransactionFlags(0),
@@ -214,13 +213,12 @@
flags |= eVisibleRegion;
this->contentDirty = true;
- const bool linearFiltering = mUseLinearFiltering;
- mUseLinearFiltering = false;
+ mNeedsFiltering = false;
if (!(mFlags & DisplayHardware::SLOW_CONFIG)) {
// we may use linear filtering, if the matrix scales us
const uint8_t type = temp.transform.getType();
if (!temp.transform.preserveRects() || (type >= Transform::SCALE)) {
- mUseLinearFiltering = true;
+ mNeedsFiltering = true;
}
}
}
@@ -260,7 +258,6 @@
// cache a few things...
mOrientation = tr.getOrientation();
mTransformedBounds = tr.makeBounds(w, h);
- mTransformed = transformed;
mLeft = tr.tx();
mTop = tr.ty();
}
@@ -341,13 +338,13 @@
*/
}
-void LayerBase::clearWithOpenGL(const Region& clip, GLclampx red,
- GLclampx green, GLclampx blue,
- GLclampx alpha) const
+void LayerBase::clearWithOpenGL(const Region& clip, GLclampf red,
+ GLclampf green, GLclampf blue,
+ GLclampf alpha) const
{
const DisplayHardware& hw(graphicPlane(0).displayHardware());
const uint32_t fbHeight = hw.getHeight();
- glColor4x(red,green,blue,alpha);
+ glColor4f(red,green,blue,alpha);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDisable(GL_DITHER);
@@ -466,7 +463,7 @@
glBindTexture(GL_TEXTURE_2D, textureName);
// TODO: reload the texture if needed
// this is currently done in loadTexture() below
- if (mUseLinearFiltering) {
+ if (needsFiltering()) {
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
@@ -500,7 +497,7 @@
// ---------------------------------------------------------------------------
-int32_t LayerBaseClient::sIdentity = 0;
+int32_t LayerBaseClient::sIdentity = 1;
LayerBaseClient::LayerBaseClient(SurfaceFlinger* flinger, DisplayID display,
const sp<Client>& client, int32_t i)
@@ -616,7 +613,8 @@
return BnSurface::onTransact(code, data, reply, flags);
}
-sp<GraphicBuffer> LayerBaseClient::Surface::requestBuffer(int index, int usage)
+sp<GraphicBuffer> LayerBaseClient::Surface::requestBuffer(int bufferIdx,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage)
{
return NULL;
}
diff --git a/libs/surfaceflinger/LayerBase.h b/libs/surfaceflinger/LayerBase.h
index 53b848f..a78424e 100644
--- a/libs/surfaceflinger/LayerBase.h
+++ b/libs/surfaceflinger/LayerBase.h
@@ -177,9 +177,9 @@
virtual bool needsDithering() const { return false; }
/**
- * transformed -- true is this surface needs a to be transformed
+ * needsLinearFiltering - true if this surface needs filtering
*/
- virtual bool transformed() const { return mTransformed; }
+ virtual bool needsFiltering() const { return mNeedsFiltering; }
/**
* isSecure - true if this surface is secure, that is if it prevents
@@ -222,8 +222,8 @@
const GraphicPlane& graphicPlane(int dpy) const;
GraphicPlane& graphicPlane(int dpy);
- void clearWithOpenGL(const Region& clip, GLclampx r, GLclampx g,
- GLclampx b, GLclampx alpha) const;
+ void clearWithOpenGL(const Region& clip, GLclampf r, GLclampf g,
+ GLclampf b, GLclampf alpha) const;
void clearWithOpenGL(const Region& clip) const;
void drawWithOpenGL(const Region& clip, const Texture& texture) const;
@@ -231,8 +231,7 @@
uint32_t mFlags;
// cached during validateVisibility()
- bool mTransformed;
- bool mUseLinearFiltering;
+ bool mNeedsFiltering;
int32_t mOrientation;
GLfloat mVertices[4][2];
Rect mTransformedBounds;
@@ -301,7 +300,8 @@
sp<LayerBaseClient> getOwner() const;
private:
- virtual sp<GraphicBuffer> requestBuffer(int index, int usage);
+ virtual sp<GraphicBuffer> requestBuffer(int bufferIdx,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
virtual status_t setBufferCount(int bufferCount);
virtual status_t registerBuffers(const ISurface::BufferHeap& buffers);
diff --git a/libs/surfaceflinger/LayerBlur.cpp b/libs/surfaceflinger/LayerBlur.cpp
index 2d77876..09c90e8 100644
--- a/libs/surfaceflinger/LayerBlur.cpp
+++ b/libs/surfaceflinger/LayerBlur.cpp
@@ -95,7 +95,9 @@
mCacheDirty = false;
} else {
if (!mAutoRefreshPending) {
- mFlinger->signalDelayedEvent(ms2ns(500));
+ mFlinger->postMessageAsync(
+ new MessageBase(MessageQueue::INVALIDATE),
+ ms2ns(500));
mAutoRefreshPending = true;
}
}
diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp
index dfcc80f..8a582da 100644
--- a/libs/surfaceflinger/LayerBuffer.cpp
+++ b/libs/surfaceflinger/LayerBuffer.cpp
@@ -118,7 +118,7 @@
source->onTransaction(flags);
uint32_t res = LayerBase::doTransaction(flags);
// we always want filtering for these surfaces
- mUseLinearFiltering = !(mFlags & DisplayHardware::SLOW_CONFIG);
+ mNeedsFiltering = !(mFlags & DisplayHardware::SLOW_CONFIG);
return res;
}
@@ -143,14 +143,6 @@
}
}
-bool LayerBuffer::transformed() const
-{
- sp<Source> source(getSource());
- if (LIKELY(source != 0))
- return source->transformed();
- return false;
-}
-
void LayerBuffer::serverDestroy()
{
sp<Source> source(clearSource());
@@ -319,9 +311,6 @@
}
void LayerBuffer::Source::unregisterBuffers() {
}
-bool LayerBuffer::Source::transformed() const {
- return mLayer.mTransformed;
-}
// ---------------------------------------------------------------------------
@@ -442,11 +431,6 @@
mBuffer = buffer;
}
-bool LayerBuffer::BufferSource::transformed() const
-{
- return mBufferHeap.transform ? true : Source::transformed();
-}
-
void LayerBuffer::BufferSource::onDraw(const Region& clip) const
{
sp<Buffer> ourBuffer(getBuffer());
@@ -542,7 +526,7 @@
// figure out if we need linear filtering
if (buffers.w * h == buffers.h * w) {
// same pixel area, don't use filtering
- mLayer.mUseLinearFiltering = false;
+ mLayer.mNeedsFiltering = false;
}
// Allocate a temporary buffer and create the corresponding EGLImageKHR
@@ -638,9 +622,9 @@
void LayerBuffer::OverlaySource::onDraw(const Region& clip) const
{
// this would be where the color-key would be set, should we need it.
- GLclampx red = 0;
- GLclampx green = 0;
- GLclampx blue = 0;
+ GLclampf red = 0;
+ GLclampf green = 0;
+ GLclampf blue = 0;
mLayer.clearWithOpenGL(clip, red, green, blue, 0);
}
diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h
index 869c74f..d38dde2 100644
--- a/libs/surfaceflinger/LayerBuffer.h
+++ b/libs/surfaceflinger/LayerBuffer.h
@@ -46,7 +46,6 @@
virtual void onVisibilityResolved(const Transform& planeTransform);
virtual void postBuffer(ssize_t offset);
virtual void unregisterBuffers();
- virtual bool transformed() const;
virtual void destroy() { }
protected:
LayerBuffer& mLayer;
@@ -66,7 +65,6 @@
virtual void onDraw(const Region& clip) const;
virtual uint32_t doTransaction(uint32_t flags);
virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
- virtual bool transformed() const;
status_t registerBuffers(const ISurface::BufferHeap& buffers);
void postBuffer(ssize_t offset);
@@ -130,7 +128,6 @@
virtual void onDraw(const Region& clip) const;
virtual void postBuffer(ssize_t offset);
virtual void unregisterBuffers();
- virtual bool transformed() const;
virtual void destroy() { }
private:
status_t initTempBuffer() const;
diff --git a/libs/surfaceflinger/MessageQueue.cpp b/libs/surfaceflinger/MessageQueue.cpp
index b43d801..d668e88 100644
--- a/libs/surfaceflinger/MessageQueue.cpp
+++ b/libs/surfaceflinger/MessageQueue.cpp
@@ -60,9 +60,9 @@
{
}
-MessageList::value_type MessageQueue::waitMessage(nsecs_t timeout)
+sp<MessageBase> MessageQueue::waitMessage(nsecs_t timeout)
{
- MessageList::value_type result;
+ sp<MessageBase> result;
bool again;
do {
@@ -132,6 +132,7 @@
if (again) {
// the message has been processed. release our reference to it
// without holding the lock.
+ result->notify();
result = 0;
}
@@ -141,7 +142,7 @@
}
status_t MessageQueue::postMessage(
- const MessageList::value_type& message, nsecs_t relTime, uint32_t flags)
+ const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags)
{
return queueMessage(message, relTime, flags);
}
@@ -154,7 +155,7 @@
}
status_t MessageQueue::queueMessage(
- const MessageList::value_type& message, nsecs_t relTime, uint32_t flags)
+ const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags)
{
Mutex::Autolock _l(mLock);
message->when = systemTime() + relTime;
@@ -167,13 +168,13 @@
return NO_ERROR;
}
-void MessageQueue::dump(const MessageList::value_type& message)
+void MessageQueue::dump(const sp<MessageBase>& message)
{
Mutex::Autolock _l(mLock);
dumpLocked(message);
}
-void MessageQueue::dumpLocked(const MessageList::value_type& message)
+void MessageQueue::dumpLocked(const sp<MessageBase>& message)
{
LIST::const_iterator cur(mMessages.begin());
LIST::const_iterator end(mMessages.end());
diff --git a/libs/surfaceflinger/MessageQueue.h b/libs/surfaceflinger/MessageQueue.h
index dc8138d..890f809 100644
--- a/libs/surfaceflinger/MessageQueue.h
+++ b/libs/surfaceflinger/MessageQueue.h
@@ -25,6 +25,7 @@
#include <utils/Timers.h>
#include <utils/List.h>
+#include "Barrier.h"
namespace android {
@@ -37,7 +38,6 @@
List< sp<MessageBase> > mList;
typedef List< sp<MessageBase> > LIST;
public:
- typedef sp<MessageBase> value_type;
inline LIST::iterator begin() { return mList.begin(); }
inline LIST::const_iterator begin() const { return mList.begin(); }
inline LIST::iterator end() { return mList.end(); }
@@ -63,11 +63,19 @@
// return true if message has a handler
virtual bool handler() { return false; }
+
+ // waits for the handler to be processed
+ void wait() const { barrier.wait(); }
+ // releases all waiters. this is done automatically if
+ // handler returns true
+ void notify() const { barrier.open(); }
+
protected:
virtual ~MessageBase() { }
private:
+ mutable Barrier barrier;
friend class LightRefBase<MessageBase>;
};
@@ -82,42 +90,33 @@
typedef List< sp<MessageBase> > LIST;
public:
- // this is a work-around the multichar constant warning. A macro would
- // work too, but would pollute the namespace.
- template <int a, int b, int c, int d>
- struct WHAT {
- static const uint32_t Value =
- (uint32_t(a&0xff)<<24)|(uint32_t(b&0xff)<<16)|
- (uint32_t(c&0xff)<<8)|uint32_t(d&0xff);
- };
-
MessageQueue();
~MessageQueue();
// pre-defined messages
enum {
- INVALIDATE = WHAT<'_','p','d','t'>::Value
+ INVALIDATE = '_upd'
};
- MessageList::value_type waitMessage(nsecs_t timeout = -1);
+ sp<MessageBase> waitMessage(nsecs_t timeout = -1);
- status_t postMessage(const MessageList::value_type& message,
+ status_t postMessage(const sp<MessageBase>& message,
nsecs_t reltime=0, uint32_t flags = 0);
-
+
status_t invalidate();
- void dump(const MessageList::value_type& message);
+ void dump(const sp<MessageBase>& message);
private:
- status_t queueMessage(const MessageList::value_type& message,
+ status_t queueMessage(const sp<MessageBase>& message,
nsecs_t reltime, uint32_t flags);
- void dumpLocked(const MessageList::value_type& message);
+ void dumpLocked(const sp<MessageBase>& message);
Mutex mLock;
Condition mCondition;
MessageList mMessages;
bool mInvalidate;
- MessageList::value_type mInvalidateMessage;
+ sp<MessageBase> mInvalidateMessage;
};
// ---------------------------------------------------------------------------
diff --git a/libs/surfaceflinger/SurfaceFlinger.cpp b/libs/surfaceflinger/SurfaceFlinger.cpp
index 62d829b..5a6893f 100644
--- a/libs/surfaceflinger/SurfaceFlinger.cpp
+++ b/libs/surfaceflinger/SurfaceFlinger.cpp
@@ -426,7 +426,7 @@
timeout = waitTime>0 ? waitTime : 0;
}
- MessageList::value_type msg = mEventQueue.waitMessage(timeout);
+ sp<MessageBase> msg = mEventQueue.waitMessage(timeout);
// see if we timed out
if (isFrozen()) {
@@ -461,9 +461,20 @@
const_cast<SurfaceFlinger*>(this)->signalEvent();
}
-void SurfaceFlinger::signalDelayedEvent(nsecs_t delay)
+status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg,
+ nsecs_t reltime, uint32_t flags)
{
- mEventQueue.postMessage( new MessageBase(MessageQueue::INVALIDATE), delay);
+ return mEventQueue.postMessage(msg, reltime, flags);
+}
+
+status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg,
+ nsecs_t reltime, uint32_t flags)
+{
+ status_t res = mEventQueue.postMessage(msg, reltime, flags);
+ if (res == NO_ERROR) {
+ msg->wait();
+ }
+ return res;
}
// ----------------------------------------------------------------------------
@@ -1135,15 +1146,11 @@
return android_atomic_and(~flags, &mTransactionFlags) & flags;
}
-uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags, nsecs_t delay)
+uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags)
{
uint32_t old = android_atomic_or(flags, &mTransactionFlags);
if ((old & flags)==0) { // wake the server up
- if (delay > 0) {
- signalDelayedEvent(delay);
- } else {
- signalEvent();
- }
+ signalEvent();
}
return old;
}
@@ -1245,7 +1252,7 @@
//LOGD("createSurface for pid %d (%d x %d)", pid, w, h);
int32_t id = client->generateId(pid);
- if (uint32_t(id) >= NUM_LAYERS_MAX) {
+ if (uint32_t(id) >= SharedBufferStack::NUM_LAYERS_MAX) {
LOGE("createSurface() failed, generateId = %d", id);
return surfaceHandle;
}
@@ -1399,7 +1406,7 @@
}
};
- mEventQueue.postMessage( new MessageDestroySurface(this, layer) );
+ postMessageAsync( new MessageDestroySurface(this, layer) );
return NO_ERROR;
}
@@ -1672,7 +1679,7 @@
int32_t Client::generateId(int pid)
{
const uint32_t i = clz( ~mBitmap );
- if (i >= NUM_LAYERS_MAX) {
+ if (i >= SharedBufferStack::NUM_LAYERS_MAX) {
return NO_MEMORY;
}
mPid = pid;
@@ -1699,7 +1706,8 @@
}
bool Client::isValid(int32_t i) const {
- return (uint32_t(i)<NUM_LAYERS_MAX) && (mBitmap & (1<<(31-i)));
+ return (uint32_t(i)<SharedBufferStack::NUM_LAYERS_MAX) &&
+ (mBitmap & (1<<(31-i)));
}
sp<LayerBaseClient> Client::getLayerUser(int32_t i) const {
diff --git a/libs/surfaceflinger/SurfaceFlinger.h b/libs/surfaceflinger/SurfaceFlinger.h
index 9c8de51..2558324 100644
--- a/libs/surfaceflinger/SurfaceFlinger.h
+++ b/libs/surfaceflinger/SurfaceFlinger.h
@@ -255,8 +255,6 @@
public: // hack to work around gcc 4.0.3 bug
void signalEvent();
private:
- void signalDelayedEvent(nsecs_t delay);
-
void handleConsoleEvents();
void handleTransaction(uint32_t transactionFlags);
void handleTransactionLocked(
@@ -286,7 +284,7 @@
void free_resources_l();
uint32_t getTransactionFlags(uint32_t flags);
- uint32_t setTransactionFlags(uint32_t flags, nsecs_t delay = 0);
+ uint32_t setTransactionFlags(uint32_t flags);
void commitTransaction();
@@ -310,7 +308,12 @@
mutable MessageQueue mEventQueue;
-
+
+ status_t postMessageAsync(const sp<MessageBase>& msg,
+ nsecs_t reltime=0, uint32_t flags = 0);
+
+ status_t postMessageSync(const sp<MessageBase>& msg,
+ nsecs_t reltime=0, uint32_t flags = 0);
// access must be protected by mStateLock
diff --git a/libs/surfaceflinger/TextureManager.cpp b/libs/surfaceflinger/TextureManager.cpp
index e5d5302..ee2159b 100644
--- a/libs/surfaceflinger/TextureManager.cpp
+++ b/libs/surfaceflinger/TextureManager.cpp
@@ -68,7 +68,7 @@
return false;
}
-status_t TextureManager::initEglImage(Texture* texture,
+status_t TextureManager::initEglImage(Image* texture,
EGLDisplay dpy, const sp<GraphicBuffer>& buffer)
{
status_t err = NO_ERROR;
@@ -108,7 +108,6 @@
err = INVALID_OPERATION;
} else {
// Everything went okay!
- texture->NPOTAdjust = false;
texture->dirty = false;
texture->width = clientBuf->width;
texture->height = clientBuf->height;
diff --git a/libs/surfaceflinger/TextureManager.h b/libs/surfaceflinger/TextureManager.h
index 90cb62b..d0acfe9 100644
--- a/libs/surfaceflinger/TextureManager.h
+++ b/libs/surfaceflinger/TextureManager.h
@@ -36,21 +36,24 @@
// ---------------------------------------------------------------------------
-struct Texture {
- Texture() : name(-1U), width(0), height(0),
- image(EGL_NO_IMAGE_KHR), transform(0),
- NPOTAdjust(false), dirty(true) { }
+struct Image {
+ Image() : name(-1U), image(EGL_NO_IMAGE_KHR), width(0), height(0),
+ transform(0), dirty(true) { }
GLuint name;
+ EGLImageKHR image;
GLuint width;
GLuint height;
+ uint32_t transform;
+ bool dirty;
+};
+
+struct Texture : public Image {
+ Texture() : Image(), NPOTAdjust(false) { }
GLuint potWidth;
GLuint potHeight;
GLfloat wScale;
GLfloat hScale;
- EGLImageKHR image;
- uint32_t transform;
bool NPOTAdjust;
- bool dirty;
};
// ---------------------------------------------------------------------------
@@ -68,7 +71,7 @@
const Region& dirty, const GGLSurface& t);
// make active buffer an EGLImage if needed
- status_t initEglImage(Texture* texture,
+ status_t initEglImage(Image* texture,
EGLDisplay dpy, const sp<GraphicBuffer>& buffer);
};
diff --git a/libs/surfaceflinger_client/ISurface.cpp b/libs/surfaceflinger_client/ISurface.cpp
index c5d0c0e..7049d9e 100644
--- a/libs/surfaceflinger_client/ISurface.cpp
+++ b/libs/surfaceflinger_client/ISurface.cpp
@@ -71,11 +71,15 @@
{
}
- virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage)
+ virtual sp<GraphicBuffer> requestBuffer(int bufferIdx,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage)
{
Parcel data, reply;
data.writeInterfaceToken(ISurface::getInterfaceDescriptor());
data.writeInt32(bufferIdx);
+ data.writeInt32(w);
+ data.writeInt32(h);
+ data.writeInt32(format);
data.writeInt32(usage);
remote()->transact(REQUEST_BUFFER, data, &reply);
sp<GraphicBuffer> buffer = new GraphicBuffer();
@@ -150,8 +154,11 @@
case REQUEST_BUFFER: {
CHECK_INTERFACE(ISurface, data, reply);
int bufferIdx = data.readInt32();
- int usage = data.readInt32();
- sp<GraphicBuffer> buffer(requestBuffer(bufferIdx, usage));
+ uint32_t w = data.readInt32();
+ uint32_t h = data.readInt32();
+ uint32_t format = data.readInt32();
+ uint32_t usage = data.readInt32();
+ sp<GraphicBuffer> buffer(requestBuffer(bufferIdx, w, h, format, usage));
if (buffer == NULL)
return BAD_VALUE;
return reply->write(*buffer);
diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp
index dab8ed8..2577dc0 100644
--- a/libs/surfaceflinger_client/SharedBufferStack.cpp
+++ b/libs/surfaceflinger_client/SharedBufferStack.cpp
@@ -44,7 +44,7 @@
// these functions are used by the clients
status_t SharedClient::validate(size_t i) const {
- if (uint32_t(i) >= uint32_t(NUM_LAYERS_MAX))
+ if (uint32_t(i) >= uint32_t(SharedBufferStack::NUM_LAYERS_MAX))
return BAD_INDEX;
return surfaces[i].status;
}
@@ -144,10 +144,10 @@
// ----------------------------------------------------------------------------
SharedBufferBase::SharedBufferBase(SharedClient* sharedClient,
- int surface, int num, int32_t identity)
+ int surface, int32_t identity)
: mSharedClient(sharedClient),
mSharedStack(sharedClient->surfaces + surface),
- mNumBuffers(num), mIdentity(identity)
+ mIdentity(identity)
{
}
@@ -155,12 +155,6 @@
{
}
-uint32_t SharedBufferBase::getIdentity()
-{
- SharedBufferStack& stack( *mSharedStack );
- return stack.identity;
-}
-
status_t SharedBufferBase::getStatus() const
{
SharedBufferStack& stack( *mSharedStack );
@@ -179,34 +173,16 @@
char buffer[SIZE];
String8 result;
SharedBufferStack& stack( *mSharedStack );
- int tail = computeTail();
snprintf(buffer, SIZE,
- "%s[ head=%2d, available=%2d, queued=%2d, tail=%2d ] "
+ "%s[ head=%2d, available=%2d, queued=%2d ] "
"reallocMask=%08x, inUse=%2d, identity=%d, status=%d",
- prefix, stack.head, stack.available, stack.queued, tail,
+ prefix, stack.head, stack.available, stack.queued,
stack.reallocMask, stack.inUse, stack.identity, stack.status);
result.append(buffer);
-
- snprintf(buffer, SIZE, " { ");
- result.append(buffer);
-
- for (int i=0 ; i<mNumBuffers ; i++) {
- snprintf(buffer, SIZE, "%d ", stack.index[i]);
- result.append(buffer);
- }
-
- snprintf(buffer, SIZE, " }\n");
- result.append(buffer);
-
+ result.append("\n");
return result;
}
-int32_t SharedBufferBase::computeTail() const
-{
- SharedBufferStack& stack( *mSharedStack );
- return (mNumBuffers + stack.head - stack.available + 1) % mNumBuffers;
-}
-
status_t SharedBufferBase::waitForCondition(const ConditionBase& condition)
{
const SharedBufferStack& stack( *mSharedStack );
@@ -270,7 +246,7 @@
}
bool SharedBufferServer::ReallocateCondition::operator()() const {
int32_t head = stack.head;
- if (uint32_t(head) >= NUM_BUFFER_MAX) {
+ if (uint32_t(head) >= SharedBufferStack::NUM_BUFFER_MAX) {
// if stack.head is messed up, we cannot allow the server to
// crash (since stack.head is mapped on the client side)
stack.status = BAD_VALUE;
@@ -318,7 +294,7 @@
}
ssize_t SharedBufferServer::RetireUpdate::operator()() {
int32_t head = stack.head;
- if (uint32_t(head) >= NUM_BUFFER_MAX)
+ if (uint32_t(head) >= SharedBufferStack::NUM_BUFFER_MAX)
return BAD_VALUE;
// Preventively lock the current buffer before updating queued.
@@ -361,14 +337,20 @@
SharedBufferClient::SharedBufferClient(SharedClient* sharedClient,
int surface, int num, int32_t identity)
- : SharedBufferBase(sharedClient, surface, num, identity),
- tail(0), undoDequeueTail(0)
+ : SharedBufferBase(sharedClient, surface, identity),
+ mNumBuffers(num), tail(0), undoDequeueTail(0)
{
SharedBufferStack& stack( *mSharedStack );
tail = computeTail();
queued_head = stack.head;
}
+int32_t SharedBufferClient::computeTail() const
+{
+ SharedBufferStack& stack( *mSharedStack );
+ return (mNumBuffers + stack.head - stack.available + 1) % mNumBuffers;
+}
+
ssize_t SharedBufferClient::dequeue()
{
SharedBufferStack& stack( *mSharedStack );
@@ -377,7 +359,9 @@
LOGW("dequeue: tail=%d, head=%d, avail=%d, queued=%d",
tail, stack.head, stack.available, stack.queued);
}
-
+
+ RWLock::AutoRLock _rd(mLock);
+
const nsecs_t dequeueTime = systemTime(SYSTEM_TIME_THREAD);
//LOGD("[%d] about to dequeue a buffer",
@@ -407,6 +391,8 @@
status_t SharedBufferClient::undoDequeue(int buf)
{
+ RWLock::AutoRLock _rd(mLock);
+
// TODO: we can only undo the previous dequeue, we should
// enforce that in the api
UndoDequeueUpdate update(this);
@@ -419,6 +405,8 @@
status_t SharedBufferClient::lock(int buf)
{
+ RWLock::AutoRLock _rd(mLock);
+
SharedBufferStack& stack( *mSharedStack );
LockCondition condition(this, buf);
status_t err = waitForCondition(condition);
@@ -427,6 +415,8 @@
status_t SharedBufferClient::queue(int buf)
{
+ RWLock::AutoRLock _rd(mLock);
+
SharedBufferStack& stack( *mSharedStack );
queued_head = (queued_head + 1) % mNumBuffers;
@@ -444,7 +434,7 @@
bool SharedBufferClient::needNewBuffer(int buf) const
{
SharedBufferStack& stack( *mSharedStack );
- const uint32_t mask = 1<<buf;
+ const uint32_t mask = 1<<(31-buf);
return (android_atomic_and(~mask, &stack.reallocMask) & mask) != 0;
}
@@ -460,21 +450,32 @@
return stack.setDirtyRegion(buf, reg);
}
-status_t SharedBufferClient::setBufferCount(int bufferCount)
+status_t SharedBufferClient::setBufferCount(
+ int bufferCount, const SetBufferCountCallback& ipc)
{
SharedBufferStack& stack( *mSharedStack );
- if (uint32_t(bufferCount) >= NUM_BUFFER_MAX)
+ if (uint32_t(bufferCount) >= SharedBufferStack::NUM_BUFFER_MAX)
return BAD_VALUE;
- mNumBuffers = bufferCount;
- queued_head = (stack.head + stack.queued) % mNumBuffers;
- return NO_ERROR;
+
+ if (uint32_t(bufferCount) < SharedBufferStack::NUM_BUFFER_MIN)
+ return BAD_VALUE;
+
+ RWLock::AutoWLock _wr(mLock);
+
+ status_t err = ipc(bufferCount);
+ if (err == NO_ERROR) {
+ mNumBuffers = bufferCount;
+ queued_head = (stack.head + stack.queued) % mNumBuffers;
+ }
+ return err;
}
// ----------------------------------------------------------------------------
SharedBufferServer::SharedBufferServer(SharedClient* sharedClient,
int surface, int num, int32_t identity)
- : SharedBufferBase(sharedClient, surface, num, identity)
+ : SharedBufferBase(sharedClient, surface, identity),
+ mNumBuffers(num)
{
mSharedStack->init(identity);
mSharedStack->head = num-1;
@@ -490,10 +491,12 @@
ssize_t SharedBufferServer::retireAndLock()
{
+ RWLock::AutoRLock _l(mLock);
+
RetireUpdate update(this, mNumBuffers);
ssize_t buf = updateCondition( update );
if (buf >= 0) {
- if (uint32_t(buf) >= NUM_BUFFER_MAX)
+ if (uint32_t(buf) >= SharedBufferStack::NUM_BUFFER_MAX)
return BAD_VALUE;
SharedBufferStack& stack( *mSharedStack );
buf = stack.index[buf];
@@ -518,11 +521,25 @@
}
}
-status_t SharedBufferServer::reallocate()
+status_t SharedBufferServer::reallocateAll()
{
+ RWLock::AutoRLock _l(mLock);
+
SharedBufferStack& stack( *mSharedStack );
- uint32_t mask = (1<<mNumBuffers)-1;
- android_atomic_or(mask, &stack.reallocMask);
+ uint32_t mask = mBufferList.getMask();
+ android_atomic_or(mask, &stack.reallocMask);
+ return NO_ERROR;
+}
+
+status_t SharedBufferServer::reallocateAllExcept(int buffer)
+{
+ RWLock::AutoRLock _l(mLock);
+
+ SharedBufferStack& stack( *mSharedStack );
+ BufferList temp(mBufferList);
+ temp.remove(buffer);
+ uint32_t mask = temp.getMask();
+ android_atomic_or(mask, &stack.reallocMask);
return NO_ERROR;
}
@@ -534,6 +551,13 @@
status_t SharedBufferServer::assertReallocate(int buf)
{
+ /*
+ * NOTE: it's safe to hold mLock for read while waiting for
+ * the ReallocateCondition because that condition is not updated
+ * by the thread that holds mLock for write.
+ */
+ RWLock::AutoRLock _l(mLock);
+
// TODO: need to validate "buf"
ReallocateCondition condition(this, buf);
status_t err = waitForCondition(condition);
@@ -546,9 +570,7 @@
return stack.getDirtyRegion(buf);
}
-
/*
- *
* NOTE: this is not thread-safe on the server-side, meaning
* 'head' cannot move during this operation. The client-side
* can safely operate an usual.
@@ -556,9 +578,11 @@
*/
status_t SharedBufferServer::resize(int newNumBuffers)
{
- if (uint32_t(newNumBuffers) >= NUM_BUFFER_MAX)
+ if (uint32_t(newNumBuffers) >= SharedBufferStack::NUM_BUFFER_MAX)
return BAD_VALUE;
+ RWLock::AutoWLock _l(mLock);
+
// for now we're not supporting shrinking
const int numBuffers = mNumBuffers;
if (newNumBuffers < numBuffers)
@@ -569,7 +593,7 @@
// read the head, make sure it's valid
int32_t head = stack.head;
- if (uint32_t(head) >= NUM_BUFFER_MAX)
+ if (uint32_t(head) >= SharedBufferStack::NUM_BUFFER_MAX)
return BAD_VALUE;
int base = numBuffers;
diff --git a/libs/surfaceflinger_client/Surface.cpp b/libs/surfaceflinger_client/Surface.cpp
index afbeafb..a26733e 100644
--- a/libs/surfaceflinger_client/Surface.cpp
+++ b/libs/surfaceflinger_client/Surface.cpp
@@ -151,75 +151,75 @@
}
status_t SurfaceControl::setLayer(int32_t layer) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setLayer(mToken, layer);
}
status_t SurfaceControl::setPosition(int32_t x, int32_t y) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setPosition(mToken, x, y);
}
status_t SurfaceControl::setSize(uint32_t w, uint32_t h) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setSize(mToken, w, h);
}
status_t SurfaceControl::hide() {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->hide(mToken);
}
status_t SurfaceControl::show(int32_t layer) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->show(mToken, layer);
}
status_t SurfaceControl::freeze() {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->freeze(mToken);
}
status_t SurfaceControl::unfreeze() {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->unfreeze(mToken);
}
status_t SurfaceControl::setFlags(uint32_t flags, uint32_t mask) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setFlags(mToken, flags, mask);
}
status_t SurfaceControl::setTransparentRegionHint(const Region& transparent) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setTransparentRegionHint(mToken, transparent);
}
status_t SurfaceControl::setAlpha(float alpha) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setAlpha(mToken, alpha);
}
status_t SurfaceControl::setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setMatrix(mToken, dsdx, dtdx, dsdy, dtdy);
}
status_t SurfaceControl::setFreezeTint(uint32_t tint) {
- const sp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
return client->setFreezeTint(mToken, tint);
}
@@ -230,23 +230,6 @@
mToken, mIdentity, mClient.get());
return NO_INIT;
}
- SharedClient const* cblk = mClient->mControl;
- if (cblk == 0) {
- LOGE("cblk is null (surface id=%d, identity=%u)", mToken, mIdentity);
- return NO_INIT;
- }
- status_t err = cblk->validate(mToken);
- if (err != NO_ERROR) {
- LOGE("surface (id=%d, identity=%u) is invalid, err=%d (%s)",
- mToken, mIdentity, err, strerror(-err));
- return err;
- }
- uint32_t identity = cblk->getIdentity(mToken);
- if (mIdentity != identity) {
- LOGE("using an invalid surface id=%d, identity=%u should be %d",
- mToken, mIdentity, identity);
- return NO_INIT;
- }
return NO_ERROR;
}
@@ -300,16 +283,15 @@
mToken(surface->mToken), mIdentity(surface->mIdentity),
mFormat(surface->mFormat), mFlags(surface->mFlags),
mBufferMapper(GraphicBufferMapper::get()), mSharedBufferClient(NULL),
+ mInitCheck(NO_INIT),
mWidth(surface->mWidth), mHeight(surface->mHeight)
{
- mSharedBufferClient = new SharedBufferClient(
- mClient->mControl, mToken, 2, mIdentity);
-
init();
}
Surface::Surface(const Parcel& parcel)
- : mBufferMapper(GraphicBufferMapper::get()), mSharedBufferClient(NULL)
+ : mBufferMapper(GraphicBufferMapper::get()),
+ mSharedBufferClient(NULL), mInitCheck(NO_INIT)
{
sp<IBinder> clientBinder = parcel.readStrongBinder();
mSurface = interface_cast<ISurface>(parcel.readStrongBinder());
@@ -323,9 +305,6 @@
// FIXME: what does that mean if clientBinder is NULL here?
if (clientBinder != NULL) {
mClient = SurfaceComposerClient::clientForConnection(clientBinder);
-
- mSharedBufferClient = new SharedBufferClient(
- mClient->mControl, mToken, 2, mIdentity);
}
init();
@@ -339,7 +318,7 @@
android_native_window_t::queueBuffer = queueBuffer;
android_native_window_t::query = query;
android_native_window_t::perform = perform;
- mSwapRectangle.makeInvalid();
+
DisplayInfo dinfo;
SurfaceComposerClient::getDisplayInfo(0, &dinfo);
const_cast<float&>(android_native_window_t::xdpi) = dinfo.xdpi;
@@ -348,16 +327,27 @@
const_cast<int&>(android_native_window_t::minSwapInterval) = 1;
const_cast<int&>(android_native_window_t::maxSwapInterval) = 1;
const_cast<uint32_t&>(android_native_window_t::flags) = 0;
- // be default we request a hardware surface
- mUsage = GRALLOC_USAGE_HW_RENDER;
+
mConnected = 0;
+ mSwapRectangle.makeInvalid();
+ // two buffers by default
+ mBuffers.setCapacity(2);
+ mBuffers.insertAt(0, 2);
+
+ if (mClient != 0) {
+ mSharedBufferClient = new SharedBufferClient(
+ mClient->getSharedClient(), mToken, 2, mIdentity);
+ }
+
+ mInitCheck = initCheck();
}
Surface::~Surface()
{
// this is a client-side operation, the surface is destroyed, unmap
// its buffers in this process.
- for (int i=0 ; i<2 ; i++) {
+ size_t size = mBuffers.size();
+ for (size_t i=0 ; i<size ; i++) {
if (mBuffers[i] != 0 && mBuffers[i]->handle != 0) {
getBufferMapper().unregisterBuffer(mBuffers[i]->handle);
}
@@ -365,62 +355,80 @@
// clear all references and trigger an IPC now, to make sure things
// happen without delay, since these resources are quite heavy.
+ mBuffers.clear();
mClient.clear();
mSurface.clear();
delete mSharedBufferClient;
IPCThreadState::self()->flushCommands();
}
-sp<SurfaceComposerClient> Surface::getClient() const {
- return mClient;
-}
-
-sp<ISurface> Surface::getISurface() const {
- return mSurface;
-}
-
-bool Surface::isValid() {
- return mToken>=0 && mClient!=0;
-}
-
-status_t Surface::validate() const
+status_t Surface::initCheck() const
{
- sp<SurfaceComposerClient> client(getClient());
if (mToken<0 || mClient==0) {
- LOGE("invalid token (%d, identity=%u) or client (%p)",
- mToken, mIdentity, client.get());
return NO_INIT;
}
- SharedClient const* cblk = mClient->mControl;
+ SharedClient const* cblk = mClient->getSharedClient();
if (cblk == 0) {
LOGE("cblk is null (surface id=%d, identity=%u)", mToken, mIdentity);
return NO_INIT;
}
+ return NO_ERROR;
+}
+
+bool Surface::isValid() {
+ return mInitCheck == NO_ERROR;
+}
+
+status_t Surface::validate() const
+{
+ // check that we initialized ourself properly
+ if (mInitCheck != NO_ERROR) {
+ LOGE("invalid token (%d, identity=%u) or client (%p)",
+ mToken, mIdentity, mClient.get());
+ return mInitCheck;
+ }
+
+ // verify the identity of this surface
+ SharedClient const* cblk = mClient->getSharedClient();
+
+ uint32_t identity = cblk->getIdentity(mToken);
+
+ // this is a bit of a (temporary) special case, identity==0 means that
+ // no operation are allowed from the client (eg: dequeue/queue), this
+ // is used with PUSH_BUFFER surfaces for instance
+ if (identity == 0) {
+ LOGE("[Surface] invalid operation (identity=%u)", mIdentity);
+ return INVALID_OPERATION;
+ }
+
+ if (mIdentity != identity) {
+ LOGE("[Surface] using an invalid surface id=%d, "
+ "identity=%u should be %d",
+ mToken, mIdentity, identity);
+ return NO_INIT;
+ }
+
+ // check the surface didn't become invalid
status_t err = cblk->validate(mToken);
if (err != NO_ERROR) {
LOGE("surface (id=%d, identity=%u) is invalid, err=%d (%s)",
mToken, mIdentity, err, strerror(-err));
return err;
}
- uint32_t identity = cblk->getIdentity(mToken);
- if (mIdentity != identity) {
- LOGE("using an invalid surface id=%d, identity=%u should be %d",
- mToken, mIdentity, identity);
- return NO_INIT;
- }
+
return NO_ERROR;
}
-
-bool Surface::isSameSurface(
- const sp<Surface>& lhs, const sp<Surface>& rhs)
-{
+bool Surface::isSameSurface(const sp<Surface>& lhs, const sp<Surface>& rhs) {
if (lhs == 0 || rhs == 0)
return false;
-
return lhs->mSurface->asBinder() == rhs->mSurface->asBinder();
}
+sp<ISurface> Surface::getISurface() const {
+ return mSurface;
+}
+
// ----------------------------------------------------------------------------
int Surface::setSwapInterval(android_native_window_t* window, int interval) {
@@ -463,9 +471,24 @@
// ----------------------------------------------------------------------------
+bool Surface::needNewBuffer(int bufIdx,
+ uint32_t *pWidth, uint32_t *pHeight,
+ uint32_t *pFormat, uint32_t *pUsage) const
+{
+ Mutex::Autolock _l(mSurfaceLock);
+
+ // Always call needNewBuffer(), since it clears the needed buffers flags
+ bool needNewBuffer = mSharedBufferClient->needNewBuffer(bufIdx);
+ bool validBuffer = mBufferInfo.validateBuffer(mBuffers[bufIdx]);
+ bool newNeewBuffer = needNewBuffer || !validBuffer;
+ if (newNeewBuffer) {
+ mBufferInfo.get(pWidth, pHeight, pFormat, pUsage);
+ }
+ return newNeewBuffer;
+}
+
int Surface::dequeueBuffer(android_native_buffer_t** buffer)
{
- sp<SurfaceComposerClient> client(getClient());
status_t err = validate();
if (err != NO_ERROR)
return err;
@@ -476,27 +499,28 @@
return bufIdx;
}
- // below we make sure we AT LEAST have the usage flags we want
- const uint32_t usage(getUsage());
- const sp<GraphicBuffer>& backBuffer(mBuffers[bufIdx]);
+ // grow the buffer array if needed
+ const size_t size = mBuffers.size();
+ const size_t needed = bufIdx+1;
+ if (size < needed) {
+ mBuffers.insertAt(size, needed-size);
+ }
- // Always call needNewBuffer(), since it clears the needed buffers flags
- bool needNewBuffer = mSharedBufferClient->needNewBuffer(bufIdx);
- if (backBuffer == 0 ||
- ((uint32_t(backBuffer->usage) & usage) != usage) ||
- needNewBuffer)
- {
- err = getBufferLocked(bufIdx, usage);
- LOGE_IF(err, "getBufferLocked(%ld, %08x) failed (%s)",
- bufIdx, usage, strerror(-err));
+ uint32_t w, h, format, usage;
+ if (needNewBuffer(bufIdx, &w, &h, &format, &usage)) {
+ err = getBufferLocked(bufIdx, w, h, format, usage);
+ LOGE_IF(err, "getBufferLocked(%ld, %u, %u, %u, %08x) failed (%s)",
+ bufIdx, w, h, format, usage, strerror(-err));
if (err == NO_ERROR) {
// reset the width/height with the what we get from the buffer
+ const sp<GraphicBuffer>& backBuffer(mBuffers[bufIdx]);
mWidth = uint32_t(backBuffer->width);
mHeight = uint32_t(backBuffer->height);
}
}
// if we still don't have a buffer here, we probably ran out of memory
+ const sp<GraphicBuffer>& backBuffer(mBuffers[bufIdx]);
if (!err && backBuffer==0) {
err = NO_MEMORY;
}
@@ -513,7 +537,6 @@
int Surface::lockBuffer(android_native_buffer_t* buffer)
{
- sp<SurfaceComposerClient> client(getClient());
status_t err = validate();
if (err != NO_ERROR)
return err;
@@ -526,7 +549,6 @@
int Surface::queueBuffer(android_native_buffer_t* buffer)
{
- sp<SurfaceComposerClient> client(getClient());
status_t err = validate();
if (err != NO_ERROR)
return err;
@@ -543,6 +565,7 @@
if (err == NO_ERROR) {
// FIXME: can we avoid this IPC if we know there is one pending?
+ const sp<SurfaceComposerClient>& client(mClient);
client->signalServer();
}
return err;
@@ -584,6 +607,12 @@
case NATIVE_WINDOW_SET_CROP:
res = dispatch_crop( args );
break;
+ case NATIVE_WINDOW_SET_BUFFER_COUNT:
+ res = dispatch_set_buffer_count( args );
+ break;
+ case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY:
+ res = dispatch_set_buffers_geometry( args );
+ break;
default:
res = NAME_NOT_FOUND;
break;
@@ -607,12 +636,21 @@
android_native_rect_t const* rect = va_arg(args, android_native_rect_t*);
return crop( reinterpret_cast<Rect const*>(rect) );
}
-
+int Surface::dispatch_set_buffer_count(va_list args) {
+ size_t bufferCount = va_arg(args, size_t);
+ return setBufferCount(bufferCount);
+}
+int Surface::dispatch_set_buffers_geometry(va_list args) {
+ int w = va_arg(args, int);
+ int h = va_arg(args, int);
+ int f = va_arg(args, int);
+ return setBuffersGeometry(w, h, f);
+}
void Surface::setUsage(uint32_t reqUsage)
{
Mutex::Autolock _l(mSurfaceLock);
- mUsage = reqUsage;
+ mBufferInfo.set(reqUsage);
}
int Surface::connect(int api)
@@ -653,18 +691,6 @@
return err;
}
-uint32_t Surface::getUsage() const
-{
- Mutex::Autolock _l(mSurfaceLock);
- return mUsage;
-}
-
-int Surface::getConnectedApi() const
-{
- Mutex::Autolock _l(mSurfaceLock);
- return mConnected;
-}
-
int Surface::crop(Rect const* rect)
{
Mutex::Autolock _l(mSurfaceLock);
@@ -678,22 +704,41 @@
sp<ISurface> s(mSurface);
if (s == 0) return NO_INIT;
- // FIXME: this needs to be synchronized dequeue/queue
+ class SetBufferCountIPC : public SharedBufferClient::SetBufferCountCallback {
+ sp<ISurface> surface;
+ virtual status_t operator()(int bufferCount) const {
+ return surface->setBufferCount(bufferCount);
+ }
+ public:
+ SetBufferCountIPC(const sp<ISurface>& surface) : surface(surface) { }
+ } ipc(s);
- status_t err = s->setBufferCount(bufferCount);
+ status_t err = mSharedBufferClient->setBufferCount(bufferCount, ipc);
LOGE_IF(err, "ISurface::setBufferCount(%d) returned %s",
bufferCount, strerror(-err));
- if (err == NO_ERROR) {
- err = mSharedBufferClient->getStatus();
- LOGE_IF(err, "Surface (identity=%d) state = %d", mIdentity, err);
- if (!err) {
- // update our local copy of the buffer count
- mSharedBufferClient->setBufferCount(bufferCount);
- }
- }
return err;
}
+int Surface::setBuffersGeometry(int w, int h, int format)
+{
+ if (w<0 || h<0 || format<0)
+ return BAD_VALUE;
+
+ if ((w && !h) || (!w && h))
+ return BAD_VALUE;
+
+ Mutex::Autolock _l(mSurfaceLock);
+ mBufferInfo.set(w, h, format);
+ return NO_ERROR;
+}
+
+// ----------------------------------------------------------------------------
+
+int Surface::getConnectedApi() const
+{
+ Mutex::Autolock _l(mSurfaceLock);
+ return mConnected;
+}
// ----------------------------------------------------------------------------
@@ -824,7 +869,8 @@
return buffer->getIndex();
}
-status_t Surface::getBufferLocked(int index, int usage)
+status_t Surface::getBufferLocked(int index,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage)
{
sp<ISurface> s(mSurface);
if (s == 0) return NO_INIT;
@@ -832,20 +878,21 @@
status_t err = NO_MEMORY;
// free the current buffer
- sp<GraphicBuffer>& currentBuffer(mBuffers[index]);
+ sp<GraphicBuffer>& currentBuffer(mBuffers.editItemAt(index));
if (currentBuffer != 0) {
getBufferMapper().unregisterBuffer(currentBuffer->handle);
currentBuffer.clear();
}
- sp<GraphicBuffer> buffer = s->requestBuffer(index, usage);
+ sp<GraphicBuffer> buffer = s->requestBuffer(index, w, h, format, usage);
LOGE_IF(buffer==0,
"ISurface::getBuffer(%d, %08x) returned NULL",
index, usage);
if (buffer != 0) { // this should never happen by construction
LOGE_IF(buffer->handle == NULL,
- "Surface (identity=%d) requestBuffer(%d, %08x) returned"
- "a buffer with a null handle", mIdentity, index, usage);
+ "Surface (identity=%d) requestBuffer(%d, %u, %u, %u, %08x) "
+ "returned a buffer with a null handle",
+ mIdentity, index, w, h, format, usage);
err = mSharedBufferClient->getStatus();
LOGE_IF(err, "Surface (identity=%d) state = %d", mIdentity, err);
if (!err && buffer->handle != NULL) {
@@ -857,11 +904,50 @@
currentBuffer->setIndex(index);
}
} else {
- err = err<0 ? err : NO_MEMORY;
+ err = err<0 ? err : status_t(NO_MEMORY);
}
}
return err;
}
+// ----------------------------------------------------------------------------
+Surface::BufferInfo::BufferInfo()
+ : mWidth(0), mHeight(0), mFormat(0),
+ mUsage(GRALLOC_USAGE_HW_RENDER), mDirty(0)
+{
+}
+
+void Surface::BufferInfo::set(uint32_t w, uint32_t h, uint32_t format) {
+ if ((mWidth != w) || (mHeight != h) || (mFormat != format)) {
+ mWidth = w;
+ mHeight = h;
+ mFormat = format;
+ mDirty |= GEOMETRY;
+ }
+}
+
+void Surface::BufferInfo::set(uint32_t usage) {
+ mUsage = usage;
+}
+
+void Surface::BufferInfo::get(uint32_t *pWidth, uint32_t *pHeight,
+ uint32_t *pFormat, uint32_t *pUsage) const {
+ *pWidth = mWidth;
+ *pHeight = mHeight;
+ *pFormat = mFormat;
+ *pUsage = mUsage;
+}
+
+bool Surface::BufferInfo::validateBuffer(const sp<GraphicBuffer>& buffer) const {
+ // make sure we AT LEAST have the usage flags we want
+ if (mDirty || buffer==0 ||
+ ((buffer->usage & mUsage) != mUsage)) {
+ mDirty = 0;
+ return false;
+ }
+ return true;
+}
+
+// ----------------------------------------------------------------------------
}; // namespace android
diff --git a/libs/surfaceflinger_client/SurfaceComposerClient.cpp b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
index 85167da..96ed566 100644
--- a/libs/surfaceflinger_client/SurfaceComposerClient.cpp
+++ b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
@@ -123,11 +123,11 @@
{
sp<ISurfaceComposer> sm(getComposerService());
if (sm == 0) {
- _init(0, 0);
+ init(0, 0);
return;
}
- _init(sm, sm->createConnection());
+ init(sm, sm->createConnection());
if (mClient != 0) {
Mutex::Autolock _l(gLock);
@@ -139,9 +139,14 @@
SurfaceComposerClient::SurfaceComposerClient(
const sp<ISurfaceComposer>& sm, const sp<IBinder>& conn)
{
- _init(sm, interface_cast<ISurfaceFlingerClient>(conn));
+ init(sm, interface_cast<ISurfaceFlingerClient>(conn));
}
+SurfaceComposerClient::~SurfaceComposerClient()
+{
+ VERBOSE("Destroying client %p, conn %p", this, mClient.get());
+ dispose();
+}
status_t SurfaceComposerClient::linkToComposerDeath(
const sp<IBinder::DeathRecipient>& recipient,
@@ -151,7 +156,7 @@
return sm->asBinder()->linkToDeath(recipient, cookie, flags);
}
-void SurfaceComposerClient::_init(
+void SurfaceComposerClient::init(
const sp<ISurfaceComposer>& sm, const sp<ISurfaceFlingerClient>& conn)
{
VERBOSE("Creating client %p, conn %p", this, conn.get());
@@ -172,10 +177,9 @@
mControl = static_cast<SharedClient *>(mControlMemory->getBase());
}
-SurfaceComposerClient::~SurfaceComposerClient()
+SharedClient* SurfaceComposerClient::getSharedClient() const
{
- VERBOSE("Destroying client %p, conn %p", this, mClient.get());
- dispose();
+ return mControl;
}
status_t SurfaceComposerClient::initCheck() const
@@ -250,7 +254,7 @@
status_t SurfaceComposerClient::getDisplayInfo(
DisplayID dpy, DisplayInfo* info)
{
- if (uint32_t(dpy)>=NUM_DISPLAY_MAX)
+ if (uint32_t(dpy)>=SharedBufferStack::NUM_DISPLAY_MAX)
return BAD_VALUE;
volatile surface_flinger_cblk_t const * cblk = get_cblk();
@@ -268,7 +272,7 @@
ssize_t SurfaceComposerClient::getDisplayWidth(DisplayID dpy)
{
- if (uint32_t(dpy)>=NUM_DISPLAY_MAX)
+ if (uint32_t(dpy)>=SharedBufferStack::NUM_DISPLAY_MAX)
return BAD_VALUE;
volatile surface_flinger_cblk_t const * cblk = get_cblk();
volatile display_cblk_t const * dcblk = cblk->displays + dpy;
@@ -277,7 +281,7 @@
ssize_t SurfaceComposerClient::getDisplayHeight(DisplayID dpy)
{
- if (uint32_t(dpy)>=NUM_DISPLAY_MAX)
+ if (uint32_t(dpy)>=SharedBufferStack::NUM_DISPLAY_MAX)
return BAD_VALUE;
volatile surface_flinger_cblk_t const * cblk = get_cblk();
volatile display_cblk_t const * dcblk = cblk->displays + dpy;
@@ -286,7 +290,7 @@
ssize_t SurfaceComposerClient::getDisplayOrientation(DisplayID dpy)
{
- if (uint32_t(dpy)>=NUM_DISPLAY_MAX)
+ if (uint32_t(dpy)>=SharedBufferStack::NUM_DISPLAY_MAX)
return BAD_VALUE;
volatile surface_flinger_cblk_t const * cblk = get_cblk();
volatile display_cblk_t const * dcblk = cblk->displays + dpy;
@@ -345,7 +349,7 @@
sp<ISurface> surface = mClient->createSurface(&data, pid, name,
display, w, h, format, flags);
if (surface != 0) {
- if (uint32_t(data.token) < NUM_LAYERS_MAX) {
+ if (uint32_t(data.token) < SharedBufferStack::NUM_LAYERS_MAX) {
result = new SurfaceControl(this, surface, data, w, h, format, flags);
}
}
@@ -488,7 +492,7 @@
return NO_ERROR;
}
-layer_state_t* SurfaceComposerClient::_get_state_l(SurfaceID index)
+layer_state_t* SurfaceComposerClient::get_state_l(SurfaceID index)
{
// API usage error, do nothing.
if (mTransactionOpen<=0) {
@@ -508,49 +512,49 @@
return mStates.editArray() + i;
}
-layer_state_t* SurfaceComposerClient::_lockLayerState(SurfaceID id)
+layer_state_t* SurfaceComposerClient::lockLayerState(SurfaceID id)
{
layer_state_t* s;
mLock.lock();
- s = _get_state_l(id);
+ s = get_state_l(id);
if (!s) mLock.unlock();
return s;
}
-void SurfaceComposerClient::_unlockLayerState()
+void SurfaceComposerClient::unlockLayerState()
{
mLock.unlock();
}
status_t SurfaceComposerClient::setPosition(SurfaceID id, int32_t x, int32_t y)
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::ePositionChanged;
s->x = x;
s->y = y;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
status_t SurfaceComposerClient::setSize(SurfaceID id, uint32_t w, uint32_t h)
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::eSizeChanged;
s->w = w;
s->h = h;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
status_t SurfaceComposerClient::setLayer(SurfaceID id, int32_t z)
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::eLayerChanged;
s->z = z;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
@@ -579,34 +583,34 @@
status_t SurfaceComposerClient::setFlags(SurfaceID id,
uint32_t flags, uint32_t mask)
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::eVisibilityChanged;
s->flags &= ~mask;
s->flags |= (flags & mask);
s->mask |= mask;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
status_t SurfaceComposerClient::setTransparentRegionHint(
SurfaceID id, const Region& transparentRegion)
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::eTransparentRegionChanged;
s->transparentRegion = transparentRegion;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
status_t SurfaceComposerClient::setAlpha(SurfaceID id, float alpha)
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::eAlphaChanged;
s->alpha = alpha;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
@@ -615,7 +619,7 @@
float dsdx, float dtdx,
float dsdy, float dtdy )
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::eMatrixChanged;
layer_state_t::matrix22_t matrix;
@@ -624,17 +628,17 @@
matrix.dsdy = dsdy;
matrix.dtdy = dtdy;
s->matrix = matrix;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
status_t SurfaceComposerClient::setFreezeTint(SurfaceID id, uint32_t tint)
{
- layer_state_t* s = _lockLayerState(id);
+ layer_state_t* s = lockLayerState(id);
if (!s) return BAD_INDEX;
s->what |= ISurfaceComposer::eFreezeTintChanged;
s->tint = tint;
- _unlockLayerState();
+ unlockLayerState();
return NO_ERROR;
}
diff --git a/libs/surfaceflinger_client/tests/SharedBufferStack/SharedBufferStackTest.cpp b/libs/surfaceflinger_client/tests/SharedBufferStack/SharedBufferStackTest.cpp
index f490a65..f409f48 100644
--- a/libs/surfaceflinger_client/tests/SharedBufferStack/SharedBufferStackTest.cpp
+++ b/libs/surfaceflinger_client/tests/SharedBufferStack/SharedBufferStackTest.cpp
@@ -54,8 +54,16 @@
printf("resize test\n");
- s.resize(6);
- c.setBufferCount(6);
+ class SetBufferCountIPC : public SharedBufferClient::SetBufferCountCallback {
+ SharedBufferServer& s;
+ virtual status_t operator()(int bufferCount) const {
+ return s.resize(bufferCount);
+ }
+ public:
+ SetBufferCountIPC(SharedBufferServer& s) : s(s) { }
+ } resize(s);
+
+ c.setBufferCount(6, resize);
int list3[6] = {3, 2, 1, 4, 5, 0};
test0(s, c, 6, list3);
diff --git a/vcard/Android.mk b/vcard/Android.mk
new file mode 100644
index 0000000..2bc17aa
--- /dev/null
+++ b/vcard/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := com.android.vcard
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+# Use google-common instead of android-common for using hidden code in telephony library.
+# Use ext for using Quoted-Printable codec.
+LOCAL_JAVA_LIBRARIES := google-common ext
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build the test package.
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/vcard/java/com/android/vcard/JapaneseUtils.java b/vcard/java/com/android/vcard/JapaneseUtils.java
new file mode 100644
index 0000000..5b44944
--- /dev/null
+++ b/vcard/java/com/android/vcard/JapaneseUtils.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vcard;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TextUtils especially for Japanese.
+ */
+/* package */ class JapaneseUtils {
+ static private final Map<Character, String> sHalfWidthMap =
+ new HashMap<Character, String>();
+
+ static {
+ sHalfWidthMap.put('\u3001', "\uFF64");
+ sHalfWidthMap.put('\u3002', "\uFF61");
+ sHalfWidthMap.put('\u300C', "\uFF62");
+ sHalfWidthMap.put('\u300D', "\uFF63");
+ sHalfWidthMap.put('\u301C', "~");
+ sHalfWidthMap.put('\u3041', "\uFF67");
+ sHalfWidthMap.put('\u3042', "\uFF71");
+ sHalfWidthMap.put('\u3043', "\uFF68");
+ sHalfWidthMap.put('\u3044', "\uFF72");
+ sHalfWidthMap.put('\u3045', "\uFF69");
+ sHalfWidthMap.put('\u3046', "\uFF73");
+ sHalfWidthMap.put('\u3047', "\uFF6A");
+ sHalfWidthMap.put('\u3048', "\uFF74");
+ sHalfWidthMap.put('\u3049', "\uFF6B");
+ sHalfWidthMap.put('\u304A', "\uFF75");
+ sHalfWidthMap.put('\u304B', "\uFF76");
+ sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u304D', "\uFF77");
+ sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u304F', "\uFF78");
+ sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u3051', "\uFF79");
+ sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u3053', "\uFF7A");
+ sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u3055', "\uFF7B");
+ sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u3057', "\uFF7C");
+ sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u3059', "\uFF7D");
+ sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u305B', "\uFF7E");
+ sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u305D', "\uFF7F");
+ sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u305F', "\uFF80");
+ sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u3061', "\uFF81");
+ sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u3063', "\uFF6F");
+ sHalfWidthMap.put('\u3064', "\uFF82");
+ sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u3066', "\uFF83");
+ sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u3068', "\uFF84");
+ sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u306A', "\uFF85");
+ sHalfWidthMap.put('\u306B', "\uFF86");
+ sHalfWidthMap.put('\u306C', "\uFF87");
+ sHalfWidthMap.put('\u306D', "\uFF88");
+ sHalfWidthMap.put('\u306E', "\uFF89");
+ sHalfWidthMap.put('\u306F', "\uFF8A");
+ sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u3072', "\uFF8B");
+ sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u3075', "\uFF8C");
+ sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u3078', "\uFF8D");
+ sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u307B', "\uFF8E");
+ sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u307E', "\uFF8F");
+ sHalfWidthMap.put('\u307F', "\uFF90");
+ sHalfWidthMap.put('\u3080', "\uFF91");
+ sHalfWidthMap.put('\u3081', "\uFF92");
+ sHalfWidthMap.put('\u3082', "\uFF93");
+ sHalfWidthMap.put('\u3083', "\uFF6C");
+ sHalfWidthMap.put('\u3084', "\uFF94");
+ sHalfWidthMap.put('\u3085', "\uFF6D");
+ sHalfWidthMap.put('\u3086', "\uFF95");
+ sHalfWidthMap.put('\u3087', "\uFF6E");
+ sHalfWidthMap.put('\u3088', "\uFF96");
+ sHalfWidthMap.put('\u3089', "\uFF97");
+ sHalfWidthMap.put('\u308A', "\uFF98");
+ sHalfWidthMap.put('\u308B', "\uFF99");
+ sHalfWidthMap.put('\u308C', "\uFF9A");
+ sHalfWidthMap.put('\u308D', "\uFF9B");
+ sHalfWidthMap.put('\u308E', "\uFF9C");
+ sHalfWidthMap.put('\u308F', "\uFF9C");
+ sHalfWidthMap.put('\u3090', "\uFF72");
+ sHalfWidthMap.put('\u3091', "\uFF74");
+ sHalfWidthMap.put('\u3092', "\uFF66");
+ sHalfWidthMap.put('\u3093', "\uFF9D");
+ sHalfWidthMap.put('\u309B', "\uFF9E");
+ sHalfWidthMap.put('\u309C', "\uFF9F");
+ sHalfWidthMap.put('\u30A1', "\uFF67");
+ sHalfWidthMap.put('\u30A2', "\uFF71");
+ sHalfWidthMap.put('\u30A3', "\uFF68");
+ sHalfWidthMap.put('\u30A4', "\uFF72");
+ sHalfWidthMap.put('\u30A5', "\uFF69");
+ sHalfWidthMap.put('\u30A6', "\uFF73");
+ sHalfWidthMap.put('\u30A7', "\uFF6A");
+ sHalfWidthMap.put('\u30A8', "\uFF74");
+ sHalfWidthMap.put('\u30A9', "\uFF6B");
+ sHalfWidthMap.put('\u30AA', "\uFF75");
+ sHalfWidthMap.put('\u30AB', "\uFF76");
+ sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u30AD', "\uFF77");
+ sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u30AF', "\uFF78");
+ sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u30B1', "\uFF79");
+ sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u30B3', "\uFF7A");
+ sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u30B5', "\uFF7B");
+ sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u30B7', "\uFF7C");
+ sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u30B9', "\uFF7D");
+ sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u30BB', "\uFF7E");
+ sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u30BD', "\uFF7F");
+ sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u30BF', "\uFF80");
+ sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u30C1', "\uFF81");
+ sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u30C3', "\uFF6F");
+ sHalfWidthMap.put('\u30C4', "\uFF82");
+ sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u30C6', "\uFF83");
+ sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u30C8', "\uFF84");
+ sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u30CA', "\uFF85");
+ sHalfWidthMap.put('\u30CB', "\uFF86");
+ sHalfWidthMap.put('\u30CC', "\uFF87");
+ sHalfWidthMap.put('\u30CD', "\uFF88");
+ sHalfWidthMap.put('\u30CE', "\uFF89");
+ sHalfWidthMap.put('\u30CF', "\uFF8A");
+ sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u30D2', "\uFF8B");
+ sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u30D5', "\uFF8C");
+ sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u30D8', "\uFF8D");
+ sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u30DB', "\uFF8E");
+ sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u30DE', "\uFF8F");
+ sHalfWidthMap.put('\u30DF', "\uFF90");
+ sHalfWidthMap.put('\u30E0', "\uFF91");
+ sHalfWidthMap.put('\u30E1', "\uFF92");
+ sHalfWidthMap.put('\u30E2', "\uFF93");
+ sHalfWidthMap.put('\u30E3', "\uFF6C");
+ sHalfWidthMap.put('\u30E4', "\uFF94");
+ sHalfWidthMap.put('\u30E5', "\uFF6D");
+ sHalfWidthMap.put('\u30E6', "\uFF95");
+ sHalfWidthMap.put('\u30E7', "\uFF6E");
+ sHalfWidthMap.put('\u30E8', "\uFF96");
+ sHalfWidthMap.put('\u30E9', "\uFF97");
+ sHalfWidthMap.put('\u30EA', "\uFF98");
+ sHalfWidthMap.put('\u30EB', "\uFF99");
+ sHalfWidthMap.put('\u30EC', "\uFF9A");
+ sHalfWidthMap.put('\u30ED', "\uFF9B");
+ sHalfWidthMap.put('\u30EE', "\uFF9C");
+ sHalfWidthMap.put('\u30EF', "\uFF9C");
+ sHalfWidthMap.put('\u30F0', "\uFF72");
+ sHalfWidthMap.put('\u30F1', "\uFF74");
+ sHalfWidthMap.put('\u30F2', "\uFF66");
+ sHalfWidthMap.put('\u30F3', "\uFF9D");
+ sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
+ sHalfWidthMap.put('\u30F5', "\uFF76");
+ sHalfWidthMap.put('\u30F6', "\uFF79");
+ sHalfWidthMap.put('\u30FB', "\uFF65");
+ sHalfWidthMap.put('\u30FC', "\uFF70");
+ sHalfWidthMap.put('\uFF01', "!");
+ sHalfWidthMap.put('\uFF02', "\"");
+ sHalfWidthMap.put('\uFF03', "#");
+ sHalfWidthMap.put('\uFF04', "$");
+ sHalfWidthMap.put('\uFF05', "%");
+ sHalfWidthMap.put('\uFF06', "&");
+ sHalfWidthMap.put('\uFF07', "'");
+ sHalfWidthMap.put('\uFF08', "(");
+ sHalfWidthMap.put('\uFF09', ")");
+ sHalfWidthMap.put('\uFF0A', "*");
+ sHalfWidthMap.put('\uFF0B', "+");
+ sHalfWidthMap.put('\uFF0C', ",");
+ sHalfWidthMap.put('\uFF0D', "-");
+ sHalfWidthMap.put('\uFF0E', ".");
+ sHalfWidthMap.put('\uFF0F', "/");
+ sHalfWidthMap.put('\uFF10', "0");
+ sHalfWidthMap.put('\uFF11', "1");
+ sHalfWidthMap.put('\uFF12', "2");
+ sHalfWidthMap.put('\uFF13', "3");
+ sHalfWidthMap.put('\uFF14', "4");
+ sHalfWidthMap.put('\uFF15', "5");
+ sHalfWidthMap.put('\uFF16', "6");
+ sHalfWidthMap.put('\uFF17', "7");
+ sHalfWidthMap.put('\uFF18', "8");
+ sHalfWidthMap.put('\uFF19', "9");
+ sHalfWidthMap.put('\uFF1A', ":");
+ sHalfWidthMap.put('\uFF1B', ";");
+ sHalfWidthMap.put('\uFF1C', "<");
+ sHalfWidthMap.put('\uFF1D', "=");
+ sHalfWidthMap.put('\uFF1E', ">");
+ sHalfWidthMap.put('\uFF1F', "?");
+ sHalfWidthMap.put('\uFF20', "@");
+ sHalfWidthMap.put('\uFF21', "A");
+ sHalfWidthMap.put('\uFF22', "B");
+ sHalfWidthMap.put('\uFF23', "C");
+ sHalfWidthMap.put('\uFF24', "D");
+ sHalfWidthMap.put('\uFF25', "E");
+ sHalfWidthMap.put('\uFF26', "F");
+ sHalfWidthMap.put('\uFF27', "G");
+ sHalfWidthMap.put('\uFF28', "H");
+ sHalfWidthMap.put('\uFF29', "I");
+ sHalfWidthMap.put('\uFF2A', "J");
+ sHalfWidthMap.put('\uFF2B', "K");
+ sHalfWidthMap.put('\uFF2C', "L");
+ sHalfWidthMap.put('\uFF2D', "M");
+ sHalfWidthMap.put('\uFF2E', "N");
+ sHalfWidthMap.put('\uFF2F', "O");
+ sHalfWidthMap.put('\uFF30', "P");
+ sHalfWidthMap.put('\uFF31', "Q");
+ sHalfWidthMap.put('\uFF32', "R");
+ sHalfWidthMap.put('\uFF33', "S");
+ sHalfWidthMap.put('\uFF34', "T");
+ sHalfWidthMap.put('\uFF35', "U");
+ sHalfWidthMap.put('\uFF36', "V");
+ sHalfWidthMap.put('\uFF37', "W");
+ sHalfWidthMap.put('\uFF38', "X");
+ sHalfWidthMap.put('\uFF39', "Y");
+ sHalfWidthMap.put('\uFF3A', "Z");
+ sHalfWidthMap.put('\uFF3B', "[");
+ sHalfWidthMap.put('\uFF3C', "\\");
+ sHalfWidthMap.put('\uFF3D', "]");
+ sHalfWidthMap.put('\uFF3E', "^");
+ sHalfWidthMap.put('\uFF3F', "_");
+ sHalfWidthMap.put('\uFF41', "a");
+ sHalfWidthMap.put('\uFF42', "b");
+ sHalfWidthMap.put('\uFF43', "c");
+ sHalfWidthMap.put('\uFF44', "d");
+ sHalfWidthMap.put('\uFF45', "e");
+ sHalfWidthMap.put('\uFF46', "f");
+ sHalfWidthMap.put('\uFF47', "g");
+ sHalfWidthMap.put('\uFF48', "h");
+ sHalfWidthMap.put('\uFF49', "i");
+ sHalfWidthMap.put('\uFF4A', "j");
+ sHalfWidthMap.put('\uFF4B', "k");
+ sHalfWidthMap.put('\uFF4C', "l");
+ sHalfWidthMap.put('\uFF4D', "m");
+ sHalfWidthMap.put('\uFF4E', "n");
+ sHalfWidthMap.put('\uFF4F', "o");
+ sHalfWidthMap.put('\uFF50', "p");
+ sHalfWidthMap.put('\uFF51', "q");
+ sHalfWidthMap.put('\uFF52', "r");
+ sHalfWidthMap.put('\uFF53', "s");
+ sHalfWidthMap.put('\uFF54', "t");
+ sHalfWidthMap.put('\uFF55', "u");
+ sHalfWidthMap.put('\uFF56', "v");
+ sHalfWidthMap.put('\uFF57', "w");
+ sHalfWidthMap.put('\uFF58', "x");
+ sHalfWidthMap.put('\uFF59', "y");
+ sHalfWidthMap.put('\uFF5A', "z");
+ sHalfWidthMap.put('\uFF5B', "{");
+ sHalfWidthMap.put('\uFF5C', "|");
+ sHalfWidthMap.put('\uFF5D', "}");
+ sHalfWidthMap.put('\uFF5E', "~");
+ sHalfWidthMap.put('\uFF61', "\uFF61");
+ sHalfWidthMap.put('\uFF62', "\uFF62");
+ sHalfWidthMap.put('\uFF63', "\uFF63");
+ sHalfWidthMap.put('\uFF64', "\uFF64");
+ sHalfWidthMap.put('\uFF65', "\uFF65");
+ sHalfWidthMap.put('\uFF66', "\uFF66");
+ sHalfWidthMap.put('\uFF67', "\uFF67");
+ sHalfWidthMap.put('\uFF68', "\uFF68");
+ sHalfWidthMap.put('\uFF69', "\uFF69");
+ sHalfWidthMap.put('\uFF6A', "\uFF6A");
+ sHalfWidthMap.put('\uFF6B', "\uFF6B");
+ sHalfWidthMap.put('\uFF6C', "\uFF6C");
+ sHalfWidthMap.put('\uFF6D', "\uFF6D");
+ sHalfWidthMap.put('\uFF6E', "\uFF6E");
+ sHalfWidthMap.put('\uFF6F', "\uFF6F");
+ sHalfWidthMap.put('\uFF70', "\uFF70");
+ sHalfWidthMap.put('\uFF71', "\uFF71");
+ sHalfWidthMap.put('\uFF72', "\uFF72");
+ sHalfWidthMap.put('\uFF73', "\uFF73");
+ sHalfWidthMap.put('\uFF74', "\uFF74");
+ sHalfWidthMap.put('\uFF75', "\uFF75");
+ sHalfWidthMap.put('\uFF76', "\uFF76");
+ sHalfWidthMap.put('\uFF77', "\uFF77");
+ sHalfWidthMap.put('\uFF78', "\uFF78");
+ sHalfWidthMap.put('\uFF79', "\uFF79");
+ sHalfWidthMap.put('\uFF7A', "\uFF7A");
+ sHalfWidthMap.put('\uFF7B', "\uFF7B");
+ sHalfWidthMap.put('\uFF7C', "\uFF7C");
+ sHalfWidthMap.put('\uFF7D', "\uFF7D");
+ sHalfWidthMap.put('\uFF7E', "\uFF7E");
+ sHalfWidthMap.put('\uFF7F', "\uFF7F");
+ sHalfWidthMap.put('\uFF80', "\uFF80");
+ sHalfWidthMap.put('\uFF81', "\uFF81");
+ sHalfWidthMap.put('\uFF82', "\uFF82");
+ sHalfWidthMap.put('\uFF83', "\uFF83");
+ sHalfWidthMap.put('\uFF84', "\uFF84");
+ sHalfWidthMap.put('\uFF85', "\uFF85");
+ sHalfWidthMap.put('\uFF86', "\uFF86");
+ sHalfWidthMap.put('\uFF87', "\uFF87");
+ sHalfWidthMap.put('\uFF88', "\uFF88");
+ sHalfWidthMap.put('\uFF89', "\uFF89");
+ sHalfWidthMap.put('\uFF8A', "\uFF8A");
+ sHalfWidthMap.put('\uFF8B', "\uFF8B");
+ sHalfWidthMap.put('\uFF8C', "\uFF8C");
+ sHalfWidthMap.put('\uFF8D', "\uFF8D");
+ sHalfWidthMap.put('\uFF8E', "\uFF8E");
+ sHalfWidthMap.put('\uFF8F', "\uFF8F");
+ sHalfWidthMap.put('\uFF90', "\uFF90");
+ sHalfWidthMap.put('\uFF91', "\uFF91");
+ sHalfWidthMap.put('\uFF92', "\uFF92");
+ sHalfWidthMap.put('\uFF93', "\uFF93");
+ sHalfWidthMap.put('\uFF94', "\uFF94");
+ sHalfWidthMap.put('\uFF95', "\uFF95");
+ sHalfWidthMap.put('\uFF96', "\uFF96");
+ sHalfWidthMap.put('\uFF97', "\uFF97");
+ sHalfWidthMap.put('\uFF98', "\uFF98");
+ sHalfWidthMap.put('\uFF99', "\uFF99");
+ sHalfWidthMap.put('\uFF9A', "\uFF9A");
+ sHalfWidthMap.put('\uFF9B', "\uFF9B");
+ sHalfWidthMap.put('\uFF9C', "\uFF9C");
+ sHalfWidthMap.put('\uFF9D', "\uFF9D");
+ sHalfWidthMap.put('\uFF9E', "\uFF9E");
+ sHalfWidthMap.put('\uFF9F', "\uFF9F");
+ sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
+ }
+
+ /**
+ * Returns half-width version of that character if possible. Returns null if not possible
+ * @param ch input character
+ * @return CharSequence object if the mapping for ch exists. Return null otherwise.
+ */
+ public static String tryGetHalfWidthText(final char ch) {
+ if (sHalfWidthMap.containsKey(ch)) {
+ return sHalfWidthMap.get(ch);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardBuilder.java b/vcard/java/com/android/vcard/VCardBuilder.java
new file mode 100644
index 0000000..6ef9ada
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardBuilder.java
@@ -0,0 +1,1996 @@
+/*
+ * 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.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>
+ * The class which lets users create their own vCard String. Typical usage is as follows:
+ * </p>
+ * <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType);
+ * builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ * .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ * .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ * .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ * .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ * .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ * .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ * .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ * .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ * .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ * .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ * .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ * return builder.toString();</pre>
+ */
+public class VCardBuilder {
+ private static final String LOG_TAG = "VCardBuilder";
+
+ // If you add the other element, please check all the columns are able to be
+ // converted to String.
+ //
+ // e.g. BLOB is not what we can handle here now.
+ private static final Set<String> sAllowedAndroidPropertySet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ Nickname.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE)));
+
+ public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
+ public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
+ public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
+
+ private static final String VCARD_DATA_VCARD = "VCARD";
+ private static final String VCARD_DATA_PUBLIC = "PUBLIC";
+
+ private static final String VCARD_PARAM_SEPARATOR = ";";
+ private static final String VCARD_END_OF_LINE = "\r\n";
+ private static final String VCARD_DATA_SEPARATOR = ":";
+ private static final String VCARD_ITEM_SEPARATOR = ";";
+ private static final String VCARD_WS = " ";
+ private static final String VCARD_PARAM_EQUAL = "=";
+
+ private static final String VCARD_PARAM_ENCODING_QP =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V30 =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_B;
+
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+
+ private final int mVCardType;
+
+ private final boolean mIsV30;
+ private final boolean mIsJapaneseMobilePhone;
+ private final boolean mOnlyOneNoteFieldIsAvailable;
+ private final boolean mIsDoCoMo;
+ private final boolean mShouldUseQuotedPrintable;
+ private final boolean mUsesAndroidProperty;
+ private final boolean mUsesDefactProperty;
+ private final boolean mAppendTypeParamName;
+ private final boolean mRefrainsQPToNameProperties;
+ private final boolean mNeedsToConvertPhoneticString;
+
+ private final boolean mShouldAppendCharsetParam;
+
+ private final String mCharset;
+ private final String mVCardCharsetParameter;
+
+ private StringBuilder mBuilder;
+ private boolean mEndAppended;
+
+ public VCardBuilder(final int vcardType) {
+ // Default charset should be used
+ this(vcardType, null);
+ }
+
+ /**
+ * @param vcardType
+ * @param charset If null, we use default charset for export.
+ */
+ public VCardBuilder(final int vcardType, String charset) {
+ mVCardType = vcardType;
+
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
+ mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
+ mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
+ mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
+ mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
+ mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
+ mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
+
+ // vCard 2.1 requires charset.
+ // vCard 3.0 does not allow it but we found some devices use it to determine
+ // the exact charset.
+ // We currently append it only when charset other than UTF_8 is used.
+ mShouldAppendCharsetParam = !(mIsV30 && "UTF-8".equalsIgnoreCase(charset));
+
+ if (VCardConfig.isDoCoMo(vcardType)) {
+ if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
+ } else {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
+ }
+ mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
+ } else {
+ if (TextUtils.isEmpty(charset)) {
+ Log.i(LOG_TAG,
+ "Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET
+ + "\" for export.");
+ mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET;
+ mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ mVCardCharsetParameter = "CHARSET=" + charset;
+ }
+ }
+ clear();
+ }
+
+ public void clear() {
+ mBuilder = new StringBuilder();
+ mEndAppended = false;
+ appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (mIsV30) {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
+ } else {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
+ }
+ }
+
+ private boolean containsNonEmptyName(final ContentValues contentValues) {
+ final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String phoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String phoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String phoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+ return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
+ TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
+ TextUtils.isEmpty(suffix) && TextUtils.isEmpty(phoneticFamilyName) &&
+ TextUtils.isEmpty(phoneticMiddleName) && TextUtils.isEmpty(phoneticGivenName) &&
+ TextUtils.isEmpty(displayName));
+ }
+
+ private ContentValues getPrimaryContentValue(final List<ContentValues> contentValuesList) {
+ ContentValues primaryContentValues = null;
+ ContentValues subprimaryContentValues = null;
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null){
+ continue;
+ }
+ Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
+ if (isSuperPrimary != null && isSuperPrimary > 0) {
+ // We choose "super primary" ContentValues.
+ primaryContentValues = contentValues;
+ break;
+ } else if (primaryContentValues == null) {
+ // We choose the first "primary" ContentValues
+ // if "super primary" ContentValues does not exist.
+ final Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+ if (isPrimary != null && isPrimary > 0 &&
+ containsNonEmptyName(contentValues)) {
+ primaryContentValues = contentValues;
+ // Do not break, since there may be ContentValues with "super primary"
+ // afterword.
+ } else if (subprimaryContentValues == null &&
+ containsNonEmptyName(contentValues)) {
+ subprimaryContentValues = contentValues;
+ }
+ }
+ }
+
+ if (primaryContentValues == null) {
+ if (subprimaryContentValues != null) {
+ // We choose the first ContentValues if any "primary" ContentValues does not exist.
+ primaryContentValues = subprimaryContentValues;
+ } else {
+ Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+ primaryContentValues = new ContentValues();
+ }
+ }
+
+ return primaryContentValues;
+ }
+
+ /**
+ * For safety, we'll emit just one value around StructuredName, as external importers
+ * may get confused with multiple "N", "FN", etc. properties, though it is valid in
+ * vCard spec.
+ */
+ public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ } else if (mIsV30) {
+ // vCard 3.0 requires "N" and "FN" properties.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ }
+ return this;
+ }
+
+ final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
+ final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+
+ if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+ final boolean reallyAppendCharsetParameterToName =
+ shouldAppendCharsetParam(familyName, givenName, middleName, prefix, suffix);
+ final boolean reallyUseQuotedPrintableToName =
+ (!mRefrainsQPToNameProperties &&
+ !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
+
+ final String formattedName;
+ if (!TextUtils.isEmpty(displayName)) {
+ formattedName = displayName;
+ } else {
+ formattedName = VCardUtils.constructNameFromElements(
+ VCardConfig.getNameOrderType(mVCardType),
+ familyName, middleName, givenName, prefix, suffix);
+ }
+ final boolean reallyAppendCharsetParameterToFN =
+ shouldAppendCharsetParam(formattedName);
+ final boolean reallyUseQuotedPrintableToFN =
+ !mRefrainsQPToNameProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
+
+ final String encodedFamily;
+ final String encodedGiven;
+ final String encodedMiddle;
+ final String encodedPrefix;
+ final String encodedSuffix;
+ if (reallyUseQuotedPrintableToName) {
+ encodedFamily = encodeQuotedPrintable(familyName);
+ encodedGiven = encodeQuotedPrintable(givenName);
+ encodedMiddle = encodeQuotedPrintable(middleName);
+ encodedPrefix = encodeQuotedPrintable(prefix);
+ encodedSuffix = encodeQuotedPrintable(suffix);
+ } else {
+ encodedFamily = escapeCharacters(familyName);
+ encodedGiven = escapeCharacters(givenName);
+ encodedMiddle = escapeCharacters(middleName);
+ encodedPrefix = escapeCharacters(prefix);
+ encodedSuffix = escapeCharacters(suffix);
+ }
+
+ final String encodedFormattedname =
+ (reallyUseQuotedPrintableToFN ?
+ encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (mIsDoCoMo) {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ // DoCoMo phones require that all the elements in the "family name" field.
+ mBuilder.append(formattedName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ } else {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFamily);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedGiven);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedMiddle);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedPrefix);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedSuffix);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+
+ // FN property
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+ if (reallyAppendCharsetParameterToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFormattedname);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (!TextUtils.isEmpty(displayName)) {
+ final boolean reallyUseQuotedPrintableToDisplayName =
+ (!mRefrainsQPToNameProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
+ final String encodedDisplayName =
+ reallyUseQuotedPrintableToDisplayName ?
+ encodeQuotedPrintable(displayName) :
+ escapeCharacters(displayName);
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToDisplayName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+
+ // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
+ // when it would be useful or necessary for external importers,
+ // assuming the external importer allows this vioration of the spec.
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (mIsV30) {
+ // vCard 3.0 specification requires these fields.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ } else if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ }
+
+ appendPhoneticNameFields(contentValues);
+ return this;
+ }
+
+ private void appendPhoneticNameFields(final ContentValues contentValues) {
+ final String phoneticFamilyName;
+ final String phoneticMiddleName;
+ final String phoneticGivenName;
+ {
+ final String tmpPhoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String tmpPhoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String tmpPhoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ if (mNeedsToConvertPhoneticString) {
+ phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
+ phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
+ phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
+ } else {
+ phoneticFamilyName = tmpPhoneticFamilyName;
+ phoneticMiddleName = tmpPhoneticMiddleName;
+ phoneticGivenName = tmpPhoneticGivenName;
+ }
+ }
+
+ if (TextUtils.isEmpty(phoneticFamilyName)
+ && TextUtils.isEmpty(phoneticMiddleName)
+ && TextUtils.isEmpty(phoneticGivenName)) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ return;
+ }
+
+ // Try to emit the field(s) related to phonetic name.
+ if (mIsV30) {
+ final String sortString = VCardUtils
+ .constructNameFromElements(mVCardType,
+ phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+ mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
+ if (shouldAppendCharsetParam(sortString)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(escapeCharacters(sortString));
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (mIsJapaneseMobilePhone) {
+ // Note: There is no appropriate property for expressing
+ // phonetic name (Yomigana in Japanese) in vCard 2.1, while there is in
+ // vCard 3.0 (SORT-STRING).
+ // We use DoCoMo's way when the device is Japanese one since it is already
+ // supported by a lot of Japanese mobile phones.
+ // This is "X-" property, so any parser hopefully would not get
+ // confused with this.
+ //
+ // Also, DoCoMo's specification requires vCard composer to use just the first
+ // column.
+ // i.e.
+ // good: SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+ // bad : SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+
+ boolean reallyUseQuotedPrintable =
+ (!mRefrainsQPToNameProperties
+ && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticFamilyName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticMiddleName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticGivenName)));
+
+ final String encodedPhoneticFamilyName;
+ final String encodedPhoneticMiddleName;
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+
+ if (shouldAppendCharsetParam(encodedPhoneticFamilyName,
+ encodedPhoneticMiddleName, encodedPhoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ {
+ boolean first = true;
+ if (!TextUtils.isEmpty(encodedPhoneticFamilyName)) {
+ mBuilder.append(encodedPhoneticFamilyName);
+ first = false;
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticMiddleName)) {
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(' ');
+ }
+ mBuilder.append(encodedPhoneticMiddleName);
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticGivenName)) {
+ if (!first) {
+ mBuilder.append(' ');
+ }
+ mBuilder.append(encodedPhoneticGivenName);
+ }
+ }
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // family;given
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // given;middle
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // middle;prefix
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // prefix;suffix
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ if (mUsesDefactProperty) {
+ if (!TextUtils.isEmpty(phoneticGivenName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME);
+ if (shouldAppendCharsetParam(phoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticGivenName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
+ if (!TextUtils.isEmpty(phoneticMiddleName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+ final String encodedPhoneticMiddleName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ } else {
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ if (shouldAppendCharsetParam(phoneticMiddleName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticMiddleName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
+ if (!TextUtils.isEmpty(phoneticFamilyName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+ final String encodedPhoneticFamilyName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME);
+ if (shouldAppendCharsetParam(phoneticFamilyName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticFamilyName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } // if (!TextUtils.isEmpty(phoneticFamilyName))
+ }
+ }
+
+ public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
+ final boolean useAndroidProperty;
+ if (mIsV30) {
+ useAndroidProperty = false;
+ } else if (mUsesAndroidProperty) {
+ useAndroidProperty = true;
+ } else {
+ // There's no way to add this field.
+ return this;
+ }
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final String nickname = contentValues.getAsString(Nickname.NAME);
+ if (TextUtils.isEmpty(nickname)) {
+ continue;
+ }
+ if (useAndroidProperty) {
+ appendAndroidSpecificProperty(Nickname.CONTENT_ITEM_TYPE, contentValues);
+ } else {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_NICKNAME, nickname);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhones(final List<ContentValues> contentValuesList) {
+ boolean phoneLineExists = false;
+ if (contentValuesList != null) {
+ Set<String> phoneSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
+ final String label = contentValues.getAsString(Phone.LABEL);
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ String phoneNumber = contentValues.getAsString(Phone.NUMBER);
+ if (phoneNumber != null) {
+ phoneNumber = phoneNumber.trim();
+ }
+ if (TextUtils.isEmpty(phoneNumber)) {
+ continue;
+ }
+
+ // PAGER number needs unformatted "phone number".
+ final int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+ if (type == Phone.TYPE_PAGER ||
+ VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
+ phoneLineExists = true;
+ if (!phoneSet.contains(phoneNumber)) {
+ phoneSet.add(phoneNumber);
+ appendTelLine(type, label, phoneNumber, isPrimary);
+ }
+ } else {
+ final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber);
+ if (phoneNumberList.isEmpty()) {
+ continue;
+ }
+ phoneLineExists = true;
+ for (String actualPhoneNumber : phoneNumberList) {
+ if (!phoneSet.contains(actualPhoneNumber)) {
+ final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
+ final String formattedPhoneNumber =
+ PhoneNumberUtils.formatNumber(actualPhoneNumber, format);
+ phoneSet.add(actualPhoneNumber);
+ appendTelLine(type, label, formattedPhoneNumber, isPrimary);
+ }
+ } // for (String actualPhoneNumber : phoneNumberList) {
+ }
+ }
+ }
+
+ if (!phoneLineExists && mIsDoCoMo) {
+ appendTelLine(Phone.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ /**
+ * <p>
+ * Splits a given string expressing phone numbers into several strings, and remove
+ * unnecessary characters inside them. The size of a returned list becomes 1 when
+ * no split is needed.
+ * </p>
+ * <p>
+ * The given number "may" have several phone numbers when the contact entry is corrupted
+ * because of its original source.
+ * e.g. "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami)"
+ * </p>
+ * <p>
+ * This kind of "phone numbers" will not be created with Android vCard implementation,
+ * but we may encounter them if the source of the input data has already corrupted
+ * implementation.
+ * </p>
+ * <p>
+ * To handle this case, this method first splits its input into multiple parts
+ * (e.g. "111-222-3333 (Miami)", "444-555-6666 (Broward", and 305653-6796 (Miami)") and
+ * removes unnecessary strings like "(Miami)".
+ * </p>
+ * <p>
+ * Do not call this method when trimming is inappropriate for its receivers.
+ * </p>
+ */
+ private List<String> splitAndTrimPhoneNumbers(final String phoneNumber) {
+ final List<String> phoneList = new ArrayList<String>();
+
+ StringBuilder builder = new StringBuilder();
+ final int length = phoneNumber.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = phoneNumber.charAt(i);
+ if (Character.isDigit(ch) || ch == '+') {
+ builder.append(ch);
+ } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
+ phoneList.add(builder.toString());
+ builder = new StringBuilder();
+ }
+ }
+ if (builder.length() > 0) {
+ phoneList.add(builder.toString());
+ }
+
+ return phoneList;
+ }
+
+ public VCardBuilder appendEmails(final List<ContentValues> contentValuesList) {
+ boolean emailAddressExists = false;
+ if (contentValuesList != null) {
+ final Set<String> addressSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ String emailAddress = contentValues.getAsString(Email.DATA);
+ if (emailAddress != null) {
+ emailAddress = emailAddress.trim();
+ }
+ if (TextUtils.isEmpty(emailAddress)) {
+ continue;
+ }
+ Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : DEFAULT_EMAIL_TYPE);
+ final String label = contentValues.getAsString(Email.LABEL);
+ Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ emailAddressExists = true;
+ if (!addressSet.contains(emailAddress)) {
+ addressSet.add(emailAddress);
+ appendEmailLine(type, label, emailAddress, isPrimary);
+ }
+ }
+ }
+
+ if (!emailAddressExists && mIsDoCoMo) {
+ appendEmailLine(Email.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ public VCardBuilder appendPostals(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_HOME);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ } else {
+ if (mIsDoCoMo) {
+ appendPostalsForDoCoMo(contentValuesList);
+ } else {
+ appendPostalsForGeneric(contentValuesList);
+ }
+ }
+
+ return this;
+ }
+
+ private static final Map<Integer, Integer> sPostalTypePriorityMap;
+
+ static {
+ sPostalTypePriorityMap = new HashMap<Integer, Integer>();
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_HOME, 0);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_WORK, 1);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_OTHER, 2);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_CUSTOM, 3);
+ }
+
+ /**
+ * Tries to append just one line. If there's no appropriate address
+ * information, append an empty line.
+ */
+ private void appendPostalsForDoCoMo(final List<ContentValues> contentValuesList) {
+ int currentPriority = Integer.MAX_VALUE;
+ int currentType = Integer.MAX_VALUE;
+ ContentValues currentContentValues = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final Integer priorityAsInteger = sPostalTypePriorityMap.get(typeAsInteger);
+ final int priority =
+ (priorityAsInteger != null ? priorityAsInteger : Integer.MAX_VALUE);
+ if (priority < currentPriority) {
+ currentPriority = priority;
+ currentType = typeAsInteger;
+ currentContentValues = contentValues;
+ if (priority == 0) {
+ break;
+ }
+ }
+ }
+
+ if (currentContentValues == null) {
+ Log.w(LOG_TAG, "Should not come here. Must have at least one postal data.");
+ return;
+ }
+
+ final String label = currentContentValues.getAsString(StructuredPostal.LABEL);
+ appendPostalLine(currentType, label, currentContentValues, false, true);
+ }
+
+ private void appendPostalsForGeneric(final List<ContentValues> contentValuesList) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final int type = (typeAsInteger != null ?
+ typeAsInteger : DEFAULT_POSTAL_TYPE);
+ final String label = contentValues.getAsString(StructuredPostal.LABEL);
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ appendPostalLine(type, label, contentValues, isPrimary, false);
+ }
+ }
+
+ private static class PostalStruct {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressData;
+ public PostalStruct(final boolean reallyUseQuotedPrintable,
+ final boolean appendCharset, final String addressData) {
+ this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
+ this.appendCharset = appendCharset;
+ this.addressData = addressData;
+ }
+ }
+
+ /**
+ * @return null when there's no information available to construct the data.
+ */
+ private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ final String rawPoBox = contentValues.getAsString(StructuredPostal.POBOX);
+ final String rawNeighborhood = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
+ final String rawStreet = contentValues.getAsString(StructuredPostal.STREET);
+ final String rawLocality = contentValues.getAsString(StructuredPostal.CITY);
+ final String rawRegion = contentValues.getAsString(StructuredPostal.REGION);
+ final String rawPostalCode = contentValues.getAsString(StructuredPostal.POSTCODE);
+ final String rawCountry = contentValues.getAsString(StructuredPostal.COUNTRY);
+ final String[] rawAddressArray = new String[]{
+ rawPoBox, rawNeighborhood, rawStreet, rawLocality,
+ rawRegion, rawPostalCode, rawCountry};
+ if (!VCardUtils.areAllEmpty(rawAddressArray)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawAddressArray));
+ final boolean appendCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawAddressArray);
+ final String encodedPoBox;
+ final String encodedStreet;
+ final String encodedLocality;
+ final String encodedRegion;
+ final String encodedPostalCode;
+ final String encodedCountry;
+ final String encodedNeighborhood;
+
+ final String rawLocality2;
+ // This looks inefficient since we encode rawLocality and rawNeighborhood twice,
+ // but this is intentional.
+ //
+ // QP encoding may add line feeds when needed and the result of
+ // - encodeQuotedPrintable(rawLocality + " " + rawNeighborhood)
+ // may be different from
+ // - encodedLocality + " " + encodedNeighborhood.
+ //
+ // We use safer way.
+ if (TextUtils.isEmpty(rawLocality)) {
+ if (TextUtils.isEmpty(rawNeighborhood)) {
+ rawLocality2 = "";
+ } else {
+ rawLocality2 = rawNeighborhood;
+ }
+ } else {
+ if (TextUtils.isEmpty(rawNeighborhood)) {
+ rawLocality2 = rawLocality;
+ } else {
+ rawLocality2 = rawLocality + " " + rawNeighborhood;
+ }
+ }
+ if (reallyUseQuotedPrintable) {
+ encodedPoBox = encodeQuotedPrintable(rawPoBox);
+ encodedStreet = encodeQuotedPrintable(rawStreet);
+ encodedLocality = encodeQuotedPrintable(rawLocality2);
+ encodedRegion = encodeQuotedPrintable(rawRegion);
+ encodedPostalCode = encodeQuotedPrintable(rawPostalCode);
+ encodedCountry = encodeQuotedPrintable(rawCountry);
+ } else {
+ encodedPoBox = escapeCharacters(rawPoBox);
+ encodedStreet = escapeCharacters(rawStreet);
+ encodedLocality = escapeCharacters(rawLocality2);
+ encodedRegion = escapeCharacters(rawRegion);
+ encodedPostalCode = escapeCharacters(rawPostalCode);
+ encodedCountry = escapeCharacters(rawCountry);
+ encodedNeighborhood = escapeCharacters(rawNeighborhood);
+ }
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(encodedPoBox);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(encodedStreet);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(encodedLocality);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(encodedRegion);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(encodedPostalCode);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
+ addressBuilder.append(encodedCountry);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
+ } else { // VCardUtils.areAllEmpty(rawAddressArray) == true
+ // Try to use FORMATTED_ADDRESS instead.
+ final String rawFormattedAddress =
+ contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+ if (TextUtils.isEmpty(rawFormattedAddress)) {
+ return null;
+ }
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawFormattedAddress));
+ final boolean appendCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawFormattedAddress);
+ final String encodedFormattedAddress;
+ if (reallyUseQuotedPrintable) {
+ encodedFormattedAddress = encodeQuotedPrintable(rawFormattedAddress);
+ } else {
+ encodedFormattedAddress = escapeCharacters(rawFormattedAddress);
+ }
+
+ // We use the second value ("Extended Address") just because Japanese mobile phones
+ // do so. If the other importer expects the value be in the other field, some flag may
+ // be needed.
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(encodedFormattedAddress);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
+ }
+ }
+
+ public VCardBuilder appendIms(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
+ if (protocolAsObject == null) {
+ continue;
+ }
+ final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
+ if (propertyName == null) {
+ continue;
+ }
+ String data = contentValues.getAsString(Im.DATA);
+ if (data != null) {
+ data = data.trim();
+ }
+ if (TextUtils.isEmpty(data)) {
+ continue;
+ }
+ final String typeAsString;
+ {
+ final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
+ switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
+ case Im.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Im.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Im.TYPE_CUSTOM: {
+ final String label = contentValues.getAsString(Im.LABEL);
+ typeAsString = (label != null ? "X-" + label : null);
+ break;
+ }
+ case Im.TYPE_OTHER: // Ignore
+ default: {
+ typeAsString = null;
+ break;
+ }
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ appendLineWithCharsetAndQPDetection(propertyName, parameterList, data);
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendWebsites(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String website = contentValues.getAsString(Website.URL);
+ if (website != null) {
+ website = website.trim();
+ }
+
+ // Note: vCard 3.0 does not allow any parameter addition toward "URL"
+ // property, while there's no document in vCard 2.1.
+ if (!TextUtils.isEmpty(website)) {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_URL, website);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendOrganizations(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String company = contentValues.getAsString(Organization.COMPANY);
+ if (company != null) {
+ company = company.trim();
+ }
+ String department = contentValues.getAsString(Organization.DEPARTMENT);
+ if (department != null) {
+ department = department.trim();
+ }
+ String title = contentValues.getAsString(Organization.TITLE);
+ if (title != null) {
+ title = title.trim();
+ }
+
+ StringBuilder orgBuilder = new StringBuilder();
+ if (!TextUtils.isEmpty(company)) {
+ orgBuilder.append(company);
+ }
+ if (!TextUtils.isEmpty(department)) {
+ if (orgBuilder.length() > 0) {
+ orgBuilder.append(';');
+ }
+ orgBuilder.append(department);
+ }
+ final String orgline = orgBuilder.toString();
+ appendLine(VCardConstants.PROPERTY_ORG, orgline,
+ !VCardUtils.containsOnlyPrintableAscii(orgline),
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
+ if (!TextUtils.isEmpty(title)) {
+ appendLine(VCardConstants.PROPERTY_TITLE, title,
+ !VCardUtils.containsOnlyPrintableAscii(title),
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhotos(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
+ if (data == null) {
+ continue;
+ }
+ final String photoType = VCardUtils.guessImageType(data);
+ if (photoType == null) {
+ Log.d(LOG_TAG, "Unknown photo type. Ignored.");
+ continue;
+ }
+ // TODO: check this works fine.
+ final String photoString = new String(Base64.encode(data, Base64.NO_WRAP));
+ if (!TextUtils.isEmpty(photoString)) {
+ appendPhotoLine(photoString, photoType);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendNotes(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ if (mOnlyOneNoteFieldIsAvailable) {
+ final StringBuilder noteBuilder = new StringBuilder();
+ boolean first = true;
+ for (final ContentValues contentValues : contentValuesList) {
+ String note = contentValues.getAsString(Note.NOTE);
+ if (note == null) {
+ note = "";
+ }
+ if (note.length() > 0) {
+ if (first) {
+ first = false;
+ } else {
+ noteBuilder.append('\n');
+ }
+ noteBuilder.append(note);
+ }
+ }
+ final String noteStr = noteBuilder.toString();
+ // This means we scan noteStr completely twice, which is redundant.
+ // But for now, we assume this is not so time-consuming..
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ } else {
+ for (ContentValues contentValues : contentValuesList) {
+ final String noteStr = contentValues.getAsString(Note.NOTE);
+ if (!TextUtils.isEmpty(noteStr)) {
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+ // There's possibility where a given object may have more than one birthday, which
+ // is inappropriate. We just build one birthday.
+ if (contentValuesList != null) {
+ String primaryBirthday = null;
+ String secondaryBirthday = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer eventTypeAsInteger = contentValues.getAsInteger(Event.TYPE);
+ final int eventType;
+ if (eventTypeAsInteger != null) {
+ eventType = eventTypeAsInteger;
+ } else {
+ eventType = Event.TYPE_OTHER;
+ }
+ if (eventType == Event.TYPE_BIRTHDAY) {
+ final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+ if (birthdayCandidate == null) {
+ continue;
+ }
+ final Integer isSuperPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
+ final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
+ (isSuperPrimaryAsInteger > 0) : false);
+ if (isSuperPrimary) {
+ // "super primary" birthday should the prefered one.
+ primaryBirthday = birthdayCandidate;
+ break;
+ }
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ // We don't break here since "super primary" birthday may exist later.
+ primaryBirthday = birthdayCandidate;
+ } else if (secondaryBirthday == null) {
+ // First entry is set to the "secondary" candidate.
+ secondaryBirthday = birthdayCandidate;
+ }
+ } else if (mUsesAndroidProperty) {
+ // Event types other than Birthday is not supported by vCard.
+ appendAndroidSpecificProperty(Event.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ if (primaryBirthday != null) {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ primaryBirthday.trim());
+ } else if (secondaryBirthday != null){
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ secondaryBirthday.trim());
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendRelation(final List<ContentValues> contentValuesList) {
+ if (mUsesAndroidProperty && contentValuesList != null) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ appendAndroidSpecificProperty(Relation.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * @param emitEveryTime If true, builder builds the line even when there's no entry.
+ */
+ public void appendPostalLine(final int type, final String label,
+ final ContentValues contentValues,
+ final boolean isPrimary, final boolean emitEveryTime) {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressValue;
+ {
+ PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+ if (postalStruct == null) {
+ if (emitEveryTime) {
+ reallyUseQuotedPrintable = false;
+ appendCharset = false;
+ addressValue = "";
+ } else {
+ return;
+ }
+ } else {
+ reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+ appendCharset = postalStruct.appendCharset;
+ addressValue = postalStruct.addressData;
+ }
+ }
+
+ List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ switch (type) {
+ case StructuredPostal.TYPE_HOME: {
+ parameterList.add(VCardConstants.PARAM_TYPE_HOME);
+ break;
+ }
+ case StructuredPostal.TYPE_WORK: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ break;
+ }
+ case StructuredPostal.TYPE_CUSTOM: {
+ if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // We're not sure whether the label is valid in the spec
+ // ("IANA-token" in the vCard 3.0 is unclear...)
+ // Just for safety, we add "X-" at the beggining of each label.
+ // Also checks the label obeys with vCard 3.0 spec.
+ parameterList.add("X-" + label);
+ }
+ break;
+ }
+ case StructuredPostal.TYPE_OTHER: {
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
+ break;
+ }
+ }
+
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ if (!parameterList.isEmpty()) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (appendCharset) {
+ // Strictly, vCard 3.0 does not allow exporters to emit charset information,
+ // but we will add it since the information should be useful for importers,
+ //
+ // Assume no parser does not emit error with this parameter in vCard 3.0.
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(addressValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendEmailLine(final int type, final String label,
+ final String rawValue, final boolean isPrimary) {
+ final String typeAsString;
+ switch (type) {
+ case Email.TYPE_CUSTOM: {
+ if (VCardUtils.isMobilePhoneLabel(label)) {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ } else if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ typeAsString = "X-" + label;
+ } else {
+ typeAsString = null;
+ }
+ break;
+ }
+ case Email.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Email.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Email.TYPE_OTHER: {
+ typeAsString = null;
+ break;
+ }
+ case Email.TYPE_MOBILE: {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown Email type: " + type);
+ typeAsString = null;
+ break;
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_EMAIL, parameterList,
+ rawValue);
+ }
+
+ public void appendTelLine(final Integer typeAsInteger, final String label,
+ final String encodedValue, boolean isPrimary) {
+ mBuilder.append(VCardConstants.PROPERTY_TEL);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+
+ final int type;
+ if (typeAsInteger == null) {
+ type = Phone.TYPE_OTHER;
+ } else {
+ type = typeAsInteger;
+ }
+
+ ArrayList<String> parameterList = new ArrayList<String>();
+ switch (type) {
+ case Phone.TYPE_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME));
+ break;
+ }
+ case Phone.TYPE_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK));
+ break;
+ }
+ case Phone.TYPE_FAX_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_FAX_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_MOBILE: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ break;
+ }
+ case Phone.TYPE_PAGER: {
+ if (mIsDoCoMo) {
+ // Not sure about the reason, but previous implementation had
+ // used "VOICE" instead of "PAGER"
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_OTHER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ break;
+ }
+ case Phone.TYPE_CAR: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CAR);
+ break;
+ }
+ case Phone.TYPE_COMPANY_MAIN: {
+ // There's no relevant field in vCard (at least 2.1).
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_ISDN: {
+ parameterList.add(VCardConstants.PARAM_TYPE_ISDN);
+ break;
+ }
+ case Phone.TYPE_MAIN: {
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_OTHER_FAX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_FAX);
+ break;
+ }
+ case Phone.TYPE_TELEX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_TLX);
+ break;
+ }
+ case Phone.TYPE_WORK_MOBILE: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_CELL));
+ break;
+ }
+ case Phone.TYPE_WORK_PAGER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ // See above.
+ if (mIsDoCoMo) {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_MMS: {
+ parameterList.add(VCardConstants.PARAM_TYPE_MSG);
+ break;
+ }
+ case Phone.TYPE_CUSTOM: {
+ if (TextUtils.isEmpty(label)) {
+ // Just ignore the custom type.
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else if (VCardUtils.isMobilePhoneLabel(label)) {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ } else {
+ final String upperLabel = label.toUpperCase();
+ if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
+ parameterList.add(upperLabel);
+ } else if (VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
+ // "TYPE=" string.
+ parameterList.add("X-" + label);
+ }
+ }
+ break;
+ }
+ case Phone.TYPE_RADIO:
+ case Phone.TYPE_TTY_TDD:
+ default: {
+ break;
+ }
+ }
+
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ if (parameterList.isEmpty()) {
+ appendUncommonPhoneType(mBuilder, type);
+ } else {
+ appendTypeParameters(parameterList);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ /**
+ * Appends phone type string which may not be available in some devices.
+ */
+ private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
+ if (mIsDoCoMo) {
+ // The previous implementation for DoCoMo had been conservative
+ // about miscellaneous types.
+ builder.append(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ String phoneType = VCardUtils.getPhoneTypeString(type);
+ if (phoneType != null) {
+ appendTypeParameter(phoneType);
+ } else {
+ Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
+ }
+ }
+ }
+
+ /**
+ * @param encodedValue Must be encoded by BASE64
+ * @param photoType
+ */
+ public void appendPhotoLine(final String encodedValue, final String photoType) {
+ StringBuilder tmpBuilder = new StringBuilder();
+ tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ if (mIsV30) {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
+ } else {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
+ }
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameter(tmpBuilder, photoType);
+ tmpBuilder.append(VCARD_DATA_SEPARATOR);
+ tmpBuilder.append(encodedValue);
+
+ final String tmpStr = tmpBuilder.toString();
+ tmpBuilder = new StringBuilder();
+ int lineCount = 0;
+ final int length = tmpStr.length();
+ final int maxNumForFirstLine = VCardConstants.MAX_CHARACTER_NUMS_BASE64_V30
+ - VCARD_END_OF_LINE.length();
+ final int maxNumInGeneral = maxNumForFirstLine - VCARD_WS.length();
+ int maxNum = maxNumForFirstLine;
+ for (int i = 0; i < length; i++) {
+ tmpBuilder.append(tmpStr.charAt(i));
+ lineCount++;
+ if (lineCount > maxNum) {
+ tmpBuilder.append(VCARD_END_OF_LINE);
+ tmpBuilder.append(VCARD_WS);
+ maxNum = maxNumInGeneral;
+ lineCount = 0;
+ }
+ }
+ mBuilder.append(tmpBuilder.toString());
+ mBuilder.append(VCARD_END_OF_LINE);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendAndroidSpecificProperty(
+ final String mimeType, ContentValues contentValues) {
+ if (!sAllowedAndroidPropertySet.contains(mimeType)) {
+ return;
+ }
+ final List<String> rawValueList = new ArrayList<String>();
+ for (int i = 1; i <= VCardConstants.MAX_DATA_COLUMN; i++) {
+ String value = contentValues.getAsString("data" + i);
+ if (value == null) {
+ value = "";
+ }
+ rawValueList.add(value);
+ }
+
+ boolean needCharset =
+ (mShouldAppendCharsetParam &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ mBuilder.append(VCardConstants.PROPERTY_X_ANDROID_CUSTOM);
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(mimeType); // Should not be encoded.
+ for (String rawValue : rawValueList) {
+ final String encodedValue;
+ if (reallyUseQuotedPrintable) {
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard 3.0
+ // (which says "When generating a content line, lines longer than
+ // 75 characters SHOULD be folded"), though several
+ // (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedValue);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final String rawValue) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValue);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(
+ final String propertyName, final List<String> rawValueList) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final String rawValue) {
+ final boolean needCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawValue);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue));
+ appendLine(propertyName, parameterList,
+ rawValue, needCharset, reallyUseQuotedPrintable);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final List<String> rawValueList) {
+ boolean needCharset =
+ (mShouldAppendCharsetParam &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ appendLine(propertyName, parameterList, rawValueList,
+ needCharset, reallyUseQuotedPrintable);
+ }
+
+ /**
+ * Appends one line with a given property name and value.
+ */
+ public void appendLine(final String propertyName, final String rawValue) {
+ appendLine(propertyName, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList) {
+ appendLine(propertyName, rawValueList, false, false);
+ }
+
+ public void appendLine(final String propertyName,
+ final String rawValue, final boolean needCharset,
+ boolean reallyUseQuotedPrintable) {
+ appendLine(propertyName, null, rawValue, needCharset, reallyUseQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue) {
+ appendLine(propertyName, parameterList, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue, final boolean needCharset,
+ boolean reallyUseQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+
+ final String encodedValue;
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard spec, though
+ // several (even well-known) applications do not care that violation.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList,
+ final boolean needCharset, boolean needQuotedPrintable) {
+ appendLine(propertyName, null, rawValueList, needCharset, needQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final List<String> rawValueList, final boolean needCharset,
+ final boolean needQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (needQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ boolean first = true;
+ for (String rawValue : rawValueList) {
+ final String encodedValue;
+ if (needQuotedPrintable) {
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard 3.0
+ // (which says "When generating a content line, lines longer than
+ // 75 characters SHOULD be folded"), though several
+ // (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ }
+ mBuilder.append(encodedValue);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ private void appendTypeParameters(final List<String> types) {
+ // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
+ // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
+ boolean first = true;
+ for (final String typeValue : types) {
+ // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
+ // we don't emit that kind of vCard 3.0 specific type since there should be
+ // high probabilyty in which external importers cannot understand them.
+ //
+ // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they
+ // are quoted.)
+ if (!VCardUtils.isV21Word(typeValue)) {
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ }
+ appendTypeParameter(typeValue);
+ }
+ }
+
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ private void appendTypeParameter(final String type) {
+ appendTypeParameter(mBuilder, type);
+ }
+
+ private void appendTypeParameter(final StringBuilder builder, final String type) {
+ // Refrain from using appendType() so that "TYPE=" is not be appended when the
+ // device is DoCoMo's (just for safety).
+ //
+ // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
+ if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
+ builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
+ }
+ builder.append(type);
+ }
+
+ /**
+ * Returns true when the property line should contain charset parameter
+ * information. This method may return true even when vCard version is 3.0.
+ *
+ * Strictly, adding charset information is invalid in VCard 3.0.
+ * However we'll add the info only when charset we use is not UTF-8
+ * in vCard 3.0 format, since parser side may be able to use the charset
+ * via this field, though we may encounter another problem by adding it.
+ *
+ * e.g. Japanese mobile phones use Shift_Jis while RFC 2426
+ * recommends UTF-8. By adding this field, parsers may be able
+ * to know this text is NOT UTF-8 but Shift_Jis.
+ */
+ private boolean shouldAppendCharsetParam(String...propertyValueList) {
+ if (!mShouldAppendCharsetParam) {
+ return false;
+ }
+ for (String propertyValue : propertyValueList) {
+ if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String encodeQuotedPrintable(final String str) {
+ if (TextUtils.isEmpty(str)) {
+ return "";
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ int index = 0;
+ int lineCount = 0;
+ byte[] strArray = null;
+
+ try {
+ strArray = str.getBytes(mCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. "
+ + "Try default charset");
+ strArray = str.getBytes();
+ }
+ while (index < strArray.length) {
+ builder.append(String.format("=%02X", strArray[index]));
+ index += 1;
+ lineCount += 3;
+
+ if (lineCount >= 67) {
+ // Specification requires CRLF must be inserted before the
+ // length of the line
+ // becomes more than 76.
+ // Assuming that the next character is a multi-byte character,
+ // it will become
+ // 6 bytes.
+ // 76 - 6 - 3 = 67
+ builder.append("=\r\n");
+ lineCount = 0;
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Append '\' to the characters which should be escaped. The character set is different
+ * not only between vCard 2.1 and vCard 3.0 but also among each device.
+ *
+ * Note that Quoted-Printable string must not be input here.
+ */
+ @SuppressWarnings("fallthrough")
+ private String escapeCharacters(final String unescaped) {
+ if (TextUtils.isEmpty(unescaped)) {
+ return "";
+ }
+
+ final StringBuilder tmpBuilder = new StringBuilder();
+ final int length = unescaped.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = unescaped.charAt(i);
+ switch (ch) {
+ case ';': {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(';');
+ break;
+ }
+ case '\r': {
+ if (i + 1 < length) {
+ char nextChar = unescaped.charAt(i);
+ if (nextChar == '\n') {
+ break;
+ } else {
+ // fall through
+ }
+ } else {
+ // fall through
+ }
+ }
+ case '\n': {
+ // In vCard 2.1, there's no specification about this, while
+ // vCard 3.0 explicitly requires this should be encoded to "\n".
+ tmpBuilder.append("\\n");
+ break;
+ }
+ case '\\': {
+ if (mIsV30) {
+ tmpBuilder.append("\\\\");
+ break;
+ } else {
+ // fall through
+ }
+ }
+ case '<':
+ case '>': {
+ if (mIsDoCoMo) {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(ch);
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ case ',': {
+ if (mIsV30) {
+ tmpBuilder.append("\\,");
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ default: {
+ tmpBuilder.append(ch);
+ break;
+ }
+ }
+ }
+ return tmpBuilder.toString();
+ }
+
+ @Override
+ public String toString() {
+ if (!mEndAppended) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+ appendLine(VCardConstants.PROPERTY_X_REDUCTION, "");
+ appendLine(VCardConstants.PROPERTY_X_NO, "");
+ appendLine(VCardConstants.PROPERTY_X_DCM_HMN_MODE, "");
+ }
+ appendLine(VCardConstants.PROPERTY_END, VCARD_DATA_VCARD);
+ mEndAppended = true;
+ }
+ return mBuilder.toString();
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardComposer.java b/vcard/java/com/android/vcard/VCardComposer.java
new file mode 100644
index 0000000..7038955
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardComposer.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.vcard;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.content.Entity.NamedContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import com.android.vcard.exception.VCardException;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * The class for composing vCard from Contacts information.
+ * </p>
+ * <p>
+ * Usually, this class should be used like this.
+ * </p>
+ * <pre class="prettyprint">VCardComposer composer = null;
+ * try {
+ * composer = new VCardComposer(context);
+ * composer.addHandler(
+ * composer.new HandlerForOutputStream(outputStream));
+ * if (!composer.init()) {
+ * // Do something handling the situation.
+ * return;
+ * }
+ * while (!composer.isAfterLast()) {
+ * if (mCanceled) {
+ * // Assume a user may cancel this operation during the export.
+ * return;
+ * }
+ * if (!composer.createOneEntry()) {
+ * // Do something handling the error situation.
+ * return;
+ * }
+ * }
+ * } finally {
+ * if (composer != null) {
+ * composer.terminate();
+ * }
+ * }</pre>
+ * <p>
+ * Users have to manually take care of memory efficiency. Even one vCard may contain
+ * image of non-trivial size for mobile devices.
+ * </p>
+ * <p>
+ * {@link VCardBuilder} is used to build each vCard.
+ * </p>
+ */
+public class VCardComposer {
+ private static final String LOG_TAG = "VCardComposer";
+
+ public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
+ "Failed to get database information";
+
+ public static final String FAILURE_REASON_NO_ENTRY =
+ "There's no exportable in the database";
+
+ public static final String FAILURE_REASON_NOT_INITIALIZED =
+ "The vCard composer object is not correctly initialized";
+
+ /** Should be visible only from developers... (no need to translate, hopefully) */
+ public static final String FAILURE_REASON_UNSUPPORTED_URI =
+ "The Uri vCard composer received is not supported by the composer.";
+
+ public static final String NO_ERROR = "No error";
+
+ public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+
+ // Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
+ // since usual vCard devices for Japanese devices already use it.
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+ private static final String UTF_8 = "UTF-8";
+
+ /**
+ * Special URI for testing.
+ */
+ public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
+ public static final Uri VCARD_TEST_AUTHORITY_URI =
+ Uri.parse("content://" + VCARD_TEST_AUTHORITY);
+ public static final Uri CONTACTS_TEST_CONTENT_URI =
+ Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");
+
+ private static final Map<Integer, String> sImMap;
+
+ static {
+ sImMap = new HashMap<Integer, String>();
+ sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+ sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+ sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+ // We don't add Google talk here since it has to be handled separately.
+ }
+
+ public static interface OneEntryHandler {
+ public boolean onInit(Context context);
+ public boolean onEntryCreated(String vcard);
+ public void onTerminate();
+ }
+
+ /**
+ * <p>
+ * An useful handler for emitting vCard String to an OutputStream object one by one.
+ * </p>
+ * <p>
+ * The input OutputStream object is closed() on {@link #onTerminate()}.
+ * Must not close the stream outside this class.
+ * </p>
+ */
+ public final class HandlerForOutputStream implements OneEntryHandler {
+ @SuppressWarnings("hiding")
+ private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
+
+ private boolean mOnTerminateIsCalled = false;
+
+ private final OutputStream mOutputStream; // mWriter will close this.
+ private Writer mWriter;
+
+ /**
+ * Input stream will be closed on the detruction of this object.
+ */
+ public HandlerForOutputStream(final OutputStream outputStream) {
+ mOutputStream = outputStream;
+ }
+
+ public boolean onInit(final Context context) {
+ try {
+ mWriter = new BufferedWriter(new OutputStreamWriter(
+ mOutputStream, mCharset));
+ } catch (UnsupportedEncodingException e1) {
+ Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
+ mErrorReason = "Encoding is not supported (usually this does not happen!): "
+ + mCharset;
+ return false;
+ }
+
+ if (mIsDoCoMo) {
+ try {
+ // Create one empty entry.
+ mWriter.write(createOneEntryInternal("-1", null));
+ } catch (VCardException e) {
+ Log.e(LOG_TAG, "VCardException has been thrown during on Init(): " +
+ e.getMessage());
+ return false;
+ } catch (IOException e) {
+ Log.e(LOG_TAG,
+ "IOException occurred during exportOneContactData: "
+ + e.getMessage());
+ mErrorReason = "IOException occurred: " + e.getMessage();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean onEntryCreated(String vcard) {
+ try {
+ mWriter.write(vcard);
+ } catch (IOException e) {
+ Log.e(LOG_TAG,
+ "IOException occurred during exportOneContactData: "
+ + e.getMessage());
+ mErrorReason = "IOException occurred: " + e.getMessage();
+ return false;
+ }
+ return true;
+ }
+
+ public void onTerminate() {
+ mOnTerminateIsCalled = true;
+ if (mWriter != null) {
+ try {
+ // Flush and sync the data so that a user is able to pull
+ // the SDCard just after
+ // the export.
+ mWriter.flush();
+ if (mOutputStream != null
+ && mOutputStream instanceof FileOutputStream) {
+ ((FileOutputStream) mOutputStream).getFD().sync();
+ }
+ } catch (IOException e) {
+ Log.d(LOG_TAG,
+ "IOException during closing the output stream: "
+ + e.getMessage());
+ } finally {
+ closeOutputStream();
+ }
+ }
+ }
+
+ public void closeOutputStream() {
+ try {
+ mWriter.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
+ }
+ }
+
+ @Override
+ public void finalize() {
+ if (!mOnTerminateIsCalled) {
+ onTerminate();
+ }
+ }
+ }
+
+ private final Context mContext;
+ private final int mVCardType;
+ private final boolean mCareHandlerErrors;
+ private final ContentResolver mContentResolver;
+
+ private final boolean mIsDoCoMo;
+ private Cursor mCursor;
+ private int mIdColumn;
+
+ private final String mCharset;
+ private boolean mTerminateIsCalled;
+ private final List<OneEntryHandler> mHandlerList;
+
+ private String mErrorReason = NO_ERROR;
+
+ private static final String[] sContactsProjection = new String[] {
+ Contacts._ID,
+ };
+
+ public VCardComposer(Context context) {
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
+ }
+
+ /**
+ * The variant which sets charset to null and sets careHandlerErrors to true.
+ */
+ public VCardComposer(Context context, int vcardType) {
+ this(context, vcardType, null, true);
+ }
+
+ public VCardComposer(Context context, int vcardType, String charset) {
+ this(context, vcardType, charset, true);
+ }
+
+ /**
+ * The variant which sets charset to null.
+ */
+ public VCardComposer(final Context context, final int vcardType,
+ final boolean careHandlerErrors) {
+ this(context, vcardType, null, careHandlerErrors);
+ }
+
+ /**
+ * Construct for supporting call log entry vCard composing.
+ *
+ * @param context Context to be used during the composition.
+ * @param vcardType The type of vCard, typically available via {@link VCardConfig}.
+ * @param charset The charset to be used. Use null when you don't need the charset.
+ * @param careHandlerErrors If true, This object returns false everytime
+ * a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
+ * If false, this ignores those errors.
+ */
+ public VCardComposer(final Context context, final int vcardType, String charset,
+ final boolean careHandlerErrors) {
+ mContext = context;
+ mVCardType = vcardType;
+ mCareHandlerErrors = careHandlerErrors;
+ mContentResolver = context.getContentResolver();
+
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mHandlerList = new ArrayList<OneEntryHandler>();
+
+ charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
+ final boolean shouldAppendCharsetParam = !(
+ VCardConfig.isV30(vcardType) && UTF_8.equalsIgnoreCase(charset));
+
+ if (mIsDoCoMo || shouldAppendCharsetParam) {
+ if (SHIFT_JIS.equalsIgnoreCase(charset)) {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
+ } else {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
+ }
+ } else {
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = UTF_8;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
+ }
+
+ Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
+ }
+
+ /**
+ * Must be called before {@link #init()}.
+ */
+ public void addHandler(OneEntryHandler handler) {
+ if (handler != null) {
+ mHandlerList.add(handler);
+ }
+ }
+
+ /**
+ * @return Returns true when initialization is successful and all the other
+ * methods are available. Returns false otherwise.
+ */
+ public boolean init() {
+ return init(null, null);
+ }
+
+ public boolean init(final String selection, final String[] selectionArgs) {
+ return init(Contacts.CONTENT_URI, selection, selectionArgs, null);
+ }
+
+ /**
+ * Note that this is unstable interface, may be deleted in the future.
+ */
+ public boolean init(final Uri contentUri, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ if (contentUri == null) {
+ return false;
+ }
+
+ if (mCareHandlerErrors) {
+ final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ mHandlerList.size());
+ for (OneEntryHandler handler : mHandlerList) {
+ if (!handler.onInit(mContext)) {
+ for (OneEntryHandler finished : finishedList) {
+ finished.onTerminate();
+ }
+ return false;
+ }
+ }
+ } else {
+ // Just ignore the false returned from onInit().
+ for (OneEntryHandler handler : mHandlerList) {
+ handler.onInit(mContext);
+ }
+ }
+
+ final String[] projection;
+ if (Contacts.CONTENT_URI.equals(contentUri) ||
+ CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
+ projection = sContactsProjection;
+ } else {
+ mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
+ return false;
+ }
+ mCursor = mContentResolver.query(
+ contentUri, projection, selection, selectionArgs, sortOrder);
+
+ if (mCursor == null) {
+ mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
+ return false;
+ }
+
+ if (getCount() == 0 || !mCursor.moveToFirst()) {
+ try {
+ mCursor.close();
+ } catch (SQLiteException e) {
+ Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
+ } finally {
+ mCursor = null;
+ mErrorReason = FAILURE_REASON_NO_ENTRY;
+ }
+ return false;
+ }
+
+ mIdColumn = mCursor.getColumnIndex(Contacts._ID);
+
+ return true;
+ }
+
+ public boolean createOneEntry() {
+ return createOneEntry(null);
+ }
+
+ /**
+ * @param getEntityIteratorMethod For Dependency Injection.
+ * @hide just for testing.
+ */
+ public boolean createOneEntry(Method getEntityIteratorMethod) {
+ if (mCursor == null || mCursor.isAfterLast()) {
+ mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
+ return false;
+ }
+ final String vcard;
+ try {
+ if (mIdColumn >= 0) {
+ vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
+ getEntityIteratorMethod);
+ } else {
+ Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
+ return true;
+ }
+ } catch (VCardException e) {
+ Log.e(LOG_TAG, "VCardException has been thrown: " + e.getMessage());
+ return false;
+ } catch (OutOfMemoryError error) {
+ // Maybe some data (e.g. photo) is too big to have in memory. But it
+ // should be rare.
+ Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry.");
+ System.gc();
+ // TODO: should tell users what happened?
+ return true;
+ } finally {
+ mCursor.moveToNext();
+ }
+
+ // This function does not care the OutOfMemoryError on the handler side :-P
+ if (mCareHandlerErrors) {
+ List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ mHandlerList.size());
+ for (OneEntryHandler handler : mHandlerList) {
+ if (!handler.onEntryCreated(vcard)) {
+ return false;
+ }
+ }
+ } else {
+ for (OneEntryHandler handler : mHandlerList) {
+ handler.onEntryCreated(vcard);
+ }
+ }
+
+ return true;
+ }
+
+ private String createOneEntryInternal(final String contactId,
+ final Method getEntityIteratorMethod) throws VCardException {
+ final Map<String, List<ContentValues>> contentValuesListMap =
+ new HashMap<String, List<ContentValues>>();
+ // The resolver may return the entity iterator with no data. It is possible.
+ // e.g. If all the data in the contact of the given contact id are not exportable ones,
+ // they are hidden from the view of this method, though contact id itself exists.
+ EntityIterator entityIterator = null;
+ try {
+ final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
+ // .appendQueryParameter("for_export_only", "1")
+ .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
+ .build();
+ final String selection = Data.CONTACT_ID + "=?";
+ final String[] selectionArgs = new String[] {contactId};
+ if (getEntityIteratorMethod != null) {
+ // Please note that this branch is executed by unit tests only
+ try {
+ entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
+ mContentResolver, uri, selection, selectionArgs, null);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "IllegalArgumentException has been thrown: " +
+ e.getMessage());
+ } catch (IllegalAccessException e) {
+ Log.e(LOG_TAG, "IllegalAccessException has been thrown: " +
+ e.getMessage());
+ } catch (InvocationTargetException e) {
+ Log.e(LOG_TAG, "InvocationTargetException has been thrown: ");
+ StackTraceElement[] stackTraceElements = e.getCause().getStackTrace();
+ for (StackTraceElement element : stackTraceElements) {
+ Log.e(LOG_TAG, " at " + element.toString());
+ }
+ throw new VCardException("InvocationTargetException has been thrown: " +
+ e.getCause().getMessage());
+ }
+ } else {
+ entityIterator = RawContacts.newEntityIterator(mContentResolver.query(
+ uri, null, selection, selectionArgs, null));
+ }
+
+ if (entityIterator == null) {
+ Log.e(LOG_TAG, "EntityIterator is null");
+ return "";
+ }
+
+ if (!entityIterator.hasNext()) {
+ Log.w(LOG_TAG, "Data does not exist. contactId: " + contactId);
+ return "";
+ }
+
+ while (entityIterator.hasNext()) {
+ Entity entity = entityIterator.next();
+ for (NamedContentValues namedContentValues : entity.getSubValues()) {
+ ContentValues contentValues = namedContentValues.values;
+ String key = contentValues.getAsString(Data.MIMETYPE);
+ if (key != null) {
+ List<ContentValues> contentValuesList =
+ contentValuesListMap.get(key);
+ if (contentValuesList == null) {
+ contentValuesList = new ArrayList<ContentValues>();
+ contentValuesListMap.put(key, contentValuesList);
+ }
+ contentValuesList.add(contentValues);
+ }
+ }
+ }
+ } finally {
+ if (entityIterator != null) {
+ entityIterator.close();
+ }
+ }
+
+ return buildVCard(contentValuesListMap);
+ }
+
+ /**
+ * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
+ * {ContactsContract}. Developers can override this method to customize the output.
+ */
+ public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
+ if (contentValuesListMap == null) {
+ Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
+ return "";
+ } else {
+ final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
+ builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ return builder.toString();
+ }
+ }
+
+ public void terminate() {
+ for (OneEntryHandler handler : mHandlerList) {
+ handler.onTerminate();
+ }
+
+ if (mCursor != null) {
+ try {
+ mCursor.close();
+ } catch (SQLiteException e) {
+ Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
+ }
+ mCursor = null;
+ }
+
+ mTerminateIsCalled = true;
+ }
+
+ @Override
+ public void finalize() {
+ if (!mTerminateIsCalled) {
+ Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
+ terminate();
+ }
+ }
+
+ /**
+ * @return returns the number of available entities. The return value is undefined
+ * when this object is not ready yet (typically when {{@link #init()} is not called
+ * or when {@link #terminate()} is already called).
+ */
+ public int getCount() {
+ if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
+ return 0;
+ }
+ return mCursor.getCount();
+ }
+
+ /**
+ * @return true when there's no entity to be built. The return value is undefined
+ * when this object is not ready yet.
+ */
+ public boolean isAfterLast() {
+ if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
+ return false;
+ }
+ return mCursor.isAfterLast();
+ }
+
+ /**
+ * @return Returns the error reason.
+ */
+ public String getErrorReason() {
+ return mErrorReason;
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardConfig.java b/vcard/java/com/android/vcard/VCardConfig.java
new file mode 100644
index 0000000..a011d8e
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardConfig.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The class representing VCard related configurations. Useful static methods are not in this class
+ * but in VCardUtils.
+ */
+public class VCardConfig {
+ private static final String LOG_TAG = "VCardConfig";
+
+ /* package */ static final int LOG_LEVEL_NONE = 0;
+ /* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
+ /* package */ static final int LOG_LEVEL_SHOW_WARNING = 0x2;
+ /* package */ static final int LOG_LEVEL_VERBOSE =
+ LOG_LEVEL_PERFORMANCE_MEASUREMENT | LOG_LEVEL_SHOW_WARNING;
+
+ /* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
+
+ /**
+ * <p>
+ * The charset used during import.
+ * </p>
+ * <p>
+ * We cannot determine which charset should be used to interpret 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";
+
+ public static final int FLAG_V21 = 0;
+ public static final int FLAG_V30 = 1;
+
+ // 0x2 is reserved for the future use ...
+
+ public static final int NAME_ORDER_DEFAULT = 0;
+ public static final int NAME_ORDER_EUROPE = 0x4;
+ public static final int NAME_ORDER_JAPANESE = 0x8;
+ private static final int NAME_ORDER_MASK = 0xC;
+
+ // 0x10 is reserved for safety
+
+ /**
+ * <p>
+ * The flag indicating the vCard composer will add some "X-" properties used only in Android
+ * when the formal vCard specification does not have appropriate fields for that data.
+ * </p>
+ * <p>
+ * For example, Android accepts nickname information while vCard 2.1 does not.
+ * When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
+ * instead of just dropping it.
+ * </p>
+ * <p>
+ * vCard parser code automatically parses the field emitted even when this flag is off.
+ * </p>
+ */
+ private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
+
+ /**
+ * <p>
+ * The flag indicating the vCard composer will add some "X-" properties seen in the
+ * vCard data emitted by the other softwares/devices when the formal vCard specification
+ * does not have appropriate field(s) for that data.
+ * </p>
+ * <p>
+ * One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
+ * for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
+ * non-Android devices/softwares. We chose to enable the vCard composer to use those
+ * defact properties since they are also useful for Android devices.
+ * </p>
+ * <p>
+ * Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
+ * allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
+ * in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
+ * </p>
+ */
+ private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
+
+ /**
+ * <p>
+ * The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
+ * mobile careers) should be used. This flag does not include any other information like
+ * that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
+ * dialect but the name order should be European", but it is not recommended.
+ * </p>
+ */
+ private static final int FLAG_DOCOMO = 0x20000000;
+
+ /**
+ * <p>
+ * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
+ * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
+ * </p>
+ * <p>
+ * We actually cannot define what is the "primary" property. Note that this is NOT defined
+ * in vCard specification either. Also be aware that it is NOT related to "primary" notion
+ * used in {@link android.provider.ContactsContract}.
+ * This notion is just for vCard composition in Android.
+ * </p>
+ * <p>
+ * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
+ * do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
+ * even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
+ * other properties like "ADR", "ORG", etc.
+ * <p>
+ * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
+ * not used in such fields.
+ * </p>
+ * <p>
+ * This flag is useful when some target importer you are going to focus on does not accept
+ * such properties with Quoted-Printable encoding.
+ * </p>
+ * <p>
+ * Again, we should not use this flag at all for complying vCard 2.1 spec.
+ * </p>
+ * <p>
+ * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
+ * kind of problem (hopefully).
+ * </p>
+ * @hide
+ */
+ public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
+
+ /**
+ * <p>
+ * The flag indicating that phonetic name related fields must be converted to
+ * appropriate form. Note that "appropriate" is not defined in any vCard specification.
+ * This is Android-specific.
+ * </p>
+ * <p>
+ * One typical (and currently sole) example where we need this flag is the time when
+ * we need to emit Japanese phonetic names into vCard entries. The property values
+ * should be encoded into half-width katakana when the target importer is Japanese mobile
+ * phones', which are probably not able to parse full-width hiragana/katakana for
+ * historical reasons, while the vCard importers embedded to softwares for PC should be
+ * able to parse them as we expect.
+ * </p>
+ */
+ public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000;
+
+ /**
+ * <p>
+ * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
+ * every time possible. The default behavior does not emit it and is valid in the spec.
+ * In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
+ * </p>
+ * <p>
+ * Detail:
+ * How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
+ * </p>
+ * <p>
+ * e.g.
+ * </p>
+ * <ol>
+ * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
+ * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
+ * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
+ * </ol>
+ * <p>
+ * If you are targeting to the importer which cannot accept TYPE params without "TYPE="
+ * strings (which should be rare though), please use this flag.
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
+ * </p>
+ */
+ public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+
+ /**
+ * <p>
+ * The flag indicating the vCard composer does touch nothing toward phone number Strings
+ * but leave it as is.
+ * </p>
+ * <p>
+ * The vCard specifications mention nothing toward phone numbers, while some devices
+ * do (wrongly, but with innevitable reasons).
+ * For example, there's a possibility Japanese mobile phones are expected to have
+ * just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones
+ * should get such characters. To make exported vCard simple for external parsers,
+ * we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and
+ * removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)"
+ * becomes "111-222-3333").
+ * Unfortunate side effect of that use was some control characters used in the other
+ * areas may be badly affected by the formatting.
+ * </p>
+ * <p>
+ * This flag disables that formatting, affecting both importer and exporter.
+ * If the user is aware of some side effects due to the implicit formatting, use this flag.
+ * </p>
+ */
+ public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
+
+ /**
+ * <p>
+ * For importer only. Ignored in exporter.
+ * </p>
+ * <p>
+ * The flag indicating the parser should handle a nested vCard, in which vCard clause starts
+ * in another vCard clause. Here's a typical example.
+ * </p>
+ * <pre class="prettyprint">BEGIN:VCARD
+ * BEGIN:VCARD
+ * VERSION:2.1
+ * ...
+ * END:VCARD
+ * END:VCARD</pre>
+ * <p>
+ * The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries,
+ * while some mobile devices emit nested ones as primary data to be imported.
+ * </p>
+ * <p>
+ * This flag forces a vCard parser to torelate such a nest and understand its content.
+ * </p>
+ */
+ public static final int FLAG_TORELATE_NEST = 0x01000000;
+
+ //// The followings are VCard types available from importer/exporter. ////
+
+ /**
+ * <p>
+ * The type indicating nothing. Used by {@link VCardSourceDetector} when it
+ * was not able to guess the exact vCard type.
+ * </p>
+ */
+ public static final int VCARD_TYPE_UNKNOWN = 0;
+
+ /**
+ * <p>
+ * Generic vCard format with the vCard 2.1. When composing a vCard entry,
+ * the US convension will be used toward formatting some values.
+ * </p>
+ * <p>
+ * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
+ * while it should be "Prefix Family Middle Given Suffix" in Japan for example.
+ * </p>
+ * <p>
+ * Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
+ * outside Android cannot accept it since vCard 2.1 specifically does not allow
+ * that charset, while we need to use it to support various languages around the world.
+ * </p>
+ * <p>
+ * If you want to use alternative charset, you should notify the charset to the other
+ * compontent to be used.
+ * </p>
+ */
+ public static final int VCARD_TYPE_V21_GENERIC =
+ (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
+
+ /**
+ * <p>
+ * General vCard format with the version 3.0. Uses UTF-8 for the charset.
+ * </p>
+ * <p>
+ * Not fully ready yet. Use with caution when you use this.
+ * </p>
+ */
+ public static final int VCARD_TYPE_V30_GENERIC =
+ (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+
+ /**
+ * <p>
+ * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
+ * Currently, only name order is considered ("Prefix Middle Given Family Suffix")
+ * </p>
+ */
+ public static final int VCARD_TYPE_V21_EUROPE =
+ (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
+
+ /**
+ * <p>
+ * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
+ * </p>
+ * <p>
+ * Not ready yet. Use with caution when you use this.
+ * </p>
+ */
+ public static final int VCARD_TYPE_V30_EUROPE =
+ (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
+
+ /**
+ * <p>
+ * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </p>
+ * <p>
+ * Not ready yet. Use with caution when you use this.
+ * </p>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
+
+ /**
+ * <p>
+ * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </p>
+ * <p>
+ * Not ready yet. Use with caution when you use this.
+ * </p>
+ */
+ public static final int VCARD_TYPE_V30_JAPANESE =
+ (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8";
+
+ /**
+ * <p>
+ * The vCard 2.1 based format which (partially) considers the convention in Japanese
+ * mobile phones, where phonetic names are translated to half-width katakana if
+ * possible, etc. It would be better to use Shift_JIS as a charset for maximum
+ * compatibility.
+ * </p>
+ * @hide Should not be available world wide.
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
+
+ /**
+ * <p>
+ * The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * </p>
+ * <p>
+ * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
+ * No Android-specific property nor defact property is included. The "Primary" properties
+ * are NOT encoded to Quoted-Printable.
+ * </p>
+ * @hide Should not be available world wide.
+ */
+ public static final int VCARD_TYPE_DOCOMO =
+ (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
+
+ /* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
+
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
+
+ private static final Map<String, Integer> sVCardTypeMap;
+ private static final Set<Integer> sJapaneseMobileTypeSet;
+
+ static {
+ sVCardTypeMap = new HashMap<String, Integer>();
+ sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+
+ sJapaneseMobileTypeSet = new HashSet<Integer>();
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
+ }
+
+ public static int getVCardTypeFromString(final String vcardTypeString) {
+ final String loweredKey = vcardTypeString.toLowerCase();
+ if (sVCardTypeMap.containsKey(loweredKey)) {
+ return sVCardTypeMap.get(loweredKey);
+ } else if ("default".equalsIgnoreCase(vcardTypeString)) {
+ return VCARD_TYPE_DEFAULT;
+ } else {
+ Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
+ return VCARD_TYPE_DEFAULT;
+ }
+ }
+
+ public static boolean isV30(final int vcardType) {
+ return ((vcardType & FLAG_V30) != 0);
+ }
+
+ public static boolean shouldUseQuotedPrintable(final int vcardType) {
+ return !isV30(vcardType);
+ }
+
+ public static int getNameOrderType(final int vcardType) {
+ return vcardType & NAME_ORDER_MASK;
+ }
+
+ public static boolean usesAndroidSpecificProperty(final int vcardType) {
+ return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
+ }
+
+ public static boolean usesDefactProperty(final int vcardType) {
+ return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
+ }
+
+ public static boolean showPerformanceLog() {
+ return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
+ }
+
+ public static boolean shouldRefrainQPToNameProperties(final int vcardType) {
+ return (!shouldUseQuotedPrintable(vcardType) ||
+ ((vcardType & FLAG_REFRAIN_QP_TO_NAME_PROPERTIES) != 0));
+ }
+
+ public static boolean appendTypeParamName(final int vcardType) {
+ return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
+ }
+
+ /**
+ * @return true if the device is Japanese and some Japanese convension is
+ * applied to creating "formatted" something like FORMATTED_ADDRESS.
+ */
+ public static boolean isJapaneseDevice(final int vcardType) {
+ // TODO: Some mask will be required so that this method wrongly interpret
+ // Japanese"-like" vCard type.
+ // e.g. VCARD_TYPE_V21_JAPANESE_SJIS | FLAG_APPEND_TYPE_PARAMS
+ return sJapaneseMobileTypeSet.contains(vcardType);
+ }
+
+ /* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) {
+ return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0);
+ }
+
+ public static boolean needsToConvertPhoneticString(final int vcardType) {
+ return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
+ }
+
+ public static boolean onlyOneNoteFieldIsAvailable(final int vcardType) {
+ return vcardType == VCARD_TYPE_DOCOMO;
+ }
+
+ public static boolean isDoCoMo(final int vcardType) {
+ return ((vcardType & FLAG_DOCOMO) != 0);
+ }
+
+ private VCardConfig() {
+ }
+}
\ No newline at end of file
diff --git a/vcard/java/com/android/vcard/VCardConstants.java b/vcard/java/com/android/vcard/VCardConstants.java
new file mode 100644
index 0000000..862c9ed
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardConstants.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+/**
+ * Constants used in both exporter and importer code.
+ */
+public class VCardConstants {
+ public static final String VERSION_V21 = "2.1";
+ public static final String VERSION_V30 = "3.0";
+
+ // The property names valid both in vCard 2.1 and 3.0.
+ public static final String PROPERTY_BEGIN = "BEGIN";
+ public static final String PROPERTY_VERSION = "VERSION";
+ public static final String PROPERTY_N = "N";
+ public static final String PROPERTY_FN = "FN";
+ public static final String PROPERTY_ADR = "ADR";
+ public static final String PROPERTY_EMAIL = "EMAIL";
+ public static final String PROPERTY_NOTE = "NOTE";
+ public static final String PROPERTY_ORG = "ORG";
+ public static final String PROPERTY_SOUND = "SOUND"; // Not fully supported.
+ public static final String PROPERTY_TEL = "TEL";
+ public static final String PROPERTY_TITLE = "TITLE";
+ public static final String PROPERTY_ROLE = "ROLE";
+ public static final String PROPERTY_PHOTO = "PHOTO";
+ public static final String PROPERTY_LOGO = "LOGO";
+ public static final String PROPERTY_URL = "URL";
+ public static final String PROPERTY_BDAY = "BDAY"; // Birthday
+ public static final String PROPERTY_END = "END";
+
+ // Valid property names not supported (not appropriately handled) by our vCard importer now.
+ public static final String PROPERTY_REV = "REV";
+ public static final String PROPERTY_AGENT = "AGENT";
+
+ // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
+ public static final String PROPERTY_NAME = "NAME";
+ public static final String PROPERTY_NICKNAME = "NICKNAME";
+ public static final String PROPERTY_SORT_STRING = "SORT-STRING";
+
+ // De-fact property values expressing phonetic names.
+ public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
+ public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
+ public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
+
+ // Properties both ContactsStruct in Eclair and de-fact vCard extensions
+ // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
+ public static final String PROPERTY_X_AIM = "X-AIM";
+ public static final String PROPERTY_X_MSN = "X-MSN";
+ public static final String PROPERTY_X_YAHOO = "X-YAHOO";
+ public static final String PROPERTY_X_ICQ = "X-ICQ";
+ public static final String PROPERTY_X_JABBER = "X-JABBER";
+ public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
+ public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
+ // Properties only ContactsStruct has. We alse use this.
+ public static final String PROPERTY_X_QQ = "X-QQ";
+ public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
+
+ // Phone number for Skype, available as usual phone.
+ public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
+
+ // Property for Android-specific fields.
+ public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
+
+ // Properties for DoCoMo vCard.
+ public static final String PROPERTY_X_CLASS = "X-CLASS";
+ public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
+ public static final String PROPERTY_X_NO = "X-NO";
+ public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
+
+ public static final String PARAM_TYPE = "TYPE";
+
+ public static final String PARAM_TYPE_HOME = "HOME";
+ public static final String PARAM_TYPE_WORK = "WORK";
+ public static final String PARAM_TYPE_FAX = "FAX";
+ public static final String PARAM_TYPE_CELL = "CELL";
+ public static final String PARAM_TYPE_VOICE = "VOICE";
+ public static final String PARAM_TYPE_INTERNET = "INTERNET";
+
+ // Abbreviation of "prefered" according to vCard 2.1 specification.
+ // We interpret this value as "primary" property during import/export.
+ //
+ // Note: Both vCard specs does not mention anything about the requirement for this parameter,
+ // but there may be some vCard importer which will get confused with more than
+ // one "PREF"s in one property name, while Android accepts them.
+ public static final String PARAM_TYPE_PREF = "PREF";
+
+ // Phone type parameters valid in vCard and known to ContactsContract, but not so common.
+ public static final String PARAM_TYPE_CAR = "CAR";
+ public static final String PARAM_TYPE_ISDN = "ISDN";
+ public static final String PARAM_TYPE_PAGER = "PAGER";
+ public static final String PARAM_TYPE_TLX = "TLX"; // Telex
+
+ // Phone types existing in vCard 2.1 but not known to ContactsContract.
+ public static final String PARAM_TYPE_MODEM = "MODEM";
+ public static final String PARAM_TYPE_MSG = "MSG";
+ public static final String PARAM_TYPE_BBS = "BBS";
+ public static final String PARAM_TYPE_VIDEO = "VIDEO";
+
+ public static final String PARAM_ENCODING_7BIT = "7BIT";
+ public static final String PARAM_ENCODING_8BIT = "8BIT";
+ public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
+ public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
+ public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
+
+ // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
+ // These types are basically encoded to "X-" parameters when composing vCard.
+ // Parser passes these when "X-" is added to the parameter or not.
+ public static final String PARAM_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
+ public static final String PARAM_PHONE_EXTRA_TYPE_RADIO = "RADIO";
+ public static final String PARAM_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
+ public static final String PARAM_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
+ // vCard composer translates this type to "WORK" + "PREF". Just for parsing.
+ public static final String PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
+ // vCard composer translates this type to "VOICE" Just for parsing.
+ public static final String PARAM_PHONE_EXTRA_TYPE_OTHER = "OTHER";
+
+ // TYPE parameters for postal addresses.
+ public static final String PARAM_ADR_TYPE_PARCEL = "PARCEL";
+ public static final String PARAM_ADR_TYPE_DOM = "DOM";
+ public static final String PARAM_ADR_TYPE_INTL = "INTL";
+
+ // TYPE parameters not officially valid but used in some vCard exporter.
+ // Do not use in composer side.
+ public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
+
+ public interface ImportOnly {
+ public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
+ // Some device emits this "X-" parameter for expressing Google Talk,
+ // which is specifically invalid but should be always properly accepted, and emitted
+ // in some special case (for that device/application).
+ public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
+ }
+
+ //// Mainly for package constants.
+
+ // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
+ // SORT-STRING invCard 3.0.
+ /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
+ /* package */ static final int MAX_DATA_COLUMN = 15;
+
+ /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
+ static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
+
+ private VCardConstants() {
+ }
+}
\ No newline at end of file
diff --git a/vcard/java/com/android/vcard/VCardEntry.java b/vcard/java/com/android/vcard/VCardEntry.java
new file mode 100644
index 0000000..624407a
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardEntry.java
@@ -0,0 +1,1423 @@
+/*
+ * 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.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.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class bridges between data structure of Contact app and VCard data.
+ */
+public class VCardEntry {
+ private static final String LOG_TAG = "VCardEntry";
+
+ private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+ private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
+
+ static {
+ sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
+ sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
+ sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
+ sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
+ sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
+ sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
+ sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+ Im.PROTOCOL_GOOGLE_TALK);
+ }
+
+ public static class PhoneData {
+ public final int type;
+ public final String data;
+ public final String label;
+ // isPrimary is (not final but) changable, only when there's no appropriate one existing
+ // in the original VCard.
+ public boolean isPrimary;
+ public PhoneData(int type, String data, String label, boolean isPrimary) {
+ this.type = type;
+ this.data = data;
+ this.label = label;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PhoneData)) {
+ return false;
+ }
+ PhoneData phoneData = (PhoneData)obj;
+ return (type == phoneData.type && data.equals(phoneData.data) &&
+ label.equals(phoneData.label) && isPrimary == phoneData.isPrimary);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
+ type, data, label, isPrimary);
+ }
+ }
+
+ public static class EmailData {
+ public final int type;
+ public final String data;
+ // Used only when TYPE is TYPE_CUSTOM.
+ public final String label;
+ public boolean isPrimary;
+ public EmailData(int type, String data, String label, boolean isPrimary) {
+ this.type = type;
+ this.data = data;
+ this.label = label;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof EmailData)) {
+ return false;
+ }
+ EmailData emailData = (EmailData)obj;
+ return (type == emailData.type && data.equals(emailData.data) &&
+ label.equals(emailData.label) && isPrimary == emailData.isPrimary);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
+ type, data, label, isPrimary);
+ }
+ }
+
+ public static class PostalData {
+ // Determined by vCard specification.
+ // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
+ public static final int ADDR_MAX_DATA_SIZE = 7;
+ private final String[] dataArray;
+ public final String pobox;
+ public final String extendedAddress;
+ public final String street;
+ public final String localty;
+ public final String region;
+ public final String postalCode;
+ public final String country;
+ public final int type;
+ public final String label;
+ public boolean isPrimary;
+
+ public PostalData(final int type, final List<String> propValueList,
+ final String label, boolean isPrimary) {
+ this.type = type;
+ dataArray = new String[ADDR_MAX_DATA_SIZE];
+
+ int size = propValueList.size();
+ if (size > ADDR_MAX_DATA_SIZE) {
+ size = ADDR_MAX_DATA_SIZE;
+ }
+
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ //
+ // Use Iterator assuming List may be LinkedList, though actually it is
+ // always ArrayList in the current implementation.
+ int i = 0;
+ for (String addressElement : propValueList) {
+ dataArray[i] = addressElement;
+ if (++i >= size) {
+ break;
+ }
+ }
+ while (i < ADDR_MAX_DATA_SIZE) {
+ dataArray[i++] = null;
+ }
+
+ this.pobox = dataArray[0];
+ this.extendedAddress = dataArray[1];
+ this.street = dataArray[2];
+ this.localty = dataArray[3];
+ this.region = dataArray[4];
+ this.postalCode = dataArray[5];
+ this.country = dataArray[6];
+ this.label = label;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PostalData)) {
+ return false;
+ }
+ final PostalData postalData = (PostalData)obj;
+ return (Arrays.equals(dataArray, postalData.dataArray) &&
+ (type == postalData.type &&
+ (type == StructuredPostal.TYPE_CUSTOM ?
+ (label == postalData.label) : true)) &&
+ (isPrimary == postalData.isPrimary));
+ }
+
+ public String getFormattedAddress(final int vcardType) {
+ StringBuilder builder = new StringBuilder();
+ boolean empty = true;
+ if (VCardConfig.isJapaneseDevice(vcardType)) {
+ // In Japan, the order is reversed.
+ for (int i = ADDR_MAX_DATA_SIZE - 1; i >= 0; i--) {
+ String addressPart = dataArray[i];
+ if (!TextUtils.isEmpty(addressPart)) {
+ if (!empty) {
+ builder.append(' ');
+ } else {
+ empty = false;
+ }
+ builder.append(addressPart);
+ }
+ }
+ } else {
+ for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) {
+ String addressPart = dataArray[i];
+ if (!TextUtils.isEmpty(addressPart)) {
+ if (!empty) {
+ builder.append(' ');
+ } else {
+ empty = false;
+ }
+ builder.append(addressPart);
+ }
+ }
+ }
+
+ return builder.toString().trim();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, label: %s, isPrimary: %s",
+ type, label, isPrimary);
+ }
+ }
+
+ public static class OrganizationData {
+ public final int type;
+ // non-final is Intentional: we may change the values since this info is separated into
+ // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in
+ // different timing.
+ public String companyName;
+ public String departmentName;
+ public String titleName;
+ public boolean isPrimary;
+
+ public OrganizationData(int type,
+ String companyName,
+ String departmentName,
+ String titleName,
+ boolean isPrimary) {
+ this.type = type;
+ this.companyName = companyName;
+ this.departmentName = departmentName;
+ this.titleName = titleName;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof OrganizationData)) {
+ return false;
+ }
+ OrganizationData organization = (OrganizationData)obj;
+ return (type == organization.type &&
+ TextUtils.equals(companyName, organization.companyName) &&
+ TextUtils.equals(departmentName, organization.departmentName) &&
+ TextUtils.equals(titleName, organization.titleName) &&
+ isPrimary == organization.isPrimary);
+ }
+
+ public String getFormattedString() {
+ final StringBuilder builder = new StringBuilder();
+ if (!TextUtils.isEmpty(companyName)) {
+ builder.append(companyName);
+ }
+
+ if (!TextUtils.isEmpty(departmentName)) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append(departmentName);
+ }
+
+ if (!TextUtils.isEmpty(titleName)) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append(titleName);
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+ type, companyName, departmentName, titleName, isPrimary);
+ }
+ }
+
+ public static class ImData {
+ public final int protocol;
+ public final String customProtocol;
+ public final int type;
+ public final String data;
+ public final boolean isPrimary;
+
+ public ImData(final int protocol, final String customProtocol, final int type,
+ final String data, final boolean isPrimary) {
+ this.protocol = protocol;
+ this.customProtocol = customProtocol;
+ this.type = type;
+ this.data = data;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ImData)) {
+ return false;
+ }
+ ImData imData = (ImData)obj;
+ return (type == imData.type && protocol == imData.protocol
+ && (customProtocol != null ? customProtocol.equals(imData.customProtocol) :
+ (imData.customProtocol == null))
+ && (data != null ? data.equals(imData.data) : (imData.data == null))
+ && isPrimary == imData.isPrimary);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
+ type, protocol, customProtocol, data, isPrimary);
+ }
+ }
+
+ public static class PhotoData {
+ public static final String FORMAT_FLASH = "SWF";
+ public final int type;
+ public final String formatName; // used when type is not defined in ContactsContract.
+ public final byte[] photoBytes;
+ public final boolean isPrimary;
+
+ public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
+ this.type = type;
+ this.formatName = formatName;
+ this.photoBytes = photoBytes;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PhotoData)) {
+ return false;
+ }
+ PhotoData photoData = (PhotoData)obj;
+ return (type == photoData.type &&
+ (formatName == null ? (photoData.formatName == null) :
+ formatName.equals(photoData.formatName)) &&
+ (Arrays.equals(photoBytes, photoData.photoBytes)) &&
+ (isPrimary == photoData.isPrimary));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
+ type, formatName, photoBytes.length, isPrimary);
+ }
+ }
+
+ /* package */ static class Property {
+ private String mPropertyName;
+ private Map<String, Collection<String>> mParameterMap =
+ new HashMap<String, Collection<String>>();
+ private List<String> mPropertyValueList = new ArrayList<String>();
+ private byte[] mPropertyBytes;
+
+ public void setPropertyName(final String propertyName) {
+ mPropertyName = propertyName;
+ }
+
+ 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 addToPropertyValueList(final String propertyValue) {
+ mPropertyValueList.add(propertyValue);
+ }
+
+ public void setPropertyBytes(final byte[] propertyBytes) {
+ mPropertyBytes = propertyBytes;
+ }
+
+ public final Collection<String> getParameters(String type) {
+ return mParameterMap.get(type);
+ }
+
+ public final List<String> getPropertyValueList() {
+ return mPropertyValueList;
+ }
+
+ public void clear() {
+ mPropertyName = null;
+ mParameterMap.clear();
+ mPropertyValueList.clear();
+ mPropertyBytes = null;
+ }
+ }
+
+ private String mFamilyName;
+ private String mGivenName;
+ private String mMiddleName;
+ private String mPrefix;
+ private String mSuffix;
+
+ // Used only when no family nor given name is found.
+ private String mFormattedName;
+
+ private String mPhoneticFamilyName;
+ private String mPhoneticGivenName;
+ private String mPhoneticMiddleName;
+
+ private String mPhoneticFullName;
+
+ private List<String> mNickNameList;
+
+ private String mDisplayName;
+
+ private String mBirthday;
+
+ private List<String> mNoteList;
+ private List<PhoneData> mPhoneList;
+ private List<EmailData> mEmailList;
+ private List<PostalData> mPostalList;
+ private List<OrganizationData> mOrganizationList;
+ private List<ImData> mImList;
+ private List<PhotoData> mPhotoList;
+ private List<String> mWebsiteList;
+ private List<List<String>> mAndroidCustomPropertyList;
+
+ private final int mVCardType;
+ private final Account mAccount;
+
+ public VCardEntry() {
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC);
+ }
+
+ public VCardEntry(int vcardType) {
+ this(vcardType, null);
+ }
+
+ public VCardEntry(int vcardType, Account account) {
+ mVCardType = vcardType;
+ mAccount = account;
+ }
+
+ private void addPhone(int type, String data, String label, boolean isPrimary) {
+ if (mPhoneList == null) {
+ mPhoneList = new ArrayList<PhoneData>();
+ }
+ final StringBuilder builder = new StringBuilder();
+ final String trimed = data.trim();
+ final String formattedNumber;
+ if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
+ formattedNumber = trimed;
+ } else {
+ final int length = trimed.length();
+ for (int i = 0; i < length; i++) {
+ char ch = trimed.charAt(i);
+ if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
+ builder.append(ch);
+ }
+ }
+
+ final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
+ formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
+ }
+ PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
+ mPhoneList.add(phoneData);
+ }
+
+ private void addNickName(final String nickName) {
+ if (mNickNameList == null) {
+ mNickNameList = new ArrayList<String>();
+ }
+ mNickNameList.add(nickName);
+ }
+
+ private void addEmail(int type, String data, String label, boolean isPrimary){
+ if (mEmailList == null) {
+ mEmailList = new ArrayList<EmailData>();
+ }
+ mEmailList.add(new EmailData(type, data, label, isPrimary));
+ }
+
+ private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
+ if (mPostalList == null) {
+ mPostalList = new ArrayList<PostalData>(0);
+ }
+ mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
+ }
+
+ /**
+ * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+ * {@link #handleTitleValue(String)}.
+ */
+ private void addNewOrganization(int type, final String companyName,
+ final String departmentName,
+ final String titleName, boolean isPrimary) {
+ if (mOrganizationList == null) {
+ mOrganizationList = new ArrayList<OrganizationData>();
+ }
+ mOrganizationList.add(new OrganizationData(type, companyName,
+ departmentName, titleName, isPrimary));
+ }
+
+ private static final List<String> sEmptyList =
+ Collections.unmodifiableList(new ArrayList<String>(0));
+
+ /**
+ * Set "ORG" related values to the appropriate data. If there's more than one
+ * {@link OrganizationData} objects, this input data are attached to the last one which
+ * does not have valid values (not including empty but only null). If there's no
+ * {@link OrganizationData} object, a new {@link OrganizationData} is created,
+ * whose title is set to null.
+ */
+ private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+ if (orgList == null) {
+ orgList = sEmptyList;
+ }
+ final String companyName;
+ final String departmentName;
+ final int size = orgList.size();
+ switch (size) {
+ case 0: {
+ companyName = "";
+ departmentName = null;
+ break;
+ }
+ case 1: {
+ companyName = orgList.get(0);
+ departmentName = null;
+ break;
+ }
+ default: { // More than 1.
+ companyName = orgList.get(0);
+ // We're not sure which is the correct string for department.
+ // In order to keep all the data, concatinate the rest of elements.
+ StringBuilder builder = new StringBuilder();
+ for (int i = 1; i < size; i++) {
+ if (i > 1) {
+ builder.append(' ');
+ }
+ builder.append(orgList.get(i));
+ }
+ departmentName = builder.toString();
+ }
+ }
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" title which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
+ // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
+ if (organizationData.companyName == null &&
+ organizationData.departmentName == null) {
+ // Probably the "TITLE" property comes before the "ORG" property via
+ // handleTitleLine().
+ organizationData.companyName = companyName;
+ organizationData.departmentName = departmentName;
+ organizationData.isPrimary = isPrimary;
+ return;
+ }
+ }
+ // No OrganizatioData is available. Create another one, with "null" title, which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ }
+
+ /**
+ * Set "title" value to the appropriate data. If there's more than one
+ * OrganizationData objects, this input is attached to the last one which does not
+ * have valid title value (not including empty but only null). If there's no
+ * OrganizationData object, a new OrganizationData is created, whose company name is
+ * set to null.
+ */
+ private void handleTitleValue(final String title) {
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ if (organizationData.titleName == null) {
+ organizationData.titleName = title;
+ return;
+ }
+ }
+ // No Organization is available. Create another one, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ }
+
+ private void addIm(int protocol, String customProtocol, int type,
+ String propValue, boolean isPrimary) {
+ if (mImList == null) {
+ mImList = new ArrayList<ImData>();
+ }
+ mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
+ }
+
+ private void addNote(final String note) {
+ if (mNoteList == null) {
+ mNoteList = new ArrayList<String>(1);
+ }
+ mNoteList.add(note);
+ }
+
+ private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
+ if (mPhotoList == null) {
+ mPhotoList = new ArrayList<PhotoData>(1);
+ }
+ final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
+ mPhotoList.add(photoData);
+ }
+
+ @SuppressWarnings("fallthrough")
+ private void handleNProperty(List<String> elems) {
+ // Family, Given, Middle, Prefix, Suffix. (1 - 5)
+ int size;
+ if (elems == null || (size = elems.size()) < 1) {
+ return;
+ }
+ if (size > 5) {
+ size = 5;
+ }
+
+ switch (size) {
+ // fallthrough
+ case 5: mSuffix = elems.get(4);
+ case 4: mPrefix = elems.get(3);
+ case 3: mMiddleName = elems.get(2);
+ case 2: mGivenName = elems.get(1);
+ default: mFamilyName = elems.get(0);
+ }
+ }
+
+ /**
+ * Note: Some Japanese mobile phones use this field for phonetic name,
+ * since vCard 2.1 does not have "SORT-STRING" type.
+ * Also, in some cases, the field has some ';'s in it.
+ * Assume the ';' means the same meaning in N property
+ */
+ @SuppressWarnings("fallthrough")
+ private void handlePhoneticNameFromSound(List<String> elems) {
+ if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
+ TextUtils.isEmpty(mPhoneticMiddleName) &&
+ TextUtils.isEmpty(mPhoneticGivenName))) {
+ // This means the other properties like "X-PHONETIC-FIRST-NAME" was already found.
+ // Ignore "SOUND;X-IRMC-N".
+ return;
+ }
+
+ int size;
+ if (elems == null || (size = elems.size()) < 1) {
+ return;
+ }
+
+ // Assume that the order is "Family, Given, Middle".
+ // This is not from specification but mere assumption. Some Japanese phones use this order.
+ if (size > 3) {
+ size = 3;
+ }
+
+ if (elems.get(0).length() > 0) {
+ boolean onlyFirstElemIsNonEmpty = true;
+ for (int i = 1; i < size; i++) {
+ if (elems.get(i).length() > 0) {
+ onlyFirstElemIsNonEmpty = false;
+ break;
+ }
+ }
+ if (onlyFirstElemIsNonEmpty) {
+ final String[] namesArray = elems.get(0).split(" ");
+ final int nameArrayLength = namesArray.length;
+ if (nameArrayLength == 3) {
+ // Assume the string is "Family Middle Given".
+ mPhoneticFamilyName = namesArray[0];
+ mPhoneticMiddleName = namesArray[1];
+ mPhoneticGivenName = namesArray[2];
+ } else if (nameArrayLength == 2) {
+ // Assume the string is "Family Given" based on the Japanese mobile
+ // phones' preference.
+ mPhoneticFamilyName = namesArray[0];
+ mPhoneticGivenName = namesArray[1];
+ } else {
+ mPhoneticFullName = elems.get(0);
+ }
+ return;
+ }
+ }
+
+ switch (size) {
+ // fallthrough
+ case 3: mPhoneticMiddleName = elems.get(2);
+ case 2: mPhoneticGivenName = elems.get(1);
+ default: mPhoneticFamilyName = elems.get(0);
+ }
+ }
+
+ public void addProperty(final Property property) {
+ final String propName = property.mPropertyName;
+ final Map<String, Collection<String>> paramMap = property.mParameterMap;
+ final List<String> propValueList = property.mPropertyValueList;
+ byte[] propBytes = property.mPropertyBytes;
+
+ if (propValueList.size() == 0) {
+ return;
+ }
+ final String propValue = listToString(propValueList).trim();
+
+ if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
+ // vCard version. Ignore this.
+ } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
+ mFormattedName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFormattedName == null) {
+ // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
+ // actually exist in the real vCard data, does not exist.
+ mFormattedName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_N)) {
+ handleNProperty(propValueList);
+ } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
+ mPhoneticFullName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
+ propName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
+ addNickName(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_SOUND)) {
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null
+ && typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) {
+ // As of 2009-10-08, Parser side does not split a property value into separated
+ // values using ';' (in other words, propValueList.size() == 1),
+ // which is correct behavior from the view of vCard 2.1.
+ // But we want it to be separated, so do the separation here.
+ final List<String> phoneticNameList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handlePhoneticNameFromSound(phoneticNameList);
+ } else {
+ // Ignore this field since Android cannot understand what it is.
+ }
+ } else if (propName.equals(VCardConstants.PROPERTY_ADR)) {
+ boolean valuesAreAllEmpty = true;
+ for (String value : propValueList) {
+ if (value.length() > 0) {
+ valuesAreAllEmpty = false;
+ break;
+ }
+ }
+ if (valuesAreAllEmpty) {
+ return;
+ }
+
+ int type = -1;
+ String label = "";
+ boolean isPrimary = false;
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null) {
+ for (String typeString : typeCollection) {
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+ isPrimary = true;
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
+ type = StructuredPostal.TYPE_HOME;
+ label = "";
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK) ||
+ typeString.equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) {
+ // "COMPANY" seems emitted by Windows Mobile, which is not
+ // specifically supported by vCard 2.1. We assume this is same
+ // as "WORK".
+ type = StructuredPostal.TYPE_WORK;
+ label = "";
+ } else if (typeString.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_DOM) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
+ // We do not have any appropriate way to store this information.
+ } else {
+ if (typeString.startsWith("X-") && type < 0) {
+ typeString = typeString.substring(2);
+ }
+ // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters
+ // emit non-standard types. We do not handle their values now.
+ type = StructuredPostal.TYPE_CUSTOM;
+ label = typeString;
+ }
+ }
+ }
+ // We use "HOME" as default
+ if (type < 0) {
+ type = StructuredPostal.TYPE_HOME;
+ }
+
+ addPostal(type, propValueList, label, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_EMAIL)) {
+ int type = -1;
+ String label = null;
+ boolean isPrimary = false;
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null) {
+ for (String typeString : typeCollection) {
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+ isPrimary = true;
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
+ type = Email.TYPE_HOME;
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK)) {
+ type = Email.TYPE_WORK;
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_CELL)) {
+ type = Email.TYPE_MOBILE;
+ } else {
+ if (typeString.startsWith("X-") && type < 0) {
+ typeString = typeString.substring(2);
+ }
+ // vCard 3.0 allows iana-token.
+ // We may have INTERNET (specified in vCard spec),
+ // SCHOOL, etc.
+ type = Email.TYPE_CUSTOM;
+ label = typeString;
+ }
+ }
+ }
+ if (type < 0) {
+ type = Email.TYPE_OTHER;
+ }
+ addEmail(type, propValue, label, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_ORG)) {
+ // vCard specification does not specify other types.
+ final int type = Organization.TYPE_WORK;
+ boolean isPrimary = false;
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null) {
+ for (String typeString : typeCollection) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+ isPrimary = true;
+ }
+ }
+ }
+ handleOrgValue(type, propValueList, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
+ handleTitleValue(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
+ // This conflicts with TITLE. Ignore for now...
+ // handleTitleValue(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_PHOTO) ||
+ propName.equals(VCardConstants.PROPERTY_LOGO)) {
+ Collection<String> paramMapValue = paramMap.get("VALUE");
+ if (paramMapValue != null && paramMapValue.contains("URL")) {
+ // Currently we do not have appropriate example for testing this case.
+ } else {
+ final Collection<String> typeCollection = paramMap.get("TYPE");
+ String formatName = null;
+ boolean isPrimary = false;
+ if (typeCollection != null) {
+ for (String typeValue : typeCollection) {
+ if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
+ isPrimary = true;
+ } else if (formatName == null){
+ formatName = typeValue;
+ }
+ }
+ }
+ addPhotoBytes(formatName, propBytes, isPrimary);
+ }
+ } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ final Object typeObject =
+ VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
+ final int type;
+ final String label;
+ if (typeObject instanceof Integer) {
+ type = (Integer)typeObject;
+ label = null;
+ } else {
+ type = Phone.TYPE_CUSTOM;
+ label = typeObject.toString();
+ }
+
+ final boolean isPrimary;
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
+ isPrimary = true;
+ } else {
+ isPrimary = false;
+ }
+ addPhone(type, propValue, label, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
+ // The phone number available via Skype.
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ final int type = Phone.TYPE_OTHER;
+ final boolean isPrimary;
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
+ isPrimary = true;
+ } else {
+ isPrimary = false;
+ }
+ addPhone(type, propValue, null, isPrimary);
+ } else if (sImMap.containsKey(propName)) {
+ final int protocol = sImMap.get(propName);
+ boolean isPrimary = false;
+ int type = -1;
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null) {
+ for (String typeString : typeCollection) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+ isPrimary = true;
+ } else if (type < 0) {
+ if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
+ type = Im.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
+ type = Im.TYPE_WORK;
+ }
+ }
+ }
+ }
+ if (type < 0) {
+ type = Phone.TYPE_HOME;
+ }
+ addIm(protocol, null, type, propValue, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
+ addNote(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_URL)) {
+ if (mWebsiteList == null) {
+ mWebsiteList = new ArrayList<String>(1);
+ }
+ mWebsiteList.add(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
+ mBirthday = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
+ mPhoneticGivenName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
+ mPhoneticMiddleName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
+ mPhoneticFamilyName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
+ final List<String> customPropertyList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handleAndroidCustomProperty(customPropertyList);
+ /*} else if (propName.equals("REV")) {
+ // Revision of this VCard entry. I think we can ignore this.
+ } else if (propName.equals("UID")) {
+ } else if (propName.equals("KEY")) {
+ // Type is X509 or PGP? I don't know how to handle this...
+ } else if (propName.equals("MAILER")) {
+ } else if (propName.equals("TZ")) {
+ } else if (propName.equals("GEO")) {
+ } else if (propName.equals("CLASS")) {
+ // vCard 3.0 only.
+ // e.g. CLASS:CONFIDENTIAL
+ } else if (propName.equals("PROFILE")) {
+ // VCard 3.0 only. Must be "VCARD". I think we can ignore this.
+ } else if (propName.equals("CATEGORIES")) {
+ // VCard 3.0 only.
+ // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
+ } else if (propName.equals("SOURCE")) {
+ // VCard 3.0 only.
+ } else if (propName.equals("PRODID")) {
+ // VCard 3.0 only.
+ // To specify the identifier for the product that created
+ // the vCard object.*/
+ } else {
+ // Unknown X- words and IANA token.
+ }
+ }
+
+ private void handleAndroidCustomProperty(final List<String> customPropertyList) {
+ if (mAndroidCustomPropertyList == null) {
+ mAndroidCustomPropertyList = new ArrayList<List<String>>();
+ }
+ mAndroidCustomPropertyList.add(customPropertyList);
+ }
+
+ /**
+ * Construct the display name. The constructed data must not be null.
+ */
+ private void constructDisplayName() {
+ // FullName (created via "FN" or "NAME" field) is prefered.
+ if (!TextUtils.isEmpty(mFormattedName)) {
+ mDisplayName = mFormattedName;
+ } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+ mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
+ mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
+ } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
+ TextUtils.isEmpty(mPhoneticGivenName))) {
+ mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
+ mPhoneticFamilyName, mPhoneticMiddleName, mPhoneticGivenName);
+ } else if (mEmailList != null && mEmailList.size() > 0) {
+ mDisplayName = mEmailList.get(0).data;
+ } else if (mPhoneList != null && mPhoneList.size() > 0) {
+ mDisplayName = mPhoneList.get(0).data;
+ } else if (mPostalList != null && mPostalList.size() > 0) {
+ mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType);
+ } else if (mOrganizationList != null && mOrganizationList.size() > 0) {
+ mDisplayName = mOrganizationList.get(0).getFormattedString();
+ }
+
+ if (mDisplayName == null) {
+ mDisplayName = "";
+ }
+ }
+
+ /**
+ * Consolidate several fielsds (like mName) using name candidates,
+ */
+ public void consolidateFields() {
+ constructDisplayName();
+
+ if (mPhoneticFullName != null) {
+ mPhoneticFullName = mPhoneticFullName.trim();
+ }
+ }
+
+ public Uri pushIntoContentResolver(ContentResolver resolver) {
+ ArrayList<ContentProviderOperation> operationList =
+ new ArrayList<ContentProviderOperation>();
+ // After applying the batch the first result's Uri is returned so it is important that
+ // the RawContact is the first operation that gets inserted into the list
+ ContentProviderOperation.Builder builder =
+ ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
+ String myGroupsId = null;
+ if (mAccount != null) {
+ builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
+ builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
+ } else {
+ builder.withValue(RawContacts.ACCOUNT_NAME, null);
+ builder.withValue(RawContacts.ACCOUNT_TYPE, null);
+ }
+ operationList.add(builder.build());
+
+ if (!nameFieldsAreEmpty()) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+
+ builder.withValue(StructuredName.GIVEN_NAME, mGivenName);
+ builder.withValue(StructuredName.FAMILY_NAME, mFamilyName);
+ builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);
+ builder.withValue(StructuredName.PREFIX, mPrefix);
+ builder.withValue(StructuredName.SUFFIX, mSuffix);
+
+ if (!(TextUtils.isEmpty(mPhoneticGivenName)
+ && TextUtils.isEmpty(mPhoneticFamilyName)
+ && TextUtils.isEmpty(mPhoneticMiddleName))) {
+ builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
+ builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
+ builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+ } else if (!TextUtils.isEmpty(mPhoneticFullName)) {
+ builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticFullName);
+ }
+
+ builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
+ operationList.add(builder.build());
+ }
+
+ if (mNickNameList != null && mNickNameList.size() > 0) {
+ for (String nickName : mNickNameList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
+ builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
+ builder.withValue(Nickname.NAME, nickName);
+ operationList.add(builder.build());
+ }
+ }
+
+ if (mPhoneList != null) {
+ for (PhoneData phoneData : mPhoneList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+
+ builder.withValue(Phone.TYPE, phoneData.type);
+ if (phoneData.type == Phone.TYPE_CUSTOM) {
+ builder.withValue(Phone.LABEL, phoneData.label);
+ }
+ builder.withValue(Phone.NUMBER, phoneData.data);
+ if (phoneData.isPrimary) {
+ builder.withValue(Phone.IS_PRIMARY, 1);
+ }
+ operationList.add(builder.build());
+ }
+ }
+
+ if (mOrganizationList != null) {
+ for (OrganizationData organizationData : mOrganizationList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
+ builder.withValue(Organization.TYPE, organizationData.type);
+ if (organizationData.companyName != null) {
+ builder.withValue(Organization.COMPANY, organizationData.companyName);
+ }
+ if (organizationData.departmentName != null) {
+ builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
+ }
+ if (organizationData.titleName != null) {
+ builder.withValue(Organization.TITLE, organizationData.titleName);
+ }
+ if (organizationData.isPrimary) {
+ builder.withValue(Organization.IS_PRIMARY, 1);
+ }
+ operationList.add(builder.build());
+ }
+ }
+
+ if (mEmailList != null) {
+ for (EmailData emailData : mEmailList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+
+ builder.withValue(Email.TYPE, emailData.type);
+ if (emailData.type == Email.TYPE_CUSTOM) {
+ builder.withValue(Email.LABEL, emailData.label);
+ }
+ builder.withValue(Email.DATA, emailData.data);
+ if (emailData.isPrimary) {
+ builder.withValue(Data.IS_PRIMARY, 1);
+ }
+ operationList.add(builder.build());
+ }
+ }
+
+ if (mPostalList != null) {
+ for (PostalData postalData : mPostalList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ VCardUtils.insertStructuredPostalDataUsingContactsStruct(
+ mVCardType, builder, postalData);
+ operationList.add(builder.build());
+ }
+ }
+
+ if (mImList != null) {
+ for (ImData imData : mImList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
+ builder.withValue(Im.TYPE, imData.type);
+ builder.withValue(Im.PROTOCOL, imData.protocol);
+ if (imData.protocol == Im.PROTOCOL_CUSTOM) {
+ builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
+ }
+ if (imData.isPrimary) {
+ builder.withValue(Data.IS_PRIMARY, 1);
+ }
+ }
+ }
+
+ if (mNoteList != null) {
+ for (String note : mNoteList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
+ builder.withValue(Note.NOTE, note);
+ operationList.add(builder.build());
+ }
+ }
+
+ if (mPhotoList != null) {
+ for (PhotoData photoData : mPhotoList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
+ builder.withValue(Photo.PHOTO, photoData.photoBytes);
+ if (photoData.isPrimary) {
+ builder.withValue(Photo.IS_PRIMARY, 1);
+ }
+ operationList.add(builder.build());
+ }
+ }
+
+ if (mWebsiteList != null) {
+ for (String website : mWebsiteList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Website.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
+ builder.withValue(Website.URL, website);
+ // There's no information about the type of URL in vCard.
+ // We use TYPE_HOMEPAGE for safety.
+ builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
+ operationList.add(builder.build());
+ }
+ }
+
+ if (!TextUtils.isEmpty(mBirthday)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
+ builder.withValue(Event.START_DATE, mBirthday);
+ builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
+ operationList.add(builder.build());
+ }
+
+ if (mAndroidCustomPropertyList != null) {
+ for (List<String> customPropertyList : mAndroidCustomPropertyList) {
+ int size = customPropertyList.size();
+ if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
+ continue;
+ } else if (size > VCardConstants.MAX_DATA_COLUMN + 1) {
+ size = VCardConstants.MAX_DATA_COLUMN + 1;
+ customPropertyList =
+ customPropertyList.subList(0, VCardConstants.MAX_DATA_COLUMN + 2);
+ }
+
+ int i = 0;
+ for (final String customPropertyValue : customPropertyList) {
+ if (i == 0) {
+ final String mimeType = customPropertyValue;
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, mimeType);
+ } else { // 1 <= i && i <= MAX_DATA_COLUMNS
+ if (!TextUtils.isEmpty(customPropertyValue)) {
+ builder.withValue("data" + i, customPropertyValue);
+ }
+ }
+
+ i++;
+ }
+ operationList.add(builder.build());
+ }
+ }
+
+ if (myGroupsId != null) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
+ builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
+ operationList.add(builder.build());
+ }
+
+ try {
+ ContentProviderResult[] results = resolver.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;
+ }
+ }
+
+ public static VCardEntry buildFromResolver(ContentResolver resolver) {
+ return buildFromResolver(resolver, Contacts.CONTENT_URI);
+ }
+
+ public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
+
+ return null;
+ }
+
+ private boolean nameFieldsAreEmpty() {
+ return (TextUtils.isEmpty(mFamilyName)
+ && TextUtils.isEmpty(mMiddleName)
+ && TextUtils.isEmpty(mGivenName)
+ && TextUtils.isEmpty(mPrefix)
+ && TextUtils.isEmpty(mSuffix)
+ && TextUtils.isEmpty(mFormattedName)
+ && TextUtils.isEmpty(mPhoneticFamilyName)
+ && TextUtils.isEmpty(mPhoneticMiddleName)
+ && TextUtils.isEmpty(mPhoneticGivenName)
+ && TextUtils.isEmpty(mPhoneticFullName));
+ }
+
+ public boolean isIgnorable() {
+ return getDisplayName().length() == 0;
+ }
+
+ private String listToString(List<String> list){
+ final int size = list.size();
+ if (size > 1) {
+ StringBuilder builder = new StringBuilder();
+ int i = 0;
+ for (String type : list) {
+ builder.append(type);
+ if (i < size - 1) {
+ builder.append(";");
+ }
+ }
+ return builder.toString();
+ } else if (size == 1) {
+ return list.get(0);
+ } else {
+ return "";
+ }
+ }
+
+ // All getter methods should be used carefully, since they may change
+ // in the future as of 2009-10-05, on which I cannot be sure this structure
+ // is completely consolidated.
+ //
+ // Also note that these getter methods should be used only after
+ // all properties being pushed into this object. If not, incorrect
+ // value will "be stored in the local cache and" be returned to you.
+
+ public String getFamilyName() {
+ return mFamilyName;
+ }
+
+ public String getGivenName() {
+ return mGivenName;
+ }
+
+ public String getMiddleName() {
+ return mMiddleName;
+ }
+
+ public String getPrefix() {
+ return mPrefix;
+ }
+
+ public String getSuffix() {
+ return mSuffix;
+ }
+
+ public String getFullName() {
+ return mFormattedName;
+ }
+
+ public String getPhoneticFamilyName() {
+ return mPhoneticFamilyName;
+ }
+
+ public String getPhoneticGivenName() {
+ return mPhoneticGivenName;
+ }
+
+ public String getPhoneticMiddleName() {
+ return mPhoneticMiddleName;
+ }
+
+ public String getPhoneticFullName() {
+ return mPhoneticFullName;
+ }
+
+ public final List<String> getNickNameList() {
+ return mNickNameList;
+ }
+
+ public String getBirthday() {
+ return mBirthday;
+ }
+
+ public final List<String> getNotes() {
+ return mNoteList;
+ }
+
+ public final List<PhoneData> getPhoneList() {
+ return mPhoneList;
+ }
+
+ public final List<EmailData> getEmailList() {
+ return mEmailList;
+ }
+
+ public final List<PostalData> getPostalList() {
+ return mPostalList;
+ }
+
+ public final List<OrganizationData> getOrganizationList() {
+ return mOrganizationList;
+ }
+
+ public final List<ImData> getImList() {
+ return mImList;
+ }
+
+ public final List<PhotoData> getPhotoList() {
+ return mPhotoList;
+ }
+
+ public final List<String> getWebsiteList() {
+ return mWebsiteList;
+ }
+
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ constructDisplayName();
+ }
+ return mDisplayName;
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardEntryCommitter.java b/vcard/java/com/android/vcard/VCardEntryCommitter.java
new file mode 100644
index 0000000..7bd314e
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardEntryCommitter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * <P>
+ * {@link VCardEntryHandler} implementation which commits the entry to ContentResolver.
+ * </P>
+ * <P>
+ * Note:<BR />
+ * Each vCard may contain big photo images encoded by BASE64,
+ * If we store all vCard entries in memory, OutOfMemoryError may be thrown.
+ * Thus, this class push each VCard entry into ContentResolver immediately.
+ * </P>
+ */
+public class VCardEntryCommitter implements VCardEntryHandler {
+ public static String LOG_TAG = "VCardEntryComitter";
+
+ private final ContentResolver mContentResolver;
+ private long mTimeToCommit;
+ private ArrayList<Uri> mCreatedUris = new ArrayList<Uri>();
+
+ public VCardEntryCommitter(ContentResolver resolver) {
+ mContentResolver = resolver;
+ }
+
+ public void onStart() {
+ }
+
+ public void onEnd() {
+ if (VCardConfig.showPerformanceLog()) {
+ Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
+ }
+ }
+
+ public void onEntryCreated(final VCardEntry vcardEntry) {
+ long start = System.currentTimeMillis();
+ mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver));
+ mTimeToCommit += System.currentTimeMillis() - start;
+ }
+
+ /**
+ * Returns the list of created Uris. This list should not be modified by the caller as it is
+ * not a clone.
+ */
+ public ArrayList<Uri> getCreatedUris() {
+ return mCreatedUris;
+ }
+}
\ No newline at end of file
diff --git a/vcard/java/com/android/vcard/VCardEntryConstructor.java b/vcard/java/com/android/vcard/VCardEntryConstructor.java
new file mode 100644
index 0000000..6cee070
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardEntryConstructor.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import android.accounts.Account;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * <p>
+ * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
+ * to easily handle each vCard entry.
+ * </p>
+ * <p>
+ * This class understand details inside vCard and translates it to {@link VCardEntry}.
+ * Then the class throw it to {@link VCardEntryHandler} registered via
+ * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
+ * are able to handle the {@link VCardEntry} object.
+ * </p>
+ * <p>
+ * If you want to know the detail inside vCard, it would be better to implement
+ * {@link VCardInterpreter} directly, instead of relying on this class and
+ * {@link VCardEntry} created by the object.
+ * </p>
+ */
+public class VCardEntryConstructor implements VCardInterpreter {
+ private static String LOG_TAG = "VCardEntryConstructor";
+
+ private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
+ private VCardEntry mCurrentVCardEntry;
+ private String mParamType;
+
+ // The charset using which {@link VCardInterpreter} parses the text.
+ // Each String is first decoded into binary stream with this charset, and encoded back
+ // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
+ // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
+ private final String mSourceCharset;
+
+ private final boolean mStrictLineBreaking;
+ private final int mVCardType;
+ private final Account mAccount;
+
+ // For measuring performance.
+ private long mTimePushIntoContentResolver;
+
+ private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+
+ public VCardEntryConstructor() {
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+ }
+
+ public VCardEntryConstructor(final int vcardType) {
+ this(vcardType, null, null, false);
+ }
+
+ public VCardEntryConstructor(final int vcardType, final Account account) {
+ this(vcardType, account, null, false);
+ }
+
+ public VCardEntryConstructor(final int vcardType, final Account account,
+ final String inputCharset) {
+ this(vcardType, account, inputCharset, false);
+ }
+
+ /**
+ * @hide Just for testing.
+ */
+ public VCardEntryConstructor(final int vcardType, final Account account,
+ final String inputCharset, final boolean strictLineBreakParsing) {
+ if (inputCharset != null) {
+ mSourceCharset = inputCharset;
+ } else {
+ mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
+ }
+ mStrictLineBreaking = strictLineBreakParsing;
+ mVCardType = vcardType;
+ mAccount = account;
+ }
+
+ public void addEntryHandler(VCardEntryHandler entryHandler) {
+ mEntryHandlers.add(entryHandler);
+ }
+
+ public void start() {
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onStart();
+ }
+ }
+
+ public void end() {
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onEnd();
+ }
+ }
+
+ public void clear() {
+ mCurrentVCardEntry = null;
+ mCurrentProperty = new VCardEntry.Property();
+ }
+
+ public void startEntry() {
+ if (mCurrentVCardEntry != null) {
+ Log.e(LOG_TAG, "Nested VCard code is not supported now.");
+ }
+ mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
+ }
+
+ public void endEntry() {
+ mCurrentVCardEntry.consolidateFields();
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onEntryCreated(mCurrentVCardEntry);
+ }
+ mCurrentVCardEntry = null;
+ }
+
+ public void startProperty() {
+ mCurrentProperty.clear();
+ }
+
+ public void endProperty() {
+ mCurrentVCardEntry.addProperty(mCurrentProperty);
+ }
+
+ public void propertyName(String name) {
+ mCurrentProperty.setPropertyName(name);
+ }
+
+ public void propertyGroup(String group) {
+ }
+
+ public void propertyParamType(String type) {
+ if (mParamType != null) {
+ Log.e(LOG_TAG, "propertyParamType() is called more than once " +
+ "before propertyParamValue() is called");
+ }
+ mParamType = type;
+ }
+
+ public void propertyParamValue(String value) {
+ if (mParamType == null) {
+ // From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
+ mParamType = "TYPE";
+ }
+ mCurrentProperty.addParameter(mParamType, value);
+ mParamType = null;
+ }
+
+ private static String encodeToSystemCharset(String originalString,
+ String sourceCharset, String targetCharset) {
+ if (sourceCharset.equalsIgnoreCase(targetCharset)) {
+ return originalString;
+ }
+ final Charset charset = Charset.forName(sourceCharset);
+ final ByteBuffer byteBuffer = charset.encode(originalString);
+ // byteBuffer.array() "may" return byte array which is larger than
+ // byteBuffer.remaining(). Here, we keep on the safe side.
+ final byte[] bytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(bytes);
+ try {
+ String ret = new String(bytes, targetCharset);
+ return ret;
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return null;
+ }
+ }
+
+ private String handleOneValue(String value,
+ String sourceCharset, String targetCharset, String encoding) {
+ if (value == null) {
+ Log.w(LOG_TAG, "Null is given.");
+ value = "";
+ }
+
+ if (encoding != null) {
+ if (encoding.equals("BASE64") || encoding.equals("B")) {
+ mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
+ return value;
+ } else if (encoding.equals("QUOTED-PRINTABLE")) {
+ return VCardUtils.parseQuotedPrintable(
+ value, mStrictLineBreaking, sourceCharset, targetCharset);
+ }
+ Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
+ }
+
+ // Just translate the charset of a given String from inputCharset to a system one.
+ return encodeToSystemCharset(value, sourceCharset, targetCharset);
+ }
+
+ public void propertyValues(List<String> values) {
+ if (values == null || values.isEmpty()) {
+ return;
+ }
+
+ final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
+ final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
+ final String encoding =
+ ((encodingCollection != null) ? encodingCollection.iterator().next() : null);
+ String targetCharset = CharsetUtils.nameForDefaultVendor(
+ ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
+ if (TextUtils.isEmpty(targetCharset)) {
+ targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
+ }
+
+ for (final String value : values) {
+ mCurrentProperty.addToPropertyValueList(
+ handleOneValue(value, mSourceCharset, targetCharset, encoding));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void showPerformanceInfo() {
+ Log.d(LOG_TAG, "time for insert ContactStruct to database: " +
+ mTimePushIntoContentResolver + " ms");
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardEntryCounter.java b/vcard/java/com/android/vcard/VCardEntryCounter.java
new file mode 100644
index 0000000..7bfe977
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardEntryCounter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import java.util.List;
+
+/**
+ * The class which just counts the number of vCard entries in the specified input.
+ */
+public class VCardEntryCounter implements VCardInterpreter {
+ private int mCount;
+
+ public int getCount() {
+ return mCount;
+ }
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ public void startEntry() {
+ }
+
+ public void endEntry() {
+ mCount++;
+ }
+
+ public void startProperty() {
+ }
+
+ public void endProperty() {
+ }
+
+ public void propertyGroup(String group) {
+ }
+
+ public void propertyName(String name) {
+ }
+
+ public void propertyParamType(String type) {
+ }
+
+ public void propertyParamValue(String value) {
+ }
+
+ public void propertyValues(List<String> values) {
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardEntryHandler.java b/vcard/java/com/android/vcard/VCardEntryHandler.java
new file mode 100644
index 0000000..ef35a20
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardEntryHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+/**
+ * <p>
+ * The interface called by {@link VCardEntryConstructor}.
+ * </p>
+ * <p>
+ * This class is useful when you don't want to know vCard data in detail. If you want to know
+ * it, it would be better to consider using {@link VCardInterpreter}.
+ * </p>
+ */
+public interface VCardEntryHandler {
+ /**
+ * Called when the parsing started.
+ */
+ public void onStart();
+
+ /**
+ * The method called when one VCard entry is successfully created
+ */
+ public void onEntryCreated(final VCardEntry entry);
+
+ /**
+ * Called when the parsing ended.
+ * Able to be use this method for showing performance log, etc.
+ */
+ public void onEnd();
+}
diff --git a/vcard/java/com/android/vcard/VCardInterpreter.java b/vcard/java/com/android/vcard/VCardInterpreter.java
new file mode 100644
index 0000000..2d98764
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardInterpreter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import java.util.List;
+
+/**
+ * <P>
+ * The interface which should be implemented by the classes which have to analyze each
+ * vCard entry minutely.
+ * </P>
+ * <P>
+ * Here, there are several terms specific to vCard (and this library).
+ * </P>
+ * <P>
+ * The term "entry" is one vCard representation in the input, which should start with "BEGIN:VCARD"
+ * and end with "END:VCARD".
+ * </P>
+ * <P>
+ * The term "property" is one line in vCard entry, which consists of "group", "property name",
+ * "parameter(param) names and values", and "property values".
+ * </P>
+ * <P>
+ * e.g. group1.propName;paramName1=paramValue1;paramName2=paramValue2;propertyValue1;propertyValue2...
+ * </P>
+ */
+public interface VCardInterpreter {
+ /**
+ * Called when vCard interpretation started.
+ */
+ void start();
+
+ /**
+ * Called when vCard interpretation finished.
+ */
+ void end();
+
+ /**
+ * Called when parsing one vCard entry started.
+ * More specifically, this method is called when "BEGIN:VCARD" is read.
+ */
+ void startEntry();
+
+ /**
+ * Called when parsing one vCard entry ended.
+ * More specifically, this method is called when "END:VCARD" is read.
+ * Note that {@link #startEntry()} may be called since
+ * vCard (especially 2.1) allows nested vCard.
+ */
+ void endEntry();
+
+ /**
+ * Called when reading one property started.
+ */
+ void startProperty();
+
+ /**
+ * Called when reading one property ended.
+ */
+ void endProperty();
+
+ /**
+ * @param group A group name. This method may be called more than once or may not be
+ * called at all, depending on how many gruoups are appended to the property.
+ */
+ void propertyGroup(String group);
+
+ /**
+ * @param name A property name like "N", "FN", "ADR", etc.
+ */
+ void propertyName(String name);
+
+ /**
+ * @param type A parameter name like "ENCODING", "CHARSET", etc.
+ */
+ void propertyParamType(String type);
+
+ /**
+ * @param value A parameter value. This method may be called without
+ * {@link #propertyParamType(String)} being called (when the vCard is vCard 2.1).
+ */
+ void propertyParamValue(String value);
+
+ /**
+ * @param values List of property values. The size of values would be 1 unless
+ * coressponding property name is "N", "ADR", or "ORG".
+ */
+ void propertyValues(List<String> values);
+}
diff --git a/vcard/java/com/android/vcard/VCardInterpreterCollection.java b/vcard/java/com/android/vcard/VCardInterpreterCollection.java
new file mode 100644
index 0000000..4a40d93
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardInterpreterCollection.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * The {@link VCardInterpreter} implementation which aggregates more than one
+ * {@link VCardInterpreter} objects and make a user object treat them as one
+ * {@link VCardInterpreter} object.
+ */
+public final class VCardInterpreterCollection implements VCardInterpreter {
+ private final Collection<VCardInterpreter> mInterpreterCollection;
+
+ public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
+ mInterpreterCollection = interpreterCollection;
+ }
+
+ public Collection<VCardInterpreter> getCollection() {
+ return mInterpreterCollection;
+ }
+
+ public void start() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.start();
+ }
+ }
+
+ public void end() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.end();
+ }
+ }
+
+ public void startEntry() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.startEntry();
+ }
+ }
+
+ public void endEntry() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.endEntry();
+ }
+ }
+
+ public void startProperty() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.startProperty();
+ }
+ }
+
+ public void endProperty() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.endProperty();
+ }
+ }
+
+ public void propertyGroup(String group) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.propertyGroup(group);
+ }
+ }
+
+ public void propertyName(String name) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.propertyName(name);
+ }
+ }
+
+ public void propertyParamType(String type) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.propertyParamType(type);
+ }
+ }
+
+ public void propertyParamValue(String value) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.propertyParamValue(value);
+ }
+ }
+
+ public void propertyValues(List<String> values) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.propertyValues(values);
+ }
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardParser.java b/vcard/java/com/android/vcard/VCardParser.java
new file mode 100644
index 0000000..b7b8291
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardParser.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface VCardParser {
+ /**
+ * <p>
+ * Parses the given stream and send the vCard data into VCardBuilderBase object.
+ * </p>.
+ * <p>
+ * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
+ * local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
+ * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
+ * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
+ * </p>
+ * <p>
+ * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
+ * vCard comes from and explicitly specify a charset using the result.
+ * </p>
+ *
+ * @param is The source to parse.
+ * @param interepreter A {@link VCardInterpreter} object which used to construct data.
+ * @throws IOException, VCardException
+ */
+ public void parse(InputStream is, VCardInterpreter interepreter)
+ throws IOException, VCardException;
+
+ /**
+ * <p>
+ * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
+ * </p>
+ * <p>
+ * Actual cancel is done after parsing the current vcard.
+ * </p>
+ */
+ public abstract void cancel();
+}
diff --git a/vcard/java/com/android/vcard/VCardParserImpl_V21.java b/vcard/java/com/android/vcard/VCardParserImpl_V21.java
new file mode 100644
index 0000000..b8343ae
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardParserImpl_V21.java
@@ -0,0 +1,962 @@
+/*
+ * 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.VCardAgentNotSupportedException;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardInvalidCommentLineException;
+import com.android.vcard.exception.VCardInvalidLineException;
+import com.android.vcard.exception.VCardNestedException;
+import com.android.vcard.exception.VCardVersionException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard parsing. Based on vCard 2.1,
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V21";
+
+ private static final class CustomBufferedReader extends BufferedReader {
+ private long mTime;
+
+ public CustomBufferedReader(Reader in) {
+ super(in);
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ long start = System.currentTimeMillis();
+ String ret = super.readLine();
+ long end = System.currentTimeMillis();
+ mTime += end - start;
+ return ret;
+ }
+
+ public long getTotalmillisecond() {
+ return mTime;
+ }
+ }
+
+ private static final String DEFAULT_ENCODING = "8BIT";
+
+ protected boolean mCanceled;
+ protected VCardInterpreter mInterpreter;
+
+ protected final String mIntermediateCharset;
+
+ /**
+ * <p>
+ * The encoding type for deconding byte streams. This member variable is
+ * reset to a default encoding every time when a new item comes.
+ * </p>
+ * <p>
+ * "Encoding" in vCard is different from "Charset". It is mainly used for
+ * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
+ * "QUOTED-PRINTABLE" are known examples.
+ * </p>
+ */
+ protected String mCurrentEncoding;
+
+ /**
+ * <p>
+ * The reader object to be used internally.
+ * </p>
+ * <p>
+ * Developers should not directly read a line from this object. Use
+ * getLine() unless there some reason.
+ * </p>
+ */
+ protected BufferedReader mReader;
+
+ /**
+ * <p>
+ * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
+ * specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownTypeSet = new HashSet<String>();
+
+ /**
+ * <p>
+ * Set for storing unkonwn VALUE attributes, which is not acceptable in
+ * vCard specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownValueSet = new HashSet<String>();
+
+
+ // In some cases, vCard is nested. Currently, we only consider the most
+ // interior vCard data.
+ // See v21_foma_1.vcf in test directory for more information.
+ // TODO: Don't ignore by using count, but read all of information outside vCard.
+ private int mNestCount;
+
+ // Used only for parsing END:VCARD.
+ private String mPreviousLine;
+
+ // For measuring performance.
+ private long mTimeTotal;
+ private long mTimeReadStartRecord;
+ private long mTimeReadEndRecord;
+ private long mTimeStartProperty;
+ private long mTimeEndProperty;
+ private long mTimeParseItems;
+ private long mTimeParseLineAndHandleGroup;
+ private long mTimeParsePropertyValues;
+ private long mTimeParseAdrOrgN;
+ private long mTimeHandleMiscPropertyValue;
+ private long mTimeHandleQuotedPrintable;
+ private long mTimeHandleBase64;
+
+ public VCardParserImpl_V21() {
+ this(VCardConfig.VCARD_TYPE_DEFAULT);
+ }
+
+ public VCardParserImpl_V21(int vcardType) {
+ if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) {
+ mNestCount = 1;
+ }
+
+ mIntermediateCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
+ }
+
+ /**
+ * <p>
+ * Parses the file at the given position.
+ * </p>
+ */
+ // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
+ protected void parseVCardFile() throws IOException, VCardException {
+ boolean readingFirstFile = true;
+ while (true) {
+ if (mCanceled) {
+ break;
+ }
+ if (!parseOneVCard(readingFirstFile)) {
+ break;
+ }
+ readingFirstFile = false;
+ }
+
+ if (mNestCount > 0) {
+ boolean useCache = true;
+ for (int i = 0; i < mNestCount; i++) {
+ readEndVCard(useCache, true);
+ useCache = false;
+ }
+ }
+ }
+
+ /**
+ * @return true when a given property name is a valid property name.
+ */
+ protected boolean isValidPropertyName(final String propertyName) {
+ if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
+ propertyName.startsWith("X-"))
+ && !mUnknownTypeSet.contains(propertyName)) {
+ mUnknownTypeSet.add(propertyName);
+ Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
+ }
+ return true;
+ }
+
+ /**
+ * @return String. It may be null, or its length may be 0
+ * @throws IOException
+ */
+ protected String getLine() throws IOException {
+ return mReader.readLine();
+ }
+
+ /**
+ * @return String with it's length > 0
+ * @throws IOException
+ * @throws VCardException when the stream reached end of line
+ */
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.trim().length() > 0) {
+ return line;
+ }
+ }
+ }
+
+ /*
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF
+ * "END" [ws] ":" [ws] "VCARD"
+ */
+ private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
+ boolean allowGarbage = false;
+ if (firstRead) {
+ if (mNestCount > 0) {
+ for (int i = 0; i < mNestCount; i++) {
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ allowGarbage = true;
+ }
+ }
+ }
+
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ long start;
+ if (mInterpreter != null) {
+ start = System.currentTimeMillis();
+ mInterpreter.startEntry();
+ mTimeReadStartRecord += System.currentTimeMillis() - start;
+ }
+ start = System.currentTimeMillis();
+ parseItems();
+ mTimeParseItems += System.currentTimeMillis() - start;
+ readEndVCard(true, false);
+ if (mInterpreter != null) {
+ start = System.currentTimeMillis();
+ mInterpreter.endEntry();
+ mTimeReadEndRecord += System.currentTimeMillis() - start;
+ }
+ return true;
+ }
+
+ /**
+ * @return True when successful. False when reaching the end of line
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ String line;
+ do {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ return false;
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ String[] strArray = line.split(":", 2);
+ int length = strArray.length;
+
+ // Though vCard 2.1/3.0 specification does not allow lower cases,
+ // vCard file emitted by some external vCard expoter have such
+ // invalid Strings.
+ // So we allow it.
+ // e.g. BEGIN:vCard
+ if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return true;
+ } else if (!allowGarbage) {
+ if (mNestCount > 0) {
+ mPreviousLine = line;
+ return false;
+ } else {
+ throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
+ + "(Instead, \"" + line + "\" came)");
+ }
+ }
+ } while (allowGarbage);
+
+ throw new VCardException("Reached where must not be reached.");
+ }
+
+ /**
+ * <p>
+ * The arguments useCache and allowGarbase are usually true and false
+ * accordingly when this function is called outside this function itself.
+ * </p>
+ *
+ * @param useCache When true, line is obtained from mPreviousline.
+ * Otherwise, getLine() is used.
+ * @param allowGarbage When true, ignore non "END:VCARD" line.
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException,
+ VCardException {
+ String line;
+ do {
+ if (useCache) {
+ // Though vCard specification does not allow lower cases,
+ // some data may have them, so we allow it.
+ line = mPreviousLine;
+ } else {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Expected END:VCARD was not found.");
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ }
+
+ String[] strArray = line.split(":", 2);
+ if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return;
+ } else if (!allowGarbage) {
+ throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
+ }
+ useCache = false;
+ } while (allowGarbage);
+ }
+
+ /*
+ * items = *CRLF item / item
+ */
+ protected void parseItems() throws IOException, VCardException {
+ boolean ended = false;
+
+ if (mInterpreter != null) {
+ long start = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - start;
+ }
+ ended = parseItem();
+ if (mInterpreter != null && !ended) {
+ long start = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - start;
+ }
+
+ while (!ended) {
+ // follow VCARD ,it wont reach endProperty
+ if (mInterpreter != null) {
+ long start = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - start;
+ }
+ try {
+ ended = parseItem();
+ } catch (VCardInvalidCommentLineException e) {
+ Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+ ended = false;
+ }
+ if (mInterpreter != null && !ended) {
+ long start = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - start;
+ }
+ }
+ }
+
+ /*
+ * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
+ * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
+ * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
+ * "AGENT" [params] ":" vcard CRLF
+ */
+ protected boolean parseItem() throws IOException, VCardException {
+ mCurrentEncoding = DEFAULT_ENCODING;
+
+ final String line = getNonEmptyLine();
+ long start = System.currentTimeMillis();
+
+ String[] propertyNameAndValue = separateLineAndHandleGroup(line);
+ if (propertyNameAndValue == null) {
+ return true;
+ }
+ if (propertyNameAndValue.length != 2) {
+ throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
+ }
+ String propertyName = propertyNameAndValue[0].toUpperCase();
+ String propertyValue = propertyNameAndValue[1];
+
+ mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
+
+ if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) {
+ start = System.currentTimeMillis();
+ handleMultiplePropertyValue(propertyName, propertyValue);
+ mTimeParseAdrOrgN += System.currentTimeMillis() - start;
+ return false;
+ } else if (propertyName.equals("AGENT")) {
+ handleAgent(propertyValue);
+ return false;
+ } else if (isValidPropertyName(propertyName)) {
+ if (propertyName.equals("BEGIN")) {
+ if (propertyValue.equals("VCARD")) {
+ throw new VCardNestedException("This vCard has nested vCard data in it.");
+ } else {
+ throw new VCardException("Unknown BEGIN type: " + propertyValue);
+ }
+ } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) {
+ throw new VCardVersionException("Incompatible version: " + propertyValue + " != "
+ + getVersionString());
+ }
+ start = System.currentTimeMillis();
+ handlePropertyValue(propertyName, propertyValue);
+ mTimeParsePropertyValues += System.currentTimeMillis() - start;
+ return false;
+ }
+
+ throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+ }
+
+ // For performance reason, the states for group and property name are merged into one.
+ static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
+ static private final int STATE_PARAMS = 1;
+ // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
+ static private final int STATE_PARAMS_IN_DQUOTE = 2;
+
+ protected String[] separateLineAndHandleGroup(String line) throws VCardException {
+ final String[] propertyNameAndValue = new String[2];
+ final int length = line.length();
+ if (length > 0 && line.charAt(0) == '#') {
+ throw new VCardInvalidCommentLineException();
+ }
+
+ int state = STATE_GROUP_OR_PROPERTY_NAME;
+ int nameIndex = 0;
+
+ // This loop is developed so that we don't have to take care of bottle neck here.
+ // Refactor carefully when you need to do so.
+ for (int i = 0; i < length; i++) {
+ final char ch = line.charAt(i);
+ switch (state) {
+ case STATE_GROUP_OR_PROPERTY_NAME: {
+ if (ch == ':') { // End of a property name.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ } else if (ch == '.') { // Each group is followed by the dot.
+ final String groupName = line.substring(nameIndex, i);
+ if (groupName.length() == 0) {
+ Log.w(LOG_TAG, "Empty group found. Ignoring.");
+ } else if (mInterpreter != null) {
+ mInterpreter.propertyGroup(groupName);
+ }
+ nameIndex = i + 1; // Next should be another group or a property name.
+ } else if (ch == ';') { // End of property name and beginneng of parameters.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ nameIndex = i + 1;
+ state = STATE_PARAMS; // Start parameter parsing.
+ }
+ break;
+ }
+ case STATE_PARAMS: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS_IN_DQUOTE;
+ } else if (ch == ';') { // Starts another param.
+ handleParams(line.substring(nameIndex, i));
+ nameIndex = i + 1;
+ } else if (ch == ':') { // End of param and beginenning of values.
+ handleParams(line.substring(nameIndex, i));
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ }
+ break;
+ }
+ case STATE_PARAMS_IN_DQUOTE: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS;
+ }
+ break;
+ }
+ }
+ }
+
+ throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
+ }
+
+ /*
+ * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
+ * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
+ * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
+ * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
+ * [ws] word / knowntype
+ */
+ protected void handleParams(String params) throws VCardException {
+ final String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ final String paramName = strArray[0].trim().toUpperCase();
+ String paramValue = strArray[1].trim();
+ if (paramName.equals("TYPE")) {
+ handleType(paramValue);
+ } else if (paramName.equals("VALUE")) {
+ handleValue(paramValue);
+ } else if (paramName.equals("ENCODING")) {
+ handleEncoding(paramValue);
+ } else if (paramName.equals("CHARSET")) {
+ handleCharset(paramValue);
+ } else if (paramName.equals("LANGUAGE")) {
+ handleLanguage(paramValue);
+ } else if (paramName.startsWith("X-")) {
+ handleAnyParam(paramName, paramValue);
+ } else {
+ throw new VCardException("Unknown type \"" + paramName + "\"");
+ }
+ } else {
+ handleParamWithoutName(strArray[0]);
+ }
+ }
+
+ /**
+ * vCard 3.0 parser implementation may throw VCardException.
+ */
+ @SuppressWarnings("unused")
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ handleType(paramValue);
+ }
+
+ /*
+ * ptypeval = knowntype / "X-" word
+ */
+ protected void handleType(final String ptypeval) {
+ if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
+ || ptypeval.startsWith("X-"))
+ && !mUnknownTypeSet.contains(ptypeval)) {
+ mUnknownTypeSet.add(ptypeval);
+ Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("TYPE");
+ mInterpreter.propertyParamValue(ptypeval);
+ }
+ }
+
+ /*
+ * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+ */
+ protected void handleValue(final String pvalueval) {
+ if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
+ || pvalueval.startsWith("X-")
+ || mUnknownValueSet.contains(pvalueval))) {
+ mUnknownValueSet.add(pvalueval);
+ Log.w(LOG_TAG, String.format(
+ "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("VALUE");
+ mInterpreter.propertyParamValue(pvalueval);
+ }
+ }
+
+ /*
+ * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
+ */
+ protected void handleEncoding(String pencodingval) throws VCardException {
+ if (getAvailableEncodingSet().contains(pencodingval) ||
+ pencodingval.startsWith("X-")) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("ENCODING");
+ mInterpreter.propertyParamValue(pencodingval);
+ }
+ mCurrentEncoding = pencodingval;
+ } else {
+ throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
+ }
+ }
+
+ /**
+ * <p>
+ * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+ * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
+ * We allow any charset.
+ * </p>
+ */
+ protected void handleCharset(String charsetval) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("CHARSET");
+ mInterpreter.propertyParamValue(charsetval);
+ }
+ }
+
+ /**
+ * See also Section 7.1 of RFC 1521
+ */
+ protected void handleLanguage(String langval) throws VCardException {
+ String[] strArray = langval.split("-");
+ if (strArray.length != 2) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ String tmp = strArray[0];
+ int length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ tmp = strArray[1];
+ length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType("LANGUAGE");
+ mInterpreter.propertyParamValue(langval);
+ }
+ }
+
+ private boolean isAsciiLetter(char ch) {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Mainly for "X-" type. This accepts any kind of type without check.
+ */
+ protected void handleAnyParam(String paramName, String paramValue) {
+ if (mInterpreter != null) {
+ mInterpreter.propertyParamType(paramName);
+ mInterpreter.propertyParamValue(paramValue);
+ }
+ }
+
+ protected void handlePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ final String upperEncoding = mCurrentEncoding.toUpperCase();
+ if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
+ final long start = System.currentTimeMillis();
+ final String result = getQuotedPrintable(propertyValue);
+ if (mInterpreter != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mInterpreter.propertyValues(v);
+ }
+ mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
+ } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
+ || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
+ final long start = System.currentTimeMillis();
+ // It is very rare, but some BASE64 data may be so big that
+ // OutOfMemoryError occurs. To ignore such cases, use try-catch.
+ try {
+ final String result = getBase64(propertyValue);
+ if (mInterpreter != null) {
+ ArrayList<String> arrayList = new ArrayList<String>();
+ arrayList.add(result);
+ mInterpreter.propertyValues(arrayList);
+ }
+ } catch (OutOfMemoryError error) {
+ Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
+ if (mInterpreter != null) {
+ mInterpreter.propertyValues(null);
+ }
+ }
+ mTimeHandleBase64 += System.currentTimeMillis() - start;
+ } else {
+ if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
+ upperEncoding.startsWith("X-"))) {
+ Log.w(LOG_TAG,
+ String.format("The encoding \"%s\" is unsupported by vCard %s",
+ mCurrentEncoding, getVersionString()));
+ }
+
+ final long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(maybeUnescapeText(propertyValue));
+ mInterpreter.propertyValues(v);
+ }
+ mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
+ }
+ }
+
+ /**
+ * <p>
+ * Parses and returns Quoted-Printable.
+ * </p>
+ *
+ * @param firstString The string following a parameter name and attributes.
+ * Example: "string" in
+ * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
+ * @return whole Quoted-Printable string, including a given argument and
+ * following lines. Excludes the last empty line following to Quoted
+ * Printable lines.
+ * @throws IOException
+ * @throws VCardException
+ */
+ private String getQuotedPrintable(String firstString) throws IOException, VCardException {
+ // Specifically, there may be some padding between = and CRLF.
+ // See the following:
+ //
+ // qp-line := *(qp-segment transport-padding CRLF)
+ // qp-part transport-padding
+ // qp-segment := qp-section *(SPACE / TAB) "="
+ // ; Maximum length of 76 characters
+ //
+ // e.g. (from RFC 2045)
+ // Now's the time =
+ // for all folk to come=
+ // to the aid of their country.
+ if (firstString.trim().endsWith("=")) {
+ // remove "transport-padding"
+ int pos = firstString.length() - 1;
+ while (firstString.charAt(pos) != '=') {
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString.substring(0, pos + 1));
+ builder.append("\r\n");
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing a Quoted-Printable String");
+ }
+ if (line.trim().endsWith("=")) {
+ // remove "transport-padding"
+ pos = line.length() - 1;
+ while (line.charAt(pos) != '=') {
+ }
+ builder.append(line.substring(0, pos + 1));
+ builder.append("\r\n");
+ } else {
+ builder.append(line);
+ break;
+ }
+ }
+ return builder.toString();
+ } else {
+ return firstString;
+ }
+ }
+
+ protected String getBase64(String firstString) throws IOException, VCardException {
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * <p>
+ * Mainly for "ADR", "ORG", and "N"
+ * </p>
+ */
+ /*
+ * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr,
+ * Street, Locality, Region, Postal Code, Country Name orgparts =
+ * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are
+ * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family,
+ * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III,
+ * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a
+ * semicolon in this string, it must be escaped ; with a "\" character. We
+ * do not care the number of "strnosemi" here. We are not sure whether we
+ * should add "\" CRLF to each value. We exclude them for now.
+ */
+ protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some
+ // softwares/devices
+ // emit such data.
+ if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ propertyValue = getQuotedPrintable(propertyValue);
+ }
+
+ if (mInterpreter != null) {
+ mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
+ (getVersion() == VCardConfig.FLAG_V30)));
+ }
+ }
+
+ /*
+ * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
+ * error toward the AGENT property.
+ * // TODO: Support AGENT property.
+ * item =
+ * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
+ * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
+ */
+ protected void handleAgent(final String propertyValue) throws VCardException {
+ if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
+ // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
+ return;
+ } else {
+ throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
+ }
+ }
+
+ /**
+ * For vCard 3.0.
+ */
+ protected String maybeUnescapeText(final String text) {
+ return text;
+ }
+
+ /**
+ * Returns unescaped String if the character should be unescaped. Return
+ * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
+ * while "\x" should not be.
+ */
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ /* package */ static String unescapeCharacter(final char ch) {
+ // Original vCard 2.1 specification does not allow transformation
+ // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
+ // implementation of
+ // this class allowed them, so keep it as is.
+ if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
+ return String.valueOf(ch);
+ } else {
+ return null;
+ }
+ }
+
+ private void showPerformanceInfo() {
+ Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
+ if (mReader instanceof CustomBufferedReader) {
+ Log.d(LOG_TAG, "Total readLine time: "
+ + ((CustomBufferedReader) mReader).getTotalmillisecond() + " ms");
+ }
+ Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms");
+ Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup
+ + " ms");
+ Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
+ Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
+ Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms");
+ Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
+ }
+
+ /**
+ * @return {@link VCardConfig#FLAG_V21}
+ */
+ protected int getVersion() {
+ return VCardConfig.FLAG_V21;
+ }
+
+ /**
+ * @return {@link VCardConfig#FLAG_V30}
+ */
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V21;
+ }
+
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V21.sKnownPropertyNameSet;
+ }
+
+ protected Set<String> getKnownTypeSet() {
+ return VCardParser_V21.sKnownTypeSet;
+ }
+
+ protected Set<String> getKnownValueSet() {
+ return VCardParser_V21.sKnownValueSet;
+ }
+
+ protected Set<String> getAvailableEncodingSet() {
+ return VCardParser_V21.sAvailableEncoding;
+ }
+
+ protected String getDefaultEncoding() {
+ return DEFAULT_ENCODING;
+ }
+
+
+ public void parse(InputStream is, VCardInterpreter interpreter)
+ throws IOException, VCardException {
+ if (is == null) {
+ throw new NullPointerException("InputStream must not be null.");
+ }
+
+ final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset);
+ if (VCardConfig.showPerformanceLog()) {
+ mReader = new CustomBufferedReader(tmpReader);
+ } else {
+ mReader = new BufferedReader(tmpReader);
+ }
+
+ mInterpreter = interpreter;
+
+ final long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ mInterpreter.start();
+ }
+ parseVCardFile();
+ if (mInterpreter != null) {
+ mInterpreter.end();
+ }
+ mTimeTotal += System.currentTimeMillis() - start;
+
+ if (VCardConfig.showPerformanceLog()) {
+ showPerformanceInfo();
+ }
+ }
+
+ public final void cancel() {
+ mCanceled = true;
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardParserImpl_V30.java b/vcard/java/com/android/vcard/VCardParserImpl_V30.java
new file mode 100644
index 0000000..def1495
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardParserImpl_V30.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import android.util.Log;
+
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard 3.0 parsing.
+ * </p>
+ * <p>
+ * This class inherits vCard 2.1 implementation since technically they are similar,
+ * while specifically there's logical no relevance between them.
+ * So that developers are not confused with the inheritance,
+ * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
+ * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V30";
+
+ private String mPreviousLine;
+ private boolean mEmittedAgentWarning = false;
+
+ public VCardParserImpl_V30() {
+ super();
+ }
+
+ public VCardParserImpl_V30(int vcardType) {
+ super(vcardType);
+ }
+
+ @Override
+ protected int getVersion() {
+ return VCardConfig.FLAG_V30;
+ }
+
+ @Override
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V30;
+ }
+
+ @Override
+ protected String getLine() throws IOException {
+ if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ } else {
+ return mReader.readLine();
+ }
+ }
+
+ /**
+ * vCard 3.0 requires that the line with space at the beginning of the line
+ * must be combined with previous line.
+ */
+ @Override
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ StringBuilder builder = null;
+ while (true) {
+ line = mReader.readLine();
+ if (line == null) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.length() == 0) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
+ if (builder != null) {
+ // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
+ // Following is the excerpts from it.
+ //
+ // DESCRIPTION:This is a long description that exists on a long line.
+ //
+ // Can be represented as:
+ //
+ // DESCRIPTION:This is a long description
+ // that exists on a long line.
+ //
+ // It could also be represented as:
+ //
+ // DESCRIPTION:This is a long descrip
+ // tion that exists o
+ // n a long line.
+ builder.append(line.substring(1));
+ } else if (mPreviousLine != null) {
+ builder = new StringBuilder();
+ builder.append(mPreviousLine);
+ mPreviousLine = null;
+ builder.append(line.substring(1));
+ } else {
+ throw new VCardException("Space exists at the beginning of the line");
+ }
+ } else {
+ if (mPreviousLine == null) {
+ mPreviousLine = line;
+ if (builder != null) {
+ return builder.toString();
+ }
+ } else {
+ String ret = mPreviousLine;
+ mPreviousLine = line;
+ return ret;
+ }
+ }
+ }
+ }
+
+ /*
+ * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+ * 1 * (contentline)
+ * ;A vCard object MUST include the VERSION, FN and N types.
+ * [group "."] "END" ":" "VCARD" 1 * CRLF
+ */
+ @Override
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ return super.readBeginVCard(allowGarbage);
+ }
+
+ @Override
+ protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ super.readEndVCard(useCache, allowGarbage);
+ }
+
+ /**
+ * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
+ */
+ @Override
+ protected void handleParams(final String params) throws VCardException {
+ try {
+ super.handleParams(params);
+ } catch (VCardException e) {
+ // maybe IANA type
+ String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ handleAnyParam(strArray[0], strArray[1]);
+ } else {
+ // Must not come here in the current implementation.
+ throw new VCardException(
+ "Unknown params value: " + params);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAnyParam(final String paramName, final String paramValue) {
+ super.handleAnyParam(paramName, paramValue);
+ }
+
+ @Override
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ super.handleParamWithoutName(paramValue);
+ }
+
+ /*
+ * vCard 3.0 defines
+ *
+ * param = param-name "=" param-value *("," param-value)
+ * param-name = iana-token / x-name
+ * param-value = ptext / quoted-string
+ * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+ */
+ @Override
+ protected void handleType(final String ptypevalues) {
+ String[] ptypeArray = ptypevalues.split(",");
+ mInterpreter.propertyParamType("TYPE");
+ for (String value : ptypeArray) {
+ int length = value.length();
+ if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
+ mInterpreter.propertyParamValue(value.substring(1, value.length() - 1));
+ } else {
+ mInterpreter.propertyParamValue(value);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAgent(final String propertyValue) {
+ // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
+ //
+ // e.g.
+ // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
+ // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
+ // ET:jfriday@host.com\nEND:VCARD\n
+ //
+ // TODO: fix this.
+ //
+ // issue:
+ // vCard 3.0 also allows this as an example.
+ //
+ // AGENT;VALUE=uri:
+ // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
+ //
+ // This is not vCard. Should we support this?
+ //
+ // Just ignore the line for now, since we cannot know how to handle it...
+ if (!mEmittedAgentWarning) {
+ Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
+ mEmittedAgentWarning = true;
+ }
+ }
+
+ /**
+ * vCard 3.0 does not require two CRLF at the last of BASE64 data.
+ * It only requires that data should be MIME-encoded.
+ */
+ @Override
+ protected String getBase64(final String firstString)
+ throws IOException, VCardException {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ final String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
+ mPreviousLine = line;
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
+ * ; \\ encodes \, \n or \N encodes newline
+ * ; \; encodes ;, \, encodes ,
+ *
+ * Note: Apple escapes ':' into '\:' while does not escape '\'
+ */
+ @Override
+ protected String maybeUnescapeText(final String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(final String text) {
+ StringBuilder builder = new StringBuilder();
+ final int length = text.length();
+ for (int i = 0; i < length; i++) {
+ char ch = text.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ final char next_ch = text.charAt(++i);
+ if (next_ch == 'n' || next_ch == 'N') {
+ builder.append("\n");
+ } else {
+ builder.append(next_ch);
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(final char ch) {
+ if (ch == 'n' || ch == 'N') {
+ return "\n";
+ } else {
+ return String.valueOf(ch);
+ }
+ }
+
+ @Override
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V30.sKnownPropertyNameSet;
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardParser_V21.java b/vcard/java/com/android/vcard/VCardParser_V21.java
new file mode 100644
index 0000000..7aa7a82
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardParser_V21.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * </p>
+ * vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
+ * </p>
+ * <p>
+ * The spec is written in 1996, and currently various types of "vCard 2.1" exist.
+ * To handle real the world vCard formats appropriately and effectively, this class does not
+ * obey with strict vCard 2.1.
+ * In stead, not only vCard spec but also real world vCard is considered.
+ * </p>
+ * e.g. A lot of devices and softwares let vCard importer/exporter to use
+ * the PNG format to determine the type of image, while it is not allowed in
+ * the original specification. As of 2010, we can see even the FLV format
+ * (possible in Japanese mobile phones).
+ * </p>
+ */
+public final class VCardParser_V21 implements VCardParser {
+ /**
+ * A unmodifiable Set storing the property names available in the vCard 2.1 specification.
+ */
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+ "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
+
+ /**
+ * A unmodifiable Set storing the types known in vCard 2.1.
+ */
+ /* package */ static final Set<String> sKnownTypeSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
+ "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
+ "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
+ "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
+ "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
+ "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
+ "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
+ "WAVE", "AIFF", "PCM", "X509", "PGP")));
+
+ /**
+ * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
+ */
+ /* package */ static final Set<String> sKnownValueSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
+
+ /**
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
+ * </p>
+ * <p>
+ * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
+ * We allow it for safety.
+ * </p>
+ */
+ /* package */ static final Set<String> sAvailableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_QP,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
+
+ private final VCardParserImpl_V21 mVCardParserImpl;
+
+ public VCardParser_V21() {
+ mVCardParserImpl = new VCardParserImpl_V21();
+ }
+
+ public VCardParser_V21(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V21(vcardType);
+ }
+
+ public void parse(InputStream is, VCardInterpreter interepreter)
+ throws IOException, VCardException {
+ mVCardParserImpl.parse(is, interepreter);
+ }
+
+ public void cancel() {
+ mVCardParserImpl.cancel();
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardParser_V30.java b/vcard/java/com/android/vcard/VCardParser_V30.java
new file mode 100644
index 0000000..475534c
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardParser_V30.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * vCard parser for vCard 3.0. See RFC 2426 for more detail.
+ * </p>
+ * <p>
+ * This parser allows vCard format which is not allowed in the RFC, since
+ * we have seen several vCard 3.0 files which don't comply with it.
+ * </p>
+ * <p>
+ * e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
+ * have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
+ * but it is not a must. We silently allow "CHARSET".
+ * </p>
+ */
+public class VCardParser_V30 implements VCardParser {
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+ "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
+ "NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
+ "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
+
+ /**
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
+ * </p>
+ * <p>
+ * Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
+ * </p>
+ * <p>
+ * "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
+ * because the encoding ambiguates how the vCard file to be parsed.
+ * </p>
+ */
+ /* package */ static final Set<String> sAcceptableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
+
+ private final VCardParserImpl_V30 mVCardParserImpl;
+
+ public VCardParser_V30() {
+ mVCardParserImpl = new VCardParserImpl_V30();
+ }
+
+ public VCardParser_V30(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V30(vcardType);
+ }
+
+ public void parse(InputStream is, VCardInterpreter interepreter)
+ throws IOException, VCardException {
+ mVCardParserImpl.parse(is, interepreter);
+ }
+
+ public void cancel() {
+ mVCardParserImpl.cancel();
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardSourceDetector.java b/vcard/java/com/android/vcard/VCardSourceDetector.java
new file mode 100644
index 0000000..e70d496
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardSourceDetector.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * The class which tries to detects the source of a vCard file from its contents.
+ * </p>
+ * <p>
+ * The specification of vCard (including both 2.1 and 3.0) is not so strict as to
+ * guess its format just by reading beginning few lines (usually we can, but in
+ * some most pessimistic case, we cannot until at almost the end of the file).
+ * Also we cannot store all vCard entries in memory, while there's no specification
+ * how big the vCard entry would become after the parse.
+ * </p>
+ * <p>
+ * This class is usually used for the "first scan", in which we can understand which vCard
+ * version is used (and how many entries exist in a file).
+ * </p>
+ */
+public class VCardSourceDetector implements VCardInterpreter {
+ private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
+ "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
+ "X-ABADR", "X-ABUID"));
+
+ private static Set<String> JAPANESE_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
+ "X-GNO", "X-GN", "X-REDUCTION"));
+
+ private static Set<String> WINDOWS_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
+ "X-MICROSOFT-ASST_TEL", "X-MICROSOFT-ASSISTANT", "X-MICROSOFT-OFFICELOC"));
+
+ // Note: these signes appears before the signs of the other type (e.g. "X-GN").
+ // In other words, Japanese FOMA mobile phones are detected as FOMA, not JAPANESE_MOBILE_PHONES.
+ private static Set<String> FOMA_SIGNS = new HashSet<String>(Arrays.asList(
+ "X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
+ "X-SD-DESCRIPTION"));
+ private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
+
+
+ // TODO: Should replace this with types in VCardConfig
+ private static final int PARSE_TYPE_UNKNOWN = 0;
+ // For Apple's software, which does not mean this type is effective for all its products.
+ // We confirmed they usually use UTF-8, but not sure about vCard type.
+ private static final int PARSE_TYPE_APPLE = 1;
+ // For Japanese mobile phones, which are usually using Shift_JIS as a charset.
+ private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
+ // For some of mobile phones released from DoCoMo, which use nested vCard.
+ private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
+ // For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
+ private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
+
+ private int mParseType = 0; // Not sure.
+
+ // Some mobile phones (like FOMA) tells us the charset of the data.
+ private boolean mNeedParseSpecifiedCharset;
+ private String mSpecifiedCharset;
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ public void startEntry() {
+ }
+
+ public void startProperty() {
+ mNeedParseSpecifiedCharset = false;
+ }
+
+ public void endProperty() {
+ }
+
+ public void endEntry() {
+ }
+
+ public void propertyGroup(String group) {
+ }
+
+ public void propertyName(String name) {
+ if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
+ mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
+ // Probably Shift_JIS is used, but we should double confirm.
+ mNeedParseSpecifiedCharset = true;
+ return;
+ }
+ if (mParseType != PARSE_TYPE_UNKNOWN) {
+ return;
+ }
+ if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
+ mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
+ } else if (FOMA_SIGNS.contains(name)) {
+ mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
+ } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
+ mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
+ } else if (APPLE_SIGNS.contains(name)) {
+ mParseType = PARSE_TYPE_APPLE;
+ }
+ }
+
+ public void propertyParamType(String type) {
+ }
+
+ public void propertyParamValue(String value) {
+ }
+
+ public void propertyValues(List<String> values) {
+ if (mNeedParseSpecifiedCharset && values.size() > 0) {
+ mSpecifiedCharset = values.get(0);
+ }
+ }
+
+ /**
+ * @return The available type can be used with vCard parser. You probably need to
+ * use {{@link #getEstimatedCharset()} to understand the charset to be used.
+ */
+ public int getEstimatedType() {
+ switch (mParseType) {
+ case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+ return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
+ case PARSE_TYPE_MOBILE_PHONE_JP:
+ return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
+ case PARSE_TYPE_APPLE:
+ case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+ default:
+ return VCardConfig.VCARD_TYPE_UNKNOWN;
+ }
+ }
+
+ /**
+ * <p>
+ * Returns charset String guessed from the source's properties.
+ * This method must be called after parsing target file(s).
+ * </p>
+ * @return Charset String. Null is returned if guessing the source fails.
+ */
+ public String getEstimatedCharset() {
+ if (TextUtils.isEmpty(mSpecifiedCharset)) {
+ return mSpecifiedCharset;
+ }
+ switch (mParseType) {
+ case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+ case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+ case PARSE_TYPE_MOBILE_PHONE_JP:
+ return "SHIFT_JIS";
+ case PARSE_TYPE_APPLE:
+ return "UTF-8";
+ default:
+ return null;
+ }
+ }
+}
diff --git a/vcard/java/com/android/vcard/VCardUtils.java b/vcard/java/com/android/vcard/VCardUtils.java
new file mode 100644
index 0000000..fb0c2e7
--- /dev/null
+++ b/vcard/java/com/android/vcard/VCardUtils.java
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+
+import android.content.ContentProviderOperation;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utilities for VCard handling codes.
+ */
+public class VCardUtils {
+ private static final String LOG_TAG = "VCardUtils";
+
+ // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
+ // converted to two parameter Strings. These only contain some minor fields valid in both
+ // vCard and current (as of 2009-08-07) Contacts structure.
+ private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
+ private static final Set<String> sPhoneTypesUnknownToContactsSet;
+ private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
+ private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
+ private static final Set<String> sMobilePhoneLabelSet;
+
+ static {
+ sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
+ sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
+
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
+
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
+
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
+ Phone.TYPE_CALLBACK);
+ sKnownPhoneTypeMap_StoI.put(
+ VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
+ Phone.TYPE_TTY_TDD);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
+ Phone.TYPE_ASSISTANT);
+
+ sPhoneTypesUnknownToContactsSet = new HashSet<String>();
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
+
+ sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
+ VCardConstants.PROPERTY_X_GOOGLE_TALK);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
+
+ // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
+ // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
+ // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
+ // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
+ sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
+ "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
+ "\uFF79\uFF72\uFF80\uFF72"));
+ }
+
+ public static String getPhoneTypeString(Integer type) {
+ return sKnownPhoneTypesMap_ItoS.get(type);
+ }
+
+ /**
+ * Returns Interger when the given types can be parsed as known type. Returns String object
+ * when not, which should be set to label.
+ */
+ public static Object getPhoneTypeFromStrings(Collection<String> types,
+ String number) {
+ if (number == null) {
+ number = "";
+ }
+ int type = -1;
+ String label = null;
+ boolean isFax = false;
+ boolean hasPref = false;
+
+ if (types != null) {
+ for (String typeString : types) {
+ if (typeString == null) {
+ continue;
+ }
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+ hasPref = true;
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
+ isFax = true;
+ } else {
+ if (typeString.startsWith("X-") && type < 0) {
+ typeString = typeString.substring(2);
+ }
+ if (typeString.length() == 0) {
+ continue;
+ }
+ final Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
+ if (tmp != null) {
+ final int typeCandidate = tmp;
+ // TYPE_PAGER is prefered when the number contains @ surronded by
+ // a pager number and a domain name.
+ // e.g.
+ // o 1111@domain.com
+ // x @domain.com
+ // x 1111@
+ final int indexOfAt = number.indexOf("@");
+ if ((typeCandidate == Phone.TYPE_PAGER
+ && 0 < indexOfAt && indexOfAt < number.length() - 1)
+ || type < 0
+ || type == Phone.TYPE_CUSTOM) {
+ type = tmp;
+ }
+ } else if (type < 0) {
+ type = Phone.TYPE_CUSTOM;
+ label = typeString;
+ }
+ }
+ }
+ }
+ if (type < 0) {
+ if (hasPref) {
+ type = Phone.TYPE_MAIN;
+ } else {
+ // default to TYPE_HOME
+ type = Phone.TYPE_HOME;
+ }
+ }
+ if (isFax) {
+ if (type == Phone.TYPE_HOME) {
+ type = Phone.TYPE_FAX_HOME;
+ } else if (type == Phone.TYPE_WORK) {
+ type = Phone.TYPE_FAX_WORK;
+ } else if (type == Phone.TYPE_OTHER) {
+ type = Phone.TYPE_OTHER_FAX;
+ }
+ }
+ if (type == Phone.TYPE_CUSTOM) {
+ return label;
+ } else {
+ return type;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public static boolean isMobilePhoneLabel(final String label) {
+ // For backward compatibility.
+ // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
+ // To support mobile type at that time, this custom label had been used.
+ return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
+ }
+
+ public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
+ return sPhoneTypesUnknownToContactsSet.contains(label);
+ }
+
+ public static String getPropertyNameForIm(final int protocol) {
+ return sKnownImPropNameMap_ItoS.get(protocol);
+ }
+
+ public static String[] sortNameElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName) {
+ final String[] list = new String[3];
+ final int nameOrderType = VCardConfig.getNameOrderType(vcardType);
+ switch (nameOrderType) {
+ case VCardConfig.NAME_ORDER_JAPANESE: {
+ if (containsOnlyPrintableAscii(familyName) &&
+ containsOnlyPrintableAscii(givenName)) {
+ list[0] = givenName;
+ list[1] = middleName;
+ list[2] = familyName;
+ } else {
+ list[0] = familyName;
+ list[1] = middleName;
+ list[2] = givenName;
+ }
+ break;
+ }
+ case VCardConfig.NAME_ORDER_EUROPE: {
+ list[0] = middleName;
+ list[1] = givenName;
+ list[2] = familyName;
+ break;
+ }
+ default: {
+ list[0] = givenName;
+ list[1] = middleName;
+ list[2] = familyName;
+ break;
+ }
+ }
+ return list;
+ }
+
+ public static int getPhoneNumberFormat(final int vcardType) {
+ if (VCardConfig.isJapaneseDevice(vcardType)) {
+ return PhoneNumberUtils.FORMAT_JAPAN;
+ } else {
+ return PhoneNumberUtils.FORMAT_NANP;
+ }
+ }
+
+ /**
+ * <p>
+ * Inserts postal data into the builder object.
+ * </p>
+ * <p>
+ * Note that the data structure of ContactsContract is different from that defined in vCard.
+ * So some conversion may be performed in this method.
+ * </p>
+ */
+ public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
+ final ContentProviderOperation.Builder builder,
+ final VCardEntry.PostalData postalData) {
+ builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
+
+ builder.withValue(StructuredPostal.TYPE, postalData.type);
+ if (postalData.type == StructuredPostal.TYPE_CUSTOM) {
+ builder.withValue(StructuredPostal.LABEL, postalData.label);
+ }
+
+ final String streetString;
+ if (TextUtils.isEmpty(postalData.street)) {
+ if (TextUtils.isEmpty(postalData.extendedAddress)) {
+ streetString = null;
+ } else {
+ streetString = postalData.extendedAddress;
+ }
+ } else {
+ if (TextUtils.isEmpty(postalData.extendedAddress)) {
+ streetString = postalData.street;
+ } else {
+ streetString = postalData.street + " " + postalData.extendedAddress;
+ }
+ }
+ builder.withValue(StructuredPostal.POBOX, postalData.pobox);
+ builder.withValue(StructuredPostal.STREET, streetString);
+ builder.withValue(StructuredPostal.CITY, postalData.localty);
+ builder.withValue(StructuredPostal.REGION, postalData.region);
+ builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
+ builder.withValue(StructuredPostal.COUNTRY, postalData.country);
+
+ builder.withValue(StructuredPostal.FORMATTED_ADDRESS,
+ postalData.getFormattedAddress(vcardType));
+ if (postalData.isPrimary) {
+ builder.withValue(Data.IS_PRIMARY, 1);
+ }
+ }
+
+ public static String constructNameFromElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName) {
+ return constructNameFromElements(vcardType, familyName, middleName, givenName,
+ null, null);
+ }
+
+ public static String constructNameFromElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName,
+ final String prefix, final String suffix) {
+ final StringBuilder builder = new StringBuilder();
+ final String[] nameList = sortNameElements(vcardType, familyName, middleName, givenName);
+ boolean first = true;
+ if (!TextUtils.isEmpty(prefix)) {
+ first = false;
+ builder.append(prefix);
+ }
+ for (final String namePart : nameList) {
+ if (!TextUtils.isEmpty(namePart)) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(' ');
+ }
+ builder.append(namePart);
+ }
+ }
+ if (!TextUtils.isEmpty(suffix)) {
+ if (!first) {
+ builder.append(' ');
+ }
+ builder.append(suffix);
+ }
+ return builder.toString();
+ }
+
+ public static List<String> constructListFromValue(final String value,
+ final boolean isV30) {
+ final List<String> list = new ArrayList<String>();
+ StringBuilder builder = new StringBuilder();
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ char nextCh = value.charAt(i + 1);
+ final String unescapedString =
+ (isV30 ? VCardParserImpl_V30.unescapeCharacter(nextCh) :
+ VCardParserImpl_V21.unescapeCharacter(nextCh));
+ if (unescapedString != null) {
+ builder.append(unescapedString);
+ i++;
+ } else {
+ builder.append(ch);
+ }
+ } else if (ch == ';') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else {
+ builder.append(ch);
+ }
+ }
+ list.add(builder.toString());
+ return list;
+ }
+
+ public static boolean containsOnlyPrintableAscii(final String...values) {
+ if (values == null) {
+ return true;
+ }
+ return containsOnlyPrintableAscii(Arrays.asList(values));
+ }
+
+ public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ for (final String value : values) {
+ if (TextUtils.isEmpty(value)) {
+ continue;
+ }
+ if (!TextUtils.isPrintableAsciiOnly(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>
+ * This is useful when checking the string should be encoded into quoted-printable
+ * or not, which is required by vCard 2.1.
+ * </p>
+ * <p>
+ * See the definition of "7bit" in vCard 2.1 spec for more information.
+ * </p>
+ */
+ public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
+ if (values == null) {
+ return true;
+ }
+ return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
+ }
+
+ public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x7E; // included
+ for (final String value : values) {
+ if (TextUtils.isEmpty(value)) {
+ continue;
+ }
+ final int length = value.length();
+ for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+ final int c = value.codePointAt(i);
+ if (!(asciiFirst <= c && c <= asciiLast)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
+ new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
+
+ /**
+ * <p>
+ * This is useful since vCard 3.0 often requires the ("X-") properties and groups
+ * should contain only alphabets, digits, and hyphen.
+ * </p>
+ * <p>
+ * Note: It is already known some devices (wrongly) outputs properties with characters
+ * which should not be in the field. One example is "X-GOOGLE TALK". We accept
+ * such kind of input but must never output it unless the target is very specific
+ * to the device which is able to parse the malformed input.
+ * </p>
+ */
+ public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
+ if (values == null) {
+ return true;
+ }
+ return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
+ }
+
+ public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ final int upperAlphabetFirst = 0x41; // A
+ final int upperAlphabetAfterLast = 0x5b; // [
+ final int lowerAlphabetFirst = 0x61; // a
+ final int lowerAlphabetAfterLast = 0x7b; // {
+ final int digitFirst = 0x30; // 0
+ final int digitAfterLast = 0x3A; // :
+ final int hyphen = '-';
+ for (final String str : values) {
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ final int length = str.length();
+ for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+ int codepoint = str.codePointAt(i);
+ if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
+ (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
+ (digitFirst <= codepoint && codepoint < digitAfterLast) ||
+ (codepoint == hyphen))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>
+ * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
+ * </p>
+ * <p>
+ * vCard 2.1 specifies:<br />
+ * word = <any printable 7bit us-ascii except []=:., >
+ * </p>
+ */
+ public static boolean isV21Word(final String value) {
+ if (TextUtils.isEmpty(value)) {
+ return true;
+ }
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x7E; // included
+ final int length = value.length();
+ for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+ final int c = value.codePointAt(i);
+ if (!(asciiFirst <= c && c <= asciiLast) ||
+ sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static String toHalfWidthString(final String orgString) {
+ if (TextUtils.isEmpty(orgString)) {
+ return null;
+ }
+ final StringBuilder builder = new StringBuilder();
+ final int length = orgString.length();
+ for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
+ // All Japanese character is able to be expressed by char.
+ // Do not need to use String#codepPointAt().
+ final char ch = orgString.charAt(i);
+ final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
+ if (halfWidthText != null) {
+ builder.append(halfWidthText);
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Guesses the format of input image. Currently just the first few bytes are used.
+ * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
+ * the guess failed.
+ * @param input Image as byte array.
+ * @return The image type or null when the type cannot be determined.
+ */
+ public static String guessImageType(final byte[] input) {
+ if (input == null) {
+ return null;
+ }
+ if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
+ return "GIF";
+ } else if (input.length >= 4 && input[0] == (byte) 0x89
+ && input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
+ // Note: vCard 2.1 officially does not support PNG, but we may have it and
+ // using X- word like "X-PNG" may not let importers know it is PNG.
+ // So we use the String "PNG" as is...
+ return "PNG";
+ } else if (input.length >= 2 && input[0] == (byte) 0xff
+ && input[1] == (byte) 0xd8) {
+ return "JPEG";
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return True when all the given values are null or empty Strings.
+ */
+ public static boolean areAllEmpty(final String...values) {
+ if (values == null) {
+ return true;
+ }
+
+ for (final String value : values) {
+ if (!TextUtils.isEmpty(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //// The methods bellow may be used by unit test.
+
+ /**
+ * @hide
+ */
+ public static String parseQuotedPrintable(String value, boolean strictLineBreaking,
+ String sourceCharset, String targetCharset) {
+ // "= " -> " ", "=\t" -> "\t".
+ // Previous code had done this replacement. Keep on the safe side.
+ final String quotedPrintable;
+ {
+ final StringBuilder builder = new StringBuilder();
+ final int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch == '=' && i < length - 1) {
+ char nextCh = value.charAt(i + 1);
+ if (nextCh == ' ' || nextCh == '\t') {
+ builder.append(nextCh);
+ i++;
+ continue;
+ }
+ }
+ builder.append(ch);
+ }
+ quotedPrintable = builder.toString();
+ }
+
+ String[] lines;
+ if (strictLineBreaking) {
+ lines = quotedPrintable.split("\r\n");
+ } else {
+ StringBuilder builder = new StringBuilder();
+ final int length = quotedPrintable.length();
+ ArrayList<String> list = new ArrayList<String>();
+ for (int i = 0; i < length; i++) {
+ char ch = quotedPrintable.charAt(i);
+ if (ch == '\n') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else if (ch == '\r') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ if (i < length - 1) {
+ char nextCh = quotedPrintable.charAt(i + 1);
+ if (nextCh == '\n') {
+ i++;
+ }
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ final String lastLine = builder.toString();
+ if (lastLine.length() > 0) {
+ list.add(lastLine);
+ }
+ lines = list.toArray(new String[0]);
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ for (String line : lines) {
+ if (line.endsWith("=")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ builder.append(line);
+ }
+
+ final String rawString = builder.toString();
+ if (TextUtils.isEmpty(rawString)) {
+ Log.w(LOG_TAG, "Given raw string is empty.");
+ }
+
+ byte[] rawBytes = null;
+ try {
+ rawBytes = rawString.getBytes(sourceCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
+ rawBytes = rawString.getBytes();
+ }
+
+ byte[] decodedBytes = null;
+ try {
+ decodedBytes = QuotedPrintableCodec.decodeQuotedPrintable(rawBytes);
+ } catch (DecoderException e) {
+ Log.e(LOG_TAG, "DecoderException is thrown.");
+ decodedBytes = rawBytes;
+ }
+
+ try {
+ return new String(decodedBytes, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(decodedBytes);
+ }
+ }
+
+ private VCardUtils() {
+ }
+}
diff --git a/vcard/java/com/android/vcard/exception/VCardAgentNotSupportedException.java b/vcard/java/com/android/vcard/exception/VCardAgentNotSupportedException.java
new file mode 100644
index 0000000..c408716
--- /dev/null
+++ b/vcard/java/com/android/vcard/exception/VCardAgentNotSupportedException.java
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/vcard/java/com/android/vcard/exception/VCardException.java b/vcard/java/com/android/vcard/exception/VCardException.java
new file mode 100644
index 0000000..3ad7fd3
--- /dev/null
+++ b/vcard/java/com/android/vcard/exception/VCardException.java
@@ -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);
+ }
+
+}
diff --git a/vcard/java/com/android/vcard/exception/VCardInvalidCommentLineException.java b/vcard/java/com/android/vcard/exception/VCardInvalidCommentLineException.java
new file mode 100644
index 0000000..342769e
--- /dev/null
+++ b/vcard/java/com/android/vcard/exception/VCardInvalidCommentLineException.java
@@ -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);
+ }
+}
diff --git a/vcard/java/com/android/vcard/exception/VCardInvalidLineException.java b/vcard/java/com/android/vcard/exception/VCardInvalidLineException.java
new file mode 100644
index 0000000..5c2250f
--- /dev/null
+++ b/vcard/java/com/android/vcard/exception/VCardInvalidLineException.java
@@ -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);
+ }
+}
diff --git a/vcard/java/com/android/vcard/exception/VCardNestedException.java b/vcard/java/com/android/vcard/exception/VCardNestedException.java
new file mode 100644
index 0000000..2b9b1ac
--- /dev/null
+++ b/vcard/java/com/android/vcard/exception/VCardNestedException.java
@@ -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);
+ }
+}
diff --git a/vcard/java/com/android/vcard/exception/VCardNotSupportedException.java b/vcard/java/com/android/vcard/exception/VCardNotSupportedException.java
new file mode 100644
index 0000000..61ff752
--- /dev/null
+++ b/vcard/java/com/android/vcard/exception/VCardNotSupportedException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/vcard/java/com/android/vcard/exception/VCardVersionException.java b/vcard/java/com/android/vcard/exception/VCardVersionException.java
new file mode 100644
index 0000000..047c580
--- /dev/null
+++ b/vcard/java/com/android/vcard/exception/VCardVersionException.java
@@ -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);
+ }
+}
diff --git a/vcard/tests/Android.mk b/vcard/tests/Android.mk
new file mode 100644
index 0000000..853ee14
--- /dev/null
+++ b/vcard/tests/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := AndroidVCardTests
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LIBRARIES := android.test.runner google-common
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard
+
+include $(BUILD_PACKAGE)
diff --git a/vcard/tests/AndroidManifest.xml b/vcard/tests/AndroidManifest.xml
new file mode 100644
index 0000000..fcbf767
--- /dev/null
+++ b/vcard/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.vcard.tests"
+ android:sharedUserId="com.android.uid.test">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!-- Run tests with "runtest common" -->
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.vcard.tests"
+ android:label="Android vCard Library Tests" />
+
+</manifest>
diff --git a/vcard/tests/res/raw/v21_backslash.vcf b/vcard/tests/res/raw/v21_backslash.vcf
new file mode 100644
index 0000000..bd3002b
--- /dev/null
+++ b/vcard/tests/res/raw/v21_backslash.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD
+VERSION:2.1
+N:;A\;B\\;C\\\;;D;\:E;\\\\;
+FN:A;B\C\;D:E\\
+END:VCARD
diff --git a/vcard/tests/res/raw/v21_complicated.vcf b/vcard/tests/res/raw/v21_complicated.vcf
new file mode 100644
index 0000000..de34e16
--- /dev/null
+++ b/vcard/tests/res/raw/v21_complicated.vcf
@@ -0,0 +1,106 @@
+BEGIN:VCARD
+VERSION:2.1
+N:Gump;Forrest;Hoge;Pos;Tao
+FN:Joe Due
+ORG:Gump Shrimp Co.;Sales Dept.\;Manager;Fish keeper
+ROLE:Fish Cake Keeper!
+X-CLASS:PUBLIC
+TITLE:Shrimp Man
+TEL;WORK;VOICE:(111) 555-1212
+TEL;HOME;VOICE:(404) 555-1212
+TEL;CELL:0311111111
+TEL;VIDEO:0322222222
+TEL;VOICE:0333333333
+ADR;WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
+LABEL;WORK;ENCODING=QUOTED-PRINTABLE:100 Waters Edge=0D=0ABaytown, LA 30314=0D=0AUnited States of America
+ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
+LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A=
+Baytown, LA 30314=0D=0A=
+United States of America
+EMAIL;PREF;INTERNET:forrestgump@walladalla.com
+EMAIL;CELL:cell@example.com
+NOTE:The following note is the example from RFC 2045.
+NOTE;ENCODING=QUOTED-PRINTABLE:Now's the time =
+for all folk to come=
+ to the aid of their country.
+
+PHOTO;ENCODING=BASE64;TYPE=JPEG:
+ /9j/4QoPRXhpZgAATU0AKgAAAAgADQEOAAIAAAAPAAAAqgEPAAIAAAAHAAAAugEQAAIAAAAG
+ AAAAwgESAAMAAAABAAEAAAEaAAUAAAABAAAAyAEbAAUAAAABAAAA0AEoAAMAAAABAAIAAAEx
+ AAIAAAAOAAAA2AEyAAIAAAAUAAAA5gITAAMAAAABAAEAAIKYAAIAAAAOAAAA+odpAAQAAAAB
+ AAABhMSlAAcAAAB8AAABCAAABB4yMDA4MTAyOTEzNTUzMQAARG9Db01vAABEOTA1aQAAAABI
+ AAAAAQAAAEgAAAABRDkwNWkgVmVyMS4wMAAyMDA4OjEwOjI5IDEzOjU1OjQ3ACAgICAgICAg
+ ICAgICAAUHJpbnRJTQAwMzAwAAAABgABABQAFAACAQAAAAADAAAANAEABQAAAAEBAQAAAAEQ
+ gAAAAAAAEQkAACcQAAAPCwAAJxAAAAWXAAAnEAAACLAAACcQAAAcAQAAJxAAAAJeAAAnEAAA
+ AIsAACcQAAADywAAJxAAABvlAAAnEAAogpoABQAAAAEAAANqgp0ABQAAAAEAAANyiCIAAwAA
+ AAEAAgAAkAAABwAAAAQwMjIwkAMAAgAAABQAAAN6kAQAAgAAABQAAAOOkQEABwAAAAQBAgMA
+ kQIABQAAAAEAAAOikgEACgAAAAEAAAOqkgIABQAAAAEAAAOykgQACgAAAAEAAAO6kgUABQAA
+ AAEAAAPCkgcAAwAAAAEAAgAAkggAAwAAAAEAAAAAkgkAAwAAAAEAAAAAkgoABQAAAAEAAAPK
+ knwABwAAAAEAAAAAkoYABwAAABYAAAPSoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIAAwAA
+ AAEAYAAAoAMAAwAAAAEASAAAoAUABAAAAAEAAAQAog4ABQAAAAEAAAPoog8ABQAAAAEAAAPw
+ ohAAAwAAAAEAAgAAohcAAwAAAAEAAgAAowAABwAAAAEDAAAAowEABwAAAAEBAAAApAEAAwAA
+ AAEAAAAApAIAAwAAAAEAAAAApAMAAwAAAAEAAAAApAQABQAAAAEAAAP4pAUAAwAAAAEAHQAA
+ pAYAAwAAAAEAAAAApAcAAwAAAAEAAAAApAgAAwAAAAEAAAAApAkAAwAAAAEAAAAApAoAAwAA
+ AAEAAAAApAwAAwAAAAEAAgAAAAAAAAAAAFMAACcQAAABXgAAAGQyMDA4OjEwOjI5IDEzOjU1
+ OjMxADIwMDg6MTA6MjkgMTM6NTU6NDcAAAApiAAAGwAAAAKyAAAAZAAAAV4AAABkAAAAAAAA
+ AGQAAAAlAAAACgAADpIAAAPoAAAAAAAAAAAyMDA4MTAyOTEzNTUzMQAAICoAAAAKAAAq4gAA
+ AAoAAAAAAAAAAQACAAEAAgAAAARSOTgAAAIABwAAAAQwMTAwAAAAAAAGAQMAAwAAAAEABgAA
+ ARoABQAAAAEAAARsARsABQAAAAEAAAR0ASgAAwAAAAEAAgAAAgEABAAAAAEAAAR8AgIABAAA
+ AAEAAAWLAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAIQAIBYYHBgUIBwaHCQiICYwUDQwLCww
+ YkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxgEiJCQwKjBeNDRe
+ xoRwhMbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbG
+ /8AAEQgAeACgAwEhAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAA
+ AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK
+ FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl
+ 5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQH
+ BQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka
+ JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
+ lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz
+ 9PX29/j5+v/aAAwDAQACEQMRAD8AFFSqKkZIoqRVpgSKKeBTEOApwFADsUYpgIRSEUANIppF
+ ICNhUTCgCMio2FICJhULCgC0oqVaAJFFSqKBkgFOApiHCnCgB2KMUCENJQA0imEUDGMKiYUA
+ RtUbUgIWqJhQBZSpFoAlWpVoGPFPFMQ7tSK2ODQA4yKO9HmKe9FxAzDHFIOlAAaYaAGNUTUD
+ ImqNqQETVE1AE6VKKAJFNSqaAHg08GmANIFFQM5Y5qJMBuT60ZNQIcrkVYSQMKuLGKaaasQx
+ qiagZE1RtSAjaomoAkQ1KpoAlU1IpoAkU07OBTArO+5qkV12Y71lfUBmaKkCRSuznrTFba2a
+ oCwGyM0E1qIjY1GxoGRNUZNICNqiagByGplNAEimpFNMB4YDvSucpxSYEIU04KazsAu1qArU
+ WELtPpTSposBNETt5pxNaoCNjUbGgCNjUZoGRtUTUgFU1KpoAkBqQHigCFnO7rUqOdlZp6gA
+ c+tODn1pXAXzD60eYfWncQvmNSGQ07gOMhCVEJGz1ptgS5yKYxqwGE1GxoAiamGkMapqVTQB
+ Kpp+eKAICfmqWM/Kaz6gANOBqQFzRmmAuaTNACsfkqMHmm9wJs8U0mtRDGNRsaAI2phpDI1N
+ SqaAJFNSA8UCISfmqSM/Kaz6jAHmnA1ICg0uaAFzSZpgKx+SmDrTe4E2eKaTWoiMmmMaAIzT
+ DSGRKakU0ASKaeDTERseakjPyms+oxAacDUgOBpc0gFzSZpgOY/KKYv3qrqIlpprQBjGoyaA
+ GGmmkMgU1IppgPBqQGgQu0Gn4wvFKwEQpwNZDHZpc0ALmigRKBleaQKBWtgA001QDGqM0gGm
+ mGkMrqakBoAepp4NMRIDTwaAE2A008GokgHxjd1pzKFpW0uAg5NSBBTirgOpDWgDTTTQAw0w
+ 0gGGmmgZWBp4pASKaeDTEOBp4NADwajbrUyBEkXWnSUdAGr1qeiAMSkNWAhphoAaaYaQDDTT
+ SGVRTwaYDxTwaBDwaeDQA4GlK5oauIeo20pGaLaAKqgU6hKwBSGmAhphoAaaYaQxhpppDKgN
+ PFMB4p4oEPFOBpgPBp4NAhwpwoAWloAKSgBDTTQMYaYaQDTTTSGA/9n/2wCEAAoHBwgHBgoI
+ CAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9
+ PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7
+ Ozs7Ozs7Ozs7Ozs7O//AABEIAEgAYAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAA
+ AQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNC
+ scEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp
+ anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS
+ 09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
+ CQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVi
+ ctEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4
+ eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY
+ 2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AJ7SLgcVr20I4rNFGvbQAAHFaEUX
+ SrQi5HCMdKk8oY6VSJYx4hjpVaWMelAFC4iGDxWPdR8mkxmRdxjBrEvI+tZjN20xtHNbNqAc
+ UIDXg6Cr0WKtCY8XKQOyzOB3FKNWsyceZ+lS6sY6NkjvPSdwImBHUmmy4q076oCjOODWPdgc
+ 0MpGPdAYNYl4o5rNjNKzkyorZtXxihAa1vIDip7m9Frb7/4jwKcnyxbEzN3ieJppZsyZ4U1H
+ urzZau4mWVlNrGk0UuWPVa1YroXEIkHfrXZh5W90RWncAHmsi6bJNdQ0ZNw3BrGuiMGs2Mks
+ puBzWzbzdOaEBeOpR2oUtkk9hTru7iuo4m8wgemKyqTi04sBsfkEf68j8KlUQZz9o/SuZRj3
+ JYriAji4/Sp7W6htbV2aXcu70ramoxle4gN7HcIXjbis+4k5NdaaauhmVcv1rHuW61DGiG1m
+ 6c1s20/TmgAv5vmj57VKk3+ixnPc1xVV70h9CVJuOtSrL71hFgxzScUkkn+iY/2q1i9xDrGT
+ 9y31pJ5Otd1L+GhMy7mTrWXO2SapjRn28vTmta3nxjmgGOvJd2w1Kkv+ipz/ABGuOoveYdCe
+ ObjrU6y5rlsA8ycUksn+ij/eNaw6iJLNsW59zTJn6816FP4EJmbO+Saz5m602UjIgk4HNadv
+ LwKaBl+MpIMOMipp490SCJeF7CoqQvF2JuRqWQ4YEGrSiQJuKnHrXByMpki73GFBNXIoh9n2
+ SrnnOK6MPTbd3sSwIVF2qMCqkzHmuy1lYRnTHrVGWpZaMKB+BWlbycYoQM0IZDxzV+GU8c1a
+ IYy5Y+dnHatAsfsAHfArmS1mPoh1gT8x9qtk1rQX7tCe5DIapzGtGBQm71SlqGWjnIH6Vowt
+ zmhAy/E3vV6F6tEMuxlWIyAfrVxCCAO1VZEEyYA4AApxNGwyJ+lVJRUsaKMw61SlFQzRAP/Z
+
+X-ATTRIBUTE:Some String
+BDAY:19800101
+GEO:35.6563854,139.6994233
+URL:http://www.example.com/
+REV:20080424T195243Z
+END:VCARD
\ No newline at end of file
diff --git a/vcard/tests/res/raw/v21_invalid_comment_line.vcf b/vcard/tests/res/raw/v21_invalid_comment_line.vcf
new file mode 100644
index 0000000..f910710
--- /dev/null
+++ b/vcard/tests/res/raw/v21_invalid_comment_line.vcf
@@ -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
diff --git a/vcard/tests/res/raw/v21_japanese_1.vcf b/vcard/tests/res/raw/v21_japanese_1.vcf
new file mode 100644
index 0000000..d05e2ff
--- /dev/null
+++ b/vcard/tests/res/raw/v21_japanese_1.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:À¡Ch;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ;;;;
+TEL;PREF;VOICE:0300000000
+END:VCARD
diff --git a/vcard/tests/res/raw/v21_japanese_2.vcf b/vcard/tests/res/raw/v21_japanese_2.vcf
new file mode 100644
index 0000000..fa54acb
--- /dev/null
+++ b/vcard/tests/res/raw/v21_japanese_2.vcf
@@ -0,0 +1,10 @@
+BEGIN:VCARD
+VERSION:2.1
+FN;CHARSET=SHIFT_JIS:À¡ Ch 1
+N;CHARSET=SHIFT_JIS:À¡;Ch1;;;
+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
diff --git a/vcard/tests/res/raw/v21_multiple_entry.vcf b/vcard/tests/res/raw/v21_multiple_entry.vcf
new file mode 100644
index 0000000..ebbb19a
--- /dev/null
+++ b/vcard/tests/res/raw/v21_multiple_entry.vcf
@@ -0,0 +1,33 @@
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:À¡Ch3;;;;
+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:À¡Ch4;;;;
+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:À¡Ch5;;;;
+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
+
+
diff --git a/vcard/tests/res/raw/v21_org_before_title.vcf b/vcard/tests/res/raw/v21_org_before_title.vcf
new file mode 100644
index 0000000..8ff1190
--- /dev/null
+++ b/vcard/tests/res/raw/v21_org_before_title.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:2.1
+FN:Normal Guy
+ORG:Company;Organization;Devision;Room;Sheet No.
+TITLE:Excellent Janitor
+END:VCARD
diff --git a/vcard/tests/res/raw/v21_pref_handling.vcf b/vcard/tests/res/raw/v21_pref_handling.vcf
new file mode 100644
index 0000000..5105310
--- /dev/null
+++ b/vcard/tests/res/raw/v21_pref_handling.vcf
@@ -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
diff --git a/vcard/tests/res/raw/v21_simple_1.vcf b/vcard/tests/res/raw/v21_simple_1.vcf
new file mode 100644
index 0000000..6aabb4c
--- /dev/null
+++ b/vcard/tests/res/raw/v21_simple_1.vcf
@@ -0,0 +1,3 @@
+BEGIN:VCARD
+N:Ando;Roid;
+END:VCARD
diff --git a/vcard/tests/res/raw/v21_simple_2.vcf b/vcard/tests/res/raw/v21_simple_2.vcf
new file mode 100644
index 0000000..f0d5ab5
--- /dev/null
+++ b/vcard/tests/res/raw/v21_simple_2.vcf
@@ -0,0 +1,3 @@
+BEGIN:VCARD
+FN:Ando Roid
+END:VCARD
diff --git a/vcard/tests/res/raw/v21_simple_3.vcf b/vcard/tests/res/raw/v21_simple_3.vcf
new file mode 100644
index 0000000..beddabb
--- /dev/null
+++ b/vcard/tests/res/raw/v21_simple_3.vcf
@@ -0,0 +1,4 @@
+BEGIN:VCARD
+N:Ando;Roid;
+FN:Ando Roid
+END:VCARD
diff --git a/vcard/tests/res/raw/v21_title_before_org.vcf b/vcard/tests/res/raw/v21_title_before_org.vcf
new file mode 100644
index 0000000..9fdc738
--- /dev/null
+++ b/vcard/tests/res/raw/v21_title_before_org.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:2.1
+FN:Nice Guy
+TITLE:Cool Title
+ORG:Marverous;Perfect;Great;Good;Bad;Poor
+END:VCARD
diff --git a/vcard/tests/res/raw/v21_winmo_65.vcf b/vcard/tests/res/raw/v21_winmo_65.vcf
new file mode 100644
index 0000000..f380d0d
--- /dev/null
+++ b/vcard/tests/res/raw/v21_winmo_65.vcf
@@ -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
diff --git a/vcard/tests/res/raw/v30_comma_separated.vcf b/vcard/tests/res/raw/v30_comma_separated.vcf
new file mode 100644
index 0000000..98a7f20
--- /dev/null
+++ b/vcard/tests/res/raw/v30_comma_separated.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD
+VERSION:3.0
+N:F;G;M;;
+TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
+END:VCARD
diff --git a/vcard/tests/res/raw/v30_simple.vcf b/vcard/tests/res/raw/v30_simple.vcf
new file mode 100644
index 0000000..418661f
--- /dev/null
+++ b/vcard/tests/res/raw/v30_simple.vcf
@@ -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
diff --git a/vcard/tests/src/com/android/vcard/tests/VCardExporterTests.java b/vcard/tests/src/com/android/vcard/tests/VCardExporterTests.java
new file mode 100644
index 0000000..b6419c3
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/VCardExporterTests.java
@@ -0,0 +1,971 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vcard.tests;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.tests.test_utils.ContactEntry;
+import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem;
+import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+/**
+ * Tests for the code related to vCard exporter, inculding vCard composer.
+ * This test class depends on vCard importer code, so if tests for vCard importer fail,
+ * the result of this class will not be reliable.
+ */
+public class VCardExporterTests extends VCardTestsBase {
+ private static final byte[] sPhotoByteArray =
+ VCardImporterTests.sPhotoByteArrayForComplicatedCase;
+
+ public void testSimpleV21() {
+ mVerifier.initForExportTest(V21);
+ mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "Roid Ando")
+ .addExpectedNode("N", "Ando;Roid;;;",
+ Arrays.asList("Ando", "Roid", "", "", ""));
+ }
+
+ private void testStructuredNameBasic(int vcardType) {
+ final boolean isV30 = VCardConfig.isV30(vcardType);
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+ .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+ .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+ .put(StructuredName.PREFIX, "AppropriatePrefix")
+ .put(StructuredName.SUFFIX, "AppropriateSuffix")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addExpectedNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addExpectedNode("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ + "AppropriatePhoneticFamily");
+ }
+ }
+
+ public void testStructuredNameBasicV21() {
+ testStructuredNameBasic(V21);
+ }
+
+ public void testStructuredNameBasicV30() {
+ testStructuredNameBasic(V30);
+ }
+
+ /**
+ * Test that only "primary" StructuredName is emitted, so that our vCard file
+ * will not confuse the external importer, assuming there may be some importer
+ * which presume that there's only one property toward each of "N", "FN", etc.
+ * Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
+ */
+ private void testStructuredNameUsePrimaryCommon(int vcardType) {
+ final boolean isV30 = (vcardType == V30);
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
+
+ // With "IS_PRIMARY=1". This is what we should use.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+ .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+ .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+ .put(StructuredName.PREFIX, "AppropriatePrefix")
+ .put(StructuredName.SUFFIX, "AppropriateSuffix")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addExpectedNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addExpectedNode("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ + "AppropriatePhoneticFamily");
+ }
+ }
+
+ public void testStructuredNameUsePrimaryV21() {
+ testStructuredNameUsePrimaryCommon(V21);
+ }
+
+ public void testStructuredNameUsePrimaryV30() {
+ testStructuredNameUsePrimaryCommon(V30);
+ }
+
+ /**
+ * Tests that only "super primary" StructuredName is emitted.
+ * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
+ */
+ private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
+ final boolean isV30 = (vcardType == V30);
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
+
+ // With "IS_PRIMARY=1", but we should ignore this time.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ // With "IS_SUPER_PRIMARY=1". This is what we should use.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+ .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+ .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+ .put(StructuredName.PREFIX, "AppropriatePrefix")
+ .put(StructuredName.SUFFIX, "AppropriateSuffix")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
+ .put(StructuredName.IS_SUPER_PRIMARY, 1);
+
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix3")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix3")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily3")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven3")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addExpectedNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addExpectedNode("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
+ + " AppropriatePhoneticFamily");
+ }
+ }
+
+ public void testStructuredNameUseSuperPrimaryV21() {
+ testStructuredNameUseSuperPrimaryCommon(V21);
+ }
+
+ public void testStructuredNameUseSuperPrimaryV30() {
+ testStructuredNameUseSuperPrimaryCommon(V30);
+ }
+
+ public void testNickNameV30() {
+ mVerifier.initForExportTest(V30);
+ mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("NICKNAME", "Nicky");
+ }
+
+ private void testPhoneBasicCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "1", new TypeSet("HOME"));
+ }
+
+ public void testPhoneBasicV21() {
+ testPhoneBasicCommon(V21);
+ }
+
+ public void testPhoneBasicV30() {
+ testPhoneBasicCommon(V30);
+ }
+
+ public void testPhoneRefrainFormatting() {
+ mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING);
+ mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1234567890(abcdefghijklmnopqrstuvwxyz)")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "1234567890(abcdefghijklmnopqrstuvwxyz)",
+ new TypeSet("HOME"));
+ }
+
+ /**
+ * Tests that vCard composer emits corresponding type param which we expect.
+ */
+ private void testPhoneVariousTypeSupport(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "10")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "20")
+ .put(Phone.TYPE, Phone.TYPE_WORK);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "30")
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "40")
+ .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "50")
+ .put(Phone.TYPE, Phone.TYPE_MOBILE);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "60")
+ .put(Phone.TYPE, Phone.TYPE_PAGER);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "70")
+ .put(Phone.TYPE, Phone.TYPE_OTHER);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "80")
+ .put(Phone.TYPE, Phone.TYPE_CAR);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "90")
+ .put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "100")
+ .put(Phone.TYPE, Phone.TYPE_ISDN);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "110")
+ .put(Phone.TYPE, Phone.TYPE_MAIN);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "120")
+ .put(Phone.TYPE, Phone.TYPE_OTHER_FAX);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "130")
+ .put(Phone.TYPE, Phone.TYPE_TELEX);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "140")
+ .put(Phone.TYPE, Phone.TYPE_WORK_MOBILE);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "150")
+ .put(Phone.TYPE, Phone.TYPE_WORK_PAGER);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "160")
+ .put(Phone.TYPE, Phone.TYPE_MMS);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "10", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "20", new TypeSet("WORK"))
+ .addExpectedNode("TEL", "30", new TypeSet("HOME", "FAX"))
+ .addExpectedNode("TEL", "40", new TypeSet("WORK", "FAX"))
+ .addExpectedNode("TEL", "50", new TypeSet("CELL"))
+ .addExpectedNode("TEL", "60", new TypeSet("PAGER"))
+ .addExpectedNode("TEL", "70", new TypeSet("VOICE"))
+ .addExpectedNode("TEL", "80", new TypeSet("CAR"))
+ .addExpectedNode("TEL", "90", new TypeSet("WORK", "PREF"))
+ .addExpectedNode("TEL", "100", new TypeSet("ISDN"))
+ .addExpectedNode("TEL", "110", new TypeSet("PREF"))
+ .addExpectedNode("TEL", "120", new TypeSet("FAX"))
+ .addExpectedNode("TEL", "130", new TypeSet("TLX"))
+ .addExpectedNode("TEL", "140", new TypeSet("WORK", "CELL"))
+ .addExpectedNode("TEL", "150", new TypeSet("WORK", "PAGER"))
+ .addExpectedNode("TEL", "160", new TypeSet("MSG"));
+ }
+
+ public void testPhoneVariousTypeSupportV21() {
+ testPhoneVariousTypeSupport(V21);
+ }
+
+ public void testPhoneVariousTypeSupportV30() {
+ testPhoneVariousTypeSupport(V30);
+ }
+
+ /**
+ * Tests that "PREF"s are emitted appropriately.
+ */
+ private void testPhonePrefHandlingCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "2")
+ .put(Phone.TYPE, Phone.TYPE_WORK)
+ .put(Phone.IS_PRIMARY, 1);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "3")
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
+ .put(Phone.IS_PRIMARY, 1);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "4")
+ .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "4", new TypeSet("WORK", "FAX"))
+ .addExpectedNode("TEL", "3", new TypeSet("HOME", "FAX", "PREF"))
+ .addExpectedNode("TEL", "2", new TypeSet("WORK", "PREF"))
+ .addExpectedNode("TEL", "1", new TypeSet("HOME"));
+ }
+
+ public void testPhonePrefHandlingV21() {
+ testPhonePrefHandlingCommon(V21);
+ }
+
+ public void testPhonePrefHandlingV30() {
+ testPhonePrefHandlingCommon(V30);
+ }
+
+ private void testMiscPhoneTypeHandling(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "Modem");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "2")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "MSG");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "3")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "BBS");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "4")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VIDEO");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "5")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "6")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "_AUTO_CELL"); // The old indicator for the type mobile.
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "7")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "\u643A\u5E2F"); // Mobile phone in Japanese Kanji
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "8")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "invalid");
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
+ elem.addExpectedNode("TEL", "1", new TypeSet("MODEM"))
+ .addExpectedNode("TEL", "2", new TypeSet("MSG"))
+ .addExpectedNode("TEL", "3", new TypeSet("BBS"))
+ .addExpectedNode("TEL", "4", new TypeSet("VIDEO"))
+ .addExpectedNode("TEL", "5", new TypeSet("VOICE"))
+ .addExpectedNode("TEL", "6", new TypeSet("CELL"))
+ .addExpectedNode("TEL", "7", new TypeSet("CELL"))
+ .addExpectedNode("TEL", "8", new TypeSet("X-invalid"));
+ }
+
+ public void testPhoneTypeHandlingV21() {
+ testMiscPhoneTypeHandling(V21);
+ }
+
+ public void testPhoneTypeHandlingV30() {
+ testMiscPhoneTypeHandling(V30);
+ }
+
+ private void testEmailBasicCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "sample@example.com");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "sample@example.com");
+ }
+
+ public void testEmailBasicV21() {
+ testEmailBasicCommon(V21);
+ }
+
+ public void testEmailBasicV30() {
+ testEmailBasicCommon(V30);
+ }
+
+ private void testEmailVariousTypeSupportCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_home@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_work@example.com")
+ .put(Email.TYPE, Email.TYPE_WORK);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_mobile@example.com")
+ .put(Email.TYPE, Email.TYPE_MOBILE);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_other@example.com")
+ .put(Email.TYPE, Email.TYPE_OTHER);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "type_work@example.com", new TypeSet("WORK"))
+ .addExpectedNode("EMAIL", "type_mobile@example.com", new TypeSet("CELL"))
+ .addExpectedNode("EMAIL", "type_other@example.com");
+ }
+
+ public void testEmailVariousTypeSupportV21() {
+ testEmailVariousTypeSupportCommon(V21);
+ }
+
+ public void testEmailVariousTypeSupportV30() {
+ testEmailVariousTypeSupportCommon(V30);
+ }
+
+ private void testEmailPrefHandlingCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_home@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME)
+ .put(Email.IS_PRIMARY, 1);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_notype@example.com")
+ .put(Email.IS_PRIMARY, 1);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "type_notype@example.com", new TypeSet("PREF"))
+ .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF"));
+ }
+
+ public void testEmailPrefHandlingV21() {
+ testEmailPrefHandlingCommon(V21);
+ }
+
+ public void testEmailPrefHandlingV30() {
+ testEmailPrefHandlingCommon(V30);
+ }
+
+ private void testPostalAddressCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "Pobox")
+ .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood")
+ .put(StructuredPostal.STREET, "Street")
+ .put(StructuredPostal.CITY, "City")
+ .put(StructuredPostal.REGION, "Region")
+ .put(StructuredPostal.POSTCODE, "100")
+ .put(StructuredPostal.COUNTRY, "Country")
+ .put(StructuredPostal.FORMATTED_ADDRESS, "Formatted Address")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK);
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal Code,
+ // ; Country Name
+ //
+ // The NEIGHBORHOOD field is appended after the CITY field.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR",
+ Arrays.asList("Pobox", "", "Street", "City Neighborhood",
+ "Region", "100", "Country"), new TypeSet("WORK"));
+ }
+
+ public void testPostalAddressV21() {
+ testPostalAddressCommon(V21);
+ }
+
+ public void testPostalAddressV30() {
+ testPostalAddressCommon(V30);
+ }
+
+ private void testPostalAddressNonNeighborhood(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.CITY, "City");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR",
+ Arrays.asList("", "", "", "City", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalAddressNonNeighborhoodV21() {
+ testPostalAddressNonNeighborhood(V21);
+ }
+
+ public void testPostalAddressNonNeighborhoodV30() {
+ testPostalAddressNonNeighborhood(V30);
+ }
+
+ private void testPostalAddressNonCity(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR",
+ Arrays.asList("", "", "", "Neighborhood", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalAddressNonCityV21() {
+ testPostalAddressNonCity(V21);
+ }
+
+ public void testPostalAddressNonCityV30() {
+ testPostalAddressNonCity(V30);
+ }
+
+ private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.REGION, "") // Must be ignored.
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "Formatted address CA 123-334 United Statue");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;",
+ Arrays.asList("", "Formatted address CA 123-334 United Statue",
+ "", "", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalOnlyWithFormattedAddressV21() {
+ testPostalOnlyWithFormattedAddressCommon(V21);
+ }
+
+ public void testPostalOnlyWithFormattedAddressV30() {
+ testPostalOnlyWithFormattedAddressCommon(V30);
+ }
+
+ /**
+ * Tests that the vCard composer honors formatted data when it is available
+ * even when it is partial.
+ */
+ private void testPostalWithBothStructuredAndFormattedCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "Pobox")
+ .put(StructuredPostal.COUNTRY, "Country")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "Formatted address CA 123-334 United Statue"); // Should be ignored
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR", "Pobox;;;;;;Country",
+ Arrays.asList("Pobox", "", "", "", "", "", "Country"),
+ new TypeSet("HOME"));
+ }
+
+ public void testPostalWithBothStructuredAndFormattedV21() {
+ testPostalWithBothStructuredAndFormattedCommon(V21);
+ }
+
+ public void testPostalWithBothStructuredAndFormattedV30() {
+ testPostalWithBothStructuredAndFormattedCommon(V30);
+ }
+
+ private void testOrganizationCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "CompanyX")
+ .put(Organization.DEPARTMENT, "DepartmentY")
+ .put(Organization.TITLE, "TitleZ")
+ .put(Organization.JOB_DESCRIPTION, "Description Rambda") // Ignored.
+ .put(Organization.OFFICE_LOCATION, "Mountain View") // Ignored.
+ .put(Organization.PHONETIC_NAME, "PhoneticName!") // Ignored
+ .put(Organization.SYMBOL, "(^o^)/~~"); // Ignore him (her).
+ entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
+ .putNull(Organization.COMPANY)
+ .put(Organization.DEPARTMENT, "DepartmentXX")
+ .putNull(Organization.TITLE);
+ entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "CompanyXYZ")
+ .putNull(Organization.DEPARTMENT)
+ .put(Organization.TITLE, "TitleXYZYX");
+ // Currently we do not use group but depend on the order.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("ORG", "CompanyX;DepartmentY",
+ Arrays.asList("CompanyX", "DepartmentY"))
+ .addExpectedNodeWithOrder("TITLE", "TitleZ")
+ .addExpectedNodeWithOrder("ORG", "DepartmentXX")
+ .addExpectedNodeWithOrder("ORG", "CompanyXYZ")
+ .addExpectedNodeWithOrder("TITLE", "TitleXYZYX");
+ }
+
+ public void testOrganizationV21() {
+ testOrganizationCommon(V21);
+ }
+
+ public void testOrganizationV30() {
+ testOrganizationCommon(V30);
+ }
+
+ private void testImVariousTypeSupportCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_MSN)
+ .put(Im.DATA, "msn");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_YAHOO)
+ .put(Im.DATA, "yahoo");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_SKYPE)
+ .put(Im.DATA, "skype");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_QQ)
+ .put(Im.DATA, "qq");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
+ .put(Im.DATA, "google talk");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_ICQ)
+ .put(Im.DATA, "icq");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_JABBER)
+ .put(Im.DATA, "jabber");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING)
+ .put(Im.DATA, "netmeeting");
+
+ // No determined way to express unknown type...
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-JABBER", "jabber")
+ .addExpectedNode("X-ICQ", "icq")
+ .addExpectedNode("X-GOOGLE-TALK", "google talk")
+ .addExpectedNode("X-QQ", "qq")
+ .addExpectedNode("X-SKYPE-USERNAME", "skype")
+ .addExpectedNode("X-YAHOO", "yahoo")
+ .addExpectedNode("X-MSN", "msn")
+ .addExpectedNode("X-NETMEETING", "netmeeting")
+ .addExpectedNode("X-AIM", "aim");
+ }
+
+ public void testImBasiV21() {
+ testImVariousTypeSupportCommon(V21);
+ }
+
+ public void testImBasicV30() {
+ testImVariousTypeSupportCommon(V30);
+ }
+
+ private void testImPrefHandlingCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim1");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim2")
+ .put(Im.TYPE, Im.TYPE_HOME)
+ .put(Im.IS_PRIMARY, 1);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-AIM", "aim1")
+ .addExpectedNode("X-AIM", "aim2", new TypeSet("HOME", "PREF"));
+ }
+
+ public void testImPrefHandlingV21() {
+ testImPrefHandlingCommon(V21);
+ }
+
+ public void testImPrefHandlingV30() {
+ testImPrefHandlingCommon(V30);
+ }
+
+ private void testWebsiteCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Website.CONTENT_ITEM_TYPE)
+ .put(Website.URL, "http://website.example.android.com/index.html")
+ .put(Website.TYPE, Website.TYPE_BLOG);
+ entry.addContentValues(Website.CONTENT_ITEM_TYPE)
+ .put(Website.URL, "ftp://ftp.example.android.com/index.html")
+ .put(Website.TYPE, Website.TYPE_FTP);
+
+ // We drop TYPE information since vCard (especially 3.0) does not allow us to emit it.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("URL", "ftp://ftp.example.android.com/index.html")
+ .addExpectedNode("URL", "http://website.example.android.com/index.html");
+ }
+
+ public void testWebsiteV21() {
+ testWebsiteCommon(V21);
+ }
+
+ public void testWebsiteV30() {
+ testWebsiteCommon(V30);
+ }
+
+ private String getAndroidPropValue(final String mimeType, String value, Integer type) {
+ return getAndroidPropValue(mimeType, value, type, null);
+ }
+
+ private String getAndroidPropValue(final String mimeType, String value,
+ Integer type, String label) {
+ return (mimeType + ";" + value + ";"
+ + (type != null ? type : "") + ";"
+ + (label != null ? label : "") + ";;;;;;;;;;;;");
+ }
+
+ private void testEventCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_ANNIVERSARY)
+ .put(Event.START_DATE, "1982-06-16");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+ .put(Event.START_DATE, "2008-10-22");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_OTHER)
+ .put(Event.START_DATE, "2018-03-12");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_CUSTOM)
+ .put(Event.LABEL, "The last day")
+ .put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed.");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+ .put(Event.START_DATE, "2009-05-19"); // Should be ignored.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("BDAY", "2008-10-22")
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE, "1982-06-16", Event.TYPE_ANNIVERSARY))
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE, "2018-03-12", Event.TYPE_OTHER))
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE,
+ "When the Tower of Hanoi with 64 rings is completed.",
+ Event.TYPE_CUSTOM, "The last day"));
+ }
+
+ public void testEventV21() {
+ testEventCommon(V21);
+ }
+
+ public void testEventV30() {
+ testEventCommon(V30);
+ }
+
+ private void testNoteCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note1");
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note2")
+ .put(Note.IS_PRIMARY, 1); // Just ignored.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("NOTE", "note1")
+ .addExpectedNodeWithOrder("NOTE", "note2");
+ }
+
+ public void testNoteV21() {
+ testNoteCommon(V21);
+ }
+
+ public void testNoteV30() {
+ testNoteCommon(V30);
+ }
+
+ private void testPhotoCommon(int vcardType) {
+ final boolean isV30 = vcardType == V30;
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "PhotoTest");
+ entry.addContentValues(Photo.CONTENT_ITEM_TYPE)
+ .put(Photo.PHOTO, sPhotoByteArray);
+
+ ContentValues contentValuesForPhoto = new ContentValues();
+ contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "PhotoTest")
+ .addExpectedNode("N", "PhotoTest;;;;",
+ Arrays.asList("PhotoTest", "", "", "", ""))
+ .addExpectedNodeWithOrder("PHOTO", null, null, sPhotoByteArray,
+ contentValuesForPhoto, new TypeSet("JPEG"), null);
+ }
+
+ public void testPhotoV21() {
+ testPhotoCommon(V21);
+ }
+
+ public void testPhotoV30() {
+ testPhotoCommon(V30);
+ }
+
+ private void testRelationCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE)
+ .put(Relation.TYPE, Relation.TYPE_MOTHER)
+ .put(Relation.NAME, "Ms. Mother");
+ mVerifier.addContentValuesVerifierElem().addExpected(Relation.CONTENT_ITEM_TYPE)
+ .put(Relation.TYPE, Relation.TYPE_MOTHER)
+ .put(Relation.NAME, "Ms. Mother");
+ }
+
+ public void testRelationV21() {
+ testRelationCommon(V21);
+ }
+
+ public void testRelationV30() {
+ testRelationCommon(V30);
+ }
+
+ public void testV30HandleEscape() {
+ mVerifier.initForExportTest(V30);
+ mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\\")
+ .put(StructuredName.GIVEN_NAME, ";")
+ .put(StructuredName.MIDDLE_NAME, ",")
+ .put(StructuredName.PREFIX, "\n")
+ .put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]");
+ // Verifies the vCard String correctly escapes each character which must be escaped.
+ mVerifier.addLineVerifierElem()
+ .addExpected("N:\\\\;\\;;\\,;\\n;")
+ .addExpected("FN:[<{Unescaped:Asciis}>]");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "[<{Unescaped:Asciis}>]")
+ .addExpectedNode("N", Arrays.asList("\\", ";", ",", "\n", ""));
+ }
+
+ /**
+ * There's no "NICKNAME" property in vCard 2.1, while there is in vCard 3.0.
+ * We use Android-specific "X-ANDROID-CUSTOM" property.
+ * This test verifies the functionality.
+ */
+ public void testNickNameV21() {
+ mVerifier.initForExportTest(V21);
+ mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;");
+ mVerifier.addContentValuesVerifierElem().addExpected(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+ }
+
+ public void testTolerateBrokenPhoneNumberEntryV21() {
+ mVerifier.initForExportTest(V21);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_HOME)
+ .put(Phone.NUMBER, "111-222-3333 (Miami)\n444-5555-666 (Tokyo);"
+ + "777-888-9999 (Chicago);111-222-3333 (Miami)");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "111-222-3333", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "444-555-5666", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "777-888-9999", new TypeSet("HOME"));
+ }
+
+ private void testPickUpNonEmptyContentValuesCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "family1"); // Not primary. Should be ignored.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.IS_PRIMARY, 1)
+ .put(StructuredName.FAMILY_NAME, "family2"); // This entry is what we want.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.IS_PRIMARY, 1)
+ .put(StructuredName.FAMILY_NAME, "family3");
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "family4");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("N", Arrays.asList("family2", "", "", "", ""))
+ .addExpectedNode("FN", "family2");
+ }
+
+ public void testPickUpNonEmptyContentValuesV21() {
+ testPickUpNonEmptyContentValuesCommon(V21);
+ }
+
+ public void testPickUpNonEmptyContentValuesV30() {
+ testPickUpNonEmptyContentValuesCommon(V30);
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/VCardImporterTests.java b/vcard/tests/src/com/android/vcard/tests/VCardImporterTests.java
new file mode 100644
index 0000000..045c0d9
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/VCardImporterTests.java
@@ -0,0 +1,1008 @@
+/*
+ * 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.Data;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+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.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.tests.test_utils.ContentValuesVerifier;
+import com.android.vcard.tests.test_utils.ContentValuesVerifierElem;
+import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+public class VCardImporterTests extends VCardTestsBase {
+ // Push data into int array at first since values like 0x80 are
+ // interpreted as int by the compiler and casting all of them is
+ // cumbersome...
+ private static final int[] sPhotoIntArrayForComplicatedCase = {
+ 0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00,
+ 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d,
+ 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
+ 0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82,
+ 0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa,
+ 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
+ 0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31,
+ 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00,
+ 0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30,
+ 0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30,
+ 0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00,
+ 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+ 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33,
+ 0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00,
+ 0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00,
+ 0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10,
+ 0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c,
+ 0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00,
+ 0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00,
+ 0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5,
+ 0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90,
+ 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a,
+ 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03,
+ 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02,
+ 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92,
+ 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca,
+ 0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00,
+ 0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30,
+ 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2,
+ 0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
+ 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
+ 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4,
+ 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00,
+ 0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64,
+ 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+ 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30,
+ 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33,
+ 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88,
+ 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00,
+ 0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00,
+ 0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30,
+ 0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31,
+ 0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a,
+ 0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00,
+ 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06,
+ 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84,
+ 0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
+ 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30,
+ 0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
+ 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e,
+ 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c,
+ 0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01,
+ 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6,
+ 0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0,
+ 0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00,
+ 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00,
+ 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03,
+ 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
+ 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
+ 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
+ 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92,
+ 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
+ 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
+ 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+ 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
+ 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00,
+ 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
+ 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
+ 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
+ 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
+ 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
+ 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+ 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
+ 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+ 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
+ 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
+ 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
+ 0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04,
+ 0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1,
+ 0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45,
+ 0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52,
+ 0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00,
+ 0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87,
+ 0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00,
+ 0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46,
+ 0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9,
+ 0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4,
+ 0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4,
+ 0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5,
+ 0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a,
+ 0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28,
+ 0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80,
+ 0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4,
+ 0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30,
+ 0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0,
+ 0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44,
+ 0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53,
+ 0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76,
+ 0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b,
+ 0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8,
+ 0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d,
+ 0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99,
+ 0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd,
+ 0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3,
+ 0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94,
+ 0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a,
+ 0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06,
+ 0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40,
+ 0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39,
+ 0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69,
+ 0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b,
+ 0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10,
+ 0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0,
+ 0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa,
+ 0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09,
+ 0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81,
+ 0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b,
+ 0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2,
+ 0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69,
+ 0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5,
+ 0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c,
+ 0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73,
+ 0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81,
+ 0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00,
+ 0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a,
+ 0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b,
+ 0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2,
+ 0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7,
+ 0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26,
+ 0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80,
+ 0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5,
+ 0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40,
+ 0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a,
+ 0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6,
+ 0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e,
+ 0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3,
+ 0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69,
+ 0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03,
+ 0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2,
+ 0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a,
+ 0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8,
+ 0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00,
+ 0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69,
+ 0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65,
+ 0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69,
+ 0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8,
+ 0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12,
+ 0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61,
+ 0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01,
+ 0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e,
+ 0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a,
+ 0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3,
+ 0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a,
+ 0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a,
+ 0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
+ 0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21,
+ 0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30,
+ 0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44,
+ 0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b,
+ 0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22,
+ 0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11,
+ 0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11,
+ 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01,
+ 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
+ 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
+ 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+ 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
+ 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
+ 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
+ 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
+ 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
+ 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
+ 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
+ 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
+ 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
+ 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
+ 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+ 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+ 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
+ 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19,
+ 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
+ 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+ 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
+ 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
+ 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2,
+ 0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6,
+ 0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4,
+ 0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31,
+ 0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88,
+ 0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77,
+ 0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31,
+ 0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0,
+ 0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc,
+ 0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52,
+ 0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60,
+ 0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38,
+ 0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18,
+ 0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a,
+ 0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a,
+ 0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27,
+ 0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc,
+ 0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59,
+ 0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58,
+ 0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f,
+ 0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35,
+ 0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac,
+ 0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6,
+ 0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85,
+ 0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a,
+ 0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf,
+ 0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65,
+ 0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b,
+ 0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6,
+ 0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d,
+ 0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66,
+ 0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d,
+ 0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6,
+ 0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc,
+ 0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4,
+ 0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92,
+ 0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93,
+ 0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68,
+ 0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d,
+ 0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0,
+ 0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c,
+ 0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39,
+ 0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14,
+ 0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92,
+ 0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14,
+ 0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4,
+ 0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02,
+ 0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3,
+ 0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76,
+ 0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02,
+ 0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc,
+ 0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7,
+ 0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51,
+ 0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61,
+ 0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e,
+ 0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c,
+ 0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63,
+ 0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b,
+ 0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab,
+ 0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7,
+ 0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3,
+ 0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1,
+ 0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23,
+ 0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04,
+ 0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9,
+ 0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15,
+ 0x0c, 0xd1, 0x00, 0xff, 0xd9};
+
+ /* package */ static final byte[] sPhotoByteArrayForComplicatedCase;
+
+ static {
+ final int length = sPhotoIntArrayForComplicatedCase.length;
+ sPhotoByteArrayForComplicatedCase = new byte[length];
+ for (int i = 0; i < length; i++) {
+ sPhotoByteArrayForComplicatedCase[i] = (byte)sPhotoIntArrayForComplicatedCase[i];
+ }
+ }
+
+ public void testV21SimpleCase1_Parsing() {
+ mVerifier.initForImportTest(V21, R.raw.v21_simple_1);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
+ }
+
+ public void testV21SimpleCase1_Type_Generic() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_simple_1);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ .put(StructuredName.DISPLAY_NAME, "Roid Ando");
+ }
+
+ public void testV21SimpleCase1_Type_Japanese() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_1);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ // If name-related strings only contains printable Ascii,
+ // the order is remained to be US's:
+ // "Prefix Given Middle Family Suffix"
+ .put(StructuredName.DISPLAY_NAME, "Roid Ando");
+ }
+
+ public void testV21SimpleCase2() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_2);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Ando Roid");
+ }
+
+ public void testV21SimpleCase3() {
+ mVerifier.initForImportTest(V21, R.raw.v21_simple_3);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ // "FN" field should be prefered since it should contain the original
+ // order intended by the author of the file.
+ .put(StructuredName.DISPLAY_NAME, "Ando Roid");
+ }
+
+ /**
+ * Tests ';' is properly handled by VCardParser implementation.
+ */
+ public void testV21BackslashCase_Parsing() {
+ mVerifier.initForImportTest(V21, R.raw.v21_backslash);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;",
+ Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
+ .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
+
+ }
+
+ /**
+ * Tests ContactStruct correctly ignores redundant fields in "N" property values and
+ * inserts name related data.
+ */
+ public void testV21BackslashCase() {
+ mVerifier.initForImportTest(V21, R.raw.v21_backslash);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ // FAMILY_NAME is empty and removed in this test...
+ .put(StructuredName.GIVEN_NAME, "A;B\\")
+ .put(StructuredName.MIDDLE_NAME, "C\\;")
+ .put(StructuredName.PREFIX, "D")
+ .put(StructuredName.SUFFIX, ":E")
+ .put(StructuredName.DISPLAY_NAME, "A;B\\C\\;D:E\\\\");
+ }
+
+ public void testOrgBeforTitle() {
+ mVerifier.initForImportTest(V21, R.raw.v21_org_before_title);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Normal Guy");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Company")
+ .put(Organization.DEPARTMENT, "Organization Devision Room Sheet No.")
+ .put(Organization.TITLE, "Excellent Janitor")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ }
+
+ public void testTitleBeforOrg() {
+ mVerifier.initForImportTest(V21, R.raw.v21_title_before_org);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Nice Guy");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Marverous")
+ .put(Organization.DEPARTMENT, "Perfect Great Good Bad Poor")
+ .put(Organization.TITLE, "Cool Title")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ }
+
+ /**
+ * Verifies that vCard importer correctly interpret "PREF" attribute to IS_PRIMARY.
+ * The data contain three cases: one "PREF", no "PREF" and multiple "PREF", in each type.
+ */
+ public void testV21PrefToIsPrimary() {
+ mVerifier.initForImportTest(V21, R.raw.v21_pref_handling);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Smith");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "2")
+ .put(Phone.TYPE, Phone.TYPE_WORK)
+ .put(Phone.IS_PRIMARY, 1);
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "3")
+ .put(Phone.TYPE, Phone.TYPE_ISDN);
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "test@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME)
+ .put(Email.IS_PRIMARY, 1);
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "test2@examination.com")
+ .put(Email.TYPE, Email.TYPE_MOBILE)
+ .put(Email.IS_PRIMARY, 1);
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Company")
+ .put(Organization.TITLE, "Engineer")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Mystery")
+ .put(Organization.TITLE, "Blogger")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Poetry")
+ .put(Organization.TITLE, "Poet")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ }
+
+ /**
+ * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser.
+ */
+ public void testV21ComplicatedCase_Parsing() {
+ mVerifier.initForImportTest(V21, R.raw.v21_complicated);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao",
+ Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao"))
+ .addExpectedNodeWithOrder("FN", "Joe Due")
+ .addExpectedNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
+ Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper"))
+ .addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!")
+ .addExpectedNodeWithOrder("TITLE", "Shrimp Man")
+ .addExpectedNodeWithOrder("X-CLASS", "PUBLIC")
+ .addExpectedNodeWithOrder("TEL", "(111) 555-1212", new TypeSet("WORK", "VOICE"))
+ .addExpectedNodeWithOrder("TEL", "(404) 555-1212", new TypeSet("HOME", "VOICE"))
+ .addExpectedNodeWithOrder("TEL", "0311111111", new TypeSet("CELL"))
+ .addExpectedNodeWithOrder("TEL", "0322222222", new TypeSet("VIDEO"))
+ .addExpectedNodeWithOrder("TEL", "0333333333", new TypeSet("VOICE"))
+ .addExpectedNodeWithOrder("ADR",
+ ";;100 Waters Edge;Baytown;LA;30314;United States of America",
+ Arrays.asList("", "", "100 Waters Edge", "Baytown",
+ "LA", "30314", "United States of America"),
+ null, null, new TypeSet("WORK"), null)
+ .addExpectedNodeWithOrder("LABEL",
+ "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, mContentValuesForQP, new TypeSet("WORK"), null)
+ .addExpectedNodeWithOrder("ADR",
+ ";;42 Plantation St.;Baytown;LA;30314;United States of America",
+ Arrays.asList("", "", "42 Plantation St.", "Baytown",
+ "LA", "30314", "United States of America"), null, null,
+ new TypeSet("HOME"), null)
+ .addExpectedNodeWithOrder("LABEL",
+ "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, mContentValuesForQP,
+ new TypeSet("HOME"), null)
+ .addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com",
+ new TypeSet("PREF", "INTERNET"))
+ .addExpectedNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL"))
+ .addExpectedNodeWithOrder("NOTE", "The following note is the example from RFC 2045.")
+ .addExpectedNodeWithOrder("NOTE",
+ "Now's the time for all folk to come to the aid of their country.",
+ null, null, mContentValuesForQP, null, null)
+ .addExpectedNodeWithOrder("PHOTO", null,
+ null, sPhotoByteArrayForComplicatedCase, mContentValuesForBase64V21,
+ new TypeSet("JPEG"), null)
+ .addExpectedNodeWithOrder("X-ATTRIBUTE", "Some String")
+ .addExpectedNodeWithOrder("BDAY", "19800101")
+ .addExpectedNodeWithOrder("GEO", "35.6563854,139.6994233")
+ .addExpectedNodeWithOrder("URL", "http://www.example.com/")
+ .addExpectedNodeWithOrder("REV", "20080424T195243Z");
+ }
+
+ /**
+ * Checks ContactStruct correctly inserts values in a complicated vCard
+ * into ContentResolver.
+ */
+ public void testV21ComplicatedCase() {
+ mVerifier.initForImportTest(V21, R.raw.v21_complicated);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Gump")
+ .put(StructuredName.GIVEN_NAME, "Forrest")
+ .put(StructuredName.MIDDLE_NAME, "Hoge")
+ .put(StructuredName.PREFIX, "Pos")
+ .put(StructuredName.SUFFIX, "Tao")
+ .put(StructuredName.DISPLAY_NAME, "Joe Due");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.TYPE, Organization.TYPE_WORK)
+ .put(Organization.COMPANY, "Gump Shrimp Co.")
+ .put(Organization.DEPARTMENT, "Sales Dept.;Manager Fish keeper")
+ .put(Organization.TITLE, "Shrimp Man");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_WORK)
+ // Phone number is expected to be formated with NAMP format in default.
+ .put(Phone.NUMBER, "111-555-1212");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_HOME)
+ .put(Phone.NUMBER, "404-555-1212");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_MOBILE)
+ .put(Phone.NUMBER, "031-111-1111");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VIDEO")
+ .put(Phone.NUMBER, "032-222-2222");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VOICE")
+ .put(Phone.NUMBER, "033-333-3333");
+ elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.COUNTRY, "United States of America")
+ .put(StructuredPostal.POSTCODE, "30314")
+ .put(StructuredPostal.REGION, "LA")
+ .put(StructuredPostal.CITY, "Baytown")
+ .put(StructuredPostal.STREET, "100 Waters Edge")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "100 Waters Edge Baytown LA 30314 United States of America");
+ elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+ .put(StructuredPostal.COUNTRY, "United States of America")
+ .put(StructuredPostal.POSTCODE, "30314")
+ .put(StructuredPostal.REGION, "LA")
+ .put(StructuredPostal.CITY, "Baytown")
+ .put(StructuredPostal.STREET, "42 Plantation St.")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "42 Plantation St. Baytown LA 30314 United States of America");
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ // "TYPE=INTERNET" -> TYPE_CUSTOM + the label "INTERNET"
+ .put(Email.TYPE, Email.TYPE_CUSTOM)
+ .put(Email.LABEL, "INTERNET")
+ .put(Email.DATA, "forrestgump@walladalla.com")
+ .put(Email.IS_PRIMARY, 1);
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ .put(Email.TYPE, Email.TYPE_MOBILE)
+ .put(Email.DATA, "cell@example.com");
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "The following note is the example from RFC 2045.");
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE,
+ "Now's the time for all folk to come to the aid of their country.");
+ elem.addExpected(Photo.CONTENT_ITEM_TYPE)
+ // No information about its image format can be inserted.
+ .put(Photo.PHOTO, sPhotoByteArrayForComplicatedCase);
+ elem.addExpected(Event.CONTENT_ITEM_TYPE)
+ .put(Event.START_DATE, "19800101")
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY);
+ elem.addExpected(Website.CONTENT_ITEM_TYPE)
+ .put(Website.URL, "http://www.example.com/")
+ .put(Website.TYPE, Website.TYPE_HOMEPAGE);
+ }
+
+ public void testV30Simple_Parsing() {
+ mVerifier.initForImportTest(V30, R.raw.v30_simple);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "3.0")
+ .addExpectedNodeWithOrder("FN", "And Roid")
+ .addExpectedNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", ""))
+ .addExpectedNodeWithOrder("ORG", "Open;Handset; Alliance",
+ Arrays.asList("Open", "Handset", " Alliance"))
+ .addExpectedNodeWithOrder("SORT-STRING", "android")
+ .addExpectedNodeWithOrder("TEL", "0300000000", new TypeSet("PREF", "VOICE"))
+ .addExpectedNodeWithOrder("CLASS", "PUBLIC")
+ .addExpectedNodeWithOrder("X-GNO", "0")
+ .addExpectedNodeWithOrder("X-GN", "group0")
+ .addExpectedNodeWithOrder("X-REDUCTION", "0")
+ .addExpectedNodeWithOrder("REV", "20081031T065854Z");
+ }
+
+ public void testV30Simple() {
+ mVerifier.initForImportTest(V30, R.raw.v30_simple);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "And")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ .put(StructuredName.DISPLAY_NAME, "And Roid")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "android");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Open")
+ .put(Organization.DEPARTMENT, "Handset Alliance")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VOICE")
+ .put(Phone.NUMBER, "030-000-0000")
+ .put(Phone.IS_PRIMARY, 1);
+ }
+
+ public void testV21Japanese1_Parsing() {
+ // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
+ // vCard 2.1/3.0 specification does not allow multiple values.
+ // Do not need to handle it as multiple values.
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_1);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "0300000000", null, null, null,
+ new TypeSet("VOICE", "PREF"), null);
+ }
+
+ private void testV21Japanese1Common(int resId, int vcardType, boolean japanese) {
+ mVerifier.initForImportTest(vcardType, resId);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
+ // While vCard parser does not split "SOUND" property values,
+ // ContactStruct care it.
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ // Phone number formatting is different.
+ .put(Phone.NUMBER, (japanese ? "03-0000-0000" : "030-000-0000"))
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VOICE")
+ .put(Phone.IS_PRIMARY, 1);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC}.
+ */
+ public void testV21Japanese1_Type_Generic_Utf8() {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC, false);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
+ */
+ public void testV21Japanese1_Type_Japanese_Sjis() {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
+ * since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
+ */
+ public void testV21Japanese1_Type_Japanese_Utf8() {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
+ }
+
+ public void testV21Japanese2_Parsing() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_2);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
+ Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
+ "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
+ null, null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("ADR",
+ ";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" +
+ "\u968E;;;;150-8512;",
+ Arrays.asList("",
+ "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+ "\u0036\u968E", "", "", "", "150-8512", ""),
+ null, mContentValuesForQPAndSJis, new TypeSet("HOME"), null)
+ .addExpectedNodeWithOrder("NOTE", "\u30E1\u30E2", null, null,
+ mContentValuesForQPAndSJis, null, null);
+ }
+
+ public void testV21Japanese2_Type_Generic_Utf8() {
+ mVerifier.initForImportTest(V21, R.raw.v21_japanese_2);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4")
+ .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9\u0031")
+ .put(StructuredName.DISPLAY_NAME,
+ "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031")
+ // ContactStruct should correctly split "SOUND" property into several elements,
+ // even though VCardParser side does not care it.
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF71\uFF9D\uFF84\uFF9E\uFF73")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF9B\uFF72\uFF84\uFF9E\u0031");
+ elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POSTCODE, "150-8512")
+ .put(StructuredPostal.STREET,
+ "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+ "\u0036\u968E")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+ "\u0036\u968E 150-8512")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "\u30E1\u30E2");
+ }
+
+ public void testV21MultipleEntryCase_Parse() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "9", new TypeSet("X-NEC-SECRET"))
+ .addExpectedNodeWithOrder("TEL", "10", new TypeSet("X-NEC-HOTEL"))
+ .addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL"))
+ .addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME"));
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "13", new TypeSet("MODEM"))
+ .addExpectedNodeWithOrder("TEL", "14", new TypeSet("PAGER"))
+ .addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY"))
+ .addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL"));
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "17", new TypeSet("X-NEC-BOY"))
+ .addExpectedNodeWithOrder("TEL", "18", new TypeSet("X-NEC-FRIEND"))
+ .addExpectedNodeWithOrder("TEL", "19", new TypeSet("X-NEC-PHS"))
+ .addExpectedNodeWithOrder("TEL", "20", new TypeSet("X-NEC-RESTAURANT"));
+ }
+
+ public void testV21MultipleEntryCase() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-SECRET")
+ .put(Phone.NUMBER, "9");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-HOTEL")
+ .put(Phone.NUMBER, "10");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-SCHOOL")
+ .put(Phone.NUMBER, "11");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
+ .put(Phone.NUMBER, "12");
+
+ elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "MODEM")
+ .put(Phone.NUMBER, "13");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_PAGER)
+ .put(Phone.NUMBER, "14");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-FAMILY")
+ .put(Phone.NUMBER, "15");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-GIRL")
+ .put(Phone.NUMBER, "16");
+
+ elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-BOY")
+ .put(Phone.NUMBER, "17");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-FRIEND")
+ .put(Phone.NUMBER, "18");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-PHS")
+ .put(Phone.NUMBER, "19");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-RESTAURANT")
+ .put(Phone.NUMBER, "20");
+ }
+
+ public void testIgnoreAgentV21_Parse() {
+ mVerifier.initForImportTest(V21, R.raw.v21_winmo_65);
+ ContentValues contentValuesForValue = new ContentValues();
+ contentValuesForValue.put("VALUE", "DATE");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", ""))
+ .addExpectedNodeWithOrder("FN", "Example")
+ .addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue)
+ .addExpectedNodeWithOrder("AGENT", "")
+ .addExpectedNodeWithOrder("X-CLASS", "PUBLIC")
+ .addExpectedNodeWithOrder("X-REDUCTION", "")
+ .addExpectedNodeWithOrder("X-NO", "");
+ }
+
+ public void testIgnoreAgentV21() {
+ mVerifier.initForImportTest(V21, R.raw.v21_winmo_65);
+ ContentValuesVerifier verifier = new ContentValuesVerifier();
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Example")
+ .put(StructuredName.DISPLAY_NAME, "Example");
+ }
+
+ public void testTolerateInvalidCommentLikeLineV21() {
+ mVerifier.initForImportTest(V21, R.raw.v21_invalid_comment_line);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.GIVEN_NAME, "Conference Call")
+ .put(StructuredName.DISPLAY_NAME, "Conference Call");
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "This is an (sharp ->#<- sharp) example. "
+ + "This message must NOT be ignored.");
+ }
+
+ public void testPagerV30_Parse() {
+ mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "3.0")
+ .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
+ .addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com",
+ new TypeSet("WORK", "MSG", "PAGER"));
+ }
+
+ public void testPagerV30() {
+ mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "F")
+ .put(StructuredName.MIDDLE_NAME, "M")
+ .put(StructuredName.GIVEN_NAME, "G")
+ .put(StructuredName.DISPLAY_NAME, "G M F");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_PAGER)
+ .put(Phone.NUMBER, "6101231234@pagersample.com");
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/VCardJapanizationTests.java b/vcard/tests/src/com/android/vcard/tests/VCardJapanizationTests.java
new file mode 100644
index 0000000..0d0b9f1
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/VCardJapanizationTests.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.tests.test_utils.ContactEntry;
+import com.android.vcard.tests.test_utils.ContentValuesBuilder;
+import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem;
+import com.android.vcard.tests.test_utils.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+public class VCardJapanizationTests extends VCardTestsBase {
+ private void testNameUtf8Common(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+ .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+ .put(StructuredName.MIDDLE_NAME, "B")
+ .put(StructuredName.PREFIX, "Dr.")
+ .put(StructuredName.SUFFIX, "Ph.D");
+ ContentValues contentValues =
+ (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+ contentValues)
+ .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
+ Arrays.asList(
+ "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
+ null, contentValues, null, null);
+ }
+
+ public void testNameUtf8V21() {
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE);
+ }
+
+ public void testNameUtf8V30() {
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE);
+ }
+
+ public void testNameShiftJis() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+ .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+ .put(StructuredName.MIDDLE_NAME, "B")
+ .put(StructuredName.PREFIX, "Dr.")
+ .put(StructuredName.SUFFIX, "Ph.D");
+
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+ mContentValuesForSJis)
+ .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
+ Arrays.asList(
+ "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
+ null, mContentValuesForSJis, null, null);
+ }
+
+ /**
+ * DoCoMo phones require all name elements should be in "family name" field.
+ */
+ public void testNameDoCoMo() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+ .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+ .put(StructuredName.MIDDLE_NAME, "B")
+ .put(StructuredName.PREFIX, "Dr.")
+ .put(StructuredName.SUFFIX, "Ph.D");
+
+ final String fullName = "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D";
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("N", fullName + ";;;;",
+ Arrays.asList(fullName, "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNode("FN", fullName, mContentValuesForSJis)
+ .addExpectedNode("SOUND", ";;;;", new TypeSet("X-IRMC-N"))
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("ADR", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "");
+ }
+
+ private void testPhoneticNameCommon(int vcardType, String charset) {
+ mVerifier.initForExportTest(vcardType, charset);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+ final ContentValues contentValues =
+ ("SHIFT_JIS".equalsIgnoreCase(charset) ?
+ (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
+ mContentValuesForQPAndSJis) :
+ (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
+ elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060",
+ contentValues)
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME",
+ "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0",
+ contentValues)
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046",
+ contentValues);
+ if (VCardConfig.isV30(vcardType)) {
+ elem.addExpectedNode("SORT-STRING",
+ "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046",
+ contentValues);
+ }
+ ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+ builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046")
+ .put(StructuredName.DISPLAY_NAME,
+ "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 " +
+ "\u305F\u308D\u3046");
+ }
+
+ public void testPhoneticNameForJapaneseV21Utf8() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, null);
+ }
+
+ public void testPhoneticNameForJapaneseV21Sjis() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
+ }
+
+ public void testPhoneticNameForJapaneseV30Utf8() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null);
+ }
+
+ public void testPhoneticNameForJapaneseV30SJis() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
+ }
+
+ public void testPhoneticNameForMobileV21_1() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("SOUND",
+ "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
+ "\uFF80\uFF9B\uFF73;;;;",
+ mContentValuesForSJis, new TypeSet("X-IRMC-N"));
+ ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+ builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME,
+ "\uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
+ .put(StructuredName.DISPLAY_NAME,
+ "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
+ "\uFF80\uFF9B\uFF73");
+ }
+
+ public void testPhoneticNameForMobileV21_2() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("SOUND", "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73;;;;",
+ mContentValuesForSJis, new TypeSet("X-IRMC-N"));
+ ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+ builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
+ .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
+ }
+
+ private void testPostalAddressWithJapaneseCommon(int vcardType, String charset) {
+ mVerifier.initForExportTest(vcardType, charset);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
+ .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
+ .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
+ .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
+ .put(StructuredPostal.POSTCODE, "494-1313")
+ .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "\u3053\u3093\u306A\u3068\u3053\u308D\u3092\u898B"
+ + "\u308B\u306A\u3093\u3066\u6687\u4EBA\u3067\u3059\u304B\uFF1F")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
+
+ ContentValues contentValues = ("UTF-8".equalsIgnoreCase(charset) ?
+ (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
+ mContentValuesForQPAndSJis) :
+ (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
+ mContentValuesForQPAndUtf8));
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
+ // LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is
+ // same as that in vCard 3.0, which can be changed in the future.
+ elem.addExpectedNode("ADR", Arrays.asList("\u79C1\u66F8\u7BB107",
+ "", "\u96DB\u898B\u6CA2\u6751", "\u9E7F\u9AA8\u5E02", "\u00D7\u00D7\u770C",
+ "494-1313", "\u65E5\u672C"),
+ contentValues);
+ mVerifier.addContentValuesVerifierElem().addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
+ .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
+ .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
+ .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
+ .put(StructuredPostal.POSTCODE, "494-1313")
+ .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "\u65E5\u672C 494-1313 \u00D7\u00D7\u770C \u9E7F\u9AA8\u5E02 " +
+ "\u96DB\u898B\u6CA2\u6751 " + "\u79C1\u66F8\u7BB107")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+ }
+ public void testPostalAddresswithJapaneseV21() {
+ testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
+ }
+
+ /**
+ * Verifies that only one address field is emitted toward DoCoMo phones.
+ * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
+ */
+ public void testPostalAdrressForDoCoMo_1() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+ .put(StructuredPostal.POBOX, "3");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom")
+ .put(StructuredPostal.POBOX, "4");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR",
+ Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalAdrressForDoCoMo_2() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom")
+ .put(StructuredPostal.POBOX, "3");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR",
+ Arrays.asList("2", "", "", "", "", "", ""), new TypeSet("WORK"));
+ }
+
+ public void testPostalAdrressForDoCoMo_3() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom1")
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom2")
+ .put(StructuredPostal.POBOX, "3");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR", Arrays.asList("2", "", "", "", "", "", ""));
+ }
+
+ /**
+ * Verifies the vCard exporter tolerates null TYPE.
+ */
+ public void testPostalAdrressForDoCoMo_4() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+ .put(StructuredPostal.POBOX, "3");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.POBOX, "4");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "5");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR",
+ Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
+ }
+
+ private void testJapanesePhoneNumberCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "0312341234")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "09012341234")
+ .put(Phone.TYPE, Phone.TYPE_MOBILE);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
+ }
+
+ public void testJapanesePhoneNumberV21_1() {
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE);
+ }
+
+ public void testJapanesePhoneNumberV30() {
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE);
+ }
+
+ public void testJapanesePhoneNumberDoCoMo() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "0312341234")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "09012341234")
+ .put(Phone.TYPE, Phone.TYPE_MOBILE);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR", "", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
+ }
+
+ public void testNoteDoCoMo() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note1");
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note2");
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note3");
+
+ // More than one note fields must be aggregated into one note.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR", "", new TypeSet("HOME"))
+ .addExpectedNode("NOTE", "note1\nnote2\nnote3", mContentValuesForQP);
+ }
+
+ public void testAndroidCustomV21() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC);
+ mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ Arrays.asList(Nickname.CONTENT_ITEM_TYPE,
+ "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC",
+ "", "", "", "", "", "", "", "", "", "", "", "", "", ""),
+ mContentValuesForQPAndUtf8);
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/VCardTestsBase.java b/vcard/tests/src/com/android/vcard/tests/VCardTestsBase.java
new file mode 100644
index 0000000..8998b3c
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/VCardTestsBase.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests;
+
+import android.content.ContentValues;
+import android.test.AndroidTestCase;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.tests.test_utils.VCardVerifier;
+
+/**
+ * BaseClass for vCard unit tests with utility classes.
+ * Please do not add each unit test here.
+ */
+/* package */ class VCardTestsBase extends AndroidTestCase {
+ public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC;
+ public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC;
+
+ // Do not modify these during tests.
+ protected final ContentValues mContentValuesForQP;
+ protected final ContentValues mContentValuesForSJis;
+ protected final ContentValues mContentValuesForUtf8;
+ protected final ContentValues mContentValuesForQPAndSJis;
+ protected final ContentValues mContentValuesForQPAndUtf8;
+ protected final ContentValues mContentValuesForBase64V21;
+ protected final ContentValues mContentValuesForBase64V30;
+
+ protected VCardVerifier mVerifier;
+ private boolean mSkipVerification;
+
+ public VCardTestsBase() {
+ super();
+ // Not using constants in vCard code since it may be wrong.
+ mContentValuesForQP = new ContentValues();
+ mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+ mContentValuesForSJis = new ContentValues();
+ mContentValuesForSJis.put("CHARSET", "SHIFT_JIS");
+ mContentValuesForUtf8 = new ContentValues();
+ mContentValuesForUtf8.put("CHARSET", "UTF-8");
+ mContentValuesForQPAndSJis = new ContentValues();
+ mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE");
+ mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS");
+ mContentValuesForQPAndUtf8 = new ContentValues();
+ mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE");
+ mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8");
+ mContentValuesForBase64V21 = new ContentValues();
+ mContentValuesForBase64V21.put("ENCODING", "BASE64");
+ mContentValuesForBase64V30 = new ContentValues();
+ mContentValuesForBase64V30.put("ENCODING", "b");
+ }
+
+ @Override
+ public void testAndroidTestCaseSetupProperly() {
+ super.testAndroidTestCaseSetupProperly();
+ mSkipVerification = true;
+ }
+
+ @Override
+ public void setUp() throws Exception{
+ super.setUp();
+ mVerifier = new VCardVerifier(this);
+ mSkipVerification = false;
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ if (!mSkipVerification) {
+ mVerifier.verify();
+ }
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/VCardUtilsTests.java b/vcard/tests/src/com/android/vcard/tests/VCardUtilsTests.java
new file mode 100644
index 0000000..732009a
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/VCardUtilsTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests;
+
+import com.android.vcard.VCardUtils;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+public class VCardUtilsTests extends TestCase {
+ public void testContainsOnlyPrintableAscii() {
+ assertTrue(VCardUtils.containsOnlyPrintableAscii((String)null));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii((String[])null));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii((List<String>)null));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii(""));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0x20; i < 0x7F; i++) {
+ builder.append((char)i);
+ }
+ assertTrue(VCardUtils.containsOnlyPrintableAscii(builder.toString()));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii("\r\n"));
+ assertFalse(VCardUtils.containsOnlyPrintableAscii("\u0019"));
+ assertFalse(VCardUtils.containsOnlyPrintableAscii("\u007F"));
+ }
+
+ public void testContainsOnlyNonCrLfPrintableAscii() {
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String)null));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String[])null));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((List<String>)null));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(""));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0x20; i < 0x7F; i++) {
+ builder.append((char)i);
+ }
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(builder.toString()));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u0019"));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u007F"));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\r"));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\n"));
+ }
+
+ public void testContainsOnlyAlphaDigitHyphen() {
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String)null));
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String[])null));
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((List<String>)null));
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen(""));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("0123456789-"));
+ for (int i = 0; i < 0x30; i++) {
+ if (i == 0x2D) { // -
+ continue;
+ }
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ for (int i = 0x3A; i < 0x41; i++) {
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ for (int i = 0x5B; i < 0x61; i++) {
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ for (int i = 0x7B; i < 0x100; i++) {
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ContactEntry.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ContactEntry.java
new file mode 100644
index 0000000..dff1f05
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ContactEntry.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * The class representing one contact, which should contain multiple ContentValues like
+ * StructuredName, Email, etc.
+ * </p>
+ */
+public final class ContactEntry {
+ private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
+
+ public ContentValuesBuilder addContentValues(String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Data.MIMETYPE, mimeType);
+ mContentValuesList.add(contentValues);
+ return new ContentValuesBuilder(contentValues);
+ }
+
+ public List<ContentValues> getList() {
+ return mContentValuesList;
+ }
+}
\ No newline at end of file
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesBuilder.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesBuilder.java
new file mode 100644
index 0000000..fb53b8f
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesBuilder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentValues;
+
+/**
+ * ContentValues-like class which enables users to chain put() methods and restricts
+ * the other methods.
+ */
+public class ContentValuesBuilder {
+ private final ContentValues mContentValues;
+
+ public ContentValuesBuilder(final ContentValues contentValues) {
+ mContentValues = contentValues;
+ }
+
+ public ContentValuesBuilder put(String key, String value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Byte value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Short value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Integer value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Long value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Float value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Double value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Boolean value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, byte[] value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder putNull(String key) {
+ mContentValues.putNull(key);
+ return this;
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesVerifier.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesVerifier.java
new file mode 100644
index 0000000..2b3e3ab
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesVerifier.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.test.AndroidTestCase;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryHandler;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ContentValuesVerifier implements VCardEntryHandler {
+ private AndroidTestCase mTestCase;
+ private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
+ new ArrayList<ContentValuesVerifierElem>();
+ private int mIndex;
+
+ public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
+ mTestCase = androidTestCase;
+ ContentValuesVerifierElem importVerifier = new ContentValuesVerifierElem(androidTestCase);
+ mContentValuesVerifierElemList.add(importVerifier);
+ return importVerifier;
+ }
+
+ public void verify(int resId, int vCardType) throws IOException, VCardException {
+ verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
+ }
+
+ public void verify(int resId, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ verify(mTestCase.getContext().getResources().openRawResource(resId),
+ vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30();
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ verify(is, vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ VCardEntryConstructor builder = new VCardEntryConstructor(vCardType, null);
+ builder.addEntryHandler(this);
+ try {
+ vCardParser.parse(is, builder);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public void onStart() {
+ for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
+ elem.onParsingStart();
+ }
+ }
+
+ public void onEntryCreated(VCardEntry entry) {
+ mTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size());
+ mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry);
+ mIndex++;
+ }
+
+ public void onEnd() {
+ for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
+ elem.onParsingEnd();
+ elem.verifyResolver();
+ }
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesVerifierElem.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesVerifierElem.java
new file mode 100644
index 0000000..6c09693
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ContentValuesVerifierElem.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.Data;
+import android.test.AndroidTestCase;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryCommitter;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryHandler;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ContentValuesVerifierElem {
+ private final AndroidTestCase mTestCase;
+ private final ImportTestResolver mResolver;
+ private final VCardEntryHandler mHandler;
+
+ public ContentValuesVerifierElem(AndroidTestCase androidTestCase) {
+ mTestCase = androidTestCase;
+ mResolver = new ImportTestResolver(androidTestCase);
+ mHandler = new VCardEntryCommitter(mResolver);
+ }
+
+ public ContentValuesBuilder addExpected(String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Data.MIMETYPE, mimeType);
+ mResolver.addExpectedContentValues(contentValues);
+ return new ContentValuesBuilder(contentValues);
+ }
+
+ public void verify(int resId, int vCardType)
+ throws IOException, VCardException {
+ verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
+ }
+
+ public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30();
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ final VCardEntryConstructor builder = new VCardEntryConstructor(vCardType, null);
+ builder.addEntryHandler(mHandler);
+ try {
+ vCardParser.parse(is, builder);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ verifyResolver();
+ }
+
+ public void verifyResolver() {
+ mResolver.verify();
+ }
+
+ public void onParsingStart() {
+ mHandler.onStart();
+ }
+
+ public void onEntryCreated(VCardEntry entry) {
+ mHandler.onEntryCreated(entry);
+ }
+
+ public void onParsingEnd() {
+ mHandler.onEnd();
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ExportTestProvider.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ExportTestProvider.java
new file mode 100644
index 0000000..caedf9d
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ExportTestProvider.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockCursor;
+
+import com.android.vcard.VCardComposer;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/* package */ class ExportTestProvider extends MockContentProvider {
+ final private TestCase mTestCase;
+ final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
+
+ private static class MockEntityIterator implements EntityIterator {
+ List<Entity> mEntityList;
+ Iterator<Entity> mIterator;
+
+ public MockEntityIterator(List<ContentValues> contentValuesList) {
+ mEntityList = new ArrayList<Entity>();
+ Entity entity = new Entity(new ContentValues());
+ for (ContentValues contentValues : contentValuesList) {
+ entity.addSubValue(Data.CONTENT_URI, contentValues);
+ }
+ mEntityList.add(entity);
+ mIterator = mEntityList.iterator();
+ }
+
+ public boolean hasNext() {
+ return mIterator.hasNext();
+ }
+
+ public Entity next() {
+ return mIterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove not supported");
+ }
+
+ public void reset() {
+ mIterator = mEntityList.iterator();
+ }
+
+ public void close() {
+ }
+ }
+
+ public ExportTestProvider(TestCase testCase) {
+ mTestCase = testCase;
+ }
+
+ public ContactEntry buildInputEntry() {
+ ContactEntry contactEntry = new ContactEntry();
+ mContactEntryList.add(contactEntry);
+ return contactEntry;
+ }
+
+ /**
+ * <p>
+ * An old method which had existed but was removed from ContentResolver.
+ * </p>
+ * <p>
+ * We still keep using this method since we don't have a propeer way to know
+ * which value in the ContentValue corresponds to the entry in Contacts database.
+ * </p>
+ */
+ public EntityIterator queryEntities(Uri uri,
+ String selection, String[] selectionArgs, String sortOrder) {
+ mTestCase.assertTrue(uri != null);
+ mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
+ final String authority = uri.getAuthority();
+ mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
+ mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
+ mTestCase.assertEquals(1, selectionArgs.length);
+ final int id = Integer.parseInt(selectionArgs[0]);
+ mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size());
+
+ return new MockEntityIterator(mContactEntryList.get(id).getList());
+ }
+
+ @Override
+ public Cursor query(Uri uri,String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
+ // In this test, following arguments are not supported.
+ mTestCase.assertNull(selection);
+ mTestCase.assertNull(selectionArgs);
+ mTestCase.assertNull(sortOrder);
+
+ return new MockCursor() {
+ int mCurrentPosition = -1;
+
+ @Override
+ public int getCount() {
+ return mContactEntryList.size();
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ mCurrentPosition = 0;
+ return true;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ if (mCurrentPosition < mContactEntryList.size()) {
+ mCurrentPosition++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isBeforeFirst() {
+ return mCurrentPosition < 0;
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ return mCurrentPosition >= mContactEntryList.size();
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ mTestCase.assertEquals(Contacts._ID, columnName);
+ return 0;
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ mTestCase.assertEquals(0, columnIndex);
+ mTestCase.assertTrue(mCurrentPosition >= 0
+ && mCurrentPosition < mContactEntryList.size());
+ return mCurrentPosition;
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ return String.valueOf(getInt(columnIndex));
+ }
+
+ @Override
+ public void close() {
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ExportTestResolver.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ExportTestResolver.java
new file mode 100644
index 0000000..3cd014c
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ExportTestResolver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentResolver;
+
+import com.android.vcard.VCardComposer;
+
+import junit.framework.TestCase;
+
+/* package */ class ExportTestResolver extends MockContentResolver {
+ private final ExportTestProvider mProvider;
+ public ExportTestResolver(TestCase testCase) {
+ mProvider = new ExportTestProvider(testCase);
+ addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
+ addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
+ }
+
+ public ContactEntry addInputContactEntry() {
+ return mProvider.buildInputEntry();
+ }
+
+ public ExportTestProvider getProvider() {
+ return mProvider;
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ImportTestProvider.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ImportTestProvider.java
new file mode 100644
index 0000000..3d7cb60
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ImportTestProvider.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.test.mock.MockContentProvider;
+import android.text.TextUtils;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+/* package */ class ImportTestProvider extends MockContentProvider {
+ private static final Set<String> sKnownMimeTypeSet =
+ new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
+ Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
+ Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE,
+ Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE,
+ Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE,
+ Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+ GroupMembership.CONTENT_ITEM_TYPE));
+
+ final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
+
+ private final TestCase mTestCase;
+
+ public ImportTestProvider(TestCase testCase) {
+ mTestCase = testCase;
+ mMimeTypeToExpectedContentValues =
+ new HashMap<String, Collection<ContentValues>>();
+ for (String acceptanbleMimeType : sKnownMimeTypeSet) {
+ // Do not use HashSet since the current implementation changes the content of
+ // ContentValues after the insertion, which make the result of hashCode()
+ // changes...
+ mMimeTypeToExpectedContentValues.put(
+ acceptanbleMimeType, new ArrayList<ContentValues>());
+ }
+ }
+
+ public void addExpectedContentValues(ContentValues expectedContentValues) {
+ final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
+ if (!sKnownMimeTypeSet.contains(mimeType)) {
+ mTestCase.fail(String.format(
+ "Unknow MimeType %s in the test code. Test code should be broken.",
+ mimeType));
+ }
+
+ final Collection<ContentValues> contentValuesCollection =
+ mMimeTypeToExpectedContentValues.get(mimeType);
+ contentValuesCollection.add(expectedContentValues);
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(
+ ArrayList<ContentProviderOperation> operations) {
+ if (operations == null) {
+ mTestCase.fail("There is no operation.");
+ }
+
+ final int size = operations.size();
+ ContentProviderResult[] fakeResultArray = new ContentProviderResult[size];
+ for (int i = 0; i < size; i++) {
+ Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i));
+ fakeResultArray[i] = new ContentProviderResult(uri);
+ }
+
+ for (int i = 0; i < size; i++) {
+ ContentProviderOperation operation = operations.get(i);
+ ContentValues contentValues = operation.resolveValueBackReferences(
+ fakeResultArray, i);
+ }
+ for (int i = 0; i < size; i++) {
+ ContentProviderOperation operation = operations.get(i);
+ ContentValues actualContentValues = operation.resolveValueBackReferences(
+ fakeResultArray, i);
+ final Uri uri = operation.getUri();
+ if (uri.equals(RawContacts.CONTENT_URI)) {
+ mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
+ mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
+ } else if (uri.equals(Data.CONTENT_URI)) {
+ final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
+ if (!sKnownMimeTypeSet.contains(mimeType)) {
+ mTestCase.fail(String.format(
+ "Unknown MimeType %s. Probably added after developing this test",
+ mimeType));
+ }
+ // Remove data meaningless in this unit tests.
+ // Specifically, Data.DATA1 - DATA7 are set to null or empty String
+ // regardless of the input, but it may change depending on how
+ // resolver-related code handles it.
+ // Here, we ignore these implementation-dependent specs and
+ // just check whether vCard importer correctly inserts rellevent data.
+ Set<String> keyToBeRemoved = new HashSet<String>();
+ for (Entry<String, Object> entry : actualContentValues.valueSet()) {
+ Object value = entry.getValue();
+ if (value == null || TextUtils.isEmpty(value.toString())) {
+ keyToBeRemoved.add(entry.getKey());
+ }
+ }
+ for (String key: keyToBeRemoved) {
+ actualContentValues.remove(key);
+ }
+ /* for testing
+ Log.d("@@@",
+ String.format("MimeType: %s, data: %s",
+ mimeType, actualContentValues.toString())); */
+ // Remove RAW_CONTACT_ID entry just for safety, since we do not care
+ // how resolver-related code handles the entry in this unit test,
+ if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) {
+ actualContentValues.remove(Data.RAW_CONTACT_ID);
+ }
+ final Collection<ContentValues> contentValuesCollection =
+ mMimeTypeToExpectedContentValues.get(mimeType);
+ if (contentValuesCollection.isEmpty()) {
+ mTestCase.fail("ContentValues for MimeType " + mimeType
+ + " is not expected at all (" + actualContentValues + ")");
+ }
+ boolean checked = false;
+ for (ContentValues expectedContentValues : contentValuesCollection) {
+ /*for testing
+ Log.d("@@@", "expected: "
+ + convertToEasilyReadableString(expectedContentValues));
+ Log.d("@@@", "actual : "
+ + convertToEasilyReadableString(actualContentValues));*/
+ if (equalsForContentValues(expectedContentValues,
+ actualContentValues)) {
+ mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
+ checked = true;
+ break;
+ }
+ }
+ if (!checked) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Unexpected: ");
+ builder.append(convertToEasilyReadableString(actualContentValues));
+ builder.append("\nExpected: ");
+ for (ContentValues expectedContentValues : contentValuesCollection) {
+ builder.append(convertToEasilyReadableString(expectedContentValues));
+ }
+ mTestCase.fail(builder.toString());
+ }
+ } else {
+ mTestCase.fail("Unexpected Uri has come: " + uri);
+ }
+ } // for (int i = 0; i < size; i++) {
+ return fakeResultArray;
+ }
+
+ public void verify() {
+ StringBuilder builder = new StringBuilder();
+ for (Collection<ContentValues> contentValuesCollection :
+ mMimeTypeToExpectedContentValues.values()) {
+ for (ContentValues expectedContentValues: contentValuesCollection) {
+ builder.append(convertToEasilyReadableString(expectedContentValues));
+ builder.append("\n");
+ }
+ }
+ if (builder.length() > 0) {
+ final String failMsg =
+ "There is(are) remaining expected ContentValues instance(s): \n"
+ + builder.toString();
+ mTestCase.fail(failMsg);
+ }
+ }
+
+ /**
+ * Utility method to print ContentValues whose content is printed with sorted keys.
+ */
+ private String convertToEasilyReadableString(ContentValues contentValues) {
+ if (contentValues == null) {
+ return "null";
+ }
+ String mimeTypeValue = "";
+ SortedMap<String, String> sortedMap = new TreeMap<String, String>();
+ for (Entry<String, Object> entry : contentValues.valueSet()) {
+ final String key = entry.getKey();
+ final Object value = entry.getValue();
+ final String valueString = (value != null ? value.toString() : null);
+ if (Data.MIMETYPE.equals(key)) {
+ mimeTypeValue = valueString;
+ } else {
+ mTestCase.assertNotNull(key);
+ sortedMap.put(key, valueString);
+ }
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(Data.MIMETYPE);
+ builder.append('=');
+ builder.append(mimeTypeValue);
+ for (Entry<String, String> entry : sortedMap.entrySet()) {
+ final String key = entry.getKey();
+ final String value = entry.getValue();
+ builder.append(' ');
+ builder.append(key);
+ builder.append("=\"");
+ builder.append(value);
+ builder.append('"');
+ }
+ return builder.toString();
+ }
+
+ private static boolean equalsForContentValues(
+ ContentValues expected, ContentValues actual) {
+ if (expected == actual) {
+ return true;
+ } else if (expected == null || actual == null || expected.size() != actual.size()) {
+ return false;
+ }
+
+ for (Entry<String, Object> entry : expected.valueSet()) {
+ final String key = entry.getKey();
+ final Object value = entry.getValue();
+ if (!actual.containsKey(key)) {
+ return false;
+ }
+ if (value instanceof byte[]) {
+ Object actualValue = actual.get(key);
+ if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
+ byte[] e = (byte[])value;
+ byte[] a = (byte[])actualValue;
+ Log.d("@@@", "expected (len: " + e.length + "): " + Arrays.toString(e));
+ Log.d("@@@", "actual (len: " + a.length + "): " + Arrays.toString(a));
+ return false;
+ }
+ } else if (!value.equals(actual.get(key))) {
+ Log.d("@@@", "different.");
+ return false;
+ }
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/ImportTestResolver.java b/vcard/tests/src/com/android/vcard/tests/test_utils/ImportTestResolver.java
new file mode 100644
index 0000000..645e9db
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/ImportTestResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentResolver;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+/* package */ class ImportTestResolver extends MockContentResolver {
+ private final ImportTestProvider mProvider;
+
+ public ImportTestResolver(TestCase testCase) {
+ mProvider = new ImportTestProvider(testCase);
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(String authority,
+ ArrayList<ContentProviderOperation> operations) {
+ equalsString(authority, RawContacts.CONTENT_URI.toString());
+ return mProvider.applyBatch(operations);
+ }
+
+ public void addExpectedContentValues(ContentValues expectedContentValues) {
+ mProvider.addExpectedContentValues(expectedContentValues);
+ }
+
+ public void verify() {
+ mProvider.verify();
+ }
+
+ private static boolean equalsString(String a, String b) {
+ if (a == null || a.length() == 0) {
+ return b == null || b.length() == 0;
+ } else {
+ return a.equals(b);
+ }
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/LineVerifier.java b/vcard/tests/src/com/android/vcard/tests/test_utils/LineVerifier.java
new file mode 100644
index 0000000..d8cfe5b
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/LineVerifier.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import com.android.vcard.VCardComposer;
+
+import android.content.Context;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+public class LineVerifier implements VCardComposer.OneEntryHandler {
+ private final TestCase mTestCase;
+ private final ArrayList<LineVerifierElem> mLineVerifierElemList;
+ private int mVCardType;
+ private int index;
+
+ public LineVerifier(TestCase testCase, int vcardType) {
+ mTestCase = testCase;
+ mLineVerifierElemList = new ArrayList<LineVerifierElem>();
+ mVCardType = vcardType;
+ }
+
+ public LineVerifierElem addLineVerifierElem() {
+ LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType);
+ mLineVerifierElemList.add(lineVerifier);
+ return lineVerifier;
+ }
+
+ public void verify(String vcard) {
+ if (index >= mLineVerifierElemList.size()) {
+ mTestCase.fail("Insufficient number of LineVerifier (" + index + ")");
+ }
+
+ LineVerifierElem lineVerifier = mLineVerifierElemList.get(index);
+ lineVerifier.verify(vcard);
+
+ index++;
+ }
+
+ public boolean onEntryCreated(String vcard) {
+ verify(vcard);
+ return true;
+ }
+
+ public boolean onInit(Context context) {
+ return true;
+ }
+
+ public void onTerminate() {
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/LineVerifierElem.java b/vcard/tests/src/com/android/vcard/tests/test_utils/LineVerifierElem.java
new file mode 100644
index 0000000..3ec6ba3
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/LineVerifierElem.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.text.TextUtils;
+
+import com.android.vcard.VCardConfig;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LineVerifierElem {
+ private final TestCase mTestCase;
+ private final List<String> mExpectedLineList = new ArrayList<String>();
+ private final boolean mIsV30;
+
+ public LineVerifierElem(TestCase testCase, int vcardType) {
+ mTestCase = testCase;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ }
+
+ public LineVerifierElem addExpected(final String line) {
+ if (!TextUtils.isEmpty(line)) {
+ mExpectedLineList.add(line);
+ }
+ return this;
+ }
+
+ public void verify(final String vcard) {
+ final String[] lineArray = vcard.split("\\r?\\n");
+ final int length = lineArray.length;
+ boolean beginExists = false;
+ boolean endExists = false;
+ boolean versionExists = false;
+
+ for (int i = 0; i < length; i++) {
+ final String line = lineArray[i];
+ if (TextUtils.isEmpty(line)) {
+ continue;
+ }
+
+ if ("BEGIN:VCARD".equalsIgnoreCase(line)) {
+ if (beginExists) {
+ mTestCase.fail("Multiple \"BEGIN:VCARD\" line found");
+ } else {
+ beginExists = true;
+ continue;
+ }
+ } else if ("END:VCARD".equalsIgnoreCase(line)) {
+ if (endExists) {
+ mTestCase.fail("Multiple \"END:VCARD\" line found");
+ } else {
+ endExists = true;
+ continue;
+ }
+ } else if ((mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) {
+ if (versionExists) {
+ mTestCase.fail("Multiple VERSION line + found");
+ } else {
+ versionExists = true;
+ continue;
+ }
+ }
+
+ if (!beginExists) {
+ mTestCase.fail("Property other than BEGIN came before BEGIN property: "
+ + line);
+ } else if (endExists) {
+ mTestCase.fail("Property other than END came after END property: "
+ + line);
+ }
+
+ final int index = mExpectedLineList.indexOf(line);
+ if (index >= 0) {
+ mExpectedLineList.remove(index);
+ } else {
+ mTestCase.fail("Unexpected line: " + line);
+ }
+ }
+
+ if (!mExpectedLineList.isEmpty()) {
+ StringBuffer buffer = new StringBuffer();
+ for (String expectedLine : mExpectedLineList) {
+ buffer.append(expectedLine);
+ buffer.append("\n");
+ }
+
+ mTestCase.fail("Expected line(s) not found:" + buffer.toString());
+ }
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNode.java b/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNode.java
new file mode 100644
index 0000000..14c8d6c
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNode.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentValues;
+
+import com.android.vcard.VCardEntry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * The class representing one property (e.g. "N;ENCODING=UTF-8:family:given:middle:prefix:suffix").
+ * </p>
+ * <p>
+ * Previously used in main vCard handling code but now exists only for testing.
+ * </p>
+ * <p>
+ * Especially useful for testing parser code (VCardParser), since all properties can be
+ * checked via this class unlike {@link VCardEntry}, which only emits the result of
+ * interpretation of the content of each vCard. We cannot know whether vCard parser or
+ * {@link VCardEntry} is wrong without this class.
+ * </p>
+ */
+public class PropertyNode {
+ public String propName;
+ public String propValue;
+ public List<String> propValue_vector;
+
+ /** Store value as byte[],after decode.
+ * Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc.
+ */
+ public byte[] propValue_bytes;
+
+ /**
+ * param store: key=paramType, value=paramValue
+ * Note that currently PropertyNode class does not support multiple param-values
+ * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
+ * one String value like "A,B", not ["A", "B"]...
+ * TODO: fix this.
+ */
+ public ContentValues paramMap;
+
+ /** Only for TYPE=??? param store. */
+ public Set<String> paramMap_TYPE;
+
+ /** Store group values. Used only in VCard. */
+ public Set<String> propGroupSet;
+
+ public PropertyNode() {
+ propName = "";
+ propValue = "";
+ propValue_vector = new ArrayList<String>();
+ paramMap = new ContentValues();
+ paramMap_TYPE = new HashSet<String>();
+ propGroupSet = new HashSet<String>();
+ }
+
+ public PropertyNode(
+ String propName, String propValue, List<String> propValue_vector,
+ byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE,
+ Set<String> propGroupSet) {
+ if (propName != null) {
+ this.propName = propName;
+ } else {
+ this.propName = "";
+ }
+ if (propValue != null) {
+ this.propValue = propValue;
+ } else {
+ this.propValue = "";
+ }
+ if (propValue_vector != null) {
+ this.propValue_vector = propValue_vector;
+ } else {
+ this.propValue_vector = new ArrayList<String>();
+ }
+ this.propValue_bytes = propValue_bytes;
+ if (paramMap != null) {
+ this.paramMap = paramMap;
+ } else {
+ this.paramMap = new ContentValues();
+ }
+ if (paramMap_TYPE != null) {
+ this.paramMap_TYPE = paramMap_TYPE;
+ } else {
+ this.paramMap_TYPE = new HashSet<String>();
+ }
+ if (propGroupSet != null) {
+ this.propGroupSet = propGroupSet;
+ } else {
+ this.propGroupSet = new HashSet<String>();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ // vCard may contain more than one same line in one entry, while HashSet or any other
+ // library which utilize hashCode() does not honor that, so intentionally throw an
+ // Exception.
+ throw new UnsupportedOperationException(
+ "PropertyNode does not provide hashCode() implementation intentionally.");
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PropertyNode)) {
+ return false;
+ }
+
+ PropertyNode node = (PropertyNode)obj;
+
+ if (propName == null || !propName.equals(node.propName)) {
+ return false;
+ } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
+ return false;
+ } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
+ return false;
+ } else if (!propGroupSet.equals(node.propGroupSet)) {
+ return false;
+ }
+
+ if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) {
+ return true;
+ } else {
+ if (!propValue.equals(node.propValue)) {
+ return false;
+ }
+
+ // The value in propValue_vector is not decoded even if it should be
+ // decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector
+ // is 1, the encoded value is stored in propValue, so we do not have to
+ // check it.
+ return (propValue_vector.equals(node.propValue_vector) ||
+ propValue_vector.size() == 1 ||
+ node.propValue_vector.size() == 1);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("propName: ");
+ builder.append(propName);
+ builder.append(", paramMap: ");
+ builder.append(paramMap.toString());
+ builder.append(", paramMap_TYPE: [");
+ boolean first = true;
+ for (String elem : paramMap_TYPE) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(", ");
+ }
+ builder.append('"');
+ builder.append(elem);
+ builder.append('"');
+ }
+ builder.append("]");
+ if (!propGroupSet.isEmpty()) {
+ builder.append(", propGroupSet: [");
+ first = true;
+ for (String elem : propGroupSet) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(", ");
+ }
+ builder.append('"');
+ builder.append(elem);
+ builder.append('"');
+ }
+ builder.append("]");
+ }
+ if (propValue_vector != null && propValue_vector.size() > 1) {
+ builder.append(", propValue_vector size: ");
+ builder.append(propValue_vector.size());
+ }
+ if (propValue_bytes != null) {
+ builder.append(", propValue_bytes size: ");
+ builder.append(propValue_bytes.length);
+ }
+ builder.append(", propValue: \"");
+ builder.append(propValue);
+ builder.append("\"");
+ return builder.toString();
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNodesVerifier.java b/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNodesVerifier.java
new file mode 100644
index 0000000..de33a36
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNodesVerifier.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.test.AndroidTestCase;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PropertyNodesVerifier extends VNodeBuilder {
+ private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
+ private final AndroidTestCase mAndroidTestCase;
+ private int mIndex;
+
+ public PropertyNodesVerifier(AndroidTestCase testCase) {
+ super();
+ mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
+ mAndroidTestCase = testCase;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+ PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
+ mPropertyNodesVerifierElemList.add(elem);
+ return elem;
+ }
+
+ public void verify(int resId, int vCardType)
+ throws IOException, VCardException {
+ verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
+ }
+
+ public void verify(int resId, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
+ vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30();
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ verify(is, vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ try {
+ vCardParser.parse(is, this);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void endEntry() {
+ super.endEntry();
+ mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
+ mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
+ mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
+ mIndex++;
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNodesVerifierElem.java b/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNodesVerifierElem.java
new file mode 100644
index 0000000..6eb8498
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/PropertyNodesVerifierElem.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentValues;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Utility class which verifies input VNode.
+ *
+ * This class first checks whether each propertyNode in the VNode is in the
+ * "ordered expected property list".
+ * If the node does not exist in the "ordered list", the class refers to
+ * "unorderd expected property set" and checks the node is expected somewhere.
+ */
+public class PropertyNodesVerifierElem {
+ public static class TypeSet extends HashSet<String> {
+ public TypeSet(String ... array) {
+ super(Arrays.asList(array));
+ }
+ }
+
+ public static class GroupSet extends HashSet<String> {
+ public GroupSet(String ... array) {
+ super(Arrays.asList(array));
+ }
+ }
+
+ private final HashMap<String, List<PropertyNode>> mOrderedNodeMap;
+ // Intentionally use ArrayList instead of Set, assuming there may be more than one
+ // exactly same objects.
+ private final ArrayList<PropertyNode> mUnorderedNodeList;
+ private final TestCase mTestCase;
+
+ public PropertyNodesVerifierElem(TestCase testCase) {
+ mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
+ mUnorderedNodeList = new ArrayList<PropertyNode>();
+ mTestCase = testCase;
+ }
+
+ // WithOrder
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue) {
+ return addExpectedNodeWithOrder(propName, propValue, null, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, String propValue, ContentValues contentValues) {
+ return addExpectedNodeWithOrder(propName, propValue, null,
+ null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, List<String> propValueList, ContentValues contentValues) {
+ return addExpectedNodeWithOrder(propName, null, propValueList,
+ null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, String propValue, List<String> propValueList) {
+ return addExpectedNodeWithOrder(propName, propValue, propValueList, null,
+ null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, List<String> propValueList) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addExpectedNodeWithOrder(propName, propValue.toString(), propValueList,
+ null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, propValue, null,
+ null, null, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, null, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ ContentValues paramMap, TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, propValue, null, null,
+ paramMap, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ List<String> propValueList, byte[] propValue_bytes,
+ ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+ if (propValue == null && propValueList != null) {
+ propValue = concatinateListWithSemiColon(propValueList);
+ }
+ PropertyNode propertyNode = new PropertyNode(propName,
+ propValue, propValueList, propValue_bytes,
+ paramMap, paramMap_TYPE, propGroupSet);
+ List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+ if (expectedNodeList == null) {
+ expectedNodeList = new ArrayList<PropertyNode>();
+ mOrderedNodeMap.put(propName, expectedNodeList);
+ }
+ expectedNodeList.add(propertyNode);
+ return this;
+ }
+
+ // WithoutOrder
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue) {
+ return addExpectedNode(propName, propValue, null, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ ContentValues contentValues) {
+ return addExpectedNode(propName, propValue, null, null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName,
+ List<String> propValueList, ContentValues contentValues) {
+ return addExpectedNode(propName, null,
+ propValueList, null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ List<String> propValueList) {
+ return addExpectedNode(propName, propValue, propValueList, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName,
+ List<String> propValueList) {
+ return addExpectedNode(propName, null, propValueList,
+ null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ TypeSet paramMap_TYPE) {
+ return addExpectedNode(propName, propValue, null, null, null, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addExpectedNode(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addExpectedNode(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ ContentValues paramMap, TypeSet paramMap_TYPE) {
+ return addExpectedNode(propName, propValue, null, null,
+ paramMap, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ List<String> propValueList, byte[] propValue_bytes,
+ ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+ if (propValue == null && propValueList != null) {
+ propValue = concatinateListWithSemiColon(propValueList);
+ }
+ mUnorderedNodeList.add(new PropertyNode(propName, propValue,
+ propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet));
+ return this;
+ }
+
+ public void verify(VNode vnode) {
+ for (PropertyNode actualNode : vnode.propList) {
+ verifyNode(actualNode.propName, actualNode);
+ }
+ if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) {
+ List<String> expectedProps = new ArrayList<String>();
+ for (List<PropertyNode> nodes : mOrderedNodeMap.values()) {
+ for (PropertyNode node : nodes) {
+ if (!expectedProps.contains(node.propName)) {
+ expectedProps.add(node.propName);
+ }
+ }
+ }
+ for (PropertyNode node : mUnorderedNodeList) {
+ if (!expectedProps.contains(node.propName)) {
+ expectedProps.add(node.propName);
+ }
+ }
+ mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+ + " was not found.");
+ }
+ }
+
+ private void verifyNode(final String propName, final PropertyNode actualNode) {
+ List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+ final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
+ if (size > 0) {
+ for (int i = 0; i < size; i++) {
+ PropertyNode expectedNode = expectedNodeList.get(i);
+ List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>();
+ if (expectedNode.propName.equals(propName)) {
+ if (expectedNode.equals(actualNode)) {
+ expectedNodeList.remove(i);
+ if (expectedNodeList.size() == 0) {
+ mOrderedNodeMap.remove(propName);
+ }
+ return;
+ } else {
+ expectedButDifferentValueList.add(expectedNode);
+ }
+ }
+
+ // "actualNode" is not in ordered expected list.
+ // Try looking over unordered expected list.
+ if (tryFoundExpectedNodeFromUnorderedList(actualNode,
+ expectedButDifferentValueList)) {
+ return;
+ }
+
+ if (!expectedButDifferentValueList.isEmpty()) {
+ // Same propName exists but with different value(s).
+ failWithExpectedNodeList(propName, actualNode,
+ expectedButDifferentValueList);
+ } else {
+ // There's no expected node with same propName.
+ mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+ }
+ }
+ } else {
+ List<PropertyNode> expectedButDifferentValueList =
+ new ArrayList<PropertyNode>();
+ if (tryFoundExpectedNodeFromUnorderedList(actualNode, expectedButDifferentValueList)) {
+ return;
+ } else {
+ if (!expectedButDifferentValueList.isEmpty()) {
+ // Same propName exists but with different value(s).
+ failWithExpectedNodeList(propName, actualNode,
+ expectedButDifferentValueList);
+ } else {
+ // There's no expected node with same propName.
+ mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+ }
+ }
+ }
+ }
+
+ private String concatinateListWithSemiColon(List<String> array) {
+ StringBuffer buffer = new StringBuffer();
+ boolean first = true;
+ for (String propValueElem : array) {
+ if (first) {
+ first = false;
+ } else {
+ buffer.append(';');
+ }
+ buffer.append(propValueElem);
+ }
+
+ return buffer.toString();
+ }
+
+ private boolean tryFoundExpectedNodeFromUnorderedList(PropertyNode actualNode,
+ List<PropertyNode> expectedButDifferentValueList) {
+ final String propName = actualNode.propName;
+ int unorderedListSize = mUnorderedNodeList.size();
+ for (int i = 0; i < unorderedListSize; i++) {
+ PropertyNode unorderedExpectedNode = mUnorderedNodeList.get(i);
+ if (unorderedExpectedNode.propName.equals(propName)) {
+ if (unorderedExpectedNode.equals(actualNode)) {
+ mUnorderedNodeList.remove(i);
+ return true;
+ }
+ expectedButDifferentValueList.add(unorderedExpectedNode);
+ }
+ }
+ return false;
+ }
+
+ private void failWithExpectedNodeList(String propName, PropertyNode actualNode,
+ List<PropertyNode> expectedNodeList) {
+ StringBuilder builder = new StringBuilder();
+ for (PropertyNode expectedNode : expectedNodeList) {
+ builder.append("expected: ");
+ builder.append(expectedNode.toString());
+ builder.append("\n");
+ }
+ mTestCase.fail("Property \"" + propName + "\" has wrong value.\n"
+ + builder.toString()
+ + " actual: " + actualNode.toString());
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/VCardVerifier.java b/vcard/tests/src/com/android/vcard/tests/test_utils/VCardVerifier.java
new file mode 100644
index 0000000..87d82d2
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/VCardVerifier.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.EntityIterator;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContext;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardInterpreter;
+import com.android.vcard.VCardInterpreterCollection;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * <p>
+ * The class lets users checks that given expected vCard data are same as given actual vCard data.
+ * Able to verify both vCard importer/exporter.
+ * </p>
+ * <p>
+ * First a user has to initialize the object by calling either
+ * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}.
+ * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported.
+ * </p>
+ */
+public class VCardVerifier {
+ private static final String LOG_TAG = "VCardVerifier";
+
+ private static class CustomMockContext extends MockContext {
+ final ContentResolver mResolver;
+ public CustomMockContext(ContentResolver resolver) {
+ mResolver = resolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+ }
+
+ private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
+ public boolean onInit(Context context) {
+ return true;
+ }
+ public boolean onEntryCreated(String vcard) {
+ verifyOneVCard(vcard);
+ return true;
+ }
+ public void onTerminate() {
+ }
+ }
+
+ private final AndroidTestCase mTestCase;
+ private final VCardVerifierInternal mVCardVerifierInternal;
+ private int mVCardType;
+ private boolean mIsV30;
+ private boolean mIsDoCoMo;
+
+ // Only one of them must be non-empty.
+ private ExportTestResolver mExportTestResolver;
+ private InputStream mInputStream;
+
+ // To allow duplication, use list instead of set.
+ // When null, we don't need to do the verification.
+ private PropertyNodesVerifier mPropertyNodesVerifier;
+ private LineVerifier mLineVerifier;
+ private ContentValuesVerifier mContentValuesVerifier;
+ private boolean mInitialized;
+ private boolean mVerified = false;
+ private String mCharset;
+
+ // Called by VCardTestsBase
+ public VCardVerifier(AndroidTestCase testCase) {
+ mTestCase = testCase;
+ mVCardVerifierInternal = new VCardVerifierInternal();
+ mExportTestResolver = null;
+ mInputStream = null;
+ mInitialized = false;
+ mVerified = false;
+ }
+
+ // Should be called at the beginning of each import test.
+ public void initForImportTest(int vcardType, int resId) {
+ if (mInitialized) {
+ mTestCase.fail("Already initialized");
+ }
+ mVCardType = vcardType;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ setInputResourceId(resId);
+ mInitialized = true;
+ }
+
+ // Should be called at the beginning of each export test.
+ public void initForExportTest(int vcardType) {
+ initForExportTest(vcardType, "UTF-8");
+ }
+
+ public void initForExportTest(int vcardType, String charset) {
+ if (mInitialized) {
+ mTestCase.fail("Already initialized");
+ }
+ mExportTestResolver = new ExportTestResolver(mTestCase);
+ mVCardType = vcardType;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mInitialized = true;
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = "UTF-8";
+ } else {
+ mCharset = charset;
+ }
+ }
+
+ private void setInputResourceId(int resId) {
+ InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
+ if (inputStream == null) {
+ mTestCase.fail("Wrong resId: " + resId);
+ }
+ setInputStream(inputStream);
+ }
+
+ private void setInputStream(InputStream inputStream) {
+ if (mExportTestResolver != null) {
+ mTestCase.fail("addInputEntry() is called.");
+ } else if (mInputStream != null) {
+ mTestCase.fail("InputStream is already set");
+ }
+ mInputStream = inputStream;
+ }
+
+ public ContactEntry addInputEntry() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mInputStream != null) {
+ mTestCase.fail("setInputStream is called");
+ }
+ return mExportTestResolver.addInputContactEntry();
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mPropertyNodesVerifier == null) {
+ mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase);
+ }
+ PropertyNodesVerifierElem elem =
+ mPropertyNodesVerifier.addPropertyNodesVerifierElem();
+ elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
+
+ return elem;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
+ if (mIsV30) {
+ elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", "");
+ } else if (mIsDoCoMo) {
+ elem.addExpectedNodeWithOrder("N", "");
+ }
+ return elem;
+ }
+
+ public LineVerifierElem addLineVerifierElem() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mLineVerifier == null) {
+ mLineVerifier = new LineVerifier(mTestCase, mVCardType);
+ }
+ return mLineVerifier.addLineVerifierElem();
+ }
+
+ public ContentValuesVerifierElem addContentValuesVerifierElem() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mContentValuesVerifier == null) {
+ mContentValuesVerifier = new ContentValuesVerifier();
+ }
+
+ return mContentValuesVerifier.addElem(mTestCase);
+ }
+
+ private void verifyOneVCard(final String vcard) {
+ Log.d(LOG_TAG, vcard);
+ final VCardInterpreter builder;
+ if (mContentValuesVerifier != null) {
+ final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
+ final VCardEntryConstructor vcardDataBuilder =
+ new VCardEntryConstructor(mVCardType);
+ vcardDataBuilder.addEntryHandler(mContentValuesVerifier);
+ if (mPropertyNodesVerifier != null) {
+ builder = new VCardInterpreterCollection(Arrays.asList(
+ mPropertyNodesVerifier, vcardDataBuilder));
+ } else {
+ builder = vnodeBuilder;
+ }
+ } else {
+ if (mPropertyNodesVerifier != null) {
+ builder = mPropertyNodesVerifier;
+ } else {
+ return;
+ }
+ }
+
+ InputStream is = null;
+ try {
+ // Note: we must not specify charset toward vCard parsers. This code checks whether
+ // those parsers are able to encode given binary without any extra information for
+ // charset.
+ final VCardParser parser = (mIsV30 ?
+ new VCardParser_V30(mVCardType) : new VCardParser_V21(mVCardType));
+ is = new ByteArrayInputStream(vcard.getBytes(mCharset));
+ parser.parse(is, builder);
+ } catch (IOException e) {
+ mTestCase.fail("Unexpected IOException: " + e.getMessage());
+ } catch (VCardException e) {
+ mTestCase.fail("Unexpected VCardException: " + e.getMessage());
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ mTestCase.fail("Unexpected IOException: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ public void verify() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized.");
+ }
+ if (mVerified) {
+ mTestCase.fail("verify() was called twice.");
+ }
+ if (mInputStream != null) {
+ try {
+ verifyForImportTest();
+ } catch (IOException e) {
+ mTestCase.fail("IOException was thrown: " + e.getMessage());
+ } catch (VCardException e) {
+ mTestCase.fail("VCardException was thrown: " + e.getMessage());
+ }
+ } else if (mExportTestResolver != null){
+ verifyForExportTest();
+ } else {
+ mTestCase.fail("No input is determined");
+ }
+ mVerified = true;
+ }
+
+ private void verifyForImportTest() throws IOException, VCardException {
+ if (mLineVerifier != null) {
+ mTestCase.fail("Not supported now.");
+ }
+ if (mContentValuesVerifier != null) {
+ mContentValuesVerifier.verify(mInputStream, mVCardType);
+ }
+ }
+
+ public static EntityIterator mockGetEntityIteratorMethod(
+ final ContentResolver resolver,
+ final Uri uri, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ if (ExportTestResolver.class.equals(resolver.getClass())) {
+ return ((ExportTestResolver)resolver).getProvider().queryEntities(
+ uri, selection, selectionArgs, sortOrder);
+ }
+
+ Log.e(LOG_TAG, "Unexpected provider given.");
+ return null;
+ }
+
+ private Method getMockGetEntityIteratorMethod()
+ throws SecurityException, NoSuchMethodException {
+ return this.getClass().getMethod("mockGetEntityIteratorMethod",
+ ContentResolver.class, Uri.class, String.class, String[].class, String.class);
+ }
+
+ private void verifyForExportTest() {
+ final VCardComposer composer =
+ new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset);
+ composer.addHandler(mLineVerifier);
+ composer.addHandler(mVCardVerifierInternal);
+ if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
+ mTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
+ }
+ mTestCase.assertFalse(composer.isAfterLast());
+ try {
+ while (!composer.isAfterLast()) {
+ try {
+ final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
+ mTestCase.assertNotNull(mockGetEntityIteratorMethod);
+ mTestCase.assertTrue(composer.createOneEntry(mockGetEntityIteratorMethod));
+ } catch (Exception e) {
+ e.printStackTrace();
+ mTestCase.fail();
+ }
+ }
+ } finally {
+ composer.terminate();
+ }
+ }
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/VNode.java b/vcard/tests/src/com/android/vcard/tests/test_utils/VNode.java
new file mode 100644
index 0000000..2ca762b
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/VNode.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import java.util.ArrayList;
+
+/**
+ * Previously used in main vCard handling code but now exists only for testing.
+ */
+public class VNode {
+ public String VName;
+
+ public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>();
+
+ /** 0:parse over. 1:parsing. */
+ public int parseStatus = 1;
+}
diff --git a/vcard/tests/src/com/android/vcard/tests/test_utils/VNodeBuilder.java b/vcard/tests/src/com/android/vcard/tests/test_utils/VNodeBuilder.java
new file mode 100644
index 0000000..77a28ad
--- /dev/null
+++ b/vcard/tests/src/com/android/vcard/tests/test_utils/VNodeBuilder.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.vcard.tests.test_utils;
+
+import android.content.ContentValues;
+import android.util.Base64;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardInterpreter;
+import com.android.vcard.VCardUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * The class storing the parse result to custom datastruct:
+ * {@link VNode}, and {@link PropertyNode}.
+ * Maybe several vcard instance, so use vNodeList to store.
+ * </p>
+ * <p>
+ * This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
+ * </p>
+ */
+/* package */ class VNodeBuilder implements VCardInterpreter {
+ static private String LOG_TAG = "VNodeBuilder";
+
+ public List<VNode> vNodeList = new ArrayList<VNode>();
+ private int mNodeListPos = 0;
+ private VNode mCurrentVNode;
+ private PropertyNode mCurrentPropNode;
+ private String mCurrentParamType;
+
+ /**
+ * The charset using which VParser parses the text.
+ */
+ private String mSourceCharset;
+
+ /**
+ * The charset with which byte array is encoded to String.
+ */
+ private String mTargetCharset;
+
+ private boolean mStrictLineBreakParsing;
+
+ public VNodeBuilder() {
+ this(VCardConfig.DEFAULT_IMPORT_CHARSET, false);
+ }
+
+ public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
+ mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
+ if (targetCharset != null) {
+ mTargetCharset = targetCharset;
+ } else {
+ mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
+ }
+ mStrictLineBreakParsing = strictLineBreakParsing;
+ }
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ // Note: I guess that this code assumes the Record may nest like this:
+ // START:VPOS
+ // ...
+ // START:VPOS2
+ // ...
+ // END:VPOS2
+ // ...
+ // END:VPOS
+ //
+ // However the following code has a bug.
+ // When error occurs after calling startRecord(), the entry which is probably
+ // the cause of the error remains to be in vNodeList, while endRecord() is not called.
+ //
+ // I leave this code as is since I'm not familiar with vcalendar specification.
+ // But I believe we should refactor this code in the future.
+ // Until this, the last entry has to be removed when some error occurs.
+ public void startEntry() {
+ VNode vnode = new VNode();
+ vnode.parseStatus = 1;
+ vnode.VName = "VCARD";
+ // I feel this should be done in endRecord(), but it cannot be done because of
+ // the reason above.
+ vNodeList.add(vnode);
+ mNodeListPos = vNodeList.size() - 1;
+ mCurrentVNode = vNodeList.get(mNodeListPos);
+ }
+
+ public void endEntry() {
+ VNode endNode = vNodeList.get(mNodeListPos);
+ endNode.parseStatus = 0;
+ while(mNodeListPos > 0){
+ mNodeListPos--;
+ if((vNodeList.get(mNodeListPos)).parseStatus == 1)
+ break;
+ }
+ mCurrentVNode = vNodeList.get(mNodeListPos);
+ }
+
+ public void startProperty() {
+ mCurrentPropNode = new PropertyNode();
+ }
+
+ public void endProperty() {
+ mCurrentVNode.propList.add(mCurrentPropNode);
+ }
+
+ public void propertyName(String name) {
+ mCurrentPropNode.propName = name;
+ }
+
+ public void propertyGroup(String group) {
+ mCurrentPropNode.propGroupSet.add(group);
+ }
+
+ public void propertyParamType(String type) {
+ mCurrentParamType = type;
+ }
+
+ public void propertyParamValue(String value) {
+ if (mCurrentParamType == null ||
+ mCurrentParamType.equalsIgnoreCase("TYPE")) {
+ mCurrentPropNode.paramMap_TYPE.add(value);
+ } else {
+ mCurrentPropNode.paramMap.put(mCurrentParamType, value);
+ }
+
+ mCurrentParamType = null;
+ }
+
+ private String encodeString(String originalString, String targetCharset) {
+ if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
+ return originalString;
+ }
+ Charset charset = Charset.forName(mSourceCharset);
+ ByteBuffer byteBuffer = charset.encode(originalString);
+ // byteBuffer.array() "may" return byte array which is larger than
+ // byteBuffer.remaining(). Here, we keep on the safe side.
+ byte[] bytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(bytes);
+ try {
+ return new String(bytes, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return null;
+ }
+ }
+
+ private String handleOneValue(String value, String targetCharset, String encoding) {
+ if (encoding != null) {
+ encoding = encoding.toUpperCase();
+ if (encoding.equals("BASE64") || encoding.equals("B")) {
+ // Assume BASE64 is used only when the number of values is 1.
+ mCurrentPropNode.propValue_bytes = Base64.decode(value.getBytes(), Base64.NO_WRAP);
+ return value;
+ } else if (encoding.equals("QUOTED-PRINTABLE")) {
+ return VCardUtils.parseQuotedPrintable(
+ value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
+ }
+ // Unknown encoding. Fall back to default.
+ }
+ return encodeString(value, targetCharset);
+ }
+
+ public void propertyValues(List<String> values) {
+ if (values == null || values.size() == 0) {
+ mCurrentPropNode.propValue_bytes = null;
+ mCurrentPropNode.propValue_vector.clear();
+ mCurrentPropNode.propValue_vector.add("");
+ mCurrentPropNode.propValue = "";
+ return;
+ }
+
+ ContentValues paramMap = mCurrentPropNode.paramMap;
+
+ String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET"));
+ String encoding = paramMap.getAsString("ENCODING");
+
+ if (targetCharset == null || targetCharset.length() == 0) {
+ targetCharset = mTargetCharset;
+ }
+
+ for (String value : values) {
+ mCurrentPropNode.propValue_vector.add(
+ handleOneValue(value, targetCharset, encoding));
+ }
+
+ mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector);
+ }
+
+ private String listToString(List<String> list){
+ int size = list.size();
+ if (size > 1) {
+ StringBuilder typeListB = new StringBuilder();
+ for (String type : list) {
+ typeListB.append(type).append(";");
+ }
+ int len = typeListB.length();
+ if (len > 0 && typeListB.charAt(len - 1) == ';') {
+ return typeListB.substring(0, len - 1);
+ }
+ return typeListB.toString();
+ } else if (size == 1) {
+ return list.get(0);
+ } else {
+ return "";
+ }
+ }
+
+ public String getResult(){
+ throw new RuntimeException("Not supported");
+ }
+}