resolved conflicts for merge of 4e3fadd0 to eclair-mr2
diff --git a/Android.mk b/Android.mk
index 5034c7e..f538d8e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -136,6 +136,7 @@
core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
core/java/com/android/internal/backup/IBackupTransport.aidl \
+ core/java/com/android/internal/os/IDropBoxService.aidl \
core/java/com/android/internal/os/IResultReceiver.aidl \
core/java/com/android/internal/view/IInputContext.aidl \
core/java/com/android/internal/view/IInputContextCallback.aidl \
@@ -217,6 +218,7 @@
frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \
frameworks/base/core/java/android/net/Uri.aidl \
frameworks/base/core/java/android/os/Bundle.aidl \
+ frameworks/base/core/java/android/os/DropBox.aidl \
frameworks/base/core/java/android/os/ParcelFileDescriptor.aidl \
frameworks/base/core/java/android/os/ParcelUuid.aidl \
frameworks/base/core/java/android/view/KeyEvent.aidl \
diff --git a/api/current.xml b/api/current.xml
index 50b45d6..caaa305 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -119272,6 +119272,23 @@
<parameter name="origId" type="long">
</parameter>
</method>
+<method name="cancelThumbnailRequest"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+</method>
<method name="getContentUri"
return="android.net.Uri"
abstract="false"
@@ -119304,6 +119321,27 @@
<parameter name="options" type="android.graphics.BitmapFactory.Options">
</parameter>
</method>
+<method name="getThumbnail"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+<parameter name="kind" type="int">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
<method name="query"
return="android.database.Cursor"
abstract="false"
@@ -119729,6 +119767,23 @@
<parameter name="origId" type="long">
</parameter>
</method>
+<method name="cancelThumbnailRequest"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+</method>
<method name="getContentUri"
return="android.net.Uri"
abstract="false"
@@ -119761,6 +119816,27 @@
<parameter name="options" type="android.graphics.BitmapFactory.Options">
</parameter>
</method>
+<method name="getThumbnail"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+<parameter name="kind" type="int">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
<field name="DATA"
type="java.lang.String"
transient="false"
@@ -132520,6 +132596,673 @@
</parameter>
</method>
</class>
+<class name="MockCursor"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.database.Cursor">
+</implements>
+<constructor name="MockCursor"
+ type="android.test.mock.MockCursor"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="abortUpdates"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="commitUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="commitUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="values" type="java.util.Map<? extends java.lang.Long, ? extends java.util.Map<java.lang.String, java.lang.Object>>">
+</parameter>
+</method>
+<method name="copyStringToBuffer"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="buffer" type="android.database.CharArrayBuffer">
+</parameter>
+</method>
+<method name="deactivate"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="deleteRow"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getBlob"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getColumnCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getColumnIndex"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnName" type="java.lang.String">
+</parameter>
+</method>
+<method name="getColumnIndexOrThrow"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnName" type="java.lang.String">
+</parameter>
+</method>
+<method name="getColumnName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getColumnNames"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDouble"
+ return="double"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getExtras"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getFloat"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getInt"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getLong"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getShort"
+ return="short"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getWantsAllOnMoveCalls"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hasUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isAfterLast"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isBeforeFirst"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isClosed"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isFirst"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isLast"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isNull"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="move"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="moveToFirst"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="moveToLast"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="moveToNext"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="moveToPosition"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="moveToPrevious"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="registerContentObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.ContentObserver">
+</parameter>
+</method>
+<method name="registerDataSetObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.DataSetObserver">
+</parameter>
+</method>
+<method name="requery"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="respond"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="setNotificationUri"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="supportsUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="unregisterContentObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.ContentObserver">
+</parameter>
+</method>
+<method name="unregisterDataSetObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.DataSetObserver">
+</parameter>
+</method>
+<method name="updateBlob"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="byte[]">
+</parameter>
+</method>
+<method name="updateDouble"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="double">
+</parameter>
+</method>
+<method name="updateFloat"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="float">
+</parameter>
+</method>
+<method name="updateInt"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="int">
+</parameter>
+</method>
+<method name="updateLong"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="long">
+</parameter>
+</method>
+<method name="updateShort"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="short">
+</parameter>
+</method>
+<method name="updateString"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="java.lang.String">
+</parameter>
+</method>
+<method name="updateToNull"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+</class>
<class name="MockDialogInterface"
extends="java.lang.Object"
abstract="false"
@@ -144456,6 +145199,29 @@
</parameter>
</method>
</interface>
+<interface name="LeadingMarginSpan.LeadingMarginSpan2"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.text.style.LeadingMarginSpan">
+</implements>
+<implements name="android.text.style.WrapTogetherSpan">
+</implements>
+<method name="getLeadingMarginLineCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
<class name="LeadingMarginSpan.Standard"
extends="java.lang.Object"
abstract="false"
diff --git a/cmds/stagefright/SineSource.cpp b/cmds/stagefright/SineSource.cpp
index e5a6ccb..e272a65 100644
--- a/cmds/stagefright/SineSource.cpp
+++ b/cmds/stagefright/SineSource.cpp
@@ -86,8 +86,8 @@
x += k;
}
- buffer->meta_data()->setInt32(kKeyTimeUnits, mPhase);
- buffer->meta_data()->setInt32(kKeyTimeScale, mSampleRate);
+ buffer->meta_data()->setInt64(
+ kKeyTime, ((int64_t)mPhase * 1000000) / mSampleRate);
mPhase += numFramesPerBuffer;
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index 323d448..0025c5e 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -23,7 +23,7 @@
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
-#include <media/stagefright/MPEG4Extractor.h>
+#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/MPEG4Writer.h>
#include <media/stagefright/MmapSource.h>
#include <media/stagefright/OMXClient.h>
@@ -32,8 +32,10 @@
using namespace android;
-#if 0
+#if 1
class DummySource : public MediaSource {
+ static const int32_t kFramerate = 24; // fps
+
public:
DummySource(int width, int height)
: mWidth(width),
@@ -52,6 +54,7 @@
}
virtual status_t start(MetaData *params) {
+ mNumFramesOutput = 0;
return OK;
}
@@ -61,6 +64,12 @@
virtual status_t read(
MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+ if (mNumFramesOutput == kFramerate * 10) {
+ // Stop returning data after 10 secs.
+ return ERROR_END_OF_STREAM;
+ }
+
+ // printf("DummySource::read\n");
status_t err = mGroup.acquire_buffer(buffer);
if (err != OK) {
return err;
@@ -69,7 +78,13 @@
char x = (char)((double)rand() / RAND_MAX * 255);
memset((*buffer)->data(), x, mSize);
(*buffer)->set_range(0, mSize);
+ (*buffer)->meta_data()->clear();
+ (*buffer)->meta_data()->setInt64(
+ kKeyTime, (mNumFramesOutput * 1000000) / kFramerate);
+ ++mNumFramesOutput;
+ // printf("DummySource::read - returning buffer\n");
+ // LOGI("DummySource::read - returning buffer");
return OK;
}
@@ -80,6 +95,7 @@
MediaBufferGroup mGroup;
int mWidth, mHeight;
size_t mSize;
+ int64_t mNumFramesOutput;;
DummySource(const DummySource &);
DummySource &operator=(const DummySource &);
@@ -88,8 +104,8 @@
sp<MediaSource> createSource(const char *filename) {
sp<MediaSource> source;
- sp<MPEG4Extractor> extractor =
- new MPEG4Extractor(new MmapSource(filename));
+ sp<MediaExtractor> extractor =
+ MediaExtractor::Create(new MmapSource(filename));
size_t num_tracks = extractor->countTracks();
@@ -144,8 +160,8 @@
success = success && meta->findInt32(kKeyHeight, &height);
CHECK(success);
#else
- int width = 320;
- int height = 240;
+ int width = 800;
+ int height = 480;
sp<MediaSource> decoder = new DummySource(width, height);
#endif
@@ -159,19 +175,26 @@
OMXCodec::Create(
client.interface(), enc_meta, true /* createEncoder */, decoder);
-#if 0
+#if 1
sp<MPEG4Writer> writer = new MPEG4Writer("/sdcard/output.mp4");
- writer->addSource(enc_meta, encoder);
+ writer->addSource(encoder);
writer->start();
- sleep(20);
- printf("stopping now.\n");
+ while (!writer->reachedEOS()) {
+ usleep(100000);
+ }
writer->stop();
#else
encoder->start();
MediaBuffer *buffer;
while (encoder->read(&buffer) == OK) {
- printf("got an output frame of size %d\n", buffer->range_length());
+ int32_t isSync;
+ if (!buffer->meta_data()->findInt32(kKeyIsSyncFrame, &isSync)) {
+ isSync = false;
+ }
+
+ printf("got an output frame of size %d%s\n", buffer->range_length(),
+ isSync ? " (SYNC)" : "");
buffer->release();
buffer = NULL;
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 4ffc8e4..d26e558 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -52,21 +52,26 @@
static void playSource(OMXClient *client, const sp<MediaSource> &source) {
sp<MetaData> meta = source->getFormat();
- int32_t durationUnits;
- int32_t timeScale;
- CHECK(meta->findInt32(kKeyDuration, &durationUnits));
- CHECK(meta->findInt32(kKeyTimeScale, &timeScale));
+ int64_t durationUs;
+ CHECK(meta->findInt64(kKeyDuration, &durationUs));
- int64_t durationUs = ((int64_t)durationUnits * 1000000) / timeScale;
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
- sp<OMXCodec> decoder = OMXCodec::Create(
+ sp<MediaSource> rawSource;
+ if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime)) {
+ rawSource = source;
+ } else {
+ rawSource = OMXCodec::Create(
client->interface(), meta, false /* createEncoder */, source);
- if (decoder == NULL) {
- return;
+ if (rawSource == NULL) {
+ fprintf(stderr, "Failed to instantiate decoder for '%s'.\n", mime);
+ return;
+ }
}
- decoder->start();
+ rawSource->start();
if (gReproduceBug >= 3 && gReproduceBug <= 5) {
status_t err;
@@ -74,24 +79,28 @@
MediaSource::ReadOptions options;
int64_t seekTimeUs = -1;
for (;;) {
- err = decoder->read(&buffer, &options);
+ err = rawSource->read(&buffer, &options);
options.clearSeekTo();
bool shouldSeek = false;
- if (err != OK) {
+ if (err == INFO_FORMAT_CHANGED) {
+ CHECK_EQ(buffer, NULL);
+
+ printf("format changed.\n");
+ continue;
+ } else if (err != OK) {
printf("reached EOF.\n");
shouldSeek = true;
} else {
- int32_t timestampUnits;
- CHECK(buffer->meta_data()->findInt32(kKeyTimeUnits, ×tampUnits));
-
- int64_t timestampUs = ((int64_t)timestampUnits * 1000000) / timeScale;
+ int64_t timestampUs;
+ CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs));
bool failed = false;
if (seekTimeUs >= 0) {
int64_t diff = timestampUs - seekTimeUs;
+
if (diff < 0) {
diff = -diff;
}
@@ -134,7 +143,7 @@
}
}
- decoder->stop();
+ rawSource->stop();
return;
}
@@ -151,12 +160,17 @@
MediaBuffer *buffer;
for (;;) {
- status_t err = decoder->read(&buffer, &options);
+ status_t err = rawSource->read(&buffer, &options);
options.clearSeekTo();
if (err != OK) {
CHECK_EQ(buffer, NULL);
+ if (err == INFO_FORMAT_CHANGED) {
+ printf("format changed.\n");
+ continue;
+ }
+
break;
}
@@ -188,7 +202,7 @@
options.setSeekTo(0);
}
- decoder->stop();
+ rawSource->stop();
printf("\n");
int64_t delay = getNowUs() - startTime;
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 4f59c4e..2d36241 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -16,20 +16,27 @@
package android.accounts;
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
-import android.content.pm.PackageInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.RegisteredServicesCacheListener;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -37,17 +44,13 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.Binder;
import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.app.PendingIntent;
-import android.app.NotificationManager;
-import android.app.Notification;
-import android.Manifest;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -58,8 +61,9 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.R;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.TelephonyIntents;
/**
* A system service that provides account, password, and authtoken management for all
@@ -90,11 +94,8 @@
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
- private static final int MESSAGE_CONNECTED = 7;
- private static final int MESSAGE_DISCONNECTED = 8;
private final AccountAuthenticatorCache mAuthenticatorCache;
- private final AuthenticatorBindHelper mBindHelper;
private final DatabaseHelper mOpenHelper;
private final SimWatcher mSimWatcher;
@@ -221,8 +222,6 @@
mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
mAuthenticatorCache.setListener(this);
- mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
- MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
mSimWatcher = new SimWatcher(mContext);
sThis.set(this);
@@ -1081,7 +1080,7 @@
}
private abstract class Session extends IAccountAuthenticatorResponse.Stub
- implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
+ implements IBinder.DeathRecipient, ServiceConnection {
IAccountManagerResponse mResponse;
final String mAccountType;
final boolean mExpectActivityLaunch;
@@ -1163,7 +1162,7 @@
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
}
- if (!mBindHelper.bind(mAccountType, this)) {
+ if (!bindToAuthenticator(mAccountType)) {
Log.d(TAG, "bind attempt failed for " + toDebugString());
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
}
@@ -1172,7 +1171,7 @@
private void unbind() {
if (mAuthenticator != null) {
mAuthenticator = null;
- mBindHelper.unbind(this);
+ mContext.unbindService(this);
}
}
@@ -1185,7 +1184,7 @@
mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
- public void onConnected(IBinder service) {
+ public void onServiceConnected(ComponentName name, IBinder service) {
mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
try {
run();
@@ -1195,9 +1194,7 @@
}
}
- public abstract void run() throws RemoteException;
-
- public void onDisconnected() {
+ public void onServiceDisconnected(ComponentName name) {
mAuthenticator = null;
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1206,6 +1203,8 @@
}
}
+ public abstract void run() throws RemoteException;
+
public void onTimedOut() {
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1275,6 +1274,39 @@
}
}
}
+
+ /**
+ * find the component name for the authenticator and initiate a bind
+ * if no authenticator or the bind fails then return false, otherwise return true
+ */
+ private boolean bindToAuthenticator(String authenticatorType) {
+ AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
+ mAuthenticatorCache.getServiceInfo(
+ AuthenticatorDescription.newKey(authenticatorType));
+ if (authenticatorInfo == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "there is no authenticator for " + authenticatorType
+ + ", bailing out");
+ }
+ return false;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+ intent.setComponent(authenticatorInfo.componentName);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
+ }
+ if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+ }
+ return false;
+ }
+
+
+ return true;
+ }
}
private class MessageHandler extends Handler {
@@ -1283,9 +1315,6 @@
}
public void handleMessage(Message msg) {
- if (mBindHelper.handleMessage(msg)) {
- return;
- }
switch (msg.what) {
case MESSAGE_TIMED_OUT:
Session session = (Session)msg.obj;
@@ -1425,16 +1454,58 @@
*/
@Override
public void onReceive(Context context, Intent intent) {
- // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
- String imsi = ((TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE)).getSubscriberId();
+ // Check IMSI on every update; nothing happens if the IMSI
+ // is missing or unchanged.
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (telephonyManager == null) {
+ Log.w(TAG, "failed to get TelephonyManager");
+ return;
+ }
+ String imsi = telephonyManager.getSubscriberId();
+
+ // If the subscriber ID is an empty string, don't do anything.
if (TextUtils.isEmpty(imsi)) return;
+ // If the current IMSI matches what's stored, don't do anything.
String storedImsi = getMetaValue("imsi");
-
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
}
+ if (imsi.equals(storedImsi)) return;
+
+ // If a CDMA phone is unprovisioned, getSubscriberId()
+ // will return a different value, but we *don't* erase the
+ // passwords. We only erase them if it has a different
+ // subscriber ID once it's provisioned.
+ if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ IBinder service = ServiceManager.checkService(Context.TELEPHONY_SERVICE);
+ if (service == null) {
+ Log.w(TAG, "call to checkService(TELEPHONY_SERVICE) failed");
+ return;
+ }
+ ITelephony telephony = ITelephony.Stub.asInterface(service);
+ if (telephony == null) {
+ Log.w(TAG, "failed to get ITelephony interface");
+ return;
+ }
+ boolean needsProvisioning;
+ try {
+ needsProvisioning = telephony.getCdmaNeedsProvisioning();
+ } catch (RemoteException e) {
+ Log.w(TAG, "exception while checking provisioning", e);
+ // default to NOT wiping out the passwords
+ needsProvisioning = true;
+ }
+ if (needsProvisioning) {
+ // if the phone needs re-provisioning, don't do anything.
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "current IMSI=" + imsi + " (needs provisioning); stored IMSI=" +
+ storedImsi);
+ }
+ return;
+ }
+ }
if (!imsi.equals(storedImsi) && !TextUtils.isEmpty(storedImsi)) {
Log.w(TAG, "wiping all passwords and authtokens because IMSI changed ("
@@ -1578,6 +1649,7 @@
}
private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+ final boolean inSystemImage = inSystemImage(callerUid);
final boolean fromAuthenticator = account != null
&& hasAuthenticatorUid(account.type, callerUid);
final boolean hasExplicitGrants = account != null
@@ -1588,7 +1660,7 @@
+ ": is authenticator? " + fromAuthenticator
+ ", has explicit permission? " + hasExplicitGrants);
}
- return fromAuthenticator || hasExplicitGrants || inSystemImage(callerUid);
+ return fromAuthenticator || hasExplicitGrants || inSystemImage;
}
private boolean hasAuthenticatorUid(String accountType, int callingUid) {
diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java
deleted file mode 100644
index 2ca1f0e..0000000
--- a/core/java/android/accounts/AuthenticatorBindHelper.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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 android.accounts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-/**
- * A helper object that simplifies binding to Account Authenticators. It uses the
- * {@link AccountAuthenticatorCache} to find the component name of the authenticators,
- * allowing the user to bind by account name. It also allows multiple, simultaneous binds
- * to the same authenticator, with each bind call guaranteed to return either
- * {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call
- * itself succeeds, even if the authenticator is already bound internally.
- * @hide
- */
-public class AuthenticatorBindHelper {
- private static final String TAG = "Accounts";
- private final Handler mHandler;
- private final Context mContext;
- private final int mMessageWhatConnected;
- private final int mMessageWhatDisconnected;
- private final Map<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
- private final Map<String, ArrayList<Callback>> mServiceUsers = Maps.newHashMap();
- private final AccountAuthenticatorCache mAuthenticatorCache;
-
- public AuthenticatorBindHelper(Context context,
- AccountAuthenticatorCache authenticatorCache, Handler handler,
- int messageWhatConnected, int messageWhatDisconnected) {
- mContext = context;
- mHandler = handler;
- mAuthenticatorCache = authenticatorCache;
- mMessageWhatConnected = messageWhatConnected;
- mMessageWhatDisconnected = messageWhatDisconnected;
- }
-
- public interface Callback {
- void onConnected(IBinder service);
- void onDisconnected();
- }
-
- public boolean bind(String authenticatorType, Callback callback) {
- // if the authenticator is connecting or connected then return true
- synchronized (mServiceConnections) {
- if (mServiceConnections.containsKey(authenticatorType)) {
- MyServiceConnection connection = mServiceConnections.get(authenticatorType);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "service connection already exists for " + authenticatorType);
- }
- mServiceUsers.get(authenticatorType).add(callback);
- if (connection.mService != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is connected, scheduling a connected message for "
- + authenticatorType);
- }
- connection.scheduleCallbackConnectedMessage(callback);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is *not* connected, waiting for for "
- + authenticatorType);
- }
- }
- return true;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no service connection for " + authenticatorType);
- }
-
- // otherwise find the component name for the authenticator and initiate a bind
- // if no authenticator or the bind fails then return false, otherwise return true
- AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
- mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(authenticatorType));
- if (authenticatorInfo == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no authenticator for " + authenticatorType
- + ", bailing out");
- }
- return false;
- }
-
- MyServiceConnection connection = new MyServiceConnection(authenticatorType);
-
- Intent intent = new Intent();
- intent.setAction("android.accounts.AccountAuthenticator");
- intent.setComponent(authenticatorInfo.componentName);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
- }
- if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
- }
- return false;
- }
-
- mServiceConnections.put(authenticatorType, connection);
- mServiceUsers.put(authenticatorType, Lists.newArrayList(callback));
- return true;
- }
- }
-
- public void unbind(Callback callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbinding callback " + callbackToUnbind);
- }
- synchronized (mServiceConnections) {
- for (Map.Entry<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) {
- final String authenticatorType = entry.getKey();
- final ArrayList<Callback> serviceUsers = entry.getValue();
- for (Callback callback : serviceUsers) {
- if (callback == callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "found callback in service" + authenticatorType);
- }
- serviceUsers.remove(callbackToUnbind);
- if (serviceUsers.isEmpty()) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there are no more callbacks for service "
- + authenticatorType + ", unbinding service");
- }
- unbindFromServiceLocked(authenticatorType);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "leaving service " + authenticatorType
- + " around since there are still callbacks using it");
- }
- }
- return;
- }
- }
- }
- Log.e(TAG, "did not find callback " + callbackToUnbind + " in any of the services");
- }
- }
-
- /**
- * You must synchronized on mServiceConnections before calling this
- */
- private void unbindFromServiceLocked(String authenticatorType) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbindService from " + authenticatorType);
- }
- mContext.unbindService(mServiceConnections.get(authenticatorType));
- mServiceUsers.remove(authenticatorType);
- mServiceConnections.remove(authenticatorType);
- }
-
- private class ConnectedMessagePayload {
- public final IBinder mService;
- public final Callback mCallback;
- public ConnectedMessagePayload(IBinder service, Callback callback) {
- mService = service;
- mCallback = callback;
- }
- }
-
- private class MyServiceConnection implements ServiceConnection {
- private final String mAuthenticatorType;
- private IBinder mService = null;
-
- public MyServiceConnection(String authenticatorType) {
- mAuthenticatorType = authenticatorType;
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceConnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is connected
- synchronized (mServiceConnections) {
- mService = service;
- for (Callback callback : mServiceUsers.get(mAuthenticatorType)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became connected, scheduling a connected "
- + "message for " + mAuthenticatorType);
- }
- scheduleCallbackConnectedMessage(callback);
- }
- }
- }
-
- private void scheduleCallbackConnectedMessage(Callback callback) {
- final ConnectedMessagePayload payload =
- new ConnectedMessagePayload(mService, callback);
- mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget();
- }
-
- public void onServiceDisconnected(ComponentName name) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceDisconnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is disconnected,
- // and unbind from the service.
- synchronized (mServiceConnections) {
- final ArrayList<Callback> callbackList = mServiceUsers.get(mAuthenticatorType);
- if (callbackList != null) {
- for (Callback callback : callbackList) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became disconnected, scheduling a "
- + "disconnected message for "
- + mAuthenticatorType);
- }
- mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget();
- }
- unbindFromServiceLocked(mAuthenticatorType);
- }
- }
- }
- }
-
- boolean handleMessage(Message message) {
- if (message.what == mMessageWhatConnected) {
- ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + payload.mCallback + " that it is connected");
- }
- payload.mCallback.onConnected(payload.mService);
- return true;
- } else if (message.what == mMessageWhatDisconnected) {
- Callback callback = (Callback)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + callback + " that it is disconnected");
- }
- callback.onDisconnected();
- return true;
- } else {
- return false;
- }
- }
-}
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index e3ed2e9..f4b7258 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -18,14 +18,16 @@
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
+import android.widget.LinearLayout;
+import android.widget.ImageView;
import android.view.View;
import android.view.LayoutInflater;
-import android.view.ViewGroup;
+import android.view.Window;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.graphics.drawable.Drawable;
import com.android.internal.R;
/**
@@ -43,62 +45,68 @@
private String mAuthTokenType;
private int mUid;
private Bundle mResultBundle = null;
+ protected LayoutInflater mInflater;
protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
- getWindow().setContentView(R.layout.grant_credentials_permission);
- mAccount = getIntent().getExtras().getParcelable(EXTRAS_ACCOUNT);
- mAuthTokenType = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_TYPE);
- mUid = getIntent().getExtras().getInt(EXTRAS_REQUESTING_UID);
- final String accountTypeLabel =
- getIntent().getExtras().getString(EXTRAS_ACCOUNT_TYPE_LABEL);
- final String[] packages = getIntent().getExtras().getStringArray(EXTRAS_PACKAGES);
+ setContentView(R.layout.grant_credentials_permission);
- findViewById(R.id.allow).setOnClickListener(this);
- findViewById(R.id.deny).setOnClickListener(this);
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- TextView messageView = (TextView) getWindow().findViewById(R.id.message);
- String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL);
- if (authTokenLabel.length() == 0) {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- mAccount.name, accountTypeLabel));
- } else {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_with_authtokenlabel_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- authTokenLabel, mAccount.name, accountTypeLabel));
- }
+ final Bundle extras = getIntent().getExtras();
+ mAccount = extras.getParcelable(EXTRAS_ACCOUNT);
+ mAuthTokenType = extras.getString(EXTRAS_AUTH_TOKEN_TYPE);
+ mUid = extras.getInt(EXTRAS_REQUESTING_UID);
+ final String accountTypeLabel = extras.getString(EXTRAS_ACCOUNT_TYPE_LABEL);
+ final String[] packages = extras.getStringArray(EXTRAS_PACKAGES);
+ final String authTokenLabel = extras.getString(EXTRAS_AUTH_TOKEN_LABEL);
- String[] packageLabels = new String[packages.length];
+ findViewById(R.id.allow_button).setOnClickListener(this);
+ findViewById(R.id.deny_button).setOnClickListener(this);
+
+ LinearLayout packagesListView = (LinearLayout) findViewById(R.id.packages_list);
+
final PackageManager pm = getPackageManager();
- for (int i = 0; i < packages.length; i++) {
+ for (String pkg : packages) {
+ String packageLabel;
try {
- packageLabels[i] =
- pm.getApplicationLabel(pm.getApplicationInfo(packages[i], 0)).toString();
+ packageLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
} catch (PackageManager.NameNotFoundException e) {
- packageLabels[i] = packages[i];
+ packageLabel = pkg;
}
+ packagesListView.addView(newPackageView(packageLabel));
}
- ((ListView) findViewById(R.id.packages_list)).setAdapter(
- new PackagesArrayAdapter(this, packageLabels));
+
+ ((TextView) findViewById(R.id.account_name)).setText(mAccount.name);
+ ((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel);
+ TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type);
+ if (TextUtils.isEmpty(authTokenLabel)) {
+ authTokenTypeView.setVisibility(View.GONE);
+ } else {
+ authTokenTypeView.setText(authTokenLabel);
+ }
+ }
+
+ private View newPackageView(String packageLabel) {
+ View view = mInflater.inflate(R.layout.permissions_package_list_item, null);
+ ((TextView) view.findViewById(R.id.package_label)).setText(packageLabel);
+ return view;
}
public void onClick(View v) {
+ final AccountManagerService accountManagerService = AccountManagerService.getSingleton();
switch (v.getId()) {
- case R.id.allow:
- AccountManagerService.getSingleton().grantAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.allow_button:
+ accountManagerService.grantAppPermission(mAccount, mAuthTokenType, mUid);
Intent result = new Intent();
result.putExtra("retry", true);
setResult(RESULT_OK, result);
setAccountAuthenticatorResult(result.getExtras());
break;
- case R.id.deny:
- AccountManagerService.getSingleton().revokeAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.deny_button:
+ accountManagerService.revokeAppPermission(mAccount, mAuthTokenType, mUid);
setResult(RESULT_CANCELED);
break;
}
@@ -110,63 +118,20 @@
}
/**
- * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ * Sends the result or a {@link AccountManager#ERROR_CODE_CANCELED} error if a
+ * result isn't present.
*/
public void finish() {
Intent intent = getIntent();
- AccountAuthenticatorResponse accountAuthenticatorResponse =
- intent.getParcelableExtra(EXTRAS_RESPONSE);
- if (accountAuthenticatorResponse != null) {
+ AccountAuthenticatorResponse response = intent.getParcelableExtra(EXTRAS_RESPONSE);
+ if (response != null) {
// send the result bundle back if set, otherwise send an error.
if (mResultBundle != null) {
- accountAuthenticatorResponse.onResult(mResultBundle);
+ response.onResult(mResultBundle);
} else {
- accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
}
}
super.finish();
}
-
- private static class PackagesArrayAdapter extends ArrayAdapter<String> {
- protected LayoutInflater mInflater;
- private static final int mResource = R.layout.simple_list_item_1;
-
- public PackagesArrayAdapter(Context context, String[] items) {
- super(context, mResource, items);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- static class ViewHolder {
- TextView label;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // A ViewHolder keeps references to children views to avoid unneccessary calls
- // to findViewById() on each row.
- ViewHolder holder;
-
- // When convertView is not null, we can reuse it directly, there is no need
- // to reinflate it. We only inflate a new View when the convertView supplied
- // by ListView is null.
- if (convertView == null) {
- convertView = mInflater.inflate(mResource, null);
-
- // Creates a ViewHolder and store references to the two children views
- // we want to bind data to.
- holder = new ViewHolder();
- holder.label = (TextView) convertView.findViewById(R.id.text1);
-
- convertView.setTag(holder);
- } else {
- // Get the ViewHolder back to get fast access to the TextView
- // and the ImageView.
- holder = (ViewHolder) convertView.getTag();
- }
-
- holder.label.setText(getItem(position));
-
- return convertView;
- }
- }
}
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index f48f150..305ee6a 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -70,6 +70,7 @@
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
+import android.os.DropBox;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
@@ -93,6 +94,8 @@
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
+import com.android.internal.os.IDropBoxService;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -182,6 +185,7 @@
private ClipboardManager mClipboardManager = null;
private boolean mRestricted;
private AccountManager mAccountManager; // protected by mSync
+ private DropBox mDropBox = null;
private final Object mSync = new Object();
@@ -896,6 +900,8 @@
return getClipboardManager();
} else if (WALLPAPER_SERVICE.equals(name)) {
return getWallpaperManager();
+ } else if (DROPBOX_SERVICE.equals(name)) {
+ return getDropBox();
}
return null;
@@ -1045,7 +1051,7 @@
}
return mVibrator;
}
-
+
private AudioManager getAudioManager()
{
if (mAudioManager == null) {
@@ -1054,6 +1060,17 @@
return mAudioManager;
}
+ private DropBox getDropBox() {
+ synchronized (mSync) {
+ if (mDropBox == null) {
+ IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
+ IDropBoxService service = IDropBoxService.Stub.asInterface(b);
+ mDropBox = new DropBox(service);
+ }
+ }
+ return mDropBox;
+ }
+
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 4684f45..45c33ef 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -46,7 +46,7 @@
*/
public final class BluetoothAdapter {
private static final String TAG = "BluetoothAdapter";
- private static final boolean DBG = false;
+ private static final boolean DBG = true; //STOPSHIP: Remove excess logging
/**
* Sentinel error value for this class. Guaranteed to not equal any other
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
index eba8715..9866862 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -747,4 +747,8 @@
public void writeSyncDataBytes(Account account, byte[] data) {
mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
}
+
+ protected ContentProvider getSyncStateProvider() {
+ return mSyncState.asContentProvider();
+ }
}
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index fb6091a..0db6155 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -21,6 +21,7 @@
import android.os.Process;
import android.os.NetStat;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.EventLog;
import java.util.concurrent.atomic.AtomicInteger;
@@ -117,6 +118,12 @@
}
}
}
+
+ public void initialize(Account account, String authority) throws RemoteException {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ startSync(null, authority, account, extras);
+ }
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8f1c671..b4ab408 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1309,7 +1309,7 @@
* @see #getSystemService
*/
public static final String APPWIDGET_SERVICE = "appwidget";
-
+
/**
* Use with {@link #getSystemService} to retrieve an
* {@blink android.backup.IBackupManager IBackupManager} for communicating
@@ -1319,7 +1319,16 @@
* @see #getSystemService
*/
public static final String BACKUP_SERVICE = "backup";
-
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@blink android.os.DropBox DropBox} instance for recording
+ * diagnostic logs.
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String DROPBOX_SERVICE = "dropbox";
+
/**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl
index 4660527..dd9d14e 100644
--- a/core/java/android/content/ISyncAdapter.aidl
+++ b/core/java/android/content/ISyncAdapter.aidl
@@ -44,4 +44,12 @@
* @param syncContext the ISyncContext that was passed to {@link #startSync}
*/
void cancelSync(ISyncContext syncContext);
+
+ /**
+ * Initialize the SyncAdapter for this account and authority.
+ *
+ * @param account the account that should be synced
+ * @param authority the authority that should be synced
+ */
+ void initialize(in Account account, String authority);
}
diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
index 88dc332..af1634e 100644
--- a/core/java/android/content/SyncAdapter.java
+++ b/core/java/android/content/SyncAdapter.java
@@ -38,6 +38,12 @@
public void cancelSync(ISyncContext syncContext) throws RemoteException {
SyncAdapter.this.cancelSync();
}
+
+ public void initialize(Account account, String authority) throws RemoteException {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ startSync(null, authority, account, extras);
+ }
}
Transport mTransport = new Transport();
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
index 587586d..cc914c0 100644
--- a/core/java/android/content/SyncContext.java
+++ b/core/java/android/content/SyncContext.java
@@ -56,7 +56,9 @@
if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
try {
mLastHeartbeatSendTime = now;
- mSyncContext.sendHeartbeat();
+ if (mSyncContext != null) {
+ mSyncContext.sendHeartbeat();
+ }
} catch (RemoteException e) {
// this should never happen
}
@@ -64,13 +66,15 @@
public void onFinished(SyncResult result) {
try {
- mSyncContext.onFinished(result);
+ if (mSyncContext != null) {
+ mSyncContext.onFinished(result);
+ }
} catch (RemoteException e) {
// this should never happen
}
}
public IBinder getSyncContextBinder() {
- return mSyncContext.asBinder();
+ return (mSyncContext == null) ? null : mSyncContext.asBinder();
}
}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index ba18615..1580c66 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -544,6 +544,46 @@
return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
}
+ private void initializeSyncAdapter(Account account, String authority) {
+ SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
+ RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
+ mSyncAdapters.getServiceInfo(syncAdapterType);
+ if (syncAdapterInfo == null) {
+ Log.w(TAG, "can't find a sync adapter for " + syncAdapterType);
+ return;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction("android.content.SyncAdapter");
+ intent.setComponent(syncAdapterInfo.componentName);
+ mContext.bindService(intent, new InitializerServiceConnection(account, authority),
+ Context.BIND_AUTO_CREATE);
+ }
+
+ private class InitializerServiceConnection implements ServiceConnection {
+ private final Account mAccount;
+ private final String mAuthority;
+
+ public InitializerServiceConnection(Account account, String authority) {
+ mAccount = account;
+ mAuthority = authority;
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
+ } catch (RemoteException e) {
+ // doesn't matter, we will retry again later
+ } finally {
+ mContext.unbindService(this);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mContext.unbindService(this);
+ }
+ }
+
/**
* Returns whether or not sync is enabled. Sync can be enabled by
* setting the system property "ro.config.sync" to the value "yes".
@@ -686,36 +726,34 @@
continue;
}
- // make this an initialization sync if the isSyncable state is unknown
- Bundle extrasCopy = extras;
- long delayCopy = delay;
+ // initialize the SyncAdapter if the isSyncable state is unknown
if (isSyncable < 0) {
- extrasCopy = new Bundle(extras);
- extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- delayCopy = -1; // expedite this
- } else {
- final boolean syncAutomatically = masterSyncAutomatically
- && mSyncStorageEngine.getSyncAutomatically(account, authority);
- boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
- if (!syncAllowed) {
- if (isLoggable) {
- Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
- + " is not allowed, dropping request");
- }
- continue;
- }
+ initializeSyncAdapter(account, authority);
+ continue;
}
+
+ final boolean syncAutomatically = masterSyncAutomatically
+ && mSyncStorageEngine.getSyncAutomatically(account, authority);
+ boolean syncAllowed =
+ manualSync || (backgroundDataUsageAllowed && syncAutomatically);
+ if (!syncAllowed) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+ + " is not allowed, dropping request");
+ }
+ continue;
+ }
+
if (isLoggable) {
Log.v(TAG, "scheduleSync:"
- + " delay " + delayCopy
+ + " delay " + delay
+ ", source " + source
+ ", account " + account
+ ", authority " + authority
- + ", extras " + extrasCopy);
+ + ", extras " + extras);
}
scheduleSyncOperation(
- new SyncOperation(account, source, authority, extrasCopy, delayCopy));
+ new SyncOperation(account, source, authority, extras, delay));
}
}
}
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
index 2d39e39..b8e17da 100644
--- a/core/java/android/net/http/Connection.java
+++ b/core/java/android/net/http/Connection.java
@@ -94,7 +94,6 @@
*/
private static final String HTTP_CONNECTION = "http.connection";
- RequestQueue.ConnectionManager mConnectionManager;
RequestFeeder mRequestFeeder;
/**
@@ -104,11 +103,9 @@
private byte[] mBuf;
protected Connection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
mContext = context;
mHost = host;
- mConnectionManager = connectionManager;
mRequestFeeder = requestFeeder;
mCanPersist = false;
@@ -124,18 +121,15 @@
* necessary
*/
static Connection getConnection(
- Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
if (host.getSchemeName().equals("http")) {
- return new HttpConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpConnection(context, host, requestFeeder);
}
// Otherwise, default to https
- return new HttpsConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpsConnection(context, host, proxy, requestFeeder);
}
/**
@@ -338,7 +332,7 @@
mRequestFeeder.requeueRequest(tReq);
empty = false;
}
- if (empty) empty = mRequestFeeder.haveRequest(mHost);
+ if (empty) empty = !mRequestFeeder.haveRequest(mHost);
}
return empty;
}
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
index 0b30e58..32191d2 100644
--- a/core/java/android/net/http/ConnectionThread.java
+++ b/core/java/android/net/http/ConnectionThread.java
@@ -108,24 +108,11 @@
if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
request.mHost + " " + request );
- HttpHost proxy = mConnectionManager.getProxyHost();
-
- HttpHost host;
- if (false) {
- // Allow https proxy
- host = proxy == null ? request.mHost : proxy;
- } else {
- // Disallow https proxy -- tmob proxy server
- // serves a request loop for https reqs
- host = (proxy == null ||
- request.mHost.getSchemeName().equals("https")) ?
- request.mHost : proxy;
- }
- mConnection = mConnectionManager.getConnection(mContext, host);
+ mConnection = mConnectionManager.getConnection(mContext,
+ request.mHost);
mConnection.processRequests(request);
if (mConnection.getCanPersist()) {
- if (!mConnectionManager.recycleConnection(host,
- mConnection)) {
+ if (!mConnectionManager.recycleConnection(mConnection)) {
mConnection.closeConnection();
}
} else {
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
index 8b12d0b..6df86bf 100644
--- a/core/java/android/net/http/HttpConnection.java
+++ b/core/java/android/net/http/HttpConnection.java
@@ -35,9 +35,8 @@
class HttpConnection extends Connection {
HttpConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
}
/**
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index 8a69d0d..f735f3d 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -131,13 +131,16 @@
*/
private boolean mAborted = false;
+ // Used when connecting through a proxy.
+ private HttpHost mProxyHost;
+
/**
* Contructor for a https connection.
*/
- HttpsConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ HttpsConnection(Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
+ mProxyHost = proxy;
}
/**
@@ -159,8 +162,7 @@
AndroidHttpClientConnection openConnection(Request req) throws IOException {
SSLSocket sslSock = null;
- HttpHost proxyHost = mConnectionManager.getProxyHost();
- if (proxyHost != null) {
+ if (mProxyHost != null) {
// If we have a proxy set, we first send a CONNECT request
// to the proxy; if the proxy returns 200 OK, we negotiate
// a secure connection to the target server via the proxy.
@@ -172,7 +174,7 @@
Socket proxySock = null;
try {
proxySock = new Socket
- (proxyHost.getHostName(), proxyHost.getPort());
+ (mProxyHost.getHostName(), mProxyHost.getPort());
proxySock.setSoTimeout(60 * 1000);
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index 190ae7a..77cd544 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -42,15 +42,13 @@
private WebAddress mUri;
private String mMethod;
private Map<String, String> mHeaders;
-
private RequestQueue mRequestQueue;
-
private Request mRequest;
-
private InputStream mBodyProvider;
private int mBodyLength;
-
private int mRedirectCount = 0;
+ // Used only with synchronous requests.
+ private Connection mConnection;
private final static String AUTHORIZATION_HEADER = "Authorization";
private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
@@ -81,6 +79,19 @@
}
/**
+ * Creates a new request session with a given Connection. This connection
+ * is used during a synchronous load to handle this request.
+ */
+ public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ InputStream bodyProvider, int bodyLength, Request request,
+ Connection conn) {
+ this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
+ request);
+ mConnection = conn;
+ }
+
+ /**
* Cancels this request
*/
public void cancel() {
@@ -262,6 +273,12 @@
mRequest.waitUntilComplete();
}
+ public void processRequest() {
+ if (mConnection != null) {
+ mConnection.processRequests(mRequest);
+ }
+ }
+
/**
* @return Digest-scheme authentication response.
*/
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
index 875caa0..84b6487 100644
--- a/core/java/android/net/http/RequestQueue.java
+++ b/core/java/android/net/http/RequestQueue.java
@@ -171,16 +171,17 @@
}
public Connection getConnection(Context context, HttpHost host) {
+ host = RequestQueue.this.determineHost(host);
Connection con = mIdleCache.getConnection(host);
if (con == null) {
mTotalConnection++;
- con = Connection.getConnection(
- mContext, host, this, RequestQueue.this);
+ con = Connection.getConnection(mContext, host, mProxyHost,
+ RequestQueue.this);
}
return con;
}
- public boolean recycleConnection(HttpHost host, Connection connection) {
- return mIdleCache.cacheConnection(host, connection);
+ public boolean recycleConnection(Connection connection) {
+ return mIdleCache.cacheConnection(connection.getHost(), connection);
}
}
@@ -342,6 +343,66 @@
req);
}
+ private static class SyncFeeder implements RequestFeeder {
+ // This is used in the case where the request fails and needs to be
+ // requeued into the RequestFeeder.
+ private Request mRequest;
+ SyncFeeder() {
+ }
+ public Request getRequest() {
+ Request r = mRequest;
+ mRequest = null;
+ return r;
+ }
+ public Request getRequest(HttpHost host) {
+ return getRequest();
+ }
+ public boolean haveRequest(HttpHost host) {
+ return mRequest != null;
+ }
+ public void requeueRequest(Request r) {
+ mRequest = r;
+ }
+ }
+
+ public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ EventHandler eventHandler, InputStream bodyProvider,
+ int bodyLength) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
+ }
+
+ HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
+
+ Request req = new Request(method, host, mProxyHost, uri.mPath,
+ bodyProvider, bodyLength, eventHandler, headers);
+
+ // Open a new connection that uses our special RequestFeeder
+ // implementation.
+ host = determineHost(host);
+ Connection conn = Connection.getConnection(mContext, host, mProxyHost,
+ new SyncFeeder());
+
+ // TODO: I would like to process the request here but LoadListener
+ // needs a RequestHandle to process some messages.
+ return new RequestHandle(this, url, uri, method, headers, bodyProvider,
+ bodyLength, req, conn);
+
+ }
+
+ // Chooses between the proxy and the request's host.
+ private HttpHost determineHost(HttpHost host) {
+ // There used to be a comment in ConnectionThread about t-mob's proxy
+ // being really bad about https. But, HttpsConnection actually looks
+ // for a proxy and connects through it anyway. I think that this check
+ // is still valid because if a site is https, we will use
+ // HttpsConnection rather than HttpConnection if the proxy address is
+ // not secure.
+ return (mProxyHost == null || "https".equals(host.getSchemeName()))
+ ? host : mProxyHost;
+ }
+
/**
* @return true iff there are any non-active requests pending
*/
@@ -478,6 +539,6 @@
interface ConnectionManager {
HttpHost getProxyHost();
Connection getConnection(Context context, HttpHost host);
- boolean recycleConnection(HttpHost host, Connection connection);
+ boolean recycleConnection(Connection connection);
}
}
diff --git a/core/java/android/os/DropBox.aidl b/core/java/android/os/DropBox.aidl
new file mode 100644
index 0000000..77abd22
--- /dev/null
+++ b/core/java/android/os/DropBox.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.os;
+
+parcelable DropBox.Entry;
diff --git a/core/java/android/os/DropBox.java b/core/java/android/os/DropBox.java
new file mode 100644
index 0000000..0551dc1
--- /dev/null
+++ b/core/java/android/os/DropBox.java
@@ -0,0 +1,276 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxService;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Enqueues chunks of data (from various sources -- application crashes, kernel
+ * log records, etc.). The queue is size bounded and will drop old data if the
+ * enqueued data exceeds the maximum size. You can think of this as a
+ * persistent, system-wide, blob-oriented "logcat".
+ *
+ * <p>You can obtain an instance of this class by calling
+ * {@link android.content.Context#getSystemService}
+ * with {@link android.content.Context#DROPBOX_SERVICE}.
+ *
+ * <p>DropBox entries are not sent anywhere directly, but other system services
+ * and debugging tools may scan and upload entries for processing.
+ *
+ * {@pending}
+ */
+public class DropBox {
+ private static final String TAG = "DropBox";
+ private final IDropBoxService mService;
+
+ /** Flag value: Entry's content was deleted to save space. */
+ public static final int IS_EMPTY = 1;
+
+ /** Flag value: Content is human-readable UTF-8 text (can be combined with IS_GZIPPED). */
+ public static final int IS_TEXT = 2;
+
+ /** Flag value: Content can be decompressed with {@link GZIPOutputStream}. */
+ public static final int IS_GZIPPED = 4;
+
+ /**
+ * A single entry retrieved from the drop box.
+ * This may include a reference to a stream, so you must call
+ * {@link #close()} when you are done using it.
+ */
+ public static class Entry implements Parcelable {
+ private final String mTag;
+ private final long mTimeMillis;
+
+ private final byte[] mData;
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final int mFlags;
+
+ /** Create a new empty Entry with no contents. */
+ public Entry(String tag, long millis) {
+ this(tag, millis, (Object) null, IS_EMPTY);
+ }
+
+ /** Create a new Entry with plain text contents. */
+ public Entry(String tag, long millis, String text) {
+ this(tag, millis, (Object) text.getBytes(), IS_TEXT);
+ }
+
+ /**
+ * Create a new Entry with byte array contents.
+ * The data array must not be modified after creating this entry.
+ */
+ public Entry(String tag, long millis, byte[] data, int flags) {
+ this(tag, millis, (Object) data, flags);
+ }
+
+ /**
+ * Create a new Entry with streaming data contents.
+ * Takes ownership of the ParcelFileDescriptor.
+ */
+ public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) {
+ this(tag, millis, (Object) data, flags);
+ }
+
+ /**
+ * Create a new Entry with the contents read from a file.
+ * The file will be read when the entry's contents are requested.
+ */
+ public Entry(String tag, long millis, File data, int flags) throws IOException {
+ this(tag, millis, (Object) ParcelFileDescriptor.open(
+ data, ParcelFileDescriptor.MODE_READ_ONLY), flags);
+ }
+
+ /** Internal constructor for CREATOR.createFromParcel(). */
+ private Entry(String tag, long millis, Object value, int flags) {
+ if (tag == null) throw new NullPointerException();
+ if (((flags & IS_EMPTY) != 0) != (value == null)) throw new IllegalArgumentException();
+
+ mTag = tag;
+ mTimeMillis = millis;
+ mFlags = flags;
+
+ if (value == null) {
+ mData = null;
+ mFileDescriptor = null;
+ } else if (value instanceof byte[]) {
+ mData = (byte[]) value;
+ mFileDescriptor = null;
+ } else if (value instanceof ParcelFileDescriptor) {
+ mData = null;
+ mFileDescriptor = (ParcelFileDescriptor) value;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /** Close the input stream associated with this entry. */
+ public void close() {
+ try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException e) { }
+ }
+
+ /** @return the tag originally attached to the entry. */
+ public String getTag() { return mTag; }
+
+ /** @return time when the entry was originally created. */
+ public long getTimeMillis() { return mTimeMillis; }
+
+ /** @return flags describing the content returned by @{link #getInputStream()}. */
+ public int getFlags() { return mFlags & ~IS_GZIPPED; } // getInputStream() decompresses.
+
+ /**
+ * @param maxBytes of string to return (will truncate at this length).
+ * @return the uncompressed text contents of the entry, null if the entry is not text.
+ */
+ public String getText(int maxBytes) {
+ if ((mFlags & IS_TEXT) == 0) return null;
+ if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length));
+
+ InputStream is = null;
+ try {
+ is = getInputStream();
+ byte[] buf = new byte[maxBytes];
+ return new String(buf, 0, Math.max(0, is.read(buf)));
+ } catch (IOException e) {
+ return null;
+ } finally {
+ try { if (is != null) is.close(); } catch (IOException e) {}
+ }
+ }
+
+ /** @return the uncompressed contents of the entry, or null if the contents were lost */
+ public InputStream getInputStream() throws IOException {
+ InputStream is;
+ if (mData != null) {
+ is = new ByteArrayInputStream(mData);
+ } else if (mFileDescriptor != null) {
+ is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
+ } else {
+ return null;
+ }
+ return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+ }
+
+ public static final Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
+ public Entry[] newArray(int size) { return new Entry[size]; }
+ public Entry createFromParcel(Parcel in) {
+ return new Entry(
+ in.readString(), in.readLong(), in.readValue(null), in.readInt());
+ }
+ };
+
+ public int describeContents() {
+ return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mTag);
+ out.writeLong(mTimeMillis);
+ if (mFileDescriptor != null) {
+ out.writeValue(mFileDescriptor);
+ } else {
+ out.writeValue(mData);
+ }
+ out.writeInt(mFlags);
+ }
+ }
+
+ /** {@hide} */
+ public DropBox(IDropBoxService service) { mService = service; }
+
+ /**
+ * Create a dummy instance for testing. All methods will fail unless
+ * overridden with an appropriate mock implementation. To obtain a
+ * functional instance, use {@link android.content.Context#getSystemService}.
+ */
+ protected DropBox() { mService = null; }
+
+ /**
+ * Stores human-readable text. The data may be discarded eventually (or even
+ * immediately) if space is limited, or ignored entirely if the tag has been
+ * blocked (see {@link #isTagEnabled}).
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ */
+ public void addText(String tag, String data) {
+ try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
+ }
+
+ /**
+ * Stores binary data, which may be ignored or discarded as with {@link #addText}.
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ * @param flags describing the data
+ */
+ public void addData(String tag, byte[] data, int flags) {
+ if (data == null) throw new NullPointerException();
+ try { mService.add(new Entry(tag, 0, data, flags)); } catch (RemoteException e) {}
+ }
+
+ /**
+ * Stores data read from a file descriptor. The data may be ignored or
+ * discarded as with {@link #addText}. You must close your
+ * ParcelFileDescriptor object after calling this method!
+ *
+ * @param tag describing the type of entry being stored
+ * @param fd file descriptor to read from
+ * @param flags describing the data
+ */
+ public void addFile(String tag, ParcelFileDescriptor fd, int flags) {
+ if (fd == null) throw new NullPointerException();
+ try { mService.add(new Entry(tag, 0, fd, flags)); } catch (RemoteException e) {}
+ }
+
+ /**
+ * Checks any blacklists (set in system settings) to see whether a certain
+ * tag is allowed. Entries with disabled tags will be dropped immediately,
+ * so you can save the work of actually constructing and sending the data.
+ *
+ * @param tag that would be used in {@link #addText} or {@link #addFile}
+ * @return whether events with that tag would be accepted
+ */
+ public boolean isTagEnabled(String tag) {
+ try { return mService.isTagEnabled(tag); } catch (RemoteException e) { return false; }
+ }
+
+ /**
+ * Gets the next entry from the drop box *after* the specified time.
+ * Requires android.permission.READ_LOGS. You must always call
+ * {@link Entry#close()} on the return value!
+ *
+ * @param tag of entry to look for, null for all tags
+ * @param msec time of the last entry seen
+ * @return the next entry, or null if there are no more entries
+ */
+ public Entry getNextEntry(String tag, long msec) {
+ try { return mService.getNextEntry(tag, msec); } catch (RemoteException e) { return null; }
+ }
+
+ // TODO: It may be useful to have some sort of notification mechanism
+ // when data is added to the dropbox, for demand-driven readers --
+ // for now readers need to poll the dropbox to find new data.
+}
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
index ca41ce5..1e2ccdf 100644
--- a/core/java/android/pim/vcard/Constants.java
+++ b/core/java/android/pim/vcard/Constants.java
@@ -16,16 +16,47 @@
package android.pim.vcard;
/**
- * Constants used in both composer and parser.
+ * Constants used in both exporter and importer code.
*/
/* package */ class Constants {
- public static final String ATTR_TYPE = "TYPE";
-
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";
- // Properties both the current (as of 2009-08-17) ContactsStruct and de-fact vCard extensions
+ // 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";
@@ -34,12 +65,24 @@
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";
- // Some device emits this "X-" attribute, 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";
-
+
+ // 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";
+
// How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
//
// e.g.
@@ -52,42 +95,69 @@
//
// So we are currently not sure which type is the best; probably we will have to change which
// type should be emitted depending on the device.
- public static final String ATTR_TYPE_HOME = "HOME";
- public static final String ATTR_TYPE_WORK = "WORK";
- public static final String ATTR_TYPE_FAX = "FAX";
- public static final String ATTR_TYPE_CELL = "CELL";
- public static final String ATTR_TYPE_VOICE = "VOICE";
- public static final String ATTR_TYPE_INTERNET = "INTERNET";
+ public static final String PARAM_TYPE_HOME = "HOME";
+ public static final String PARAM_TYPE_WORK = "WORK";
+ public static final String PARAM_TYPE_FAX = "FAX";
+ public static final String PARAM_TYPE_CELL = "CELL";
+ public static final String PARAM_TYPE_VOICE = "VOICE";
+ public static final String PARAM_TYPE_INTERNET = "INTERNET";
- public static final String ATTR_TYPE_PREF = "PREF";
+ // 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 types valid in vCard and known to ContactsContract, but not so common.
- public static final String ATTR_TYPE_CAR = "CAR";
- public static final String ATTR_TYPE_ISDN = "ISDN";
- public static final String ATTR_TYPE_PAGER = "PAGER";
+ // 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.
// TODO: should make parser make these TYPE_CUSTOM.
- public static final String ATTR_TYPE_MODEM = "MODEM";
- public static final String ATTR_TYPE_MSG = "MSG";
- public static final String ATTR_TYPE_BBS = "BBS";
- public static final String ATTR_TYPE_VIDEO = "VIDEO";
+ 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";
- // Phone types existing in the current Contacts structure but not valid in vCard (at least 2.1)
- // These types are encoded to "X-" attributes when composing vCard for now.
- // Parser passes these even if "X-" is added to the attribute.
- public static final String ATTR_TYPE_PHONE_EXTRA_OTHER = "OTHER";
- public static final String ATTR_TYPE_PHONE_EXTRA_CALLBACK = "CALLBACK";
- // TODO: may be "TYPE=COMPANY,PREF", not "COMPANY-MAIN".
- public static final String ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN = "COMPANY-MAIN";
- public static final String ATTR_TYPE_PHONE_EXTRA_RADIO = "RADIO";
- public static final String ATTR_TYPE_PHONE_EXTRA_TELEX = "TELEX";
- public static final String ATTR_TYPE_PHONE_EXTRA_TTY_TDD = "TTY-TDD";
- public static final String ATTR_TYPE_PHONE_EXTRA_ASSISTANT = "ASSISTANT";
+ // 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";
- // DoCoMo specific attribute. Used with "SOUND" property, which is alternate of SORT-STRING in
+ // 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";
+
+ // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
// vCard 3.0.
- public static final String ATTR_TYPE_X_IRMC_N = "X-IRMC-N";
+ public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
+ 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";
+ }
+
+ // TODO: Should be in ContactsContract?
+ /* package */ static final int MAX_DATA_COLUMN = 15;
private Constants() {
}
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index 36e5e23..f01659e8 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -46,7 +46,6 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -55,11 +54,11 @@
*/
public class ContactStruct {
private static final String LOG_TAG = "vcard.ContactStruct";
-
+
// Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
// Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
-
+
static {
sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
@@ -68,12 +67,10 @@
sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
- sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(Constants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+ Im.PROTOCOL_GOOGLE_TALK);
}
- /**
- * @hide only for testing
- */
static public class PhoneData {
public final int type;
public final String data;
@@ -90,7 +87,7 @@
@Override
public boolean equals(Object obj) {
- if (obj instanceof PhoneData) {
+ if (!(obj instanceof PhoneData)) {
return false;
}
PhoneData phoneData = (PhoneData)obj;
@@ -125,7 +122,7 @@
@Override
public boolean equals(Object obj) {
- if (obj instanceof EmailData) {
+ if (!(obj instanceof EmailData)) {
return false;
}
EmailData emailData = (EmailData)obj;
@@ -202,7 +199,7 @@
@Override
public boolean equals(Object obj) {
- if (obj instanceof PostalData) {
+ if (!(obj instanceof PostalData)) {
return false;
}
PostalData postalData = (PostalData)obj;
@@ -251,87 +248,117 @@
}
}
- /**
- * @hide only for testing.
- */
static public class OrganizationData {
public final int type;
- public final String companyName;
- // can be changed in some VCard format.
- public String positionName;
+ // non-final is Intended: we may change the values since this info is separated into
+ // two parts in vCard: "ORG" + "TITLE".
+ public String companyName;
+ public String departmentName;
+ public String titleName;
// isPrimary is changable only when there's no appropriate one existing in
// the original VCard.
public boolean isPrimary;
- public OrganizationData(int type, String companyName, String positionName,
+ public OrganizationData(int type,
+ String companyName,
+ String departmentName,
+ String titleName,
boolean isPrimary) {
this.type = type;
this.companyName = companyName;
- this.positionName = positionName;
+ this.departmentName = departmentName;
+ this.titleName = titleName;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof OrganizationData) {
+ if (!(obj instanceof OrganizationData)) {
return false;
}
OrganizationData organization = (OrganizationData)obj;
- return (type == organization.type && companyName.equals(organization.companyName) &&
- positionName.equals(organization.positionName) &&
+ return (type == organization.type &&
+ TextUtils.equals(companyName, organization.companyName) &&
+ TextUtils.equals(departmentName, organization.departmentName) &&
+ TextUtils.equals(titleName, organization.titleName) &&
isPrimary == organization.isPrimary);
}
-
+
@Override
public String toString() {
- return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
- type, companyName, positionName, isPrimary);
+ return String.format(
+ "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+ type, companyName, departmentName, titleName, isPrimary);
}
}
static public class ImData {
+ public final int protocol;
+ public final String customProtocol;
public final int type;
public final String data;
- public final String label;
public final boolean isPrimary;
- // TODO: ContactsConstant#PROTOCOL, ContactsConstant#CUSTOM_PROTOCOL should be used?
- public ImData(int type, String data, String label, boolean isPrimary) {
+ public ImData(int protocol, String customProtocol, int type,
+ String data, boolean isPrimary) {
+ this.protocol = protocol;
+ this.customProtocol = customProtocol;
this.type = type;
this.data = data;
- this.label = label;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof ImData) {
+ if (!(obj instanceof ImData)) {
return false;
}
ImData imData = (ImData)obj;
- return (type == imData.type && data.equals(imData.data) &&
- label.equals(imData.label) && isPrimary == imData.isPrimary);
+ 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, data: %s, label: %s, isPrimary: %s",
- type, data, label, isPrimary);
+ return String.format(
+ "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
+ type, protocol, customProtocol, data, isPrimary);
}
}
- /**
- * @hide only for testing.
- */
static public 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) {
+ 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);
}
}
@@ -342,10 +369,6 @@
private List<String> mPropertyValueList = new ArrayList<String>();
private byte[] mPropertyBytes;
- public Property() {
- clear();
- }
-
public void setPropertyName(final String propertyName) {
mPropertyName = propertyName;
}
@@ -385,6 +408,7 @@
mPropertyName = null;
mParameterMap.clear();
mPropertyValueList.clear();
+ mPropertyBytes = null;
}
}
@@ -417,21 +441,13 @@
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;
- // Each Column of four properties has ISPRIMARY field
- // (See android.provider.Contacts)
- // If false even after the parsing loop, we choose the first entry as a "primary"
- // entry.
- private boolean mPrefIsSet_Address;
- private boolean mPrefIsSet_Phone;
- private boolean mPrefIsSet_Email;
- private boolean mPrefIsSet_Organization;
-
public ContactStruct() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC);
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
}
public ContactStruct(int vcardType) {
@@ -444,186 +460,6 @@
}
/**
- * @hide only for testing.
- */
- public ContactStruct(String givenName,
- String familyName,
- String middleName,
- String prefix,
- String suffix,
- String phoneticGivenName,
- String pheneticFamilyName,
- String phoneticMiddleName,
- List<byte[]> photoBytesList,
- List<String> notes,
- List<PhoneData> phoneList,
- List<EmailData> emailList,
- List<PostalData> postalList,
- List<OrganizationData> organizationList,
- List<PhotoData> photoList,
- List<String> websiteList) {
- this(VCardConfig.VCARD_TYPE_DEFAULT);
- mGivenName = givenName;
- mFamilyName = familyName;
- mPrefix = prefix;
- mSuffix = suffix;
- mPhoneticGivenName = givenName;
- mPhoneticFamilyName = familyName;
- mPhoneticMiddleName = middleName;
- mEmailList = emailList;
- mPostalList = postalList;
- mOrganizationList = organizationList;
- mPhotoList = photoList;
- mWebsiteList = websiteList;
- }
-
- // All getter methods should be used carefully, since they may change
- // in the future as of 2009-09-24, on which I cannot be sure this structure
- // is completely consolidated.
- // When we are sure we will no longer change them, we'll be happy to
- // make it complete public (withouth @hide tag)
- //
- // 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.
-
- /**
- * @hide
- */
- public String getFamilyName() {
- return mFamilyName;
- }
-
- /**
- * @hide
- */
- public String getGivenName() {
- return mGivenName;
- }
-
- /**
- * @hide
- */
- public String getMiddleName() {
- return mMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPrefix() {
- return mPrefix;
- }
-
- /**
- * @hide
- */
- public String getSuffix() {
- return mSuffix;
- }
-
- /**
- * @hide
- */
- public String getFullName() {
- return mFullName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFamilyName() {
- return mPhoneticFamilyName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticGivenName() {
- return mPhoneticGivenName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticMiddleName() {
- return mPhoneticMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFullName() {
- return mPhoneticFullName;
- }
-
- /**
- * @hide
- */
- public final List<String> getNickNameList() {
- return mNickNameList;
- }
-
- /**
- * @hide
- */
- public String getDisplayName() {
- if (mDisplayName == null) {
- constructDisplayName();
- }
- return mDisplayName;
- }
-
- /**
- * @hide
- */
- public String getBirthday() {
- return mBirthday;
- }
-
- /**
- * @hide
- */
- public final List<PhotoData> getPhotoList() {
- return mPhotoList;
- }
-
- /**
- * @hide
- */
- public final List<String> getNotes() {
- return mNoteList;
- }
-
- /**
- * @hide
- */
- public final List<PhoneData> getPhoneList() {
- return mPhoneList;
- }
-
- /**
- * @hide
- */
- public final List<EmailData> getEmailList() {
- return mEmailList;
- }
-
- /**
- * @hide
- */
- public final List<PostalData> getPostalList() {
- return mPostalList;
- }
-
- /**
- * @hide
- */
- public final List<OrganizationData> getOrganizationList() {
- return mOrganizationList;
- }
-
- /**
* Add a phone info to phoneList.
* @param data phone number
* @param type type col of content://contacts/phones
@@ -635,18 +471,24 @@
}
StringBuilder builder = new StringBuilder();
String trimed = data.trim();
- 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 String formattedNumber;
+ if (type == Phone.TYPE_PAGER) {
+ 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);
+ }
}
+
+ // Use NANP in default when there's no information about locale.
+ final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
+ PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+ formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
}
-
- PhoneData phoneData = new PhoneData(type,
- PhoneNumberUtils.formatNumber(builder.toString()),
- label, isPrimary);
-
+ PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
mPhoneList.add(phoneData);
}
@@ -666,24 +508,122 @@
private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
if (mPostalList == null) {
- mPostalList = new ArrayList<PostalData>();
+ mPostalList = new ArrayList<PostalData>(0);
}
mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
}
- private void addOrganization(int type, final String companyName,
- final String positionName, boolean 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, positionName, isPrimary));
+ mOrganizationList.add(new OrganizationData(type, companyName,
+ departmentName, titleName, isPrimary));
}
+
+ private static final List<String> sEmptyList = new ArrayList<String>(0);
- private void addIm(int type, String data, String label, boolean isPrimary) {
+ /**
+ * Set "ORG" related values to the appropriate data. If there's more than one
+ * 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
+ * OrganizationData object, a new 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);
+ }
+
+ private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+ /**
+ * 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(type, data, label, isPrimary));
+ mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
}
private void addNote(final String note) {
@@ -693,43 +633,14 @@
mNoteList.add(note);
}
- private void addPhotoBytes(String formatName, byte[] photoBytes) {
+ 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);
+ final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
mPhotoList.add(photoData);
}
- /**
- * Set "position" value to the appropriate data. If there's more than one
- * OrganizationData objects, the value is set to the last one. If there's no
- * OrganizationData object, a new OrganizationData is created, whose company name is
- * empty.
- *
- * TODO: incomplete logic. fix this:
- *
- * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not
- * know how to handle it in general cases...
- * ----
- * TITLE:Software Engineer
- * ORG:Google
- * ----
- */
- private void setPosition(String positionValue) {
- if (mOrganizationList == null) {
- mOrganizationList = new ArrayList<OrganizationData>();
- }
- int size = mOrganizationList.size();
- if (size == 0) {
- addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
- "", null, false);
- size = 1;
- }
- OrganizationData lastData = mOrganizationList.get(size - 1);
- lastData.positionName = positionValue;
- }
-
@SuppressWarnings("fallthrough")
private void handleNProperty(List<String> elems) {
// Family, Given, Middle, Prefix, Suffix. (1 - 5)
@@ -755,7 +666,7 @@
mFamilyName = elems.get(0);
}
}
-
+
/**
* Some Japanese mobile phones use this field for phonetic name,
* since vCard 2.1 does not have "SORT-STRING" type.
@@ -796,28 +707,36 @@
}
final String propValue = listToString(propValueList).trim();
- if (propName.equals("VERSION")) {
+ if (propName.equals(Constants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
- } else if (propName.equals("FN")) {
+ } else if (propName.equals(Constants.PROPERTY_FN)) {
mFullName = propValue;
- } else if (propName.equals("NAME") && mFullName == null) {
+ } else if (propName.equals(Constants.PROPERTY_NAME) && mFullName == 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.
mFullName = propValue;
- } else if (propName.equals("N")) {
+ } else if (propName.equals(Constants.PROPERTY_N)) {
handleNProperty(propValueList);
- } else if (propName.equals("SORT-STRING")) {
+ } else if (propName.equals(Constants.PROPERTY_SORT_STRING)) {
mPhoneticFullName = propValue;
- } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) {
+ } else if (propName.equals(Constants.PROPERTY_NICKNAME) ||
+ propName.equals(Constants.ImportOnly.PROPERTY_X_NICKNAME)) {
addNickName(propValue);
- } else if (propName.equals("SOUND")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) {
- handlePhoneticNameFromSound(propValueList);
+ } else if (propName.equals(Constants.PROPERTY_SOUND)) {
+ Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ if (typeCollection != null && typeCollection.contains(Constants.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("ADR")) {
+ } else if (propName.equals(Constants.PROPERTY_ADR)) {
boolean valuesAreAllEmpty = true;
for (String value : propValueList) {
if (value.length() > 0) {
@@ -832,27 +751,25 @@
int type = -1;
String label = "";
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
- // Only first "PREF" is considered.
- mPrefIsSet_Address = true;
+ if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ } else if (typeString.equals(Constants.PARAM_TYPE_HOME)) {
type = StructuredPostal.TYPE_HOME;
label = "";
- } else if (typeString.equals(Constants.ATTR_TYPE_WORK) ||
- typeString.equalsIgnoreCase("COMPANY")) {
+ } else if (typeString.equals(Constants.PARAM_TYPE_WORK) ||
+ typeString.equalsIgnoreCase(Constants.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("PARCEL") ||
- typeString.equals("DOM") ||
- typeString.equals("INTL")) {
+ } else if (typeString.equals(Constants.PARAM_ADR_TYPE_PARCEL) ||
+ typeString.equals(Constants.PARAM_ADR_TYPE_DOM) ||
+ typeString.equals(Constants.PARAM_ADR_TYPE_INTL)) {
// We do not have any appropriate way to store this information.
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -871,23 +788,21 @@
}
addPostal(type, propValueList, label, isPrimary);
- } else if (propName.equals("EMAIL")) {
+ } else if (propName.equals(Constants.PROPERTY_EMAIL)) {
int type = -1;
String label = null;
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
- // Only first "PREF" is considered.
- mPrefIsSet_Email = true;
+ if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ } else if (typeString.equals(Constants.PARAM_TYPE_HOME)) {
type = Email.TYPE_HOME;
- } else if (typeString.equals(Constants.ATTR_TYPE_WORK)) {
+ } else if (typeString.equals(Constants.PARAM_TYPE_WORK)) {
type = Email.TYPE_WORK;
- } else if (typeString.equals(Constants.ATTR_TYPE_CELL)) {
+ } else if (typeString.equals(Constants.PARAM_TYPE_CELL)) {
type = Email.TYPE_MOBILE;
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -905,50 +820,48 @@
type = Email.TYPE_OTHER;
}
addEmail(type, propValue, label, isPrimary);
- } else if (propName.equals("ORG")) {
+ } else if (propName.equals(Constants.PROPERTY_ORG)) {
// vCard specification does not specify other types.
- int type = Organization.TYPE_WORK;
+ final int type = Organization.TYPE_WORK;
boolean isPrimary = false;
-
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
- // vCard specification officially does not have PREF in ORG.
- // This is just for safety.
- mPrefIsSet_Organization = true;
+ if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
isPrimary = true;
}
}
}
-
- StringBuilder builder = new StringBuilder();
- for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) {
- builder.append(iter.next());
- if (iter.hasNext()) {
- builder.append(' ');
- }
- }
- addOrganization(type, builder.toString(), "", isPrimary);
- } else if (propName.equals("TITLE")) {
- setPosition(propValue);
- } else if (propName.equals("ROLE")) {
- setPosition(propValue);
- } else if (propName.equals("PHOTO") || propName.equals("LOGO")) {
- String formatName = null;
- Collection<String> typeCollection = paramMap.get("TYPE");
- if (typeCollection != null) {
- formatName = typeCollection.iterator().next();
- }
+ handleOrgValue(type, propValueList, isPrimary);
+ } else if (propName.equals(Constants.PROPERTY_TITLE)) {
+ handleTitleValue(propValue);
+ } else if (propName.equals(Constants.PROPERTY_ROLE)) {
+ // This conflicts with TITLE. Ignore for now...
+ // handleTitleValue(propValue);
+ } else if (propName.equals(Constants.PROPERTY_PHOTO) ||
+ propName.equals(Constants.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 {
- addPhotoBytes(formatName, propBytes);
+ final Collection<String> typeCollection = paramMap.get("TYPE");
+ String formatName = null;
+ boolean isPrimary = false;
+ if (typeCollection != null) {
+ for (String typeValue : typeCollection) {
+ if (Constants.PARAM_TYPE_PREF.equals(typeValue)) {
+ isPrimary = true;
+ } else if (formatName == null){
+ formatName = typeValue;
+ }
+ }
+ }
+ addPhotoBytes(formatName, propBytes, isPrimary);
}
- } else if (propName.equals("TEL")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+ } else if (propName.equals(Constants.PROPERTY_TEL)) {
+ final Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+ final Object typeObject =
+ VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
final int type;
final String label;
if (typeObject instanceof Integer) {
@@ -960,9 +873,7 @@
}
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
@@ -970,53 +881,59 @@
addPhone(type, propValue, label, isPrimary);
} else if (propName.equals(Constants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
// The phone number available via Skype.
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
// XXX: should use TYPE_CUSTOM + the label "Skype"? (which may need localization)
int type = Phone.TYPE_OTHER;
final String label = null;
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
}
addPhone(type, propValue, label, isPrimary);
- } else if (sImMap.containsKey(propName)){
- int type = sImMap.get(propName);
+ } else if (sImMap.containsKey(propName)) {
+ final int protocol = sImMap.get(propName);
boolean isPrimary = false;
- final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ int type = -1;
+ final Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+ if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) {
- type = Phone.TYPE_HOME;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) {
- type = Phone.TYPE_WORK;
+ } else if (type < 0) {
+ if (typeString.equalsIgnoreCase(Constants.PARAM_TYPE_HOME)) {
+ type = Im.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase(Constants.PARAM_TYPE_WORK)) {
+ type = Im.TYPE_WORK;
+ }
}
}
}
if (type < 0) {
type = Phone.TYPE_HOME;
}
- addIm(type, propValue, null, isPrimary);
- } else if (propName.equals("NOTE")) {
+ addIm(protocol, null, type, propValue, isPrimary);
+ } else if (propName.equals(Constants.PROPERTY_NOTE)) {
addNote(propValue);
- } else if (propName.equals("URL")) {
+ } else if (propName.equals(Constants.PROPERTY_URL)) {
if (mWebsiteList == null) {
mWebsiteList = new ArrayList<String>(1);
}
mWebsiteList.add(propValue);
- } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
- mPhoneticGivenName = propValue;
- } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
- mPhoneticMiddleName = propValue;
- } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
- mPhoneticFamilyName = propValue;
- } else if (propName.equals("BDAY")) {
+ } else if (propName.equals(Constants.PROPERTY_BDAY)) {
mBirthday = propValue;
+ } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
+ mPhoneticGivenName = propValue;
+ } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
+ mPhoneticMiddleName = propValue;
+ } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_LAST_NAME)) {
+ mPhoneticFamilyName = propValue;
+ } else if (propName.equals(Constants.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")) {
@@ -1044,11 +961,21 @@
}
}
+ 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() {
- if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+ // FullName (created via "FN" or "NAME" field) is prefered.
+ if (!TextUtils.isEmpty(mFullName)) {
+ mDisplayName = mFullName;
+ } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
StringBuilder builder = new StringBuilder();
List<String> nameList;
switch (VCardConfig.getNameOrderType(mVCardType)) {
@@ -1079,8 +1006,6 @@
}
}
mDisplayName = builder.toString();
- } else if (!TextUtils.isEmpty(mFullName)) {
- mDisplayName = mFullName;
} else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
TextUtils.isEmpty(mPhoneticGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
@@ -1097,33 +1022,18 @@
mDisplayName = "";
}
}
-
+
/**
* Consolidate several fielsds (like mName) using name candidates,
*/
public void consolidateFields() {
constructDisplayName();
-
+
if (mPhoneticFullName != null) {
mPhoneticFullName = mPhoneticFullName.trim();
}
-
- // If there is no "PREF", we choose the first entries as primary.
- if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) {
- mPhoneList.get(0).isPrimary = true;
- }
-
- if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) {
- mPostalList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) {
- mEmailList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) {
- mOrganizationList.get(0).isPrimary = true;
- }
}
-
+
// From GoogleSource.java in Contacts app.
private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
@@ -1161,7 +1071,7 @@
}
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);
@@ -1172,31 +1082,31 @@
builder.withValue(StructuredName.PREFIX, mPrefix);
builder.withValue(StructuredName.SUFFIX, mSuffix);
- builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
- builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
- builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+ 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) {
- boolean first = true;
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);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
- }
operationList.add(builder.build());
}
}
-
+
if (mPhoneList != null) {
for (PhoneData phoneData : mPhoneList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1209,30 +1119,34 @@
}
builder.withValue(Phone.NUMBER, phoneData.data);
if (phoneData.isPrimary) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ builder.withValue(Phone.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mOrganizationList != null) {
- boolean first = true;
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);
-
- // Currently, we do not use TYPE_CUSTOM.
builder.withValue(Organization.TYPE, organizationData.type);
- builder.withValue(Organization.COMPANY, organizationData.companyName);
- builder.withValue(Organization.TITLE, organizationData.positionName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ 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);
@@ -1265,12 +1179,11 @@
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);
- if (imData.type == Im.TYPE_CUSTOM) {
- builder.withValue(Im.LABEL, imData.label);
+ builder.withValue(Im.PROTOCOL, imData.protocol);
+ if (imData.protocol == Im.PROTOCOL_CUSTOM) {
+ builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
}
- builder.withValue(Im.DATA, imData.data);
if (imData.isPrimary) {
builder.withValue(Data.IS_PRIMARY, 1);
}
@@ -1282,22 +1195,19 @@
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) {
- boolean first = true;
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 (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
+ if (photoData.isPrimary) {
+ builder.withValue(Photo.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
@@ -1310,12 +1220,12 @@
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_HOME for safety.
- builder.withValue(Website.TYPE, Website.TYPE_HOME);
+ // 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);
@@ -1325,6 +1235,36 @@
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 > Constants.MAX_DATA_COLUMN + 1) {
+ size = Constants.MAX_DATA_COLUMN + 1;
+ customPropertyList =
+ customPropertyList.subList(0, Constants.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);
@@ -1342,6 +1282,19 @@
}
}
+ private boolean nameFieldsAreEmpty() {
+ return (TextUtils.isEmpty(mFamilyName)
+ && TextUtils.isEmpty(mMiddleName)
+ && TextUtils.isEmpty(mGivenName)
+ && TextUtils.isEmpty(mPrefix)
+ && TextUtils.isEmpty(mSuffix)
+ && TextUtils.isEmpty(mFullName)
+ && TextUtils.isEmpty(mPhoneticFamilyName)
+ && TextUtils.isEmpty(mPhoneticMiddleName)
+ && TextUtils.isEmpty(mPhoneticGivenName)
+ && TextUtils.isEmpty(mPhoneticFullName));
+ }
+
public boolean isIgnorable() {
return getDisplayName().length() == 0;
}
@@ -1364,4 +1317,99 @@
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 mFullName;
+ }
+
+ 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/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 9638262..9d72c5f 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -53,6 +53,7 @@
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -72,19 +73,38 @@
* 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>
+ * <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>
*/
public class VCardComposer {
private static final String LOG_TAG = "vcard.VCardComposer";
- private static final String DEFAULT_EMAIL_TYPE = Constants.ATTR_TYPE_INTERNET;
+ // TODO: Should be configurable?
+ 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;
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
@@ -95,28 +115,84 @@
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";
+
+ // Property for call log entry
+ private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
+ private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
+ private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
+ private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
+
+ 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=QUOTED-PRINTABLE";
+
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
+ private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+
+ 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 Uri sDataRequestUri;
+ private static final Map<Integer, String> sImMap;
+
+ /**
+ * See the comment in {@link VCardConfig#FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES}.
+ */
+ private static final Set<String> sPrimaryPropertyNameSet;
static {
Uri.Builder builder = RawContacts.CONTENT_URI.buildUpon();
builder.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1");
sDataRequestUri = builder.build();
+ sImMap = new HashMap<Integer, String>();
+ sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
+ sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
+ sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
+ sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
+ sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
+ sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+ // Google talk is a special case.
+
+ // TODO: incomplete. Implement properly
+ sPrimaryPropertyNameSet = new HashSet<String>();
+ sPrimaryPropertyNameSet.add(Constants.PROPERTY_N);
+ sPrimaryPropertyNameSet.add(Constants.PROPERTY_FN);
+ sPrimaryPropertyNameSet.add(Constants.PROPERTY_SOUND);
}
public static interface OneEntryHandler {
public boolean onInit(Context context);
-
public boolean onEntryCreated(String vcard);
-
public void onTerminate();
}
/**
* <p>
- * An useful example handler, which emits VCard String to outputstream one
- * by one.
+ * An useful example handler, which emits VCard String to outputstream one by one.
* </p>
* <p>
* The input OutputStream object is closed() on {{@link #onTerminate()}.
@@ -211,65 +287,6 @@
}
}
- public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
- private static final String VCARD_PROPERTY_ADR = "ADR";
- private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
- private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
- private static final String VCARD_PROPERTY_END = "END";
- private static final String VCARD_PROPERTY_NAME = "N";
- private static final String VCARD_PROPERTY_FULL_NAME = "FN";
- private static final String VCARD_PROPERTY_NOTE = "NOTE";
- private static final String VCARD_PROPERTY_ORG = "ORG";
- private static final String VCARD_PROPERTY_SOUND = "SOUND";
- private static final String VCARD_PROPERTY_SORT_STRING = "SORT-STRING";
- private static final String VCARD_PROPERTY_NICKNAME = "NICKNAME";
- private static final String VCARD_PROPERTY_TEL = "TEL";
- private static final String VCARD_PROPERTY_TITLE = "TITLE";
- private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
- private static final String VCARD_PROPERTY_VERSION = "VERSION";
- private static final String VCARD_PROPERTY_URL = "URL";
- private static final String VCARD_PROPERTY_BIRTHDAY = "BDAY";
-
- private static final String VCARD_PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
-
- // Android specific properties
- // TODO: ues extra MIME-TYPE instead of adding this kind of inflexible fields
- private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
-
- // Property for call log entry
- private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
- private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
- private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
- private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
-
- // Properties for DoCoMo vCard.
- private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
- private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
- private static final String VCARD_PROPERTY_X_NO = "X-NO";
- private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
- private static final String VCARD_DATA_VCARD = "VCARD";
- private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
- private static final String VCARD_ATTR_SEPARATOR = ";";
- private static final String VCARD_COL_SEPARATOR = "\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_ATTR_EQUAL = "=";
-
- // Type strings are now in VCardConstants.java.
-
- private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
- private static final String SHIFT_JIS = "SHIFT_JIS";
-
private final Context mContext;
private final int mVCardType;
private final boolean mCareHandlerErrors;
@@ -286,34 +303,21 @@
private final boolean mUsesDefactProperty;
private final boolean mUsesUtf8;
private final boolean mUsesShiftJis;
- private final boolean mUsesQPToPrimaryProperties;
+ private final boolean mAppendTypeParamName;
+ private final boolean mRefrainsQPToPrimaryProperties;
+ private final boolean mNeedsToConvertPhoneticString;
private Cursor mCursor;
private int mIdColumn;
private final String mCharsetString;
- private final String mVCardAttributeCharset;
+ private final String mVCardCharsetParameter;
private boolean mTerminateIsCalled;
final private List<OneEntryHandler> mHandlerList;
private String mErrorReason = NO_ERROR;
- private static final Map<Integer, String> sImMap;
-
- static {
- sImMap = new HashMap<Integer, String>();
- sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
- sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
- sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
- sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
- sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
- sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
- // Google talk is a special case.
- }
-
- private boolean mIsCallLogComposer = false;
-
- private boolean mNeedPhotoForVCard = true;
+ private boolean mIsCallLogComposer;
private static final String[] sContactsProjection = new String[] {
Contacts._ID,
@@ -334,108 +338,98 @@
private static final String FLAG_TIMEZONE_UTC = "Z";
public VCardComposer(Context context) {
- this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false, true);
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
}
- public VCardComposer(Context context, String vcardTypeStr,
- boolean careHandlerErrors) {
- this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
- careHandlerErrors, false, true);
+ public VCardComposer(Context context, int vcardType) {
+ this(context, vcardType, true);
}
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
- this(context, vcardType, careHandlerErrors, false, true);
+ public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
+ this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
}
/**
* Construct for supporting call log entry vCard composing.
- *
- * @param isCallLogComposer true if this composer is for creating Call Log vCard.
*/
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors,
- boolean isCallLogComposer, boolean needPhotoInVCard) {
+ public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
mCareHandlerErrors = careHandlerErrors;
- mIsCallLogComposer = isCallLogComposer;
- mNeedPhotoForVCard = needPhotoInVCard;
mContentResolver = context.getContentResolver();
mIsV30 = VCardConfig.isV30(vcardType);
mUsesQuotedPrintable = VCardConfig.usesQuotedPrintable(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mIsJapaneseMobilePhone = VCardConfig
- .needsToConvertPhoneticString(vcardType);
- mOnlyOneNoteFieldIsAvailable = VCardConfig
- .onlyOneNoteFieldIsAvailable(vcardType);
- mUsesAndroidProperty = VCardConfig
- .usesAndroidSpecificProperty(vcardType);
+ mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
+ mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
+ mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
- mUsesQPToPrimaryProperties = VCardConfig.usesQPToPrimaryProperties(vcardType);
+ mRefrainsQPToPrimaryProperties = VCardConfig.refrainsQPToPrimaryProperties(vcardType);
+ mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
+ mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
if (mIsDoCoMo) {
- mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ String charset;
+ 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;
+ }
+ mCharsetString = charset;
// Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
// may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
// Android, not shown to the public).
- mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else if (mUsesShiftJis) {
- mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ String charset;
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ mCharsetString = charset;
+ mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else {
- mCharsetString = "UTF-8";
- mVCardAttributeCharset = "CHARSET=UTF-8";
+ mCharsetString = UTF_8;
+ mVCardCharsetParameter = "CHARSET=" + UTF_8;
}
}
/**
- * This static function is to compose vCard for phone own number
- */
- public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
- String phoneNumber, boolean vcardVer21) {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (!vcardVer21) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
- boolean needCharset = false;
- if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
- needCharset = true;
- }
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, phoneName, needCharset, false);
-
- String label = Integer.toString(phonetype);
- appendVCardTelephoneLine(builder, phonetype, label, phoneNumber);
-
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
- return builder.toString();
- }
-
- /**
* Must call before {{@link #init()}.
*/
public void addHandler(OneEntryHandler handler) {
- mHandlerList.add(handler);
- }
-
- public boolean init() {
- return init(null, null);
+ 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) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -454,13 +448,19 @@
}
}
- if (mIsCallLogComposer) {
- mCursor = mContentResolver.query(CallLog.Calls.CONTENT_URI, sCallLogProjection,
- selection, selectionArgs, null);
+ final String[] projection;
+ if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
+ projection = sCallLogProjection;
+ mIsCallLogComposer = true;
+ } else if (Contacts.CONTENT_URI.equals(contentUri) ||
+ CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
+ projection = sContactsProjection;
} else {
- mCursor = mContentResolver.query(Contacts.CONTENT_URI, sContactsProjection,
- selection, selectionArgs, null);
+ 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;
@@ -537,89 +537,6 @@
return true;
}
- /**
- * Format according to RFC 2445 DATETIME type.
- * The format is: ("%Y%m%dT%H%M%SZ").
- */
- private final String toRfc2455Format(final long millSecs) {
- Time startDate = new Time();
- startDate.set(millSecs);
- String date = startDate.format2445();
- return date + FLAG_TIMEZONE_UTC;
- }
-
- /**
- * Try to append the property line for a call history time stamp field if possible.
- * Do nothing if the call log type gotton from the database is invalid.
- */
- private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
- // Extension for call history as defined in
- // in the Specification for Ic Mobile Communcation - ver 1.1,
- // Oct 2000. This is used to send the details of the call
- // history - missed, incoming, outgoing along with date and time
- // to the requesting device (For example, transferring phone book
- // when connected over bluetooth)
- //
- // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
- final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
- final String callLogTypeStr;
- switch (callLogType) {
- case Calls.INCOMING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
- break;
- }
- case Calls.OUTGOING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
- break;
- }
- case Calls.MISSED_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
- break;
- }
- default: {
- Log.w(LOG_TAG, "Call log type not correct.");
- return;
- }
- }
-
- final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
- builder.append(VCARD_PROPERTY_X_TIMESTAMP);
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, callLogTypeStr);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(toRfc2455Format(dateAsLong));
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private String createOneCallLogEntryInternal() {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
- String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
- if (TextUtils.isEmpty(name)) {
- name = mCursor.getString(NUMBER_COLUMN_INDEX);
- }
- final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, name, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, name, needCharset, false);
-
- String number = mCursor.getString(NUMBER_COLUMN_INDEX);
- int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
- String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
- if (TextUtils.isEmpty(label)) {
- label = Integer.toString(type);
- }
- appendVCardTelephoneLine(builder, type, label, number);
- tryAppendCallHistoryTimeStampField(builder);
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
- return builder.toString();
- }
-
private String createOneEntryInternal(final String contactId) {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
@@ -636,8 +553,7 @@
dataExists = entityIterator.hasNext();
while (entityIterator.hasNext()) {
Entity entity = entityIterator.next();
- for (NamedContentValues namedContentValues : entity
- .getSubValues()) {
+ for (NamedContentValues namedContentValues : entity.getSubValues()) {
ContentValues contentValues = namedContentValues.values;
String key = contentValues.getAsString(Data.MIMETYPE);
if (key != null) {
@@ -666,11 +582,11 @@
}
final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
} else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
}
appendStructuredNames(builder, contentValuesListMap);
@@ -682,20 +598,18 @@
appendWebsites(builder, contentValuesListMap);
appendBirthday(builder, contentValuesListMap);
appendOrganizations(builder, contentValuesListMap);
- if (mNeedPhotoForVCard) {
- appendPhotos(builder, contentValuesListMap);
- }
+ appendPhotos(builder, contentValuesListMap);
appendNotes(builder, contentValuesListMap);
- // TODO: GroupMembership
+ // TODO: GroupMembership, Relation, Event other than birthday.
if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
- appendVCardLine(builder, VCARD_PROPERTY_X_REDUCTION, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_NO, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_DCM_HMN_MODE, "");
+ appendVCardLine(builder, Constants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+ appendVCardLine(builder, Constants.PROPERTY_X_REDUCTION, "");
+ appendVCardLine(builder, Constants.PROPERTY_X_NO, "");
+ appendVCardLine(builder, Constants.PROPERTY_X_DCM_HMN_MODE, "");
}
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
+ appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
return builder.toString();
}
@@ -753,11 +667,11 @@
if (contentValuesList != null && contentValuesList.size() > 0) {
appendStructuredNamesInternal(builder, contentValuesList);
} else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
} else if (mIsV30) {
// vCard 3.0 requires "N" and "FN" properties.
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
+ appendVCardLine(builder, Constants.PROPERTY_FN, "");
}
}
@@ -767,51 +681,80 @@
// may get confused with multiple "N", "FN", etc. properties, though it is valid in
// vCard spec.
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) {
+ if (isSuperPrimary != null && isSuperPrimary > 0) {
// We choose "super primary" ContentValues.
primaryContentValues = contentValues;
break;
- } else if (primaryContentValues == null && contentValues != null) {
- // We choose the first ContentValues if "super primary" ContentValues does not exist.
- primaryContentValues = contentValues;
+ } else if (primaryContentValues == null) {
+ // We choose the first "primary" ContentValues
+ // if "super primary" ContentValues does not exist.
+ Integer primary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+ if (primary != null && primary > 0) {
+ primaryContentValues = contentValues;
+ // Do not break, since there may be ContentValues with "super primary"
+ // afterword.
+ } else if (subprimaryContentValues == null) {
+ subprimaryContentValues = contentValues;
+ }
}
}
if (primaryContentValues == null) {
- Log.e(LOG_TAG, "All ContentValues given from database is empty.");
- primaryContentValues = new ContentValues();
+ 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();
+ }
}
- final String familyName = primaryContentValues
- .getAsString(StructuredName.FAMILY_NAME);
- final String middleName = primaryContentValues
- .getAsString(StructuredName.MIDDLE_NAME);
- final String givenName = primaryContentValues
- .getAsString(StructuredName.GIVEN_NAME);
- final String prefix = primaryContentValues
- .getAsString(StructuredName.PREFIX);
- final String suffix = primaryContentValues
- .getAsString(StructuredName.SUFFIX);
- final String displayName = primaryContentValues
- .getAsString(StructuredName.DISPLAY_NAME);
+ final String familyName = primaryContentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = primaryContentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = primaryContentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = primaryContentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = primaryContentValues.getAsString(StructuredName.SUFFIX);
+ final String displayName = primaryContentValues.getAsString(StructuredName.DISPLAY_NAME);
if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+ final boolean shouldAppendCharsetParameterToName =
+ !(mIsV30 && UTF_8.equalsIgnoreCase(mCharsetString)) &&
+ shouldAppendCharsetParameters(Arrays.asList(
+ familyName, givenName, middleName, prefix, suffix));
+ final boolean reallyUseQuotedPrintableToName =
+ (!mRefrainsQPToPrimaryProperties &&
+ !(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 shouldAppendCharsetParameterToFN =
+ !(mIsV30 && UTF_8.equalsIgnoreCase(mCharsetString)) &&
+ shouldAppendCharsetParameter(formattedName);
+ final boolean reallyUseQuotedPrintableToFN =
+ !mRefrainsQPToPrimaryProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
+
final String encodedFamily;
final String encodedGiven;
final String encodedMiddle;
final String encodedPrefix;
final String encodedSuffix;
-
- final boolean reallyUseQuotedPrintableToName =
- (mUsesQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
-
if (reallyUseQuotedPrintableToName) {
encodedFamily = encodeQuotedPrintable(familyName);
encodedGiven = encodeQuotedPrintable(givenName);
@@ -826,72 +769,79 @@
encodedSuffix = escapeCharacters(suffix);
}
- // N property. This order is specified by vCard spec and does not depend on countries.
- builder.append(VCARD_PROPERTY_NAME);
- if (shouldAppendCharsetAttribute(Arrays.asList(
- familyName, givenName, middleName, prefix, suffix))) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ final String encodedFormattedname =
+ (reallyUseQuotedPrintableToFN ?
+ encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
+
+ builder.append(Constants.PROPERTY_N);
+ if (mIsDoCoMo) {
+ if (shouldAppendCharsetParameterToName) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ builder.append(VCARD_DATA_SEPARATOR);
+ // DoCoMo phones require that all the elements in the "family name" field.
+ builder.append(formattedName);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ } else {
+ if (shouldAppendCharsetParameterToName) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedFamily);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedGiven);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedMiddle);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedPrefix);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedSuffix);
}
- if (reallyUseQuotedPrintableToName) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFamily);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedGiven);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedMiddle);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPrefix);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedSuffix);
- builder.append(VCARD_COL_SEPARATOR);
-
- final String fullname = VCardUtils.constructNameFromElements(
- VCardConfig.getNameOrderType(mVCardType),
- encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
- final boolean reallyUseQuotedPrintableToFullname =
- mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(fullname);
-
- final String encodedFullname =
- reallyUseQuotedPrintableToFullname ?
- encodeQuotedPrintable(fullname) :
- escapeCharacters(fullname);
+ builder.append(VCARD_END_OF_LINE);
// FN property
- builder.append(VCARD_PROPERTY_FULL_NAME);
- if (shouldAppendCharsetAttribute(encodedFullname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ builder.append(Constants.PROPERTY_FN);
+ if (shouldAppendCharsetParameterToFN) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
- if (reallyUseQuotedPrintableToFullname) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
+ if (reallyUseQuotedPrintableToFN) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
}
builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFullname);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(encodedFormattedname);
+ builder.append(VCARD_END_OF_LINE);
} else if (!TextUtils.isEmpty(displayName)) {
final boolean reallyUseQuotedPrintableToDisplayName =
- (mUsesQPToPrimaryProperties &&
+ (!mRefrainsQPToPrimaryProperties &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
final String encodedDisplayName =
reallyUseQuotedPrintableToDisplayName ?
encodeQuotedPrintable(displayName) :
escapeCharacters(displayName);
- builder.append(VCARD_PROPERTY_NAME);
- if (shouldAppendCharsetAttribute(encodedDisplayName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ builder.append(Constants.PROPERTY_N);
+ if (shouldAppendCharsetParameter(displayName)) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
if (reallyUseQuotedPrintableToDisplayName) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
}
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedDisplayName);
@@ -899,68 +849,85 @@
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ builder.append(VCARD_END_OF_LINE);
+ if (mIsV30) {
+ builder.append(Constants.PROPERTY_FN);
+ // TODO: Not allowed formally...
+ if (shouldAppendCharsetParameter(displayName)) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
+ }
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedDisplayName);
+ builder.append(VCARD_END_OF_LINE);
+ }
} else if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+ // vCard 3.0 specification requires these fields.
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
+ appendVCardLine(builder, Constants.PROPERTY_FN, "");
+ } else if (mIsDoCoMo) {
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
}
- String phoneticFamilyName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
- String phoneticMiddleName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
- String phoneticGivenName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
- if (!(TextUtils.isEmpty(phoneticFamilyName)
- && TextUtils.isEmpty(phoneticMiddleName) &&
- TextUtils.isEmpty(phoneticGivenName))) { // if not empty
- if (mIsJapaneseMobilePhone) {
- phoneticFamilyName = VCardUtils
- .toHalfWidthString(phoneticFamilyName);
- phoneticMiddleName = VCardUtils
- .toHalfWidthString(phoneticMiddleName);
- phoneticGivenName = VCardUtils
- .toHalfWidthString(phoneticGivenName);
+ final String phoneticFamilyName;
+ final String phoneticMiddleName;
+ final String phoneticGivenName;
+ {
+ String tmpPhoneticFamilyName =
+ primaryContentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ String tmpPhoneticMiddleName =
+ primaryContentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ String tmpPhoneticGivenName =
+ primaryContentValues.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 (mIsV30) {
final String sortString = VCardUtils
.constructNameFromElements(mVCardType,
- phoneticFamilyName,
- phoneticMiddleName,
- phoneticGivenName);
- builder.append(VCARD_PROPERTY_SORT_STRING);
+ phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+ builder.append(Constants.PROPERTY_SORT_STRING);
// Do not need to care about QP, since vCard 3.0 does not allow it.
final String encodedSortString = escapeCharacters(sortString);
- if (shouldAppendCharsetAttribute(encodedSortString)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ if (shouldAppendCharsetParameter(encodedSortString)) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedSortString);
- builder.append(VCARD_COL_SEPARATOR);
- } else {
+ builder.append(VCARD_END_OF_LINE);
+ } else if (mIsJapaneseMobilePhone) {
// Note: There is no appropriate property for expressing
// phonetic name in vCard 2.1, while there is in
// vCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way since it is supported by
+ // We chose to use DoCoMo's way when the device is Japanese one
+ // since it is supported by
// a lot of Japanese mobile phones. This is "X-" property, so
// any parser hopefully would not get confused with this.
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+ builder.append(Constants.PROPERTY_SOUND);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(Constants.PARAM_TYPE_X_IRMC_N);
boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticFamilyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticMiddleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticGivenName)));
+ (!mRefrainsQPToPrimaryProperties
+ && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticFamilyName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticMiddleName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticGivenName)));
final String encodedPhoneticFamilyName;
final String encodedPhoneticMiddleName;
@@ -975,11 +942,11 @@
encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
}
- if (shouldAppendCharsetAttribute(Arrays.asList(
+ if (shouldAppendCharsetParameters(Arrays.asList(
encodedPhoneticFamilyName, encodedPhoneticMiddleName,
encodedPhoneticGivenName))) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedPhoneticFamilyName);
@@ -989,89 +956,86 @@
builder.append(encodedPhoneticMiddleName);
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
} else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+ builder.append(Constants.PROPERTY_SOUND);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(Constants.PARAM_TYPE_X_IRMC_N);
builder.append(VCARD_DATA_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
if (mUsesDefactProperty) {
if (!TextUtils.isEmpty(phoneticGivenName)) {
final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName);
final String encodedPhoneticGivenName;
if (reallyUseQuotedPrintable) {
encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
} else {
encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
}
- builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticGivenName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ builder.append(Constants.PROPERTY_X_PHONETIC_FIRST_NAME);
+ if (shouldAppendCharsetParameter(encodedPhoneticGivenName)) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
}
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedPhoneticGivenName);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
if (!TextUtils.isEmpty(phoneticMiddleName)) {
final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName);
final String encodedPhoneticMiddleName;
if (reallyUseQuotedPrintable) {
encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
} else {
encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
}
- builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticMiddleName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ builder.append(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ if (shouldAppendCharsetParameter(encodedPhoneticMiddleName)) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
}
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedPhoneticMiddleName);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
if (!TextUtils.isEmpty(phoneticFamilyName)) {
final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName);
final String encodedPhoneticFamilyName;
if (reallyUseQuotedPrintable) {
encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
} else {
encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
}
- builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticFamilyName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ builder.append(Constants.PROPERTY_X_PHONETIC_LAST_NAME);
+ if (shouldAppendCharsetParameter(encodedPhoneticFamilyName)) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
}
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedPhoneticFamilyName);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
}
}
@@ -1080,45 +1044,31 @@
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Nickname.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- final String propertyNickname;
- if (mIsV30) {
- propertyNickname = VCARD_PROPERTY_NICKNAME;
- } else if (mUsesAndroidProperty) {
- propertyNickname = VCARD_PROPERTY_X_NICKNAME;
- } else {
- // There's no way to add this field.
- return;
+ if (contentValuesList == null) {
+ return;
+ }
+
+ final boolean useAndroidProperty;
+ if (mIsV30) {
+ useAndroidProperty = false;
+ } else if (mUsesAndroidProperty) {
+ useAndroidProperty = true;
+ } else {
+ // There's no way to add this field.
+ return;
+ }
+
+ for (ContentValues contentValues : contentValuesList) {
+ final String nickname = contentValues.getAsString(Nickname.NAME);
+ if (TextUtils.isEmpty(nickname)) {
+ continue;
}
-
- for (ContentValues contentValues : contentValuesList) {
- final String nickname = contentValues.getAsString(Nickname.NAME);
- if (TextUtils.isEmpty(nickname)) {
- continue;
- }
-
- final String encodedNickname;
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname));
- if (reallyUseQuotedPrintable) {
- encodedNickname = encodeQuotedPrintable(nickname);
- } else {
- encodedNickname = escapeCharacters(nickname);
- }
-
- builder.append(propertyNickname);
- if (shouldAppendCharsetAttribute(propertyNickname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedNickname);
- builder.append(VCARD_COL_SEPARATOR);
+ if (useAndroidProperty) {
+ appendAndroidSpecificProperty(builder, Nickname.CONTENT_ITEM_TYPE,
+ contentValues);
+ } else {
+ appendVCardLineWithCharsetAndQPDetection(builder,
+ Constants.PROPERTY_NICKNAME, nickname);
}
}
}
@@ -1133,6 +1083,9 @@
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();
@@ -1141,18 +1094,18 @@
continue;
}
phoneLineExists = true;
- int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
+ int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
// TODO: Premature, since this allows two phone numbers which are
// same from the view of phone number format (e.g. "100" v.s. "1-0-0")
if (!phoneSet.contains(phoneNumber)) {
phoneSet.add(phoneNumber);
- appendVCardTelephoneLine(builder, type, label, phoneNumber);
+ appendVCardTelephoneLine(builder, type, label, phoneNumber, isPrimary);
}
}
}
if (!phoneLineExists && mIsDoCoMo) {
- appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
+ appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "", false);
}
}
@@ -1160,14 +1113,11 @@
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Email.CONTENT_ITEM_TYPE);
+
boolean emailAddressExists = false;
if (contentValuesList != null) {
- Set<String> addressSet = new HashSet<String>();
+ final Set<String> addressSet = new HashSet<String>();
for (ContentValues contentValues : contentValuesList) {
- Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
- final int type = (typeAsObject != null ?
- typeAsObject : Email.TYPE_OTHER);
- final String label = contentValues.getAsString(Email.LABEL);
String emailAddress = contentValues.getAsString(Email.DATA);
if (emailAddress != null) {
emailAddress = emailAddress.trim();
@@ -1175,16 +1125,23 @@
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);
- appendVCardEmailLine(builder, type, label, emailAddress);
+ appendVCardEmailLine(builder, type, label, emailAddress, isPrimary);
}
}
}
if (!emailAddressExists && mIsDoCoMo) {
- appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
+ appendVCardEmailLine(builder, Email.TYPE_HOME, "", "", false);
}
}
@@ -1199,11 +1156,11 @@
appendPostalsForGeneric(builder, contentValuesList);
}
} else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_HOME);
+ builder.append(Constants.PROPERTY_ADR);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(Constants.PARAM_TYPE_HOME);
builder.append(VCARD_DATA_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
}
@@ -1241,7 +1198,10 @@
final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
final String label = contentValues.getAsString(StructuredPostal.LABEL);
if (type == preferedType) {
- appendVCardPostalLine(builder, type, label, contentValues);
+ // Note: Not sure why we need to emit "empty" line even when actual
+ // data does not exist. There may be some reason or may not.
+ // We keep safer side since the previous implementation did so.
+ appendVCardPostalLine(builder, type, label, contentValues, true, true);
return true;
}
}
@@ -1251,11 +1211,18 @@
private void appendPostalsForGeneric(final StringBuilder builder,
final List<ContentValues> contentValuesList) {
for (ContentValues contentValues : contentValuesList) {
- final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
- final String label = contentValues.getAsString(StructuredPostal.LABEL);
- if (type != null) {
- appendVCardPostalLine(builder, type, label, contentValues);
+ if (contentValues == null) {
+ continue;
}
+ final Integer typeAsObject = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : 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);
+ appendVCardPostalLine(builder, type, label, contentValues, isPrimary, false);
}
}
@@ -1263,24 +1230,63 @@
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Im.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- Integer protocol = contentValues.getAsInteger(Im.PROTOCOL);
- String data = contentValues.getAsString(Im.DATA);
- if (data != null) {
- data = data.trim();
- }
- if (TextUtils.isEmpty(data)) {
- continue;
- }
-
- if (protocol != null && protocol == Im.PROTOCOL_GOOGLE_TALK) {
- if (VCardConfig.usesAndroidSpecificProperty(mVCardType)) {
- appendVCardLine(builder, Constants.PROPERTY_X_GOOGLE_TALK, data);
+ if (contentValuesList == null) {
+ return;
+ }
+ 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 = Constants.PARAM_TYPE_HOME;
+ break;
}
- // TODO: add "X-GOOGLE TALK" case...
+ case Im.TYPE_WORK: {
+ typeAsString = Constants.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;
+ }
}
}
+
+ 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(Constants.PARAM_TYPE_PREF);
+ }
+
+ appendVCardLineWithCharsetAndQPDetection(
+ builder, propertyName, parameterList, data);
}
}
@@ -1288,39 +1294,84 @@
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Website.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- String website = contentValues.getAsString(Website.URL);
- if (website != null) {
- website = website.trim();
- }
- if (!TextUtils.isEmpty(website)) {
- appendVCardLine(builder, VCARD_PROPERTY_URL, website);
- }
+ if (contentValuesList == null) {
+ return;
+ }
+ 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.
+ //
+ // TODO: Should we allow adding it when appropriate?
+ // (Actually, we drop some data. Using "group.X-URL-TYPE" or something
+ // may help)
+ if (!TextUtils.isEmpty(website)) {
+ appendVCardLine(builder, Constants.PROPERTY_URL, website);
}
}
}
+ /**
+ * Theoretically, there must be only one birthday for each vCard entry.
+ * Also, we are afraid of some importer's parse error during its import.
+ * We emit only one birthday entry even when there are more than one.
+ */
private void appendBirthday(final StringBuilder builder,
final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Event.CONTENT_ITEM_TYPE);
- if (contentValuesList != null && contentValuesList.size() > 0) {
- Integer eventType = contentValuesList.get(0).getAsInteger(Event.TYPE);
+ final List<ContentValues> contentValuesList =
+ contentValuesListMap.get(Event.CONTENT_ITEM_TYPE);
+ if (contentValuesList == null) {
+ return;
+ }
+ String primaryBirthday = null;
+ String secondaryBirthday = null;
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer eventType = contentValues.getAsInteger(Event.TYPE);
if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
- return;
+ continue;
}
- // Theoretically, there must be only one birthday for each vCard data and
- // we are afraid of some parse error occuring in some devices, so
- // we emit only one birthday entry for now.
- String birthday = contentValuesList.get(0).getAsString(Event.START_DATE);
- if (birthday != null) {
- birthday = birthday.trim();
+ final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+ if (birthdayCandidate == null) {
+ continue;
}
- if (!TextUtils.isEmpty(birthday)) {
- appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
+ 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;
}
}
+
+ final String birthday;
+ if (primaryBirthday != null) {
+ birthday = primaryBirthday.trim();
+ } else if (secondaryBirthday != null){
+ birthday = secondaryBirthday.trim();
+ } else {
+ return;
+ }
+ appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_BDAY, birthday);
}
private void appendOrganizations(final StringBuilder builder,
@@ -1329,25 +1380,37 @@
.get(Organization.CONTENT_ITEM_TYPE);
if (contentValuesList != null) {
for (ContentValues contentValues : contentValuesList) {
- String company = contentValues
- .getAsString(Organization.COMPANY);
+ String company = contentValues.getAsString(Organization.COMPANY);
if (company != null) {
company = company.trim();
}
- String title = contentValues
- .getAsString(Organization.TITLE);
+ 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)) {
- appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
- !VCardUtils.containsOnlyPrintableAscii(company),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
+ orgBuilder.append(company);
}
+ if (!TextUtils.isEmpty(department)) {
+ if (orgBuilder.length() > 0) {
+ orgBuilder.append(';');
+ }
+ orgBuilder.append(department);
+ }
+ final String orgline = orgBuilder.toString();
+ appendVCardLine(builder, Constants.PROPERTY_ORG, orgline,
+ !VCardUtils.containsOnlyPrintableAscii(orgline),
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
if (!TextUtils.isEmpty(title)) {
- appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
+ appendVCardLine(builder, Constants.PROPERTY_TITLE, title,
!VCardUtils.containsOnlyPrintableAscii(title),
(mUsesQuotedPrintable &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
@@ -1374,11 +1437,9 @@
photoType = "GIF";
} else if (data.length >= 4 && data[0] == (byte) 0x89
&& data[1] == 'P' && data[2] == 'N' && data[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...
+ // 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...
photoType = "PNG";
} else if (data.length >= 2 && data[0] == (byte) 0xff
&& data[1] == (byte) 0xd8) {
@@ -1425,7 +1486,7 @@
final boolean reallyUseQuotedPrintable =
(mUsesQuotedPrintable &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+ appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
shouldAppendCharsetInfo, reallyUseQuotedPrintable);
} else {
for (ContentValues contentValues : contentValuesList) {
@@ -1436,7 +1497,7 @@
final boolean reallyUseQuotedPrintable =
(mUsesQuotedPrintable &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+ appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
shouldAppendCharsetInfo, reallyUseQuotedPrintable);
}
}
@@ -1444,6 +1505,33 @@
}
}
+ private void appendAndroidSpecificProperty(final StringBuilder builder,
+ final String mimeType, ContentValues contentValues) {
+ List<String> rawDataList = new ArrayList<String>();
+ rawDataList.add(mimeType);
+ final List<String> columnNameList;
+ if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
+
+ } else {
+ // If you add the other field, please check all the columns are able to be
+ // converted to String.
+ //
+ // e.g. BLOB is not what we can handle here now.
+ return;
+ }
+
+ for (int i = 1; i <= Constants.MAX_DATA_COLUMN; i++) {
+ String value = contentValues.getAsString("data" + i);
+ if (value == null) {
+ value = "";
+ }
+ rawDataList.add(value);
+ }
+
+ appendVCardLineWithCharsetAndQPDetection(builder,
+ Constants.PROPERTY_X_ANDROID_CUSTOM, rawDataList);
+ }
+
/**
* 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.
@@ -1459,7 +1547,7 @@
final StringBuilder tmpBuilder = new StringBuilder();
final int length = unescaped.length();
for (int i = 0; i < length; i++) {
- char ch = unescaped.charAt(i);
+ final char ch = unescaped.charAt(i);
switch (ch) {
case ';': {
tmpBuilder.append('\\');
@@ -1470,7 +1558,7 @@
if (i + 1 < length) {
char nextChar = unescaped.charAt(i);
if (nextChar == '\n') {
- continue;
+ break;
} else {
// fall through
}
@@ -1522,15 +1610,15 @@
private void appendVCardPhotoLine(final StringBuilder builder,
final String encodedData, final String photoType) {
StringBuilder tmpBuilder = new StringBuilder();
- tmpBuilder.append(VCARD_PROPERTY_PHOTO);
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
+ tmpBuilder.append(Constants.PROPERTY_PHOTO);
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
if (mIsV30) {
- tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
} else {
- tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
}
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(tmpBuilder, photoType);
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameter(tmpBuilder, photoType);
tmpBuilder.append(VCARD_DATA_SEPARATOR);
tmpBuilder.append(encodedData);
@@ -1542,69 +1630,136 @@
tmpBuilder.append(tmpStr.charAt(i));
lineCount++;
if (lineCount > 72) {
- tmpBuilder.append(VCARD_COL_SEPARATOR);
+ tmpBuilder.append(VCARD_END_OF_LINE);
tmpBuilder.append(VCARD_WS);
lineCount = 0;
}
}
builder.append(tmpBuilder.toString());
- builder.append(VCARD_COL_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
+ builder.append(VCARD_END_OF_LINE);
}
- private void appendVCardPostalLine(final StringBuilder builder,
- final Integer typeAsObject, final String label,
- final ContentValues contentValues) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
+ private 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;
+ }
+ }
- // Note: Not sure why we need to emit "empty" line even when actual data does not exist.
- // There may be some reason or may not be any. We keep safer side.
- // TODO: investigate this.
- boolean dataExists = false;
+ /**
+ * @return null when there's no information available to construct the data.
+ */
+ private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+ boolean reallyUseQuotedPrintable = false;
+ boolean appendCharset = false;
+
+ boolean dataArrayExists = false;
String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
- boolean actuallyUseQuotedPrintable = false;
- boolean shouldAppendCharset = false;
for (String data : dataArray) {
if (!TextUtils.isEmpty(data)) {
- dataExists = true;
- if (!shouldAppendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
- shouldAppendCharset = true;
+ dataArrayExists = true;
+ if (!appendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
+ appendCharset = true;
}
if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
- actuallyUseQuotedPrintable = true;
+ reallyUseQuotedPrintable = true;
break;
}
}
}
- int length = dataArray.length;
- for (int i = 0; i < length; i++) {
- String data = dataArray[i];
- if (!TextUtils.isEmpty(data)) {
- if (actuallyUseQuotedPrintable) {
- dataArray[i] = encodeQuotedPrintable(data);
+ if (dataArrayExists) {
+ StringBuffer addressBuffer = new StringBuffer();
+ boolean first = true;
+ for (String data : dataArray) {
+ if (first) {
+ first = false;
} else {
- dataArray[i] = escapeCharacters(data);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
}
+ if (!TextUtils.isEmpty(data)) {
+ if (reallyUseQuotedPrintable) {
+ addressBuffer.append(encodeQuotedPrintable(data));
+ } else {
+ addressBuffer.append(escapeCharacters(data));
+ }
+ }
+ }
+ return new PostalStruct(reallyUseQuotedPrintable, appendCharset,
+ addressBuffer.toString());
+ }
+
+ String formattedAddress =
+ contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+ if (!TextUtils.isEmpty(formattedAddress)) {
+ reallyUseQuotedPrintable =
+ !VCardUtils.containsOnlyPrintableAscii(formattedAddress);
+ appendCharset =
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedAddress);
+ if (reallyUseQuotedPrintable) {
+ formattedAddress = encodeQuotedPrintable(formattedAddress);
+ } else {
+ formattedAddress = escapeCharacters(formattedAddress);
+ }
+ // We use the second value ("Extended Address").
+ //
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ StringBuffer addressBuffer = new StringBuffer();
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(formattedAddress);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ }
+ return null; // There's no data available.
+ }
+
+ private void appendVCardPostalLine(final StringBuilder builder,
+ final int type, final String label, final ContentValues contentValues,
+ final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressData;
+ {
+ PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+ if (postalStruct == null) {
+ if (emitLineEveryTime) {
+ reallyUseQuotedPrintable = false;
+ appendCharset = false;
+ addressData = "";
+ } else {
+ return;
+ }
+ } else {
+ reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+ appendCharset = postalStruct.appendCharset;
+ addressData = postalStruct.addressData;
}
}
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = StructuredPostal.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
+ List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(Constants.PARAM_TYPE_PREF);
}
-
- String typeAsString = null;
- switch (typeAsPrimitive) {
+ switch (type) {
case StructuredPostal.TYPE_HOME: {
- typeAsString = Constants.ATTR_TYPE_HOME;
+ parameterList.add(Constants.PARAM_TYPE_HOME);
break;
}
case StructuredPostal.TYPE_WORK: {
- typeAsString = Constants.ATTR_TYPE_WORK;
+ parameterList.add(Constants.PARAM_TYPE_WORK);
break;
}
case StructuredPostal.TYPE_CUSTOM: {
@@ -1614,9 +1769,7 @@
// ("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.
- builder.append("X-");
- builder.append(label);
- builder.append(VCARD_DATA_SEPARATOR);
+ parameterList.add("X-" + label);
}
break;
}
@@ -1624,132 +1777,111 @@
break;
}
default: {
- Log.e(LOG_TAG, "Unknown StructuredPostal type: " + typeAsPrimitive);
+ Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
break;
}
}
- // Attribute(s).
+ // Actual data construction starts from here.
+ // TODO: add a new version of appendVCardLine() for this purpose.
+
+ builder.append(Constants.PROPERTY_ADR);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ // Parameters
{
- boolean shouldAppendAttrSeparator = false;
- if (typeAsString != null) {
- appendTypeAttribute(builder, typeAsString);
- shouldAppendAttrSeparator = true;
+ boolean shouldAppendParamSeparator = false;
+ if (!parameterList.isEmpty()) {
+ appendTypeParameters(builder, parameterList);
+ shouldAppendParamSeparator = true;
}
- if (dataExists) {
- if (shouldAppendCharset) {
- // 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 attribute in vCard 3.0.
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(mVCardAttributeCharset);
- shouldAppendAttrSeparator = true;
+ 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.
+ if (shouldAppendParamSeparator) {
+ builder.append(VCARD_PARAM_SEPARATOR);
}
+ builder.append(mVCardCharsetParameter);
+ shouldAppendParamSeparator = true;
+ }
- if (actuallyUseQuotedPrintable) {
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(VCARD_ATTR_ENCODING_QP);
- shouldAppendAttrSeparator = true;
+ if (reallyUseQuotedPrintable) {
+ if (shouldAppendParamSeparator) {
+ builder.append(VCARD_PARAM_SEPARATOR);
}
+ builder.append(VCARD_PARAM_ENCODING_QP);
+ shouldAppendParamSeparator = true;
}
}
- // Property values.
-
builder.append(VCARD_DATA_SEPARATOR);
- if (dataExists) {
- // The elements in dataArray are already encoded to quoted printable
- // if needed.
- // See above.
- //
- // TODO: in vCard 3.0, one line may become too huge. Fix this.
- builder.append(dataArray[0]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[1]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[2]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[3]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[4]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[5]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[6]);
- }
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(addressData);
+ builder.append(VCARD_END_OF_LINE);
}
private void appendVCardEmailLine(final StringBuilder builder,
- final Integer typeAsObject, final String label, final String data) {
- builder.append(VCARD_PROPERTY_EMAIL);
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = Email.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
+ final int type, final String label,
+ final String rawData, final boolean isPrimary) {
final String typeAsString;
- switch (typeAsPrimitive) {
+ switch (type) {
case Email.TYPE_CUSTOM: {
// 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.
if (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME
.equals(label)) {
- typeAsString = Constants.ATTR_TYPE_CELL;
+ typeAsString = Constants.PARAM_TYPE_CELL;
} else if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
&& VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
typeAsString = "X-" + label;
} else {
- typeAsString = DEFAULT_EMAIL_TYPE;
+ typeAsString = null;
}
break;
}
case Email.TYPE_HOME: {
- typeAsString = Constants.ATTR_TYPE_HOME;
+ typeAsString = Constants.PARAM_TYPE_HOME;
break;
}
case Email.TYPE_WORK: {
- typeAsString = Constants.ATTR_TYPE_WORK;
+ typeAsString = Constants.PARAM_TYPE_WORK;
break;
}
case Email.TYPE_OTHER: {
- typeAsString = DEFAULT_EMAIL_TYPE;
+ typeAsString = null;
break;
}
case Email.TYPE_MOBILE: {
- typeAsString = Constants.ATTR_TYPE_CELL;
+ typeAsString = Constants.PARAM_TYPE_CELL;
break;
}
default: {
- Log.e(LOG_TAG, "Unknown Email type: " + typeAsPrimitive);
- typeAsString = DEFAULT_EMAIL_TYPE;
+ Log.e(LOG_TAG, "Unknown Email type: " + type);
+ typeAsString = null;
break;
}
}
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, typeAsString);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(data);
- builder.append(VCARD_COL_SEPARATOR);
+ final List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(Constants.PARAM_TYPE_PREF);
+ }
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+
+ appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_EMAIL,
+ parameterList, rawData);
}
private void appendVCardTelephoneLine(final StringBuilder builder,
final Integer typeAsObject, final String label,
- String encodedData) {
- builder.append(VCARD_PROPERTY_TEL);
- builder.append(VCARD_ATTR_SEPARATOR);
+ final String encodedData, boolean isPrimary) {
+ builder.append(Constants.PROPERTY_TEL);
+ builder.append(VCARD_PARAM_SEPARATOR);
final int typeAsPrimitive;
if (typeAsObject == null) {
@@ -1758,56 +1890,105 @@
typeAsPrimitive = typeAsObject;
}
+ ArrayList<String> parameterList = new ArrayList<String>();
switch (typeAsPrimitive) {
case Phone.TYPE_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
+ parameterList.addAll(
+ Arrays.asList(Constants.PARAM_TYPE_HOME));
break;
case Phone.TYPE_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
+ parameterList.addAll(
+ Arrays.asList(Constants.PARAM_TYPE_WORK));
break;
case Phone.TYPE_FAX_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
+ parameterList.addAll(
+ Arrays.asList(Constants.PARAM_TYPE_HOME, Constants.PARAM_TYPE_FAX));
break;
case Phone.TYPE_FAX_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
+ parameterList.addAll(
+ Arrays.asList(Constants.PARAM_TYPE_WORK, Constants.PARAM_TYPE_FAX));
break;
case Phone.TYPE_MOBILE:
- builder.append(Constants.ATTR_TYPE_CELL);
+ parameterList.add(Constants.PARAM_TYPE_CELL);
break;
case Phone.TYPE_PAGER:
if (mIsDoCoMo) {
// Not sure about the reason, but previous implementation had
// used "VOICE" instead of "PAGER"
- // Also, refrain from using appendType() so that "TYPE=" is never be appended.
- builder.append(Constants.ATTR_TYPE_VOICE);
+ parameterList.add(Constants.PARAM_TYPE_VOICE);
} else {
- appendTypeAttribute(builder, Constants.ATTR_TYPE_PAGER);
+ parameterList.add(Constants.PARAM_TYPE_PAGER);
}
break;
case Phone.TYPE_OTHER:
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
+ parameterList.add(Constants.PARAM_TYPE_VOICE);
+ break;
+ case Phone.TYPE_CAR:
+ parameterList.add(Constants.PARAM_TYPE_CAR);
+ break;
+ case Phone.TYPE_COMPANY_MAIN:
+ // There's no relevant field in vCard (at least 2.1).
+ parameterList.add(Constants.PARAM_TYPE_WORK);
+ isPrimary = true;
+ break;
+ case Phone.TYPE_ISDN:
+ parameterList.add(Constants.PARAM_TYPE_ISDN);
+ break;
+ case Phone.TYPE_MAIN:
+ isPrimary = true;
+ break;
+ case Phone.TYPE_OTHER_FAX:
+ parameterList.add(Constants.PARAM_TYPE_FAX);
+ break;
+ case Phone.TYPE_TELEX:
+ parameterList.add(Constants.PARAM_TYPE_TLX);
+ break;
+ case Phone.TYPE_WORK_MOBILE:
+ parameterList.addAll(
+ Arrays.asList(Constants.PARAM_TYPE_WORK, Constants.PARAM_TYPE_CELL));
+ break;
+ case Phone.TYPE_WORK_PAGER:
+ parameterList.add(Constants.PARAM_TYPE_WORK);
+ // See above.
+ if (mIsDoCoMo) {
+ parameterList.add(Constants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(Constants.PARAM_TYPE_PAGER);
+ }
+ break;
+ case Phone.TYPE_MMS:
+ parameterList.add(Constants.PARAM_TYPE_MSG);
break;
case Phone.TYPE_CUSTOM:
if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
&& VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- appendTypeAttribute(builder, "X-" + label);
+ // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
+ // "TYPE=" string.
+ parameterList.add("X-" + label);
} else {
// Just ignore the custom type.
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
+ parameterList.add(Constants.PARAM_TYPE_VOICE);
}
break;
+ case Phone.TYPE_RADIO:
+ case Phone.TYPE_TTY_TDD:
default:
- appendUncommonPhoneType(builder, typeAsPrimitive);
break;
}
+ if (isPrimary) {
+ parameterList.add(Constants.PARAM_TYPE_PREF);
+ }
+
+ if (parameterList.isEmpty()) {
+ appendUncommonPhoneType(builder, typeAsPrimitive);
+ } else {
+ appendTypeParameters(builder, parameterList);
+ }
+
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedData);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
/**
@@ -1817,35 +1998,65 @@
if (mIsDoCoMo) {
// The previous implementation for DoCoMo had been conservative
// about miscellaneous types.
- builder.append(Constants.ATTR_TYPE_VOICE);
+ builder.append(Constants.PARAM_TYPE_VOICE);
} else {
- String phoneAttribute = VCardUtils.getPhoneAttributeString(type);
- if (phoneAttribute != null) {
- appendTypeAttribute(builder, phoneAttribute);
+ String phoneType = VCardUtils.getPhoneTypeString(type);
+ if (phoneType != null) {
+ appendTypeParameter(builder, phoneType);
} else {
Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
}
}
}
+ // appendVCardLine() variants accepting one String.
+
+ private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+ final String propertyName, final String rawData) {
+ appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawData);
+ }
+
+ private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+ final String propertyName,
+ final List<String> parameterList, final String rawData) {
+ final boolean needCharset =
+ (mUsesQuotedPrintable && !VCardUtils.containsOnlyPrintableAscii(rawData));
+ final boolean reallyUseQuotedPrintable =
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData);
+ appendVCardLine(builder, propertyName, parameterList,
+ rawData, needCharset, reallyUseQuotedPrintable);
+ }
+
private void appendVCardLine(final StringBuilder builder,
final String propertyName, final String rawData) {
appendVCardLine(builder, propertyName, rawData, false, false);
}
private void appendVCardLine(final StringBuilder builder,
- final String field, final String rawData, final boolean needCharset,
+ final String propertyName, final String rawData, final boolean needCharset,
boolean needQuotedPrintable) {
- builder.append(field);
+ appendVCardLine(builder, propertyName, null, rawData, needCharset, needQuotedPrintable);
+ }
+
+ private void appendVCardLine(final StringBuilder builder,
+ final String propertyName,
+ final List<String> parameterList,
+ final String rawData, final boolean needCharset,
+ boolean needQuotedPrintable) {
+ builder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(builder, parameterList);
+ }
if (needCharset) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
}
final String encodedData;
if (needQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
encodedData = encodeQuotedPrintable(rawData);
} else {
// TODO: one line may be too huge, which may be invalid in vCard spec, though
@@ -1855,10 +2066,94 @@
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedData);
- builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_END_OF_LINE);
}
- private void appendTypeAttributes(final StringBuilder builder,
+ // appendVCardLine() variants accepting List<String>.
+
+ private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+ final String propertyName, final List<String> rawDataList) {
+ appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawDataList);
+ }
+
+ private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+ final String propertyName,
+ final List<String> parameterList, final List<String> rawDataList) {
+ boolean needCharset = false;
+ boolean reallyUseQuotedPrintable = false;
+ for (String rawData : rawDataList) {
+ if (!needCharset && mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyPrintableAscii(rawData)) {
+ needCharset = true;
+ }
+ if (!reallyUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData)) {
+ reallyUseQuotedPrintable = true;
+ }
+ if (needCharset && reallyUseQuotedPrintable) {
+ break;
+ }
+ }
+
+ appendVCardLine(builder, propertyName, parameterList,
+ rawDataList, needCharset, reallyUseQuotedPrintable);
+ }
+
+ /*
+ private void appendVCardLine(final StringBuilder builder,
+ final String propertyName, final List<String> rawDataList) {
+ appendVCardLine(builder, propertyName, rawDataList, false, false);
+ }
+
+ private void appendVCardLine(final StringBuilder builder,
+ final String propertyName, final List<String> rawDataList,
+ final boolean needCharset, boolean needQuotedPrintable) {
+ appendVCardLine(builder, propertyName, null, rawDataList, needCharset, needQuotedPrintable);
+ }*/
+
+ private void appendVCardLine(final StringBuilder builder,
+ final String propertyName,
+ final List<String> parameterList,
+ final List<String> rawDataList, final boolean needCharset,
+ boolean needQuotedPrintable) {
+ builder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(builder, parameterList);
+ }
+ if (needCharset) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(mVCardCharsetParameter);
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ boolean first = true;
+ for (String rawData : rawDataList) {
+ final String encodedData;
+ if (needQuotedPrintable) {
+ builder.append(VCARD_PARAM_SEPARATOR);
+ builder.append(VCARD_PARAM_ENCODING_QP);
+ encodedData = encodeQuotedPrintable(rawData);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard spec, though
+ // several (even well-known) applications do not care this.
+ encodedData = escapeCharacters(rawData);
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ builder.append(VCARD_ITEM_SEPARATOR);
+ }
+ builder.append(encodedData);
+ }
+ builder.append(VCARD_END_OF_LINE);
+ }
+
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ private void appendTypeParameters(final StringBuilder builder,
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.
@@ -1867,47 +2162,54 @@
if (first) {
first = false;
} else {
- builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(VCARD_PARAM_SEPARATOR);
}
- appendTypeAttribute(builder, type);
+ appendTypeParameter(builder, type);
}
}
- private void appendTypeAttribute(final StringBuilder builder, final String type) {
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ 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) {
- builder.append(Constants.ATTR_TYPE).append(VCARD_ATTR_EQUAL);
+ if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
+ builder.append(Constants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
}
builder.append(type);
}
/**
- * Returns true when the property line should contain charset attribute
+ * 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 used charset is not UTF-8
+ * 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...
+ * 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 shouldAppendCharsetAttribute(final String propertyValue) {
+ private boolean shouldAppendCharsetParameter(final String propertyValue) {
return (!VCardUtils.containsOnlyPrintableAscii(propertyValue) &&
(!mIsV30 || !mUsesUtf8));
}
- private boolean shouldAppendCharsetAttribute(final List<String> propertyValueList) {
- boolean shouldAppendBasically = false;
+ private boolean shouldAppendCharsetParameters(final List<String> propertyValueList) {
+ if (mIsV30 && mUsesUtf8) {
+ return false;
+ }
for (String propertyValue : propertyValueList) {
if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
- shouldAppendBasically = true;
- break;
+ return true;
}
}
- return shouldAppendBasically && (!mIsV30 || !mUsesUtf8);
+ return false;
}
private String encodeQuotedPrintable(String str) {
@@ -1966,4 +2268,116 @@
return tmpBuilder.toString();
}
+
+ //// The methods bellow are for call log history ////
+
+ /**
+ * This static function is to compose vCard for phone own number
+ */
+ public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
+ String phoneNumber, boolean vcardVer21) {
+ final StringBuilder builder = new StringBuilder();
+ appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (!vcardVer21) {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+ } else {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+ }
+
+ boolean needCharset = false;
+ if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
+ needCharset = true;
+ }
+ appendVCardLine(builder, Constants.PROPERTY_FN, phoneName, needCharset, false);
+ appendVCardLine(builder, Constants.PROPERTY_N, phoneName, needCharset, false);
+
+ String label = Integer.toString(phonetype);
+ appendVCardTelephoneLine(builder, phonetype, label, phoneNumber, false);
+
+ appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+
+ return builder.toString();
+ }
+
+ /**
+ * Format according to RFC 2445 DATETIME type.
+ * The format is: ("%Y%m%dT%H%M%SZ").
+ */
+ private final String toRfc2455Format(final long millSecs) {
+ Time startDate = new Time();
+ startDate.set(millSecs);
+ String date = startDate.format2445();
+ return date + FLAG_TIMEZONE_UTC;
+ }
+
+ /**
+ * Try to append the property line for a call history time stamp field if possible.
+ * Do nothing if the call log type gotton from the database is invalid.
+ */
+ private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
+ // Extension for call history as defined in
+ // in the Specification for Ic Mobile Communcation - ver 1.1,
+ // Oct 2000. This is used to send the details of the call
+ // history - missed, incoming, outgoing along with date and time
+ // to the requesting device (For example, transferring phone book
+ // when connected over bluetooth)
+ //
+ // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
+ final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
+ final String callLogTypeStr;
+ switch (callLogType) {
+ case Calls.INCOMING_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
+ break;
+ }
+ case Calls.OUTGOING_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
+ break;
+ }
+ case Calls.MISSED_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
+ break;
+ }
+ default: {
+ Log.w(LOG_TAG, "Call log type not correct.");
+ return;
+ }
+ }
+
+ final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
+ builder.append(VCARD_PROPERTY_X_TIMESTAMP);
+ builder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameter(builder, callLogTypeStr);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(toRfc2455Format(dateAsLong));
+ builder.append(VCARD_END_OF_LINE);
+ }
+
+ private String createOneCallLogEntryInternal() {
+ final StringBuilder builder = new StringBuilder();
+ appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (mIsV30) {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+ } else {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+ }
+ String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
+ if (TextUtils.isEmpty(name)) {
+ name = mCursor.getString(NUMBER_COLUMN_INDEX);
+ }
+ final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
+ appendVCardLine(builder, Constants.PROPERTY_FN, name, needCharset, false);
+ appendVCardLine(builder, Constants.PROPERTY_N, name, needCharset, false);
+
+ String number = mCursor.getString(NUMBER_COLUMN_INDEX);
+ int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+ String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+ if (TextUtils.isEmpty(label)) {
+ label = Integer.toString(type);
+ }
+ appendVCardTelephoneLine(builder, type, label, number, false);
+ tryAppendCallHistoryTimeStampField(builder);
+ appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+ return builder.toString();
+ }
}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..545c09b 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,14 +15,20 @@
*/
package android.pim.vcard;
+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 = "vcard.VCardConfig";
+
// TODO: may be better to make the instance of this available and stop using static methods and
// one integer.
@@ -41,8 +47,8 @@
// TODO: make the other codes use this flag
public static final boolean IGNORE_CASE_EXCEPT_VALUE = true;
- private static final int FLAG_V21 = 0;
- private static final int FLAG_V30 = 1;
+ public static final int FLAG_V21 = 0;
+ public static final int FLAG_V30 = 1;
// 0x2 is reserved for the future use ...
@@ -95,96 +101,187 @@
private static final int FLAG_DOCOMO = 0x20000000;
/**
- * The flag indicating the vCard composer use Quoted-Printable toward even "primary" types.
- * In this context, "primary" types means "N", "FN", etc. which are usually "not" encoded
- * into Quoted-Printable format in external exporters.
- * This flag is useful when some target importer does not accept "primary" property values
- * without Quoted-Printable encoding.
- *
- * @hide Temporaly made public. We don't strictly define "primary", so we may change the
- * behavior around this flag in the future. Do not use this flag without any reason.
+ * <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 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 "primary" property values with Quoted-Printable encoding.
+ * </P>
+ * <P>
+ * Again, we should not use this flag at all for complying vCard 2.1 spec.
+ * </P>
+ * <P>
+ * We will change the behavior around this flag in the future, after understanding the other
+ * real vCard cases around this problem. Please use this flag with extreme caution even when
+ * needed.
+ * </P>
+ * <P>
+ * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
+ * kind of problem (hopefully).
+ * </P>
*/
- public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
-
- // VCard types
+ public static final int FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
/**
- * General vCard format with the version 2.1. Uses UTF-8 for the charset.
- * When composing a vCard entry, the US convension will be used.
- *
- * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
- * while in Japan, it should be "Prefix Family Middle Given Suffix".
+ * <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 VCARD_TYPE_V21_GENERIC =
+ public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+
+ /**
+ * <P>
+ * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string every time
+ * possible. The default behavior does not emit it and is valid, while adding "TYPE="
+ * is also valid. In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in
+ * vCard 3.0 specification.
+ *
+ * If you are targeting to some importer which cannot accept type parameters
+ * without "TYPE=" string (which should be rare though), please use this flag.
+ *
+ * XXX: Really rare?
+ *
+ * e.g. int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
+ */
+ public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+
+ //// The followings are VCard types available from importer/exporter. ////
+
+ /**
+ * <P>
+ * General vCard format with the version 2.1. Uses UTF-8 for the charset.
+ * 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.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
(FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
+ /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
/**
+ * <P>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
- *
- * Note that this type is not fully implemented, so probably some bugs remain both in
- * parsing and composing.
- *
- * TODO: implement this type correctly.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_GENERIC =
+ public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
(FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
/**
- * General vCard format with the version 2.1 with some Europe convension. Uses Utf-8.
+ * <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 =
+ public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
(FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_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 =
+ public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
(FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
-
- /**
- * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
- * parsing/composing the vCard data.
- */
- public static final int VCARD_TYPE_V21_JAPANESE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese";
-
/**
- * vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <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_UTF8 =
(FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+
+ /**
+ * <P>
+ * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
+ * parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
/**
+ * <P>
* vCard format for miscellaneous Japanese devices, using Shift_Jis for
* parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_JAPANESE =
+ public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese";
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
/**
- * vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <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_UTF8 =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
@@ -193,38 +290,72 @@
/* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
/**
- * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
- * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
- * No Android-specific property nor defact property is included.
+ * <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.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS |
+ FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES);
+
+ public static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
+
+ /**
+ * <P>
+ * 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>
*/
public static final int VCARD_TYPE_DOCOMO =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | FLAG_DOCOMO);
+ (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
private static final String VCARD_TYPE_DOCOMO_STR = "docomo";
- public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
- private static final Map<String, Integer> VCARD_TYPES_MAP;
+ private static final Map<String, Integer> sVCardTypeMap;
+ private static final Set<Integer> sJapaneseMobileTypeSet;
static {
- VCARD_TYPES_MAP = new HashMap<String, Integer>();
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+ sVCardTypeMap = new HashMap<String, Integer>();
+ sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+ 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_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
public static int getVCardTypeFromString(String vcardTypeString) {
String loweredKey = vcardTypeString.toLowerCase();
- if (VCARD_TYPES_MAP.containsKey(loweredKey)) {
- return VCARD_TYPES_MAP.get(loweredKey);
+ if (sVCardTypeMap.containsKey(loweredKey)) {
+ return sVCardTypeMap.get(loweredKey);
} else {
// XXX: should return the value indicating the input is invalid?
+ Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
return VCARD_TYPE_DEFAULT;
}
}
@@ -237,22 +368,6 @@
return !isV30(vcardType);
}
- public static boolean isDoCoMo(int vcardType) {
- return ((vcardType & FLAG_DOCOMO) != 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(int vcardType) {
- return ((vcardType == VCARD_TYPE_V21_JAPANESE) ||
- (vcardType == VCARD_TYPE_V21_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_DOCOMO));
- }
-
public static boolean usesUtf8(int vcardType) {
return ((vcardType & FLAG_CHARSET_UTF8) != 0);
}
@@ -261,17 +376,6 @@
return ((vcardType & FLAG_CHARSET_SHIFT_JIS) != 0);
}
- /**
- * @return true when Japanese phonetic string must be converted to a string
- * containing only half-width katakana. This method exists since Japanese mobile
- * phones usually use only half-width katakana for expressing phonetic names and
- * some devices are not ready for parsing other phonetic strings like hiragana and
- * full-width katakana.
- */
- public static boolean needsToConvertPhoneticString(int vcardType) {
- return (vcardType == VCARD_TYPE_DOCOMO);
- }
-
public static int getNameOrderType(int vcardType) {
return vcardType & NAME_ORDER_MASK;
}
@@ -284,20 +388,37 @@
return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
}
- public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
- return vcardType == VCARD_TYPE_DOCOMO;
- }
-
public static boolean showPerformanceLog() {
return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
}
+ public static boolean refrainsQPToPrimaryProperties(int vcardType) {
+ return (!usesQuotedPrintable(vcardType) ||
+ ((vcardType & FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES) != 0));
+ }
+
+ public static boolean appendTypeParamName(int vcardType) {
+ return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
+ }
+
/**
- * @hide
+ * @return true if the device is Japanese and some Japanese convension is
+ * applied to creating "formatted" something like FORMATTED_ADDRESS.
*/
- public static boolean usesQPToPrimaryProperties(int vcardType) {
- return (usesQuotedPrintable(vcardType) &&
- ((vcardType & FLAG_USE_QP_TO_PRIMARY_PROPERTIES) != 0));
+ public static boolean isJapaneseDevice(int vcardType) {
+ return sJapaneseMobileTypeSet.contains(vcardType);
+ }
+
+ public static boolean needsToConvertPhoneticString(int vcardType) {
+ return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
+ }
+
+ public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
+ return vcardType == VCARD_TYPE_DOCOMO;
+ }
+
+ public static boolean isDoCoMo(int vcardType) {
+ return ((vcardType & FLAG_DOCOMO) != 0);
}
private VCardConfig() {
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardDataBuilder.java
index d2026d0..76ad482 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardDataBuilder.java
@@ -69,7 +69,7 @@
private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
public VCardDataBuilder() {
- this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+ this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
}
/**
@@ -86,7 +86,7 @@
boolean strictLineBreakParsing, int vcardType, Account account) {
this(null, charset, strictLineBreakParsing, vcardType, account);
}
-
+
/**
* @hide
*/
@@ -127,6 +127,18 @@
}
/**
+ * Called when the parse failed between startRecord() and endRecord().
+ * Currently it happens only when the vCard format is 3.0.
+ * (VCardVersionException is thrown by VCardParser_V21 and this object is reused by
+ * VCardParser_V30. At that time, startRecord() is called twice before endRecord() is called.)
+ * TODO: Should this be in VCardBuilder interface?
+ */
+ public void clear() {
+ mCurrentContactStruct = null;
+ mCurrentProperty = new ContactStruct.Property();
+ }
+
+ /**
* Assume that VCard is not nested. In other words, this code does not accept
*/
public void startRecord(String type) {
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index b5e5049..0ffdb6c 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -35,7 +35,7 @@
*
* In order to avoid "misunderstanding" charset as much as possible, this method
* use "ISO-8859-1" for reading the stream. When charset is specified in some property
- * (with "CHARSET=..." attribute), the string is decoded to raw bytes and encoded to
+ * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
* the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
* characters, which is not completely sure. In some cases, this "decoding-encoding"
* scheme may fail. To avoid the case,
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 974fca8..b3ff8fa 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -144,10 +144,14 @@
}
}
- protected String getVersion() {
- return "2.1";
+ protected int getVersion() {
+ return VCardConfig.FLAG_V21;
}
-
+
+ protected String getVersionString() {
+ return Constants.VERSION_V21;
+ }
+
/**
* @return true when the propertyName is a valid property name.
*/
@@ -356,7 +360,7 @@
* / [groups "."] "ADR" [params] ":" addressparts CRLF
* / [groups "."] "ORG" [params] ":" orgparts CRLF
* / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
+ * / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
@@ -392,9 +396,10 @@
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
- } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
+ } else if (propertyName.equals("VERSION") &&
+ !propertyValue.equals(getVersionString())) {
throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersion());
+ propertyValue + " != " + getVersionString());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
@@ -520,11 +525,19 @@
throw new VCardException("Unknown type \"" + paramName + "\"");
}
} else {
- handleType(strArray[0]);
+ handleParamWithoutName(strArray[0]);
}
}
/**
+ * vCard 3.0 parser may throw VCardException.
+ */
+ @SuppressWarnings("unused")
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ handleType(paramValue);
+ }
+
+ /**
* ptypeval = knowntype / "X-" word
*/
protected void handleType(String ptypeval) {
@@ -761,32 +774,11 @@
}
if (mBuilder != null) {
- StringBuilder builder = new StringBuilder();
- ArrayList<String> list = new ArrayList<String>();
- int length = propertyValue.length();
- for (int i = 0; i < length; i++) {
- char ch = propertyValue.charAt(i);
- if (ch == '\\' && i < length - 1) {
- char nextCh = propertyValue.charAt(i + 1);
- String unescapedString = maybeUnescapeCharacter(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());
- mBuilder.propertyValues(list);
+ mBuilder.propertyValues(VCardUtils.constructListFromValue(
+ propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
}
}
-
+
/**
* vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
*
@@ -819,12 +811,16 @@
protected String maybeUnescapeText(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(char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(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.
@@ -844,6 +840,9 @@
@Override
public boolean parse(InputStream is, String charset, VCardBuilder builder)
throws IOException, VCardException {
+ if (charset == null) {
+ charset = VCardConfig.DEFAULT_CHARSET;
+ }
final InputStreamReader tmpReader = new InputStreamReader(is, charset);
if (VCardConfig.showPerformanceLog()) {
mReader = new CustomBufferedReader(tmpReader);
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..86e7625 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -46,14 +46,42 @@
private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
private String mPreviousLine;
-
+
private boolean mEmittedAgentWarning = false;
-
+
+ /**
+ * True when the caller wants the parser to be strict about the input.
+ * Currently this is only for testing.
+ */
+ private final boolean mStrictParsing;
+
+ public VCardParser_V30() {
+ super();
+ mStrictParsing = false;
+ }
+
+ /**
+ * @param strictParsing when true, this object throws VCardException when the vcard is not
+ * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
+ * is not fully yet for being used with this flag and may not notice invalid line(s).
+ *
+ * @hide currently only for testing!
+ */
+ public VCardParser_V30(boolean strictParsing) {
+ super();
+ mStrictParsing = strictParsing;
+ }
+
@Override
- protected String getVersion() {
+ protected int getVersion() {
+ return VCardConfig.FLAG_V30;
+ }
+
+ @Override
+ protected String getVersionString() {
return Constants.VERSION_V30;
}
-
+
@Override
protected boolean isValidPropertyName(String propertyName) {
if (!(sAcceptablePropsWithParam.contains(propertyName) ||
@@ -199,7 +227,16 @@
// TODO: fix this.
super.handleAnyParam(paramName, paramValue);
}
-
+
+ @Override
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ if (mStrictParsing) {
+ throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
+ } else {
+ super.handleParamWithoutName(paramValue);
+ }
+ }
+
/**
* vCard 3.0 defines
*
@@ -284,6 +321,10 @@
*/
@Override
protected String maybeUnescapeText(String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(String text) {
StringBuilder builder = new StringBuilder();
int length = text.length();
for (int i = 0; i < length; i++) {
@@ -299,15 +340,19 @@
builder.append(ch);
}
}
- return builder.toString();
+ return builder.toString();
}
@Override
protected String maybeUnescapeCharacter(char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
- }
+ }
}
}
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 4f50103..05e6730 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -18,13 +18,16 @@
import android.content.ContentProviderOperation;
import android.content.ContentValues;
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.text.TextUtils;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -37,53 +40,69 @@
*/
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
- // converted to two attribute Strings. These only contain some minor fields valid in both
+ // 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> sPhoneTypesSetUnknownToContacts;
-
- private static final Map<String, Integer> sKnownPhoneTypesMap_StoI;
-
+
+ private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
+
+ private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
+
static {
sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
- sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>();
+ sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.PARAM_TYPE_CAR);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.PARAM_TYPE_PAGER);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.PARAM_TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK);
- sKnownPhoneTypesMap_StoI.put(
- Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_CALLBACK, Phone.TYPE_CALLBACK);
+ sKnownPhoneTypeMap_StoI.put(
+ Constants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD, Phone.TYPE_TTY_TDD);
+ sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
+ Phone.TYPE_ASSISTANT);
sPhoneTypesSetUnknownToContacts = new HashSet<String>();
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MSG);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
+ sPhoneTypesSetUnknownToContacts.add(Constants.PARAM_TYPE_MODEM);
+ sPhoneTypesSetUnknownToContacts.add(Constants.PARAM_TYPE_BBS);
+ sPhoneTypesSetUnknownToContacts.add(Constants.PARAM_TYPE_VIDEO);
+
+ sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK, Constants.PROPERTY_X_GOOGLE_TALK);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, Constants.PROPERTY_X_QQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, Constants.PROPERTY_X_NETMEETING);
}
-
- public static String getPhoneAttributeString(Integer type) {
+
+ 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) {
+ public static Object getPhoneTypeFromStrings(Collection<String> types,
+ String number) {
+ if (number == null) {
+ number = "";
+ }
int type = -1;
String label = null;
boolean isFax = false;
@@ -92,17 +111,30 @@
if (types != null) {
for (String typeString : types) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+ if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
hasPref = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_FAX)) {
+ } else if (typeString.equals(Constants.PARAM_TYPE_FAX)) {
isFax = true;
} else {
if (typeString.startsWith("X-") && type < 0) {
typeString = typeString.substring(2);
}
- Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
+ Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
if (tmp != null) {
- type = tmp;
+ 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;
@@ -133,15 +165,19 @@
return type;
}
}
-
- public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) {
+
+ public static String getPropertyNameForIm(int protocol) {
+ return sKnownImPropNameMap_ItoS.get(protocol);
+ }
+
+ public static boolean isValidPhoneType(String phoneType, int vcardType) {
// TODO: check the following.
// - it may violate vCard spec
// - it may contain non-ASCII characters
//
// TODO: use vcardType
- return (phoneAttribute.startsWith("X-") || phoneAttribute.startsWith("x-") ||
- sPhoneTypesSetUnknownToContacts.contains(phoneAttribute));
+ return (phoneType.startsWith("X-") || phoneType.startsWith("x-") ||
+ sPhoneTypesSetUnknownToContacts.contains(phoneType));
}
public static String[] sortNameElements(int vcardType,
@@ -187,7 +223,10 @@
}
builder.withValue(StructuredPostal.POBOX, postalData.pobox);
- // Extended address is dropped since there's no relevant entry in ContactsContract.
+ // TODO: Japanese phone seems to use this field for expressing all the address including
+ // region, city, etc. Not sure we're ok to store them into NEIGHBORHOOD, while it would be
+ // better than dropping them all.
+ builder.withValue(StructuredPostal.NEIGHBORHOOD, postalData.extendedAddress);
builder.withValue(StructuredPostal.STREET, postalData.street);
builder.withValue(StructuredPostal.CITY, postalData.localty);
builder.withValue(StructuredPostal.REGION, postalData.region);
@@ -200,12 +239,12 @@
builder.withValue(Data.IS_PRIMARY, 1);
}
}
-
+
/**
* Returns String[] containing address information based on vCard spec
* (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
* All String objects are non-null ("" is used when the relevant data is empty).
- *
+ *
* Note that the data structure of ContactsContract is different from that defined in vCard.
* So some conversion may be performed in this method. See also
* {{@link #insertStructuredPostalDataUsingContactsStruct(int,
@@ -213,13 +252,20 @@
* android.pim.vcard.ContactStruct.PostalData)}
*/
public static String[] getVCardPostalElements(ContentValues contentValues) {
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
String[] dataArray = new String[7];
dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
if (dataArray[0] == null) {
dataArray[0] = "";
}
- // Extended addr. There's no relevant data in ContactsContract.
- dataArray[1] = "";
+ // We keep all the data in StructuredPostal, presuming NEIGHBORHOOD is
+ // similar to "Extended Address".
+ dataArray[1] = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
+ if (dataArray[1] == null) {
+ dataArray[1] = "";
+ }
dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
if (dataArray[2] == null) {
dataArray[2] = "";
@@ -282,12 +328,41 @@
}
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 ? VCardParser_V30.unescapeCharacter(nextCh) :
+ VCardParser_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(String str) {
if (TextUtils.isEmpty(str)) {
return true;
}
-
+
final int length = str.length();
final int asciiFirst = 0x20;
final int asciiLast = 0x126;
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 08a2a9f..197d976 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -188,17 +188,7 @@
mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Preference);
- if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
- a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
- // This preference has a custom layout defined (not one taken from
- // the default style)
- mHasSpecifiedLayout = true;
- }
- a.recycle();
-
- a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
- defStyle, 0);
+ com.android.internal.R.styleable.Preference, defStyle, 0);
for (int i = a.getIndexCount(); i >= 0; i--) {
int attr = a.getIndex(i);
switch (attr) {
@@ -252,6 +242,11 @@
}
}
a.recycle();
+
+ if (!getClass().getName().startsWith("android.preference")) {
+ // For subclasses not in this package, assume the worst and don't cache views
+ mHasSpecifiedLayout = true;
+ }
}
/**
@@ -332,11 +327,11 @@
* @see #setWidgetLayoutResource(int)
*/
public void setLayoutResource(int layoutResId) {
-
- if (!mHasSpecifiedLayout) {
+ if (layoutResId != mLayoutResId) {
+ // Layout changed
mHasSpecifiedLayout = true;
}
-
+
mLayoutResId = layoutResId;
}
@@ -360,6 +355,10 @@
* @see #setLayoutResource(int)
*/
public void setWidgetLayoutResource(int widgetLayoutResId) {
+ if (widgetLayoutResId != mWidgetLayoutResId) {
+ // Layout changed
+ mHasSpecifiedLayout = true;
+ }
mWidgetLayoutResId = widgetLayoutResId;
}
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 14c0054..a908ecd 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -69,7 +69,9 @@
* count once--when the adapter is being set). We will not recycle views for
* Preference subclasses seen after the count has been returned.
*/
- private List<String> mPreferenceClassNames;
+ private ArrayList<PreferenceLayout> mPreferenceLayouts;
+
+ private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
/**
* Blocks the mPreferenceClassNames from being changed anymore.
@@ -86,14 +88,37 @@
}
};
+ private static class PreferenceLayout implements Comparable<PreferenceLayout> {
+ private int resId;
+ private int widgetResId;
+ private String name;
+
+ public int compareTo(PreferenceLayout other) {
+ int compareNames = name.compareTo(other.name);
+ if (compareNames == 0) {
+ if (resId == other.resId) {
+ if (widgetResId == other.widgetResId) {
+ return 0;
+ } else {
+ return widgetResId - other.widgetResId;
+ }
+ } else {
+ return resId - other.resId;
+ }
+ } else {
+ return compareNames;
+ }
+ }
+ }
+
public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
mPreferenceGroup = preferenceGroup;
// If this group gets or loses any children, let us know
mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
-
+
mPreferenceList = new ArrayList<Preference>();
- mPreferenceClassNames = new ArrayList<String>();
-
+ mPreferenceLayouts = new ArrayList<PreferenceLayout>();
+
syncMyPreferences();
}
@@ -102,7 +127,7 @@
if (mIsSyncing) {
return;
}
-
+
mIsSyncing = true;
}
@@ -128,7 +153,7 @@
preferences.add(preference);
- if (!mHasReturnedViewTypeCount) {
+ if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
addPreferenceClassName(preference);
}
@@ -143,15 +168,28 @@
}
}
+ /**
+ * Creates a string that includes the preference name, layout id and widget layout id.
+ * If a particular preference type uses 2 different resources, they will be treated as
+ * different view types.
+ */
+ private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
+ PreferenceLayout pl = in != null? in : new PreferenceLayout();
+ pl.name = preference.getClass().getName();
+ pl.resId = preference.getLayoutResource();
+ pl.widgetResId = preference.getWidgetLayoutResource();
+ return pl;
+ }
+
private void addPreferenceClassName(Preference preference) {
- final String name = preference.getClass().getName();
- int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
-
+ final PreferenceLayout pl = createPreferenceLayout(preference, null);
+ int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
+
// Only insert if it doesn't exist (when it is negative).
if (insertPos < 0) {
// Convert to insert index
insertPos = insertPos * -1 - 1;
- mPreferenceClassNames.add(insertPos, name);
+ mPreferenceLayouts.add(insertPos, pl);
}
}
@@ -171,19 +209,15 @@
public View getView(int position, View convertView, ViewGroup parent) {
final Preference preference = this.getItem(position);
-
- if (preference.hasSpecifiedLayout()) {
- // If the preference had specified a layout (as opposed to the
- // default), don't use convert views.
+ // Build a PreferenceLayout to compare with known ones that are cacheable.
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ // If it's not one of the cached ones, set the convertView to null so that
+ // the layout gets re-created by the Preference.
+ if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
convertView = null;
- } else {
- // TODO: better way of doing this
- final String name = preference.getClass().getName();
- if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
- convertView = null;
- }
}
-
+
return preference.getView(convertView, parent);
}
@@ -225,8 +259,9 @@
return IGNORE_ITEM_VIEW_TYPE;
}
- final String name = preference.getClass().getName();
- int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
if (viewType < 0) {
// This is a class that was seen after we returned the count, so
// don't recycle it.
@@ -242,7 +277,7 @@
mHasReturnedViewTypeCount = true;
}
- return Math.max(1, mPreferenceClassNames.size());
+ return Math.max(1, mPreferenceLayouts.size());
}
}
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f046cef..b8cf6c2 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -76,6 +76,15 @@
Uri.parse("content://" + AUTHORITY);
/**
+ * An optional insert, update or delete URI parameter that allows the caller
+ * to specify that it is a sync adapter. The default value is false. If true
+ * the dirty flag is not automatically set and the "syncToNetwork" parameter
+ * is set to false when calling
+ * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ */
+ public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+
+ /**
* Columns from the Calendars table that other tables join into themselves.
*/
public interface CalendarsColumns
@@ -341,11 +350,11 @@
* This field is copied here so that we can efficiently filter out
* events that are declined without having to look in the Attendees
* table.
- *
+ *
* <P>Type: INTEGER (int)</P>
*/
public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
-
+
/**
* The comments feed uri.
* <P>Type: TEXT</P>
@@ -514,6 +523,12 @@
* <P>Type: String</P>
*/
public static final String OWNER_ACCOUNT = "ownerAccount";
+
+ /**
+ * Whether the row has been deleted. A deleted row should be ignored.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DELETED = "deleted";
}
/**
@@ -862,10 +877,10 @@
*/
public static final String MAX_BUSYBITS = "maxBusyBits";
}
-
+
public static final class CalendarMetaData implements CalendarMetaDataColumns {
}
-
+
public interface BusyBitsColumns {
/**
* The Julian day number.
@@ -889,22 +904,22 @@
*/
public static final String ALL_DAY_COUNT = "allDayCount";
}
-
+
public static final class BusyBits implements BusyBitsColumns {
public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when");
public static final String[] PROJECTION = { DAY, BUSYBITS, ALL_DAY_COUNT };
-
+
// The number of minutes represented by one busy bit
public static final int MINUTES_PER_BUSY_INTERVAL = 60;
-
+
// The number of intervals in a day
public static final int INTERVALS_PER_DAY = 24 * 60 / MINUTES_PER_BUSY_INTERVAL;
/**
* Retrieves the busy bits for the Julian days starting at "startDay"
* for "numDays".
- *
+ *
* @param cr the ContentResolver
* @param startDay the first Julian day in the range
* @param numDays the number of days to load (must be at least 1)
@@ -1032,14 +1047,14 @@
CalendarAlertsColumns, EventsColumns, CalendarsColumns {
public static final String TABLE_NAME = "CalendarAlerts";
public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
-
+
/**
* This URI is for grouping the query results by event_id and begin
* time. This will return one result per instance of an event. So
* events with multiple alarms will appear just once, but multiple
* instances of a repeating event will show up multiple times.
*/
- public static final Uri CONTENT_URI_BY_INSTANCE =
+ public static final Uri CONTENT_URI_BY_INSTANCE =
Uri.parse("content://calendar/calendar_alerts/by_instance");
public static final Uri insert(ContentResolver cr, long eventId,
@@ -1063,11 +1078,11 @@
return cr.query(CONTENT_URI, projection, selection, selectionArgs,
DEFAULT_SORT_ORDER);
}
-
+
/**
* Finds the next alarm after (or equal to) the given time and returns
* the time of that alarm or -1 if no such alarm exists.
- *
+ *
* @param cr the ContentResolver
* @param millis the time in UTC milliseconds
* @return the next alarm time greater than or equal to "millis", or -1
@@ -1091,13 +1106,13 @@
}
return alarmTime;
}
-
+
/**
* Searches the CalendarAlerts table for alarms that should have fired
* but have not and then reschedules them. This method can be called
* at boot time to restore alarms that may have been lost due to a
* phone reboot.
- *
+ *
* @param cr the ContentResolver
* @param context the Context
* @param manager the AlarmManager
@@ -1125,7 +1140,7 @@
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "missed alarms found: " + cursor.getCount());
}
-
+
try {
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
@@ -1146,14 +1161,14 @@
} finally {
cursor.close();
}
-
+
}
-
+
/**
* Searches for an entry in the CalendarAlerts table that matches
* the given event id, begin time and alarm time. If one is found
* then this alarm already exists and this method returns true.
- *
+ *
* @param cr the ContentResolver
* @param eventId the event id to match
* @param begin the start time of the event in UTC millis
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 062080d..cd71682 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -238,6 +238,7 @@
private static final int FULL_SCREEN_KIND = 2;
private static final int MICRO_KIND = 3;
private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
+ static final int DEFAULT_GROUP_ID = 0;
/**
* This method cancels the thumbnail request so clients waiting for getThumbnail will be
@@ -246,11 +247,14 @@
*
* @param cr ContentResolver
* @param origId original image or video id. use -1 to cancel all requests.
+ * @param groupId the same groupId used in getThumbnail
* @param baseUri the base URI of requested thumbnails
*/
- static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) {
+ static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
+ long groupId) {
Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
Cursor c = null;
try {
c = cr.query(cancelUri, PROJECTION, null, null, null);
@@ -271,9 +275,10 @@
* @param kind could be MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @param baseUri the base URI of requested thumbnails
+ * @param groupId the id of group to which this request belongs
* @return Bitmap bitmap of specified thumbnail kind
*/
- static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
Bitmap bitmap = null;
String filePath = null;
@@ -297,7 +302,8 @@
Cursor c = null;
try {
Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
c = cr.query(blockingUri, PROJECTION, null, null, null);
// This happens when original image/video doesn't exist.
if (c == null) return null;
@@ -354,7 +360,7 @@
}
if (isVideo) {
bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
- if (kind == MICRO_KIND) {
+ if (kind == MICRO_KIND && bitmap != null) {
bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
@@ -669,7 +675,8 @@
* @param origId original image id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
}
/**
@@ -685,7 +692,39 @@
*/
public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, false);
+ }
+
+ /**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original image id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, false);
}
@@ -1598,7 +1637,8 @@
* @param origId original video id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
}
/**
@@ -1607,18 +1647,50 @@
*
* @param cr ContentResolver used to dispatch queries to MediaProvider.
* @param origId Original image id associated with thumbnail of interest.
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, true);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
* @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @return A Bitmap instance. It could be null if the original image associated with
* origId doesn't exist or memory is not enough.
*/
- public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
- BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, true);
}
/**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original video id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
* Get the content:// style URI for the image media table on the
* given volume.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cb3dc16..9a8fd50 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -43,8 +43,10 @@
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
/**
@@ -476,7 +478,10 @@
private static class NameValueCache {
private final String mVersionSystemProperty;
- private final HashMap<String, String> mValues = Maps.newHashMap();
+ // the following needs synchronization because this structure is accessed from different
+ // threads and they could be performing clear(), get(), put() at the same time.
+ private final Map<String, String> mValues =
+ Collections.synchronizedMap(new HashMap<String, String>());
private long mValuesVersion = 0;
private final Uri mUri;
@@ -491,8 +496,29 @@
mValues.clear();
mValuesVersion = newValuesVersion;
}
- if (!mValues.containsKey(name)) {
- String value = null;
+ /*
+ * don't look for the key using containsKey() method because (key, object) mapping
+ * could be removed from mValues before get() is done like so:
+ *
+ * say, mValues contains mapping for "foo"
+ * Thread# 1
+ * performs containsKey("foo")
+ * receives true
+ * Thread #2
+ * triggers mValues.clear()
+ * Thread#1
+ * since containsKey("foo") = true, performs get("foo")
+ * receives null
+ * thats incorrect!
+ *
+ * to avoid the above, thread#1 should do get("foo") instead of containsKey("foo")
+ * since mValues is synchronized, get() will get a consistent value.
+ *
+ * we don't want to make this method synchronized tho - because
+ * holding mutex is not desirable while a call could be made to database.
+ */
+ String value = mValues.get(name);
+ if (value == null) {
Cursor c = null;
try {
c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
@@ -505,10 +531,8 @@
} finally {
if (c != null) c.close();
}
- return value;
- } else {
- return mValues.get(name);
}
+ return value;
}
}
@@ -2502,6 +2526,13 @@
public static final String OVERRIDE_ACTION =
"com.google.gservices.intent.action.GSERVICES_OVERRIDE";
+ /**
+ * Intent action to set Gservices with new values. (Requires WRITE_GSERVICES permission.)
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String UPDATE_ACTION =
+ "com.google.gservices.intent.action.GSERVICES_UPDATE";
+
private static volatile NameValueCache mNameValueCache = null;
private static final Object mNameValueCacheLock = new Object();
@@ -3110,6 +3141,13 @@
= "google_login_generic_auth_service";
/**
+ * Duration in milliseconds after setup at which market does not reconcile applications
+ * which are installed during restore.
+ */
+ public static final String VENDING_RESTORE_WINDOW_MS = "vending_restore_window_ms";
+
+
+ /**
* Frequency in milliseconds at which we should sync the locally installed Vending Machine
* content with the server.
*/
@@ -3623,7 +3661,6 @@
*/
public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT =
"search_per_source_concurrent_query_limit";
-
/**
* Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
* on application crashes and ANRs. If this is disabled, the crash/ANR dialog
@@ -3638,6 +3675,32 @@
public static final String LAST_KMSG_KB = "last_kmsg_kb";
/**
+ * Maximum age of entries kept by {@link android.os.IDropBox}.
+ */
+ public static final String DROPBOX_AGE_SECONDS =
+ "dropbox_age_seconds";
+ /**
+ * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+ */
+ public static final String DROPBOX_QUOTA_KB =
+ "dropbox_quota_kb";
+ /**
+ * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+ */
+ public static final String DROPBOX_QUOTA_PERCENT =
+ "dropbox_quota_percent";
+ /**
+ * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+ */
+ public static final String DROPBOX_RESERVE_PERCENT =
+ "dropbox_reserve_percent";
+ /**
+ * Prefix for per-tag dropbox disable/enable settings.
+ */
+ public static final String DROPBOX_TAG_PREFIX =
+ "dropbox:";
+
+ /**
* @deprecated
* @hide
*/
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d8c5a53..9a72d93 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -152,6 +152,12 @@
* <P>Type: INTEGER (boolean)</P>
*/
public static final String LOCKED = "locked";
+
+ /**
+ * Error code associated with sending or receiving this message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "error_code";
}
/**
@@ -243,7 +249,7 @@
* @return true if the operation succeeded
*/
public static boolean moveMessageToFolder(Context context,
- Uri uri, int folder) {
+ Uri uri, int folder, int error) {
if (uri == null) {
return false;
}
@@ -266,7 +272,7 @@
return false;
}
- ContentValues values = new ContentValues(2);
+ ContentValues values = new ContentValues(3);
values.put(TYPE, folder);
if (markAsUnread) {
@@ -274,6 +280,7 @@
} else if (markAsRead) {
values.put(READ, Integer.valueOf(1));
}
+ values.put(ERROR_CODE, error);
return 1 == SqliteWrapper.update(context, context.getContentResolver(),
uri, values, null, null);
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index d1dd311..b32aacc 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -61,7 +61,7 @@
public class BluetoothService extends IBluetooth.Stub {
private static final String TAG = "BluetoothService";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private int mNativeData;
private BluetoothEventLoop mEventLoop;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index a92800d..afc6864 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -294,7 +294,12 @@
lbaseline, lbottom, buf,
start, end, par, this);
- left += margin.getLeadingMargin(par);
+ boolean useMargin = par;
+ if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
+ useMargin = count > i;
+ }
+ left += margin.getLeadingMargin(useMargin);
}
}
}
@@ -1293,7 +1298,13 @@
LeadingMarginSpan.class);
for (int i = 0; i < spans.length; i++) {
- left += spans[i].getLeadingMargin(par);
+ boolean margin = par;
+ LeadingMarginSpan span = spans[i];
+ if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
+ margin = count >= line;
+ }
+ left += span.getLeadingMargin(margin);
}
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f0a5ffd..fbf1261 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -161,6 +161,7 @@
else
end++;
+ int firstWidthLineCount = 1;
int firstwidth = outerwidth;
int restwidth = outerwidth;
@@ -171,8 +172,12 @@
sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
firstwidth -= sp[i].getLeadingMargin(true);
restwidth -= sp[i].getLeadingMargin(false);
+ if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+ }
}
chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
@@ -750,7 +755,9 @@
fitascent = fitdescent = fittop = fitbottom = 0;
okascent = okdescent = oktop = okbottom = 0;
- width = restwidth;
+ if (--firstWidthLineCount <= 0) {
+ width = restwidth;
+ }
}
}
}
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 8e212e3..cb55329 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -33,6 +33,11 @@
CharSequence text, int start, int end,
boolean first, Layout layout);
+
+ public interface LeadingMarginSpan2 extends LeadingMarginSpan, WrapTogetherSpan {
+ public int getLeadingMarginLineCount();
+ };
+
public static class Standard implements LeadingMarginSpan, ParcelableSpan {
private final int mFirst, mRest;
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index f4593f5..e4f2b63 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -68,6 +68,7 @@
View.AttachInfo.Callbacks {
private static final String TAG = "ViewRoot";
private static final boolean DBG = false;
+ private static final boolean SHOW_FPS = false;
@SuppressWarnings({"ConstantConditionalExpression"})
private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
/** @noinspection PointlessBooleanExpression*/
@@ -1244,7 +1245,7 @@
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglErrors();
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
@@ -1356,7 +1357,7 @@
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 9456ae1..d1db35e 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -19,17 +19,21 @@
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.AssetManager;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.ParseException;
+import android.net.Uri;
import android.net.WebAddress;
import android.net.http.SslCertificate;
import android.os.Handler;
import android.os.Message;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.TypedValue;
import junit.framework.Assert;
+import java.io.InputStream;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@@ -463,6 +467,63 @@
}
/**
+ * Called by JNI. Given a URI, find the associated file and return its size
+ * @param uri A String representing the URI of the desired file.
+ * @return int The size of the given file.
+ */
+ private int getFileSize(String uri) {
+ int size = 0;
+ Cursor cursor = mContext.getContentResolver().query(Uri.parse(uri),
+ new String[] { OpenableColumns.SIZE },
+ null,
+ null,
+ null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ size = cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Called by JNI. Given a URI, a buffer, and an offset into the buffer,
+ * copy the resource into buffer.
+ * @param uri A String representing the URI of the desired file.
+ * @param buffer The byte array to copy the data into.
+ * @param offset The offet into buffer to place the data.
+ * @param expectSize The size that the buffer has allocated for this file.
+ * @return int The size of the given file, or zero if it fails.
+ */
+ private int getFile(String uri, byte[] buffer, int offset,
+ int expectedSize) {
+ int size = 0;
+ try {
+ InputStream stream = mContext.getContentResolver()
+ .openInputStream(Uri.parse(uri));
+ size = stream.available();
+ if (size <= expectedSize && buffer != null
+ && buffer.length - offset >= size) {
+ stream.read(buffer, offset, size);
+ } else {
+ size = 0;
+ }
+ stream.close();
+ } catch (java.io.FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException:" + e);
+ size = 0;
+ } catch (java.io.IOException e2) {
+ Log.e(LOGTAG, "IOException: " + e2);
+ size = 0;
+ }
+ return size;
+ }
+
+ /**
* Start loading a resource.
* @param loaderHandle The native ResourceLoader that is the target of the
* data.
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
index 145411c..334526b 100644
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -16,6 +16,8 @@
package android.webkit;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
import java.util.LinkedList;
import java.util.ListIterator;
@@ -23,47 +25,37 @@
them back out. It does not optimize for returning the result in a
single array, though this is supported in the API. It is fastest
if the retrieval can be done via iterating through chunks.
-
- Things to add:
- - consider dynamically increasing our min_capacity,
- as we see mTotalSize increase
*/
class ByteArrayBuilder {
private static final int DEFAULT_CAPACITY = 8192;
+ // Global pool of chunks to be used by other ByteArrayBuilders.
+ private static final LinkedList<SoftReference<Chunk>> sPool =
+ new LinkedList<SoftReference<Chunk>>();
+ // Reference queue for processing gc'd entries.
+ private static final ReferenceQueue<Chunk> sQueue =
+ new ReferenceQueue<Chunk>();
+
private LinkedList<Chunk> mChunks;
- /** free pool */
- private LinkedList<Chunk> mPool;
-
- private int mMinCapacity;
-
public ByteArrayBuilder() {
- init(0);
- }
-
- public ByteArrayBuilder(int minCapacity) {
- init(minCapacity);
- }
-
- private void init(int minCapacity) {
mChunks = new LinkedList<Chunk>();
- mPool = new LinkedList<Chunk>();
-
- if (minCapacity <= 0) {
- minCapacity = DEFAULT_CAPACITY;
- }
- mMinCapacity = minCapacity;
- }
-
- public void append(byte[] array) {
- append(array, 0, array.length);
}
public synchronized void append(byte[] array, int offset, int length) {
while (length > 0) {
- Chunk c = appendChunk(length);
+ Chunk c = null;
+ if (mChunks.isEmpty()) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ } else {
+ c = mChunks.getLast();
+ if (c.mLength == c.mArray.length) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ }
+ }
int amount = Math.min(length, c.mArray.length - c.mLength);
System.arraycopy(array, offset, c.mArray, c.mLength, amount);
c.mLength += amount;
@@ -75,7 +67,7 @@
/**
* The fastest way to retrieve the data is to iterate through the
* chunks. This returns the first chunk. Note: this pulls the
- * chunk out of the queue. The caller must call releaseChunk() to
+ * chunk out of the queue. The caller must call Chunk.release() to
* dispose of it.
*/
public synchronized Chunk getFirstChunk() {
@@ -83,23 +75,11 @@
return mChunks.removeFirst();
}
- /**
- * recycles chunk
- */
- public synchronized void releaseChunk(Chunk c) {
- c.mLength = 0;
- mPool.addLast(c);
- }
-
- public boolean isEmpty() {
+ public synchronized boolean isEmpty() {
return mChunks.isEmpty();
}
- public int size() {
- return mChunks.size();
- }
-
- public int getByteSize() {
+ public synchronized int getByteSize() {
int total = 0;
ListIterator<Chunk> it = mChunks.listIterator(0);
while (it.hasNext()) {
@@ -112,37 +92,40 @@
public synchronized void clear() {
Chunk c = getFirstChunk();
while (c != null) {
- releaseChunk(c);
+ c.release();
c = getFirstChunk();
}
}
- private Chunk appendChunk(int length) {
- if (length < mMinCapacity) {
- length = mMinCapacity;
- }
-
- Chunk c;
- if (mChunks.isEmpty()) {
- c = obtainChunk(length);
- } else {
- c = mChunks.getLast();
- if (c.mLength == c.mArray.length) {
- c = obtainChunk(length);
+ // Must be called with lock held on sPool.
+ private void processPoolLocked() {
+ while (true) {
+ SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
+ if (entry == null) {
+ break;
}
+ sPool.remove(entry);
}
- return c;
}
private Chunk obtainChunk(int length) {
- Chunk c;
- if (mPool.isEmpty()) {
- c = new Chunk(length);
- } else {
- c = mPool.removeFirst();
+ // Correct a small length.
+ if (length < DEFAULT_CAPACITY) {
+ length = DEFAULT_CAPACITY;
}
- mChunks.addLast(c);
- return c;
+ synchronized (sPool) {
+ // Process any queued references and remove them from the pool.
+ processPoolLocked();
+ if (!sPool.isEmpty()) {
+ Chunk c = sPool.removeFirst().get();
+ // The first item may have been queued after processPoolLocked
+ // so check for null.
+ if (c != null) {
+ return c;
+ }
+ }
+ return new Chunk(length);
+ }
}
public static class Chunk {
@@ -153,5 +136,19 @@
mArray = new byte[length];
mLength = 0;
}
+
+ /**
+ * Release the chunk and make it available for reuse.
+ */
+ public void release() {
+ mLength = 0;
+ synchronized (sPool) {
+ // Add the chunk back to the pool as a SoftReference so it can
+ // be gc'd if needed.
+ sPool.offer(new SoftReference<Chunk>(this, sQueue));
+ sPool.notifyAll();
+ }
+ }
+
}
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 8d55247..4f8c227 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -107,6 +107,7 @@
private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
private static final int RECEIVED_TOUCH_ICON_URL = 132;
private static final int GET_VISITED_HISTORY = 133;
+ private static final int OPEN_FILE_CHOOSER = 134;
// Message triggered by the client to resume execution
private static final int NOTIFY = 200;
@@ -149,6 +150,16 @@
}
/**
+ * Get the WebViewClient.
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mWebViewClient;
+ }
+
+ /**
* Set the WebChromeClient.
* @param client An implementation of WebChromeClient.
*/
@@ -240,8 +251,10 @@
break;
case PAGE_FINISHED:
+ String finishedUrl = (String) msg.obj;
+ mWebView.onPageFinished(finishedUrl);
if (mWebViewClient != null) {
- mWebViewClient.onPageFinished(mWebView, (String) msg.obj);
+ mWebViewClient.onPageFinished(mWebView, finishedUrl);
}
break;
@@ -662,6 +675,12 @@
mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
}
break;
+
+ case OPEN_FILE_CHOOSER:
+ if (mWebChromeClient != null) {
+ mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+ }
+ break;
}
}
@@ -760,11 +779,6 @@
}
public void onPageFinished(String url) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
// Performance probe
if (PERF_PROBE) {
// un-comment this if PERF_PROBE is true
@@ -1002,10 +1016,10 @@
public void onProgressChanged(int newProgress) {
// Synchronize so that mLatestProgress is up-to-date.
synchronized (this) {
- mLatestProgress = newProgress;
- if (mWebChromeClient == null) {
+ if (mWebChromeClient == null || mLatestProgress == newProgress) {
return;
}
+ mLatestProgress = newProgress;
if (!mProgressUpdatePending) {
sendEmptyMessage(PROGRESS);
mProgressUpdatePending = true;
@@ -1348,4 +1362,40 @@
msg.obj = callback;
sendMessage(msg);
}
+
+ private class UploadFile implements ValueCallback<Uri> {
+ private Uri mValue;
+ public void onReceiveValue(Uri value) {
+ mValue = value;
+ synchronized (CallbackProxy.this) {
+ CallbackProxy.this.notify();
+ }
+ }
+ public Uri getResult() {
+ return mValue;
+ }
+ }
+
+ /**
+ * Called by WebViewCore to open a file chooser.
+ */
+ /* package */ Uri openFileChooser() {
+ if (mWebChromeClient == null) {
+ return null;
+ }
+ Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
+ UploadFile uploadFile = new UploadFile();
+ myMessage.obj = uploadFile;
+ synchronized (this) {
+ sendMessage(myMessage);
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(LOGTAG,
+ "Caught exception while waiting for openFileChooser");
+ Log.e(LOGTAG, Log.getStackTraceString(e));
+ }
+ }
+ return uploadFile.getResult();
+ }
}
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index b7a9065..3e0be1c 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -199,6 +199,8 @@
public void playbackEnded() {
Message msg = Message.obtain(mWebCoreHandler, ENDED);
mWebCoreHandler.sendMessage(msg);
+ // also send a message to ourselves to return to the WebView
+ sendMessage(obtainMessage(ENDED));
}
// Handler for the messages from WebCore thread to the UI thread.
@@ -224,6 +226,7 @@
VideoPlayer.pause(this);
break;
}
+ case ENDED:
case ERROR: {
WebChromeClient client = mWebView.getWebChromeClient();
if (client != null) {
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java
index 2f46f2b..042953c 100644
--- a/core/java/android/webkit/HttpDateTime.java
+++ b/core/java/android/webkit/HttpDateTime.java
@@ -50,13 +50,15 @@
* Wdy Mon DD HH:MM:SS YYYY GMT
*
* HH can be H if the first digit is zero.
+ *
+ * Mon can be the full name of the month.
*/
private static final String HTTP_DATE_RFC_REGEXP =
- "([0-9]{1,2})[- ]([A-Za-z]{3,3})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
private static final String HTTP_DATE_ANSIC_REGEXP =
- "[ ]([A-Za-z]{3,3})[ ]+([0-9]{1,2})[ ]"
+ "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
/**
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 4c17f99..5c0ce3c 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -78,7 +78,7 @@
private static int sNativeLoaderCount;
- private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
+ private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
private String mUrl;
private WebAddress mUri;
@@ -522,17 +522,18 @@
* IMPORTANT: as this is called from network thread, can't call native
* directly
* XXX: Unlike the other network thread methods, this method can do the
- * work of decoding the data and appending it to the data builder because
- * mDataBuilder is a thread-safe structure.
+ * work of decoding the data and appending it to the data builder.
*/
public void data(byte[] data, int length) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener.data(): url: " + url());
}
- // Synchronize on mData because commitLoad may write mData to WebCore
- // and we don't want to replace mData or mDataLength at the same time
- // as a write.
+ // The reason isEmpty() and append() need to synchronized together is
+ // because it is possible for getFirstChunk() to be called multiple
+ // times between isEmpty() and append(). This could cause commitLoad()
+ // to finish before processing the newly appended data and no message
+ // will be sent.
boolean sendMessage = false;
synchronized (mDataBuilder) {
sendMessage = mDataBuilder.isEmpty();
@@ -1009,28 +1010,34 @@
if (mIsMainPageLoader) {
String type = sCertificateTypeMap.get(mMimeType);
if (type != null) {
- // In the case of downloading certificate, we will save it to
- // the KeyStore and stop the current loading so that it will not
- // generate a new history page
- byte[] cert = new byte[mDataBuilder.getByteSize()];
- int offset = 0;
- while (true) {
- ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
- if (c == null) break;
+ // This must be synchronized so that no more data can be added
+ // after getByteSize returns.
+ synchronized (mDataBuilder) {
+ // In the case of downloading certificate, we will save it
+ // to the KeyStore and stop the current loading so that it
+ // will not generate a new history page
+ byte[] cert = new byte[mDataBuilder.getByteSize()];
+ int offset = 0;
+ while (true) {
+ ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
+ if (c == null) break;
- if (c.mLength != 0) {
- System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
- offset += c.mLength;
+ if (c.mLength != 0) {
+ System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
+ offset += c.mLength;
+ }
+ c.release();
}
- mDataBuilder.releaseChunk(c);
+ CertTool.addCertificate(mContext, type, cert);
+ mBrowserFrame.stopLoading();
+ return;
}
- CertTool.addCertificate(mContext, type, cert);
- mBrowserFrame.stopLoading();
- return;
}
}
- // Give the data to WebKit now
+ // Give the data to WebKit now. We don't have to synchronize on
+ // mDataBuilder here because pulling each chunk removes it from the
+ // internal list so it cannot be modified.
PerfChecker checker = new PerfChecker();
ByteArrayBuilder.Chunk c;
while (true) {
@@ -1047,7 +1054,7 @@
}
nativeAddData(c.mArray, c.mLength);
}
- mDataBuilder.releaseChunk(c);
+ c.release();
checker.responseAlert("res nativeAddData");
}
}
@@ -1210,8 +1217,17 @@
// mRequestHandle can be null when the request was satisfied
// by the cache, and the cache returned a redirect
if (mRequestHandle != null) {
- mRequestHandle.setupRedirect(mUrl, mStatusCode,
- mRequestHeaders);
+ try {
+ mRequestHandle.setupRedirect(mUrl, mStatusCode,
+ mRequestHeaders);
+ } catch(RuntimeException e) {
+ Log.e(LOGTAG, e.getMessage());
+ // Signal a bad url error if we could not load the
+ // redirection.
+ handleError(EventHandler.ERROR_BAD_URL,
+ mContext.getString(R.string.httpErrorBadUrl));
+ return;
+ }
} else {
// If the original request came from the cache, there is no
// RequestHandle, we have to create a new one through
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index fffba1b..84a8a3c 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -431,6 +431,8 @@
sMimeTypeMap.loadEntry("text/calendar", "icz");
sMimeTypeMap.loadEntry("text/comma-separated-values", "csv");
sMimeTypeMap.loadEntry("text/css", "css");
+ sMimeTypeMap.loadEntry("text/html", "htm");
+ sMimeTypeMap.loadEntry("text/html", "html");
sMimeTypeMap.loadEntry("text/h323", "323");
sMimeTypeMap.loadEntry("text/iuls", "uls");
sMimeTypeMap.loadEntry("text/mathml", "mml");
@@ -481,6 +483,7 @@
sMimeTypeMap.loadEntry("video/dv", "dif");
sMimeTypeMap.loadEntry("video/dv", "dv");
sMimeTypeMap.loadEntry("video/fli", "fli");
+ sMimeTypeMap.loadEntry("video/m4v", "m4v");
sMimeTypeMap.loadEntry("video/mpeg", "mpeg");
sMimeTypeMap.loadEntry("video/mpeg", "mpg");
sMimeTypeMap.loadEntry("video/mpeg", "mpe");
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index af0cb1e..b53e404 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -180,20 +180,24 @@
}
RequestQueue q = mRequestQueue;
+ RequestHandle handle = null;
if (loader.isSynchronous()) {
- q = new RequestQueue(loader.getContext(), 1);
- }
-
- RequestHandle handle = q.queueRequest(
- url, loader.getWebAddress(), method, headers, loader,
- bodyProvider, bodyLength);
- loader.attachRequestHandle(handle);
-
- if (loader.isSynchronous()) {
- handle.waitUntilComplete();
+ handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
+ method, headers, loader, bodyProvider, bodyLength);
+ loader.attachRequestHandle(handle);
+ handle.processRequest();
loader.loadSynchronousMessages();
- q.shutdown();
+ } else {
+ handle = q.queueRequest(url, loader.getWebAddress(), method,
+ headers, loader, bodyProvider, bodyLength);
+ // FIXME: Although this is probably a rare condition, normal network
+ // requests are processed in a separate thread. This means that it
+ // is possible to process part of the request before setting the
+ // request handle on the loader. We should probably refactor this to
+ // ensure the handle is attached before processing begins.
+ loader.attachRequestHandle(handle);
}
+
return true;
}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 232ed36..211e5e4 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -367,19 +367,23 @@
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
- Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+ Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+ Pattern.CASE_INSENSITIVE);
/*
* Parse the Content-Disposition HTTP Header. The format of the header
* is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
* This header provides a filename for content that is going to be
* downloaded to the file system. We only support the attachment type.
+ * Note that RFC 2616 specifies the filename value must be double-quoted.
+ * Unfortunately some servers do not quote the value so to maintain
+ * consistent behaviour with other browsers, we allow unquoted values too.
*/
static String parseContentDisposition(String contentDisposition) {
try {
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) {
- return m.group(1);
+ return m.group(2);
}
} catch (IllegalStateException ex) {
// This function is defined as returning null when it can't parse the header
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 7f5b862..ae4f7c2 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Message;
import android.view.View;
@@ -302,4 +303,13 @@
public void getVisitedHistory(ValueCallback<String[]> callback) {
}
+ /**
+ * Tell the client to open a file chooser.
+ * @param uploadFile A ValueCallback to set the URI of the file to upload.
+ * onReceiveValue must be called to wake up the thread.
+ * @hide
+ */
+ public void openFileChooser(ValueCallback<Uri> uploadFile) {
+ uploadFile.onReceiveValue(null);
+ }
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 4fedec9..79d8c03 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1007,7 +1007,8 @@
* should never be null.
*/
public synchronized void setGeolocationDatabasePath(String databasePath) {
- if (databasePath != null && !databasePath.equals(mDatabasePath)) {
+ if (databasePath != null
+ && !databasePath.equals(mGeolocationDatabasePath)) {
mGeolocationDatabasePath = databasePath;
postSync();
}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index e0d41c2..71b1f9f 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -84,13 +84,24 @@
// True if the most recent drag event has caused either the TextView to
// scroll or the web page to scroll. Gets reset after a touch down.
private boolean mScrolled;
- // Gets set to true when the the IME jumps to the next textfield. When this
- // happens, the next time the user hits a key it is okay for the focus
- // pointer to not match the WebTextView's node pointer
+ // Gets set to true any time the WebTextView has focus, but the navigation
+ // cache does not yet know that the focus has been changed. This happens
+ // if the user presses "Next", if the user moves the cursor to a textfield
+ // and starts typing or clicks the trackball/center key, and when the user
+ // touches a textfield.
boolean mOkayForFocusNotToMatch;
// Whether or not a selection change was generated from webkit. If it was,
// we do not need to pass the selection back to webkit.
private boolean mFromWebKit;
+ // Whether or not a selection change was generated from the WebTextView
+ // gaining focus. If it is, we do not want to pass it to webkit. This
+ // selection comes from the MovementMethod, but we behave differently. If
+ // WebTextView gained focus from a touch, webkit will determine the
+ // selection.
+ private boolean mFromFocusChange;
+ // Whether or not a selection change was generated from setInputType. We
+ // do not want to pass this change to webkit.
+ private boolean mFromSetInputType;
private boolean mGotTouchDown;
private boolean mInSetTextAndKeepSelection;
// Array to store the final character added in onTextChanged, so that its
@@ -136,20 +147,23 @@
isArrowKey = true;
break;
}
- if (!isArrowKey && !mOkayForFocusNotToMatch
- && mWebView.nativeFocusNodePointer() != mNodePointer) {
- mWebView.nativeClearCursor();
- // Do not call remove() here, which hides the soft keyboard. If
- // the soft keyboard is being displayed, the user will still want
- // it there.
- mWebView.removeView(this);
- mWebView.requestFocus();
- return mWebView.dispatchKeyEvent(event);
- }
- // After a jump to next textfield and the first key press, the cursor
- // and focus will once again match, so reset this value.
- mOkayForFocusNotToMatch = false;
+ if (down) {
+ if (mOkayForFocusNotToMatch) {
+ if (mWebView.nativeFocusNodePointer() == mNodePointer) {
+ mOkayForFocusNotToMatch = false;
+ }
+ } else if (mWebView.nativeFocusNodePointer() != mNodePointer
+ && !isArrowKey) {
+ mWebView.nativeClearCursor();
+ // Do not call remove() here, which hides the soft keyboard. If
+ // the soft keyboard is being displayed, the user will still want
+ // it there.
+ mWebView.removeView(this);
+ mWebView.requestFocus();
+ return mWebView.dispatchKeyEvent(event);
+ }
+ }
Spannable text = (Spannable) getText();
int oldLength = text.length();
// Normally the delete key's dom events are sent via onTextChanged.
@@ -185,7 +199,7 @@
}
// Center key should be passed to a potential onClick
if (!down) {
- mWebView.shortPressOnTextField();
+ mWebView.centerKeyPressOnTextField();
}
// Pass to super to handle longpress.
return super.dispatchKeyEvent(event);
@@ -303,15 +317,19 @@
public void onEditorAction(int actionCode) {
switch (actionCode) {
case EditorInfo.IME_ACTION_NEXT:
- mWebView.nativeMoveCursorToNextTextInput();
- // Preemptively rebuild the WebTextView, so that the action will
- // be set properly.
- mWebView.rebuildWebTextView();
// Since the cursor will no longer be in the same place as the
// focus, set the focus controller back to inactive
mWebView.setFocusControllerInactive();
- mWebView.invalidate();
+ mWebView.nativeMoveCursorToNextTextInput();
mOkayForFocusNotToMatch = true;
+ // Pass the click to set the focus to the textfield which will now
+ // have the cursor.
+ mWebView.centerKeyPressOnTextField();
+ // Preemptively rebuild the WebTextView, so that the action will
+ // be set properly.
+ mWebView.rebuildWebTextView();
+ setDefaultSelection();
+ mWebView.invalidate();
break;
case EditorInfo.IME_ACTION_DONE:
super.onEditorAction(actionCode);
@@ -331,6 +349,14 @@
}
@Override
+ protected void onFocusChanged(boolean focused, int direction,
+ Rect previouslyFocusedRect) {
+ mFromFocusChange = true;
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ mFromFocusChange = false;
+ }
+
+ @Override
protected void onSelectionChanged(int selStart, int selEnd) {
// This code is copied from TextView.onDraw(). That code does not get
// executed, however, because the WebTextView does not draw, allowing
@@ -342,7 +368,8 @@
int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
}
- if (!mFromWebKit && mWebView != null) {
+ if (!mFromWebKit && !mFromFocusChange && !mFromSetInputType
+ && mWebView != null) {
if (DebugFlags.WEB_TEXT_VIEW) {
Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart
+ " selEnd=" + selEnd);
@@ -591,6 +618,17 @@
}
/**
+ * Sets the selection when the user clicks on a textfield or textarea with
+ * the trackball or center key, or starts typing into it without clicking on
+ * it.
+ */
+ /* package */ void setDefaultSelection() {
+ Spannable text = (Spannable) getText();
+ int selection = mSingle ? text.length() : 0;
+ Selection. setSelection(text, selection, selection);
+ }
+
+ /**
* Determine whether to use the system-wide password disguising method,
* or to use none.
* @param inPassword True if the textfield is a password field.
@@ -660,6 +698,13 @@
setTextColor(Color.BLACK);
}
+ @Override
+ public void setInputType(int type) {
+ mFromSetInputType = true;
+ super.setInputType(type);
+ mFromSetInputType = false;
+ }
+
/* package */ void setMaxLength(int maxLength) {
mMaxLength = maxLength;
if (-1 == maxLength) {
@@ -761,32 +806,6 @@
}
/**
- * Set the text for this WebTextView, and set the selection to (start, end)
- * @param text Text to go into this WebTextView.
- * @param start Beginning of the selection.
- * @param end End of the selection.
- */
- /* package */ void setText(CharSequence text, int start, int end) {
- mPreChange = text.toString();
- setText(text);
- Spannable span = (Spannable) getText();
- int length = span.length();
- if (end > length) {
- end = length;
- }
- if (start < 0) {
- start = 0;
- } else if (start > length) {
- start = length;
- }
- if (DebugFlags.WEB_TEXT_VIEW) {
- Log.v(LOGTAG, "setText start=" + start
- + " end=" + end);
- }
- Selection.setSelection(span, start, end);
- }
-
- /**
* Set the text to the new string, but use the old selection, making sure
* to keep it within the new string.
* @param text The new text to place in the textfield.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 06c70ca..630450d 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -64,7 +64,9 @@
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.Toast;
@@ -82,6 +84,8 @@
import java.util.List;
import java.util.Map;
+import junit.framework.Assert;
+
/**
* <p>A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
@@ -390,6 +394,8 @@
private static final int LONG_PRESS_TIMEOUT = 1000;
// needed to avoid flinging after a pause of no movement
private static final int MIN_FLING_TIME = 250;
+ // draw unfiltered after drag is held without movement
+ private static final int MOTIONLESS_TIME = 100;
// The time that the Zoom Controls are visible before fading away
private static final long ZOOM_CONTROLS_TIMEOUT =
ViewConfiguration.getZoomControlsTimeout();
@@ -427,6 +433,10 @@
private Scroller mScroller;
private boolean mWrapContent;
+ private static final int MOTIONLESS_FALSE = 0;
+ private static final int MOTIONLESS_PENDING = 1;
+ private static final int MOTIONLESS_TRUE = 2;
+ private int mHeldMotionless;
/**
* Private message ids
@@ -438,6 +448,8 @@
private static final int RELEASE_SINGLE_TAP = 5;
private static final int REQUEST_FORM_DATA = 6;
private static final int RESUME_WEBCORE_UPDATE = 7;
+ private static final int DRAG_HELD_MOTIONLESS = 8;
+ private static final int AWAKEN_SCROLL_BARS = 9;
//! arg1=x, arg2=y
static final int SCROLL_TO_MSG_ID = 10;
@@ -450,6 +462,7 @@
static final int UPDATE_TEXT_ENTRY_MSG_ID = 15;
static final int WEBCORE_INITIALIZED_MSG_ID = 16;
static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17;
+ static final int FIND_AGAIN = 18;
static final int MOVE_OUT_OF_PLUGIN = 19;
static final int CLEAR_TEXT_ENTRY = 20;
static final int UPDATE_TEXT_SELECTION_MSG_ID = 21;
@@ -468,9 +481,9 @@
"SWITCH_TO_LONGPRESS", // = 4;
"RELEASE_SINGLE_TAP", // = 5;
"REQUEST_FORM_DATA", // = 6;
- "SWITCH_TO_CLICK", // = 7;
- "RESUME_WEBCORE_UPDATE", // = 8;
- "9",
+ "RESUME_WEBCORE_UPDATE", // = 7;
+ "DRAG_HELD_MOTIONLESS", // = 8;
+ "AWAKEN_SCROLL_BARS", // = 9;
"SCROLL_TO_MSG_ID", // = 10;
"SCROLL_BY_MSG_ID", // = 11;
"SPAWN_SCROLL_TO_MSG_ID", // = 12;
@@ -479,7 +492,7 @@
"UPDATE_TEXT_ENTRY_MSG_ID", // = 15;
"WEBCORE_INITIALIZED_MSG_ID", // = 16;
"UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17;
- "18", // = 18;
+ "FIND_AGAIN", // = 18;
"MOVE_OUT_OF_PLUGIN", // = 19;
"CLEAR_TEXT_ENTRY", // = 20;
"UPDATE_TEXT_SELECTION_MSG_ID", // = 21;
@@ -535,11 +548,10 @@
private boolean mUserScroll = false;
private int mSnapScrollMode = SNAP_NONE;
- private static final int SNAP_NONE = 1;
- private static final int SNAP_X = 2;
- private static final int SNAP_Y = 3;
- private static final int SNAP_X_LOCK = 4;
- private static final int SNAP_Y_LOCK = 5;
+ private static final int SNAP_NONE = 0;
+ private static final int SNAP_LOCK = 1; // not a separate state
+ private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
+ private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
private boolean mSnapPositive;
// Used to match key downs and key ups
@@ -2351,6 +2363,7 @@
}
int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
invalidate();
+ mLastFind = find;
return result;
}
@@ -2358,6 +2371,9 @@
// or not we draw the highlights for matches.
private boolean mFindIsUp;
private int mFindHeight;
+ // Keep track of the last string sent, so we can search again after an
+ // orientation change or the dismissal of the soft keyboard.
+ private String mLastFind;
/**
* Return the first substring consisting of the address of a physical
@@ -2532,6 +2548,41 @@
}
}
+ /**
+ * Called by CallbackProxy when the page finishes loading.
+ * @param url The URL of the page which has finished loading.
+ */
+ /* package */ void onPageFinished(String url) {
+ if (mPageThatNeedsToSlideTitleBarOffScreen != null) {
+ // If the user is now on a different page, or has scrolled the page
+ // past the point where the title bar is offscreen, ignore the
+ // scroll request.
+ if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url)
+ && mScrollX == 0 && mScrollY == 0) {
+ pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true,
+ SLIDE_TITLE_DURATION);
+ }
+ mPageThatNeedsToSlideTitleBarOffScreen = null;
+ }
+ }
+
+ /**
+ * The URL of a page that sent a message to scroll the title bar off screen.
+ *
+ * Many mobile sites tell the page to scroll to (0,1) in order to scroll the
+ * title bar off the screen. Sometimes, the scroll position is set before
+ * the page finishes loading. Rather than scrolling while the page is still
+ * loading, keep track of the URL and new scroll position so we can perform
+ * the scroll once the page finishes loading.
+ */
+ private String mPageThatNeedsToSlideTitleBarOffScreen;
+
+ /**
+ * The destination Y scroll position to be used when the page finishes
+ * loading. See mPageThatNeedsToSlideTitleBarOffScreen.
+ */
+ private int mYDistanceToSlideTitleOffScreen;
+
// scale from content to view coordinates, and pin
// return true if pin caused the final x/y different than the request cx/cy,
// and a future scroll may reach the request cx/cy after our size has
@@ -2566,8 +2617,18 @@
// page, assume this is an attempt to scroll off the title bar, and
// animate the title bar off screen slowly enough that the user can see
// it.
- if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0) {
- pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+ if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0
+ && mTitleBar != null) {
+ // FIXME: 100 should be defined somewhere as our max progress.
+ if (getProgress() < 100) {
+ // Wait to scroll the title bar off screen until the page has
+ // finished loading. Keep track of the URL and the destination
+ // Y position
+ mPageThatNeedsToSlideTitleBarOffScreen = getUrl();
+ mYDistanceToSlideTitleOffScreen = vy;
+ } else {
+ pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+ }
// Since we are animating, we have not yet reached the desired
// scroll position. Do not return true to request another attempt
return false;
@@ -2608,12 +2669,12 @@
if (mHeightCanMeasure) {
if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else if (mWidthCanMeasure) {
if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else {
@@ -2633,6 +2694,16 @@
}
/**
+ * Gets the WebViewClient
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mCallbackProxy.getWebViewClient();
+ }
+
+ /**
* Register the interface to be used when content can not be handled by
* the rendering engine, and should be downloaded instead. This will replace
* the current handler.
@@ -2813,10 +2884,7 @@
public boolean performLongClick() {
if (mNativeClass != 0 && nativeCursorIsTextInput()) {
// Send the click so that the textfield is in focus
- // FIXME: When we start respecting changes to the native textfield's
- // selection, need to make sure that this does not change it.
- mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
- nativeCursorNodePointer());
+ centerKeyPressOnTextField();
rebuildWebTextView();
}
if (inEditingMode()) {
@@ -2839,6 +2907,21 @@
*/
private boolean mNeedToAdjustWebTextView;
+ private boolean didUpdateTextViewBounds(boolean allowIntersect) {
+ Rect contentBounds = nativeFocusCandidateNodeBounds();
+ Rect vBox = contentToViewRect(contentBounds);
+ Rect visibleRect = new Rect();
+ calcOurVisibleRect(visibleRect);
+ if (allowIntersect ? Rect.intersects(visibleRect, vBox) :
+ visibleRect.contains(vBox)) {
+ mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
+ vBox.height());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
private void drawCoreAndCursorRing(Canvas canvas, int color,
boolean drawCursorRing) {
if (mDrawHistory) {
@@ -2848,8 +2931,22 @@
}
boolean animateZoom = mZoomScale != 0;
- boolean animateScroll = !mScroller.isFinished()
- || mVelocityTracker != null;
+ boolean animateScroll = (!mScroller.isFinished()
+ || mVelocityTracker != null)
+ && (mTouchMode != TOUCH_DRAG_MODE ||
+ mHeldMotionless != MOTIONLESS_TRUE);
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ if (mHeldMotionless == MOTIONLESS_PENDING) {
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_FALSE;
+ }
+ if (mHeldMotionless == MOTIONLESS_FALSE) {
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
+ mHeldMotionless = MOTIONLESS_PENDING;
+ }
+ }
if (animateZoom) {
float zoomScale;
int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
@@ -2866,19 +2963,13 @@
invalidate();
if (mNeedToAdjustWebTextView) {
mNeedToAdjustWebTextView = false;
- Rect contentBounds = nativeFocusCandidateNodeBounds();
- Rect vBox = contentToViewRect(contentBounds);
- Rect visibleRect = new Rect();
- calcOurVisibleRect(visibleRect);
- if (visibleRect.contains(vBox)) {
- // As a result of the zoom, the textfield is now on
- // screen. Place the WebTextView in its new place,
- // accounting for our new scroll/zoom values.
+ // As a result of the zoom, the textfield is now on
+ // screen. Place the WebTextView in its new place,
+ // accounting for our new scroll/zoom values.
+ if (didUpdateTextViewBounds(false)) {
mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
contentToViewDimension(
nativeFocusCandidateTextSize()));
- mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
- vBox.height());
// If it is a password field, start drawing the
// WebTextView once again.
if (nativeFocusCandidateIsPassword()) {
@@ -2930,11 +3021,12 @@
if (mNativeClass == 0) return;
if (mShiftIsPressed && !animateZoom) {
- if (mTouchSelection) {
+ if (mTouchSelection || mExtendSelection) {
nativeDrawSelectionRegion(canvas);
- } else {
- nativeDrawSelection(canvas, mInvActualScale, getTitleHeight(),
- mSelectX, mSelectY, mExtendSelection);
+ }
+ if (!mTouchSelection) {
+ nativeDrawSelectionPointer(canvas, mInvActualScale, mSelectX,
+ mSelectY - getTitleHeight(), mExtendSelection);
}
} else if (drawCursorRing) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
@@ -2954,6 +3046,10 @@
if (mFindIsUp && !animateScroll) {
nativeDrawMatches(canvas);
}
+ if (mFocusSizeChanged) {
+ mFocusSizeChanged = false;
+ didUpdateTextViewBounds(true);
+ }
}
// draw history
@@ -3049,6 +3145,16 @@
imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
}
+ /**
+ * Only for calling from JNI. Allows a click on an unfocused textfield to
+ * put the textfield in focus.
+ */
+ private void setOkayNotToMatch() {
+ if (inEditingMode()) {
+ mWebTextView.mOkayForFocusNotToMatch = true;
+ }
+ }
+
/*
* This method checks the current focus and cursor and potentially rebuilds
* mWebTextView to have the appropriate properties, such as password,
@@ -3104,6 +3210,7 @@
&& nativeTextGeneration() == mTextGeneration) {
mWebTextView.setTextAndKeepSelection(text);
} else {
+ // FIXME: Determine whether this is necessary.
Selection.setSelection(spannable, start, end);
}
} else {
@@ -3136,34 +3243,12 @@
mWebTextView.setSingleLine(isTextField);
mWebTextView.setInPassword(nativeFocusCandidateIsPassword());
if (null == text) {
- mWebTextView.setText("", 0, 0);
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "rebuildWebTextView null == text");
}
- } else {
- // Change to true to enable the old style behavior, where
- // entering a textfield/textarea always set the selection to the
- // whole field. This was desirable for the case where the user
- // intends to scroll past the field using the trackball.
- // However, it causes a problem when replying to emails - the
- // user expects the cursor to be at the beginning of the
- // textarea. Testing out a new behavior, where textfields set
- // selection at the end, and textareas at the beginning.
- if (false) {
- mWebTextView.setText(text, 0, text.length());
- } else if (isTextField) {
- int length = text.length();
- mWebTextView.setText(text, length, length);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "rebuildWebTextView length=" + length);
- }
- } else {
- mWebTextView.setText(text, 0, 0);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "rebuildWebTextView !isTextField");
- }
- }
+ text = "";
}
+ mWebTextView.setTextAndKeepSelection(text);
mWebTextView.requestFocus();
}
}
@@ -3230,23 +3315,22 @@
if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false
&& (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
- mExtendSelection = false;
- mShiftIsPressed = true;
- if (nativeHasCursorNode()) {
- Rect rect = nativeCursorNodeBounds();
- mSelectX = contentToViewX(rect.left);
- mSelectY = contentToViewY(rect.top);
- } else {
- mSelectX = mScrollX + (int) mLastTouchX;
- mSelectY = mScrollY + (int) mLastTouchY;
- }
- nativeHideCursor();
- }
+ setUpSelectXY();
+ }
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
// always handle the navigation keys in the UI thread
switchOutDrawHistory();
+ if (mShiftIsPressed) {
+ int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+ ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
+ int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
+ -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0;
+ int multiplier = event.getRepeatCount() + 1;
+ moveSelection(xRate * multiplier, yRate * multiplier);
+ return true;
+ }
if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) {
playSoundEffect(keyCodeToSoundsEffect(keyCode));
return true;
@@ -3258,6 +3342,9 @@
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
switchOutDrawHistory();
if (event.getRepeatCount() == 0) {
+ if (mShiftIsPressed) {
+ return true; // discard press if copy in progress
+ }
mGotCenterDown = true;
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
@@ -3307,10 +3394,7 @@
}
}
- if (nativeCursorIsPlugin()) {
- nativeUpdatePluginReceivesEvents();
- invalidate();
- } else if (nativeCursorIsTextInput()) {
+ if (nativeCursorIsTextInput()) {
// This message will put the node in focus, for the DOM's notion
// of focus, and make the focuscontroller active
mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
@@ -3319,13 +3403,17 @@
// our view system's notion of focus
rebuildWebTextView();
// Now we need to pass the event to it
- return mWebTextView.onKeyDown(keyCode, event);
+ if (inEditingMode()) {
+ mWebTextView.setDefaultSelection();
+ mWebTextView.mOkayForFocusNotToMatch = true;
+ return mWebTextView.dispatchKeyEvent(event);
+ }
} else if (nativeHasFocusNode()) {
// In this case, the cursor is not on a text input, but the focus
// might be. Check it, and if so, hand over to the WebTextView.
rebuildWebTextView();
if (inEditingMode()) {
- return mWebTextView.onKeyDown(keyCode, event);
+ return mWebTextView.dispatchKeyEvent(event);
}
}
@@ -3390,7 +3478,13 @@
mGotCenterDown = false;
if (mShiftIsPressed) {
- return false;
+ if (mExtendSelection) {
+ commitCopy();
+ } else {
+ mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
+ }
+ return true; // discard press if copy in progress
}
// perform the single click
@@ -3400,21 +3494,23 @@
if (!nativeCursorIntersects(visibleRect)) {
return false;
}
- nativeSetFollowedLink(true);
- nativeUpdatePluginReceivesEvents();
WebViewCore.CursorData data = cursorData();
mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
playSoundEffect(SoundEffectConstants.CLICK);
- boolean isTextInput = nativeCursorIsTextInput();
- if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading(
- nativeCursorText())) {
+ if (nativeCursorIsTextInput()) {
+ rebuildWebTextView();
+ centerKeyPressOnTextField();
+ if (inEditingMode()) {
+ mWebTextView.setDefaultSelection();
+ mWebTextView.mOkayForFocusNotToMatch = true;
+ }
+ return true;
+ }
+ nativeSetFollowedLink(true);
+ if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
nativeCursorNodePointer());
}
- if (isTextInput) {
- rebuildWebTextView();
- displaySoftKeyboard(true);
- }
return true;
}
@@ -3430,14 +3526,29 @@
return false;
}
+ private void setUpSelectXY() {
+ mExtendSelection = false;
+ mShiftIsPressed = true;
+ if (nativeHasCursorNode()) {
+ Rect rect = nativeCursorNodeBounds();
+ mSelectX = contentToViewX(rect.left);
+ mSelectY = contentToViewY(rect.top);
+ } else if (mLastTouchY > getVisibleTitleHeight()) {
+ mSelectX = mScrollX + (int) mLastTouchX;
+ mSelectY = mScrollY + (int) mLastTouchY;
+ } else {
+ mSelectX = mScrollX + getViewWidth() / 2;
+ mSelectY = mScrollY + getViewHeightWithTitle() / 2;
+ }
+ nativeHideCursor();
+ }
+
/**
* @hide
*/
public void emulateShiftHeld() {
if (0 == mNativeClass) return; // client isn't initialized
- mExtendSelection = false;
- mShiftIsPressed = true;
- nativeHideCursor();
+ setUpSelectXY();
}
private boolean commitCopy() {
@@ -3455,6 +3566,7 @@
mExtendSelection = false;
}
mShiftIsPressed = false;
+ invalidate(); // remove selection region and pointer
if (mTouchMode == TOUCH_SELECT_MODE) {
mTouchMode = TOUCH_INIT_MODE;
}
@@ -3598,6 +3710,24 @@
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
+ /**
+ * @hide
+ */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean changed = super.setFrame(left, top, right, bottom);
+ if (!changed && mHeightCanMeasure) {
+ // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
+ // in WebViewCore after we get the first layout. We do call
+ // requestLayout() when we get contentSizeChanged(). But the View
+ // system won't call onSizeChanged if the dimension is not changed.
+ // In this case, we need to call sendViewSizeZoom() explicitly to
+ // notify the WebKit about the new dimensions.
+ sendViewSizeZoom();
+ }
+ return changed;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
@@ -3706,8 +3836,10 @@
mLastSentTouchTime = eventTime;
}
- int deltaX = (int) (mLastTouchX - x);
- int deltaY = (int) (mLastTouchY - y);
+ float fDeltaX = mLastTouchX - x;
+ float fDeltaY = mLastTouchY - y;
+ int deltaX = (int) fDeltaX;
+ int deltaY = (int) fDeltaY;
switch (action) {
case MotionEvent.ACTION_DOWN: {
@@ -3729,6 +3861,7 @@
nativeMoveSelection(viewToContentX(mSelectX),
viewToContentY(mSelectY), false);
mTouchSelection = mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
} else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
@@ -3833,12 +3966,21 @@
// do pan
int newScrollX = pinLocX(mScrollX + deltaX);
- deltaX = newScrollX - mScrollX;
+ int newDeltaX = newScrollX - mScrollX;
+ if (deltaX != newDeltaX) {
+ deltaX = newDeltaX;
+ fDeltaX = (float) newDeltaX;
+ }
int newScrollY = pinLocY(mScrollY + deltaY);
- deltaY = newScrollY - mScrollY;
+ int newDeltaY = newScrollY - mScrollY;
+ if (deltaY != newDeltaY) {
+ deltaY = newDeltaY;
+ fDeltaY = (float) newDeltaY;
+ }
boolean done = false;
- if (deltaX == 0 && deltaY == 0) {
- done = true;
+ boolean keepScrollBarsVisible = false;
+ if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
+ keepScrollBarsVisible = done = true;
} else {
if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
int ax = Math.abs(deltaX);
@@ -3850,56 +3992,47 @@
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
- ((mSnapPositive &&
- deltaX < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaX > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_X_LOCK;
+ if (ax > MAX_SLOPE_FOR_DIAG * ay &&
+ (mSnapPositive
+ ? deltaX < -mMinLockSnapReverseDistance
+ : deltaX > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
} else {
// radical change means getting out of snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay)
+ if (ax > MAX_SLOPE_FOR_DIAG * ay
&& ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
- ((mSnapPositive &&
- deltaY < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaY > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_Y_LOCK;
+ if (ay > MAX_SLOPE_FOR_DIAG * ax &&
+ (mSnapPositive
+ ? deltaY < -mMinLockSnapReverseDistance
+ : deltaY > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
}
}
-
- if (mSnapScrollMode == SNAP_X
- || mSnapScrollMode == SNAP_X_LOCK) {
- if (deltaX == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
+ if (mSnapScrollMode != SNAP_NONE) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
+ deltaY = 0;
} else {
- scrollBy(deltaX, 0);
+ deltaX = 0;
}
- mLastTouchX = x;
- } else if (mSnapScrollMode == SNAP_Y
- || mSnapScrollMode == SNAP_Y_LOCK) {
- if (deltaY == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
- } else {
- scrollBy(0, deltaY);
- }
- mLastTouchY = y;
- } else {
+ }
+ if ((deltaX | deltaY) != 0) {
scrollBy(deltaX, deltaY);
- mLastTouchX = x;
- mLastTouchY = y;
+ if (deltaX != 0) {
+ mLastTouchX = x;
+ }
+ if (deltaY != 0) {
+ mLastTouchY = y;
+ }
+ mHeldMotionless = MOTIONLESS_FALSE;
+ } else {
+ // keep the scrollbar on the screen even there is no
+ // scroll
+ keepScrollBarsVisible = true;
}
mLastTouchTime = eventTime;
mUserScroll = true;
@@ -3918,13 +4051,17 @@
}
}
- if (done) {
+ if (keepScrollBarsVisible) {
+ if (mHeldMotionless != MOTIONLESS_TRUE) {
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ }
// keep the scrollbar on the screen even there is no scroll
awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
false);
// return false to indicate that we can't pan out of the
// view space
- return false;
+ return !done;
}
break;
}
@@ -3977,6 +4114,9 @@
break;
}
case TOUCH_DRAG_MODE:
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
// redraw in high-quality, as we're done dragging
invalidate();
// if the user waits a while w/o moving before the
@@ -4016,6 +4156,9 @@
}
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
mTouchMode = TOUCH_DONE_MODE;
nativeHideCursor();
break;
@@ -4042,6 +4185,7 @@
private static final int SELECT_CURSOR_OFFSET = 16;
private int mSelectX = 0;
private int mSelectY = 0;
+ private boolean mFocusSizeChanged = false;
private boolean mShiftIsPressed = false;
private boolean mTrackballDown = false;
private long mTrackballUpTime = 0;
@@ -4100,6 +4244,7 @@
commitCopy();
} else {
mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
}
return true; // discard press if copy in progress
}
@@ -4147,8 +4292,8 @@
return;
int width = getViewWidth();
int height = getViewHeight();
- mSelectX += scaleTrackballX(xRate, width);
- mSelectY += scaleTrackballY(yRate, height);
+ mSelectX += xRate;
+ mSelectY += yRate;
int maxX = width + mScrollX;
int maxY = height + mScrollY;
mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
@@ -4230,8 +4375,11 @@
}
float xRate = mTrackballRemainsX * 1000 / elapsed;
float yRate = mTrackballRemainsY * 1000 / elapsed;
+ int viewWidth = getViewWidth();
+ int viewHeight = getViewHeight();
if (mShiftIsPressed) {
- moveSelection(xRate, yRate);
+ moveSelection(scaleTrackballX(xRate, viewWidth),
+ scaleTrackballY(yRate, viewHeight));
mTrackballRemainsX = mTrackballRemainsY = 0;
return;
}
@@ -4245,8 +4393,8 @@
+ " mTrackballRemainsX=" + mTrackballRemainsX
+ " mTrackballRemainsY=" + mTrackballRemainsY);
}
- int width = mContentWidth - getViewWidth();
- int height = mContentHeight - getViewHeight();
+ int width = mContentWidth - viewWidth;
+ int height = mContentHeight - viewHeight;
if (width < 0) width = 0;
if (height < 0) height = 0;
ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
@@ -4321,7 +4469,7 @@
int vy = (int) mVelocityTracker.getYVelocity();
if (mSnapScrollMode != SNAP_NONE) {
- if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
vy = 0;
} else {
vx = 0;
@@ -4601,21 +4749,21 @@
}
int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
- // In case the soft keyboard has been dismissed, bring it back up.
- InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView,
- 0);
if (nativeFocusNodePointer() != nativeCursorNodePointer()) {
nativeMotionUp(x, y, mNavSlop);
}
nativeTextInputMotionUp(x, y);
}
- /*package*/ void shortPressOnTextField() {
- if (inEditingMode()) {
- View v = mWebTextView;
- int x = viewToContentX((v.getLeft() + v.getRight()) >> 1);
- int y = viewToContentY((v.getTop() + v.getBottom()) >> 1);
- nativeTextInputMotionUp(x, y);
+ /**
+ * Called when pressing the center key or trackball on a textfield.
+ */
+ /*package*/ void centerKeyPressOnTextField() {
+ mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
+ nativeCursorNodePointer());
+ // Need to show the soft keyboard if it's not readonly.
+ if (!nativeCursorIsReadOnly()) {
+ displaySoftKeyboard(true);
}
}
@@ -4685,15 +4833,6 @@
mCallbackProxy.uiOverrideUrlLoading(url);
}
- // called by JNI
- private void sendPluginState(int state) {
- WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData();
- psd.mFrame = nativeCursorFramePointer();
- psd.mNode = nativeCursorNodePointer();
- psd.mState = state;
- mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd);
- }
-
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
boolean result = false;
@@ -4842,14 +4981,6 @@
}
/* package */ void passToJavaScript(String currentText, KeyEvent event) {
- if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) {
- mWebViewCore.sendMessage(EventHub.CLICK);
- if (mWebTextView.mOkayForFocusNotToMatch) {
- int select = nativeFocusCandidateIsTextField() ?
- nativeFocusCandidateMaxLength() : 0;
- setSelection(select, select);
- }
- }
WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
arg.mEvent = event;
arg.mCurrentText = currentText;
@@ -4880,9 +5011,10 @@
class PrivateHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- if (DebugFlags.WEB_VIEW) {
+ // exclude INVAL_RECT_MSG_ID since it is frequently output
+ if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
- > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
+ > REQUEST_KEYBOARD ? Integer.toString(msg.what)
: HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
}
if (mWebViewCore == null) {
@@ -5044,6 +5176,9 @@
/ mZoomOverviewWidth, false);
}
}
+ if (draw.mFocusSizeChanged && inEditingMode()) {
+ mFocusSizeChanged = true;
+ }
break;
}
case WEBCORE_INITIALIZED_MSG_ID:
@@ -5084,9 +5219,7 @@
}
break;
case MOVE_OUT_OF_PLUGIN:
- if (nativePluginEatsNavKey()) {
- navHandledKey(msg.arg1, 1, false, 0, true);
- }
+ navHandledKey(msg.arg1, 1, false, 0, true);
break;
case UPDATE_TEXT_ENTRY_MSG_ID:
// this is sent after finishing resize in WebViewCore. Make
@@ -5170,9 +5303,36 @@
hideSoftKeyboard();
} else {
displaySoftKeyboard(false);
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "REQUEST_KEYBOARD"
+ + " focusCandidateIsPlugin="
+ + nativeFocusCandidateIsPlugin());
+ }
}
break;
+ case FIND_AGAIN:
+ // Ignore if find has been dismissed.
+ if (mFindIsUp) {
+ findAll(mLastFind);
+ }
+ break;
+
+ case DRAG_HELD_MOTIONLESS:
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ // fall through to keep scrollbars awake
+
+ case AWAKEN_SCROLL_BARS:
+ if (mTouchMode == TOUCH_DRAG_MODE
+ && mHeldMotionless == MOTIONLESS_TRUE) {
+ awakenScrollBars(ViewConfiguration
+ .getScrollDefaultDelay(), false);
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(AWAKEN_SCROLL_BARS),
+ ViewConfiguration.getScrollDefaultDelay());
+ }
+ break;
default:
super.handleMessage(msg);
break;
@@ -5196,8 +5356,16 @@
// Need these to provide stable ids to my ArrayAdapter,
// which normally does not have stable ids. (Bug 1250098)
private class Container extends Object {
+ /**
+ * Possible values for mEnabled. Keep in sync with OptionStatus in
+ * WebViewCore.cpp
+ */
+ final static int OPTGROUP = -1;
+ final static int OPTION_DISABLED = 0;
+ final static int OPTION_ENABLED = 1;
+
String mString;
- boolean mEnabled;
+ int mEnabled;
int mId;
public String toString() {
@@ -5218,6 +5386,54 @@
}
@Override
+ public View getView(int position, View convertView,
+ ViewGroup parent) {
+ // Always pass in null so that we will get a new CheckedTextView
+ // Otherwise, an item which was previously used as an <optgroup>
+ // element (i.e. has no check), could get used as an <option>
+ // element, which needs a checkbox/radio, but it would not have
+ // one.
+ convertView = super.getView(position, null, parent);
+ Container c = item(position);
+ if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
+ // ListView does not draw dividers between disabled and
+ // enabled elements. Use a LinearLayout to provide dividers
+ LinearLayout layout = new LinearLayout(mContext);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ if (position > 0) {
+ View dividerTop = new View(mContext);
+ dividerTop.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerTop);
+ }
+
+ if (Container.OPTGROUP == c.mEnabled) {
+ // Currently select_dialog_multichoice and
+ // select_dialog_singlechoice are CheckedTextViews. If
+ // that changes, the class cast will no longer be valid.
+ Assert.assertTrue(
+ convertView instanceof CheckedTextView);
+ ((CheckedTextView) convertView).setCheckMarkDrawable(
+ null);
+ } else {
+ // c.mEnabled == Container.OPTION_DISABLED
+ // Draw the disabled element in a disabled state.
+ convertView.setEnabled(false);
+ }
+
+ layout.addView(convertView);
+ if (position < getCount() - 1) {
+ View dividerBottom = new View(mContext);
+ dividerBottom.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerBottom);
+ }
+ return layout;
+ }
+ return convertView;
+ }
+
+ @Override
public boolean hasStableIds() {
// AdapterView's onChanged method uses this to determine whether
// to restore the old state. Return false so that the old (out
@@ -5252,12 +5468,11 @@
if (item == null) {
return false;
}
- return item.mEnabled;
+ return Container.OPTION_ENABLED == item.mEnabled;
}
}
- private InvokeListBox(String[] array,
- boolean[] enabled, int[] selected) {
+ private InvokeListBox(String[] array, int[] enabled, int[] selected) {
mMultiple = true;
mSelectedArray = selected;
@@ -5271,8 +5486,7 @@
}
}
- private InvokeListBox(String[] array, boolean[] enabled, int
- selection) {
+ private InvokeListBox(String[] array, int[] enabled, int selection) {
mSelection = selection;
mMultiple = false;
@@ -5403,10 +5617,11 @@
* Request a dropdown menu for a listbox with multiple selection.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selectedArray Which positions are initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int[]
+ void requestListBox(String[] array, int[] enabledArray, int[]
selectedArray) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selectedArray));
@@ -5417,10 +5632,11 @@
* <select> element.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selection Which position is initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int selection) {
+ void requestListBox(String[] array, int[] enabledArray, int selection) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selection));
}
@@ -5504,7 +5720,7 @@
if (mNativeClass == 0) {
return false;
}
- if (ignorePlugin == false && nativePluginEatsNavKey()) {
+ if (ignorePlugin == false && nativeFocusIsPlugin()) {
KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN
, keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0)
| (false ? KeyEvent.META_ALT_ON : 0) // FIXME
@@ -5579,6 +5795,17 @@
}
/**
+ * Draw the HTML page into the specified canvas. This call ignores any
+ * view-specific zoom, scroll offset, or other changes. It does not draw
+ * any view-specific chrome, such as progress or URL bars.
+ *
+ * @hide only needs to be accessible to Browser and testing
+ */
+ public void drawPage(Canvas canvas) {
+ mWebViewCore.drawContentPicture(canvas, 0, false, false);
+ }
+
+ /**
* Update our cache with updatedText.
* @param updatedText The new text to put in our cache.
*/
@@ -5596,7 +5823,7 @@
/* package */ native boolean nativeCursorMatchesFocus();
private native boolean nativeCursorIntersects(Rect visibleRect);
private native boolean nativeCursorIsAnchor();
- private native boolean nativeCursorIsPlugin();
+ private native boolean nativeCursorIsReadOnly();
private native boolean nativeCursorIsTextInput();
private native Point nativeCursorPosition();
private native String nativeCursorText();
@@ -5609,13 +5836,15 @@
private native void nativeDestroy();
private native void nativeDrawCursorRing(Canvas content);
private native void nativeDrawMatches(Canvas canvas);
- private native void nativeDrawSelection(Canvas content, float scale,
- int offset, int x, int y, boolean extendSelection);
+ private native void nativeDrawSelectionPointer(Canvas content,
+ float scale, int x, int y, boolean extendSelection);
private native void nativeDrawSelectionRegion(Canvas content);
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
private native void nativeFindNext(boolean forward);
+ private native int nativeFocusCandidateFramePointer();
private native boolean nativeFocusCandidateIsPassword();
+ private native boolean nativeFocusCandidateIsPlugin();
private native boolean nativeFocusCandidateIsRtlText();
private native boolean nativeFocusCandidateIsTextField();
private native boolean nativeFocusCandidateIsTextInput();
@@ -5625,6 +5854,7 @@
/* package */ native int nativeFocusCandidatePointer();
private native String nativeFocusCandidateText();
private native int nativeFocusCandidateTextSize();
+ private native boolean nativeFocusIsPlugin();
/* package */ native int nativeFocusNodePointer();
private native Rect nativeGetCursorRingBounds();
private native Region nativeGetSelection();
@@ -5642,7 +5872,6 @@
private native int nativeMoveGeneration();
private native void nativeMoveSelection(int x, int y,
boolean extendSelection);
- private native boolean nativePluginEatsNavKey();
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
private native void nativeRecordButtons(boolean focused,
@@ -5664,7 +5893,6 @@
// we always want to pass in our generation number.
private native void nativeUpdateCachedTextfield(String updatedText,
int generation);
- private native void nativeUpdatePluginReceivesEvents();
// return NO_LEFTEDGE means failure.
private static final int NO_LEFTEDGE = -1;
private native int nativeGetBlockLeftEdge(int x, int y, float scale);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index a5a4852..bfaa353 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.DrawFilter;
import android.graphics.Paint;
@@ -26,11 +27,13 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.provider.Browser;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
@@ -273,6 +276,39 @@
mCallbackProxy.onJsAlert(url, message);
}
+
+ /**
+ * Called by JNI. Open a file chooser to upload a file.
+ * @return String version of the URI plus the name of the file.
+ * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call
+ * into Java to get the filename.
+ */
+ private String openFileChooser() {
+ Uri uri = mCallbackProxy.openFileChooser();
+ if (uri == null) return "";
+ // Find out the name, and append it to the URI.
+ // Webkit will treat the name as the filename, and
+ // the URI as the path. The URI will be used
+ // in BrowserFrame to get the actual data.
+ Cursor cursor = mContext.getContentResolver().query(
+ uri,
+ new String[] { OpenableColumns.DISPLAY_NAME },
+ null,
+ null,
+ null);
+ String name = "";
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ name = cursor.getString(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return uri.toString() + "/" + name;
+ }
+
/**
* Notify the browser that the origin has exceeded it's database quota.
* @param url The URL that caused the overflow.
@@ -422,6 +458,8 @@
*/
private native boolean nativeRecordContent(Region invalRegion, Point wh);
+ private native boolean nativeFocusBoundsChanged();
+
/**
* Splits slow parts of the picture set. Called from the webkit
* thread after nativeDrawContent returns true.
@@ -522,8 +560,6 @@
*/
private native void nativeSetNewStorageLimit(long limit);
- private native void nativeUpdatePluginState(int framePtr, int nodePtr, int state);
-
/**
* Provide WebCore with a Geolocation permission state for the specified
* origin.
@@ -678,12 +714,6 @@
int mY;
}
- static class PluginStateData {
- int mFrame;
- int mNode;
- int mState;
- }
-
static class GeolocationPermissionsData {
String mOrigin;
boolean mAllow;
@@ -720,7 +750,7 @@
"SINGLE_LISTBOX_CHOICE", // = 124;
"MESSAGE_RELAY", // = 125;
"SET_BACKGROUND_COLOR", // = 126;
- "PLUGIN_STATE", // = 127;
+ "127", // = 127
"SAVE_DOCUMENT_STATE", // = 128;
"GET_SELECTION", // = 129;
"WEBKIT_DRAW", // = 130;
@@ -771,7 +801,6 @@
static final int SINGLE_LISTBOX_CHOICE = 124;
static final int MESSAGE_RELAY = 125;
static final int SET_BACKGROUND_COLOR = 126;
- static final int PLUGIN_STATE = 127; // plugin notifications
static final int SAVE_DOCUMENT_STATE = 128;
static final int GET_SELECTION = 129;
static final int WEBKIT_DRAW = 130;
@@ -1031,11 +1060,6 @@
nativeFreeMemory();
break;
- case PLUGIN_STATE:
- PluginStateData psd = (PluginStateData) msg.obj;
- nativeUpdatePluginState(psd.mFrame, psd.mNode, psd.mState);
- break;
-
case SET_NETWORK_STATE:
if (BrowserFrame.sJavaBridge == null) {
throw new IllegalStateException("No WebView " +
@@ -1593,6 +1617,7 @@
int mMinPrefWidth;
RestoreState mRestoreState; // only non-null if it is for the first
// picture set after the first layout
+ boolean mFocusSizeChanged;
}
private void webkitDraw() {
@@ -1607,6 +1632,7 @@
if (mWebView != null) {
// Send the native view size that was used during the most recent
// layout.
+ draw.mFocusSizeChanged = nativeFocusBoundsChanged();
draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
if (mSettings.getUseWideViewPort()) {
draw.mMinPrefWidth = Math.max(
@@ -1643,6 +1669,9 @@
final DrawFilter mZoomFilter =
new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
+ final DrawFilter mScrollFilter = null;
+ // If we need to trade more speed for less quality on slower devices
+ // use this: new PaintFlagsDrawFilter(SCROLL_BITS, 0);
/* package */ void drawContentPicture(Canvas canvas, int color,
boolean animatingZoom,
@@ -1651,7 +1680,7 @@
if (animatingZoom) {
df = mZoomFilter;
} else if (animatingScroll) {
- df = null;
+ df = mScrollFilter;
}
canvas.setDrawFilter(df);
boolean tookTooLong = nativeDrawContent(canvas, color);
@@ -2013,9 +2042,16 @@
// know the exact scale. If mRestoredScale is non-zero, use it;
// otherwise just use mTextWrapScale as the initial scale.
data.mScale = mRestoreState.mViewScale == 0
- ? (mRestoredScale > 0 ? mRestoredScale
+ ? (mRestoredScale > 0 ? mRestoredScale / 100.0f
: mRestoreState.mTextWrapScale)
: mRestoreState.mViewScale;
+ if (DebugFlags.WEB_VIEW_CORE) {
+ Log.v(LOGTAG, "setupViewport"
+ + " mRestoredScale=" + mRestoredScale
+ + " mViewScale=" + mRestoreState.mViewScale
+ + " mTextWrapScale=" + mRestoreState.mTextWrapScale
+ );
+ }
data.mWidth = Math.round(webViewWidth / data.mScale);
data.mHeight = mCurrentViewHeight * data.mWidth / viewportWidth;
data.mTextWrapWidth = Math.round(webViewWidth
@@ -2086,6 +2122,13 @@
WebView.CLEAR_TEXT_ENTRY).sendToTarget();
}
+ // called by JNI
+ private void sendFindAgain() {
+ if (mWebView == null) return;
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.FIND_AGAIN).sendToTarget();
+ }
+
private native void nativeUpdateFrameCacheIfLoading();
/**
@@ -2099,7 +2142,7 @@
private native void nativeSetGlobalBounds(int x, int y, int w, int h);
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int[] selectedArray) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selectedArray);
@@ -2107,7 +2150,7 @@
}
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int selection) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selection);
@@ -2173,6 +2216,11 @@
return view;
}
+ private void updateSurface(ViewManager.ChildView childView, int x, int y,
+ int width, int height) {
+ childView.attachView(x, y, width, height);
+ }
+
private void destroySurface(ViewManager.ChildView childView) {
childView.removeView();
}
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 6e10811..110e4f8 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -27,6 +27,7 @@
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import android.webkit.CookieManager.Cookie;
@@ -174,7 +175,16 @@
public static synchronized WebViewDatabase getInstance(Context context) {
if (mInstance == null) {
mInstance = new WebViewDatabase();
- mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
+ try {
+ mDatabase = context
+ .openOrCreateDatabase(DATABASE_FILE, 0, null);
+ } catch (SQLiteException e) {
+ // try again by deleting the old db and create a new one
+ if (context.deleteDatabase(DATABASE_FILE)) {
+ mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
+ null);
+ }
+ }
// mDatabase should not be null,
// the only case is RequestAPI test has problem to create db
@@ -194,8 +204,16 @@
mDatabase.setLockingEnabled(false);
}
- mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE,
- 0, null);
+ try {
+ mCacheDatabase = context.openOrCreateDatabase(
+ CACHE_DATABASE_FILE, 0, null);
+ } catch (SQLiteException e) {
+ // try again by deleting the old db and create a new one
+ if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
+ mCacheDatabase = context.openOrCreateDatabase(
+ CACHE_DATABASE_FILE, 0, null);
+ }
+ }
// mCacheDatabase should not be null,
// the only case is RequestAPI test has problem to create db
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 165794a..5991ad4 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3560,6 +3560,7 @@
// into the scrap heap
int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
+ removeDetachedView(scrap, false);
return;
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index fd590ed..aa9062b 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -117,11 +117,11 @@
* @param d The Drawable to use for the checkmark.
*/
public void setCheckMarkDrawable(Drawable d) {
+ if (mCheckMarkDrawable != null) {
+ mCheckMarkDrawable.setCallback(null);
+ unscheduleDrawable(mCheckMarkDrawable);
+ }
if (d != null) {
- if (mCheckMarkDrawable != null) {
- mCheckMarkDrawable.setCallback(null);
- unscheduleDrawable(mCheckMarkDrawable);
- }
d.setCallback(this);
d.setVisible(getVisibility() == VISIBLE, false);
d.setState(CHECKED_STATE_SET);
@@ -130,10 +130,10 @@
mCheckMarkWidth = d.getIntrinsicWidth();
mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
d.setState(getDrawableState());
- mCheckMarkDrawable = d;
} else {
mPaddingRight = mBasePaddingRight;
}
+ mCheckMarkDrawable = d;
requestLayout();
}
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 8019f14..07c3e4b 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -25,9 +25,9 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.util.AttributeSet;
@@ -55,21 +55,28 @@
static final private int TOKEN_PHONE_LOOKUP = 1;
static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
+ static final private int TOKEN_CONTACT_LOOKUP_AND_TRIGGER = 4;
static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
RawContacts.CONTACT_ID,
Contacts.LOOKUP_KEY,
};
- static int EMAIL_ID_COLUMN_INDEX = 0;
- static int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int EMAIL_ID_COLUMN_INDEX = 0;
+ static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
PhoneLookup._ID,
PhoneLookup.LOOKUP_KEY,
};
- static int PHONE_ID_COLUMN_INDEX = 0;
- static int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int PHONE_ID_COLUMN_INDEX = 0;
+ static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
+ Contacts._ID,
+ Contacts.LOOKUP_KEY,
+ };
+ static final int CONTACT_ID_COLUMN_INDEX = 0;
+ static final int CONTACT_LOOKUPKEY_COLUMN_INDEX = 1;
public QuickContactBadge(Context context) {
@@ -181,9 +188,9 @@
public void onClick(View v) {
if (mContactUri != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri lookupUri = Contacts.getLookupUri(resolver, mContactUri);
- trigger(lookupUri);
+ mQueryHandler.startQuery(TOKEN_CONTACT_LOOKUP_AND_TRIGGER, null,
+ mContactUri,
+ CONTACT_LOOKUP_PROJECTION, null, null, null);
} else if (mContactEmail != null) {
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
@@ -249,6 +256,17 @@
lookupUri = Contacts.getLookupUri(contactId, lookupKey);
}
}
+
+ case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: {
+ if (cursor != null && cursor.moveToFirst()) {
+ long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+ String lookupKey = cursor.getString(CONTACT_LOOKUPKEY_COLUMN_INDEX);
+ lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ trigger = true;
+ }
+
+ break;
+ }
}
} finally {
if (cursor != null) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 596fd98..f55ca3f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5197,7 +5197,7 @@
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
- height = Math.min(desired, height);
+ height = Math.min(desired, heightSize);
}
}
diff --git a/core/java/com/android/internal/os/IDropBoxService.aidl b/core/java/com/android/internal/os/IDropBoxService.aidl
new file mode 100644
index 0000000..f940041
--- /dev/null
+++ b/core/java/com/android/internal/os/IDropBoxService.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.os;
+
+import android.os.DropBox;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * "Backend" interface used by {@link android.os.DropBox} to talk to the
+ * DropBoxService that actually implements the drop box functionality.
+ *
+ * @see DropBox
+ * @hide
+ */
+interface IDropBoxService {
+ /**
+ * @see DropBox#addText
+ * @see DropBox#addData
+ * @see DropBox#addFile
+ */
+ void add(in DropBox.Entry entry);
+
+ /** @see DropBox#getNextEntry */
+ boolean isTagEnabled(String tag);
+
+ /** @see DropBox#getNextEntry */
+ DropBox.Entry getNextEntry(String tag, long millis);
+}
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index dc72008..e1e9536 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -27,6 +27,9 @@
#include "SkShader.h"
#include "SkTemplates.h"
+#include "SkBoundaryPatch.h"
+#include "SkMeshUtils.h"
+
#define TIME_DRAWx
static uint32_t get_thread_msec() {
@@ -861,8 +864,6 @@
*matrix = canvas->getTotalMatrix();
}
};
-
-///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gCanvasMethods[] = {
{"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
@@ -965,6 +966,42 @@
{"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches}
};
+///////////////////////////////////////////////////////////////////////////////
+
+static void BoundaryPatch_computeCubic(JNIEnv* env, jobject, jfloatArray jpts,
+ int texW, int texH, int rows, int cols,
+ jfloatArray jverts, jshortArray jidx) {
+ AutoJavaFloatArray ptsArray(env, jpts, 24, kRO_JNIAccess);
+
+ int vertCount = rows * cols;
+ AutoJavaFloatArray vertsArray(env, jverts, vertCount * 4, kRW_JNIAccess);
+ SkPoint* verts = (SkPoint*)vertsArray.ptr();
+ SkPoint* texs = verts + vertCount;
+
+ int idxCount = (rows - 1) * (cols - 1) * 6;
+ AutoJavaShortArray idxArray(env, jidx, idxCount, kRW_JNIAccess);
+ uint16_t* idx = (uint16_t*)idxArray.ptr(); // cast from int16_t*
+
+ SkCubicBoundary cubic;
+ memcpy(cubic.fPts, ptsArray.ptr(), 12 * sizeof(SkPoint));
+
+ SkBoundaryPatch patch;
+ patch.setBoundary(&cubic);
+ // generate our verts
+ patch.evalPatch(verts, rows, cols);
+
+ SkMeshIndices mesh;
+ // generate our texs and idx
+ mesh.init(texs, idx, texW, texH, rows, cols);
+}
+
+static JNINativeMethod gBoundaryPatchMethods[] = {
+ {"nativeComputeCubicPatch", "([FIIII[F[S)V",
+ (void*)BoundaryPatch_computeCubic },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
#include <android_runtime/AndroidRuntime.h>
#define REG(env, name, array) \
@@ -976,6 +1013,7 @@
int result;
REG(env, "android/graphics/Canvas", gCanvasMethods);
+ REG(env, "android/graphics/utils/BoundaryPatch", gBoundaryPatchMethods);
return result;
}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 2e0caed..01aad93 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -56,7 +56,7 @@
///////////////////////////////////////////////////////////////////////////////
AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
- int minLength)
+ int minLength, JNIAccess access)
: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
SkASSERT(env);
if (array) {
@@ -66,11 +66,12 @@
}
fPtr = env->GetFloatArrayElements(array, NULL);
}
+ fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0;
}
AutoJavaFloatArray::~AutoJavaFloatArray() {
if (fPtr) {
- fEnv->ReleaseFloatArrayElements(fArray, fPtr, 0);
+ fEnv->ReleaseFloatArrayElements(fArray, fPtr, fReleaseMode);
}
}
@@ -94,7 +95,7 @@
}
AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array,
- int minLength)
+ int minLength, JNIAccess access)
: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
SkASSERT(env);
if (array) {
@@ -104,11 +105,12 @@
}
fPtr = env->GetShortArrayElements(array, NULL);
}
+ fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0;
}
AutoJavaShortArray::~AutoJavaShortArray() {
if (fPtr) {
- fEnv->ReleaseShortArrayElements(fArray, fPtr, 0);
+ fEnv->ReleaseShortArrayElements(fArray, fPtr, fReleaseMode);
}
}
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 7adadbc..fe24b05 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -80,9 +80,15 @@
bool fReportSizeToVM;
};
+enum JNIAccess {
+ kRO_JNIAccess,
+ kRW_JNIAccess
+};
+
class AutoJavaFloatArray {
public:
- AutoJavaFloatArray(JNIEnv* env, jfloatArray array, int minLength = 0);
+ AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
+ int minLength = 0, JNIAccess = kRW_JNIAccess);
~AutoJavaFloatArray();
float* ptr() const { return fPtr; }
@@ -93,6 +99,7 @@
jfloatArray fArray;
float* fPtr;
int fLen;
+ int fReleaseMode;
};
class AutoJavaIntArray {
@@ -112,7 +119,8 @@
class AutoJavaShortArray {
public:
- AutoJavaShortArray(JNIEnv* env, jshortArray array, int minLength = 0);
+ AutoJavaShortArray(JNIEnv* env, jshortArray array,
+ int minLength = 0, JNIAccess = kRW_JNIAccess);
~AutoJavaShortArray();
jshort* ptr() const { return fPtr; }
@@ -123,6 +131,7 @@
jshortArray fArray;
jshort* fPtr;
int fLen;
+ int fReleaseMode;
};
class AutoJavaByteArray {
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 38f3fda..46000c9 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -20,6 +20,7 @@
#include <utils/misc.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
+#include <utils/String16.h>
#include "wifi.h"
@@ -92,7 +93,8 @@
if (doCommand(cmd, reply, sizeof(reply)) != 0) {
return env->NewStringUTF(NULL);
} else {
- return env->NewStringUTF(reply);
+ String16 str((char *)reply);
+ return env->NewString((const jchar *)str.string(), str.size());
}
}
diff --git a/core/res/assets/images/combobox-disabled.png b/core/res/assets/images/combobox-disabled.png
index 42fc0c5..fe220e4 100644
--- a/core/res/assets/images/combobox-disabled.png
+++ b/core/res/assets/images/combobox-disabled.png
Binary files differ
diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png
index 838dc65..abcdf72 100644
--- a/core/res/assets/images/combobox-noHighlight.png
+++ b/core/res/assets/images/combobox-noHighlight.png
Binary files differ
diff --git a/core/res/assets/webkit/youtube.html b/core/res/assets/webkit/youtube.html
index 2aaaa15..45d9c5e 100644
--- a/core/res/assets/webkit/youtube.html
+++ b/core/res/assets/webkit/youtube.html
@@ -30,14 +30,8 @@
</head>
<body>
<div id="bg">
- <table height="100%" width="100%" border="0" cellpadding="0"
- cellspacing="0">
- <tr>
- <td valign="middle">
- <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg" width="100%"/>
- </td>
- </tr>
- </table>
+ <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg"
+ style="width:100%; height:100%"/>
</div>
<div id="main">
<table height="100%" width="100%">
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index cf2de05..40e3f42 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -50,11 +50,11 @@
android:paddingTop="6dip"
android:paddingRight="10dip"
android:src="@drawable/ic_dialog_info" />
- <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
+ <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
style="?android:attr/textAppearanceLarge"
android:singleLine="true"
android:ellipsize="end"
- android:layout_width="fill_parent"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView android:id="@+id/titleDivider"
@@ -63,9 +63,7 @@
android:visibility="gone"
android:scaleType="fitXY"
android:gravity="fill_horizontal"
- android:src="@android:drawable/dialog_divider_horizontal_light"
- android:layout_marginLeft="10dip"
- android:layout_marginRight="10dip"/>
+ android:src="@android:drawable/divider_horizontal_dark" />
<!-- If the client uses a customTitle, it will be added here. -->
</LinearLayout>
@@ -88,7 +86,7 @@
android:padding="5dip" />
</ScrollView>
</LinearLayout>
-
+
<FrameLayout android:id="@+id/customPanel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@@ -99,13 +97,13 @@
android:paddingTop="5dip"
android:paddingBottom="5dip" />
</FrameLayout>
-
+
<LinearLayout android:id="@+id/buttonPanel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:minHeight="54dip"
- android:orientation="vertical" >
- <LinearLayout
+ android:orientation="vertical" >
+ <LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
diff --git a/core/res/res/layout/grant_credentials_permission.xml b/core/res/res/layout/grant_credentials_permission.xml
index fe1c22e..84b6623 100644
--- a/core/res/res/layout/grant_credentials_permission.xml
+++ b/core/res/res/layout/grant_credentials_permission.xml
@@ -1,41 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-/*
-** Copyright 2007, 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.
-*/
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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.
+ */
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="fill_parent">
+
+ <!-- The header -->
<TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/message" />
- <Button android:id="@+id/allow"
+ android:id="@+id/header_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:text="@string/allow" />
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/white"
+ android:textStyle="bold"
+ android:text="@string/grant_permissions_header_text"
+ android:shadowColor="@color/shadow"
+ android:shadowRadius="2"
+ android:singleLine="true"
+ android:background="@drawable/title_bar_medium"
+ android:gravity="left|center_vertical"
+ android:paddingLeft="19dip"
+ android:ellipsize="marquee" />
- <Button android:id="@+id/deny"
+ <!-- The list of packages that correspond to the requesting UID
+ and the account/authtokenType that is being requested -->
+ <ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:text="@string/deny" />
+ android:fillViewport="true"
+ android:layout_weight="1"
+ android:gravity="top|center_horizontal"
+ android:foreground="@drawable/title_bar_shadow">
- <ListView android:id="@+id/packages_list"
- android:layout_width="fill_parent" android:layout_height="fill_parent"/>
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingTop="14dip"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/grant_credentials_permission_message_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/grant_credentials_permission_message_header"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:paddingLeft="19dip"
+ android:paddingBottom="12dip" />
+
+ <LinearLayout
+ android:id="@+id/packages_list"
+ android:orientation="vertical"
+ android:paddingLeft="16dip"
+ android:paddingRight="12dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <RelativeLayout
+ android:paddingLeft="16dip"
+ android:paddingRight="12dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/permission_icon"
+ android:layout_width="30dip"
+ android:layout_height="30dip"
+ android:src="@drawable/ic_bullet_key_permission"
+ android:layout_alignParentLeft="true"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/account_type"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/perms_dangerous_perm_color"
+ android:textStyle="bold"
+ android:paddingLeft="6dip"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/account_name"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/perms_dangerous_perm_color"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_type"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/authtoken_type"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/perms_dangerous_perm_color"
+ android:textStyle="bold"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_name"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/grant_credentials_permission_message_footer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/grant_credentials_permission_message_footer"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:paddingLeft="19dip"
+ android:paddingBottom="12dip" />
+ </LinearLayout>
+ </ScrollView>
+
+ <!-- The buttons to allow or deny -->
+ <LinearLayout
+ android:id="@+id/buttons"
+ android:layout_width="fill_parent"
+ android:layout_height="52dip"
+ android:background="@drawable/bottom_bar"
+ android:paddingTop="4dip"
+ android:paddingLeft="2dip"
+ android:paddingRight="2dip">
+
+ <Button
+ android:id="@+id/allow_button"
+ android:text="@string/allow"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="2" />
+
+ <Button
+ android:id="@+id/deny_button"
+ android:text="@string/deny"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="2" />
+
+ </LinearLayout>
</LinearLayout>
+
diff --git a/core/res/res/layout/permissions_account_and_authtokentype.xml b/core/res/res/layout/permissions_account_and_authtokentype.xml
new file mode 100644
index 0000000..4494a2c
--- /dev/null
+++ b/core/res/res/layout/permissions_account_and_authtokentype.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+ Defines the layout of an account and authtoken type permission item.
+ Contains an icon, the account type and name and the authtoken type.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/permission_icon"
+ android:layout_width="30dip"
+ android:layout_height="30dip"
+ android:drawable="@drawable/ic_bullet_key_permission"
+ android:layout_alignParentLeft="true"
+ android:scaleType="fitCenter" />
+
+
+ <TextView
+ android:id="@+id/account_type"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:paddingLeft="6dip"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/account_name"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_type"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/authtoken_type"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_name"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/permissions_package_list_item.xml b/core/res/res/layout/permissions_package_list_item.xml
new file mode 100644
index 0000000..1bffe51
--- /dev/null
+++ b/core/res/res/layout/permissions_package_list_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+ Defines the layout of a single package item.
+ Contains a bullet point icon and the name of the package.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/package_icon"
+ android:layout_width="30dip"
+ android:layout_height="30dip"
+ android:layout_alignParentLeft="true"
+ android:src="@drawable/ic_text_dot"
+ android:scaleType="fitCenter" />
+
+
+ <TextView
+ android:id="@+id/package_label"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:paddingLeft="6dip"
+ android:layout_toRightOf="@id/package_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/screen_title_icons.xml b/core/res/res/layout/screen_title_icons.xml
index 4d7a6c8..5415909 100644
--- a/core/res/res/layout/screen_title_icons.xml
+++ b/core/res/res/layout/screen_title_icons.xml
@@ -66,22 +66,25 @@
android:layout_toLeftOf="@id/progress_circular"
android:layout_toRightOf="@android:id/left_icon"
>
+ <TextView android:id="@android:id/title"
+ style="?android:attr/windowTitleStyle"
+ android:layout_width="0dip"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:background="@null"
+ android:fadingEdge="horizontal"
+ android:scrollHorizontally="true"
+ android:gravity="center_vertical"
+ android:layout_marginRight="2dip"
+ />
<!-- 2dip between the icon and the title text, if icon is present. -->
<ImageView android:id="@android:id/right_icon"
android:visibility="gone"
android:layout_width="16dip"
android:layout_height="16dip"
+ android:layout_weight="0"
android:layout_gravity="center_vertical"
android:scaleType="fitCenter"
- android:layout_marginRight="2dip" />
- <TextView android:id="@android:id/title"
- style="?android:attr/windowTitleStyle"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@null"
- android:fadingEdge="horizontal"
- android:scrollHorizontally="true"
- android:gravity="center_vertical"
/>
</LinearLayout>
</RelativeLayout>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index f93fb01..6b5b168 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Vytvořit kontakt"\n"pro <xliff:g id="NUMBER">%s</xliff:g>."</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"Zaškrtnuto"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"Nezaškrtnuto"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Uvedené aplikace od <xliff:g id="APPLICATION">%2$s</xliff:g> požadují oprávnění přistupovat k přihlašovacím údajům účtu <xliff:g id="ACCOUNT">%1$s</xliff:g>. Chcete toto oprávnění udělit? Pokud je udělíte, vaše odpověď se uloží a tato výzva se již nebude zobrazovat."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Uvedené aplikace požadují od <xliff:g id="APPLICATION">%3$s</xliff:g> oprávnění přistupovat k přihlašovacím údajům (typ: <xliff:g id="TYPE">%1$s</xliff:g>) účtu <xliff:g id="ACCOUNT">%2$s</xliff:g>. Chcete toto oprávnění udělit? Pokud je udělíte, vaše odpověď se uloží a tato výzva se již nebude zobrazovat."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Povolit"</string>
<string name="deny" msgid="2081879885755434506">"Odepřít"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Požadováno oprávnění"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protokol L2TP (Layer 2 Tunneling Protocol)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Síť VPN L2TP/IPSec s předsdíleným klíčem"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Síť VPN L2TP/IPSec s certifikátem"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index fa89fea..33c21e3 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Opret kontakt"\n"ved hjælp af <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"kontrolleret"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"ikke kontrolleret"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"De nævnte programmer beder om tilladelse til at få adgang til loginoplysningerne til kontoen <xliff:g id="ACCOUNT">%1$s</xliff:g> fra <xliff:g id="APPLICATION">%2$s</xliff:g>. Ønsker du at give denne tilladelse? Hvis ja, så huskes dit svar, og du vil ikke blive spurgt om det igen."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"De nævnte programmer beder om tilladelse til at få adgang til <xliff:g id="TYPE">%1$s</xliff:g>-loginoplysningerne til kontoen <xliff:g id="ACCOUNT">%2$s</xliff:g> fra <xliff:g id="APPLICATION">%3$s</xliff:g>. Vil du give denne tilladelse? Hvis ja, så huskes dit svar, og du vil ikke blive spurgt om det igen."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Tillad"</string>
<string name="deny" msgid="2081879885755434506">"Afvis"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Der er anmodet om tilladelse"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN baseret på forhåndsdelt nøglekodning"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatbaseret L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ebf3b66..167faa0 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Neuer Kontakt"\n"mit <xliff:g id="NUMBER">%s</xliff:g> erstellen"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"aktiviert"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"nicht aktiviert"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Die aufgelisteten Anwendungen fordern eine Berechtigung zum Zugriff auf die Anmeldeinformationen für das Konto <xliff:g id="ACCOUNT">%1$s</xliff:g> von <xliff:g id="APPLICATION">%2$s</xliff:g> an. Möchten Sie diese Berechtigung erteilen? Wenn ja, wird Ihre Antwort gespeichert und Sie erhalten keine erneute Aufforderung."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Die aufgelisteten Anwendungen fordern eine Berechtigung zum Zugriff auf die <xliff:g id="TYPE">%1$s</xliff:g>-Anmeldeinformationen für das Konto <xliff:g id="APPLICATION">%3$s</xliff:g> von <xliff:g id="ACCOUNT">%2$s</xliff:g> an. Möchten Sie diese Berechtigung erteilen? Wenn ja, wird Ihre Antwort gespeichert und Sie erhalten keine erneute Aufforderung."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Zulassen"</string>
<string name="deny" msgid="2081879885755434506">"Ablehnen"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Berechtigung angefordert"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer-2-Tunneling-Protokoll"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Zertifikat mit vorinstalliertem Schlüssel"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 062658d..5b02a65 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Δημιουργία επαφής"\n"με τη χρήση του <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"επιλεγμένο"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"δεν ελέγχθηκε"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Οι εφαρμογές που παραθέτονται στη λίστα ζητούν άδεια για να αποκτήσουν πρόσβαση στα διαπιστευτήρια σύνδεσης για τον λογαριασμό <xliff:g id="ACCOUNT">%1$s</xliff:g> από <xliff:g id="APPLICATION">%2$s</xliff:g>. Θα αποδεχτείτε το αίτημα; Εάν το αποδεχτείτε, η απάντησή σας θα αποθηκευτεί και δεν θα ερωτηθείτε ξανά."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Οι εφαρμογές που παραθέτονται στη λίστα ζητούν άδεια για να αποκτήσουν πρόσβαση στα διαπιστευτήρια σύνδεσης <xliff:g id="TYPE">%1$s</xliff:g> για τον λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g> από <xliff:g id="APPLICATION">%3$s</xliff:g>. Θα αποδεχτείτε το αίτημα; Εάν το αποδεχτείτε, η απάντησή σας θα αποθηκευτεί και δεν θα ερωτηθείτε ξανά."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Να επιτρέπεται"</string>
<string name="deny" msgid="2081879885755434506">"Άρνηση"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Απαιτείται άδεια"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Πρωτόκολλο Layer 2 Tunneling Protocol (L2TP)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Κλειδί pre-shared βάσει L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Πιστοποιητικό βάσει L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 88f16a9..e0408b3 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Crear contacto "\n"con <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"verificado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"no verificado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Las aplicaciones enumeradas requieren permiso para acceder a las credenciales de inicio de sesión para la cuenta <xliff:g id="ACCOUNT">%1$s</xliff:g> desde <xliff:g id="APPLICATION">%2$s</xliff:g> ¿Deseas otorgar este permiso? Si es así, el sistema recordará tu respuesta y no volverá a solicitarla."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Las aplicaciones enumeradas requieren permiso para acceder a las <xliff:g id="TYPE">%1$s</xliff:g> credenciales de inicio de sesión para la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> desde <xliff:g id="APPLICATION">%3$s</xliff:g>.¿Deseas otorgar este permiso? Si es así, el sistema recordará tu respuesta y no volverá a solicitarla."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Denegar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Permiso solicitado"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de nivel 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clave previamente compartida según L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificado según L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 2757978..fa6160a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Crear un contacto"\n"a partir de <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"seleccionado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"no seleccionado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Las aplicaciones de la lista están solicitando permiso para acceder a las credenciales de acceso de la cuenta <xliff:g id="ACCOUNT">%1$s</xliff:g> desde <xliff:g id="APPLICATION">%2$s</xliff:g>. ¿Deseas conceder este permiso? En tal caso, se recordará tu respuesta y no se te volverá a preguntar."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Las aplicaciones de la lista están solicitando permiso para acceder a las credenciales de acceso (<xliff:g id="TYPE">%1$s</xliff:g>) de la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> desde <xliff:g id="APPLICATION">%3$s</xliff:g>. ¿Deseas conceder este permiso? En tal caso, se recordará tu respuesta y no se te volverá a preguntar."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Denegar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Permiso solicitado"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de nivel 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Red privada virtual L2TP/IPSec basada en clave compartida previamente"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Red privada virtual L2TP/IPSec basada en certificado"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7a07a7c..75f7406 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Ajouter un contact"\n"en utilisant <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"sélectionné"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"non sélectionné"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Les applications répertoriées demandent l\'autorisation d\'accéder aux informations d\'identification du compte <xliff:g id="ACCOUNT">%1$s</xliff:g> depuis <xliff:g id="APPLICATION">%2$s</xliff:g>. Souhaitez-vous accorder cette autorisation ? Si vous acceptez, votre choix sera enregistré et cette question ne vous sera plus posée."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Les applications répertoriées demandent l\'autorisation d\'accéder aux informations d\'identification des <xliff:g id="TYPE">%1$s</xliff:g> associés au compte <xliff:g id="ACCOUNT">%2$s</xliff:g> depuis <xliff:g id="APPLICATION">%3$s</xliff:g>. Souhaitez-vous accorder cette autorisation ? Si vous acceptez, votre choix sera enregistré et cette question ne vous sera plus posée."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Autoriser"</string>
<string name="deny" msgid="2081879885755434506">"Refuser"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorisation demandée"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocole de tunnelisation de niveau 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clé pré-partagée basée sur L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificat basé sur L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 8d2aefa..f89b9ad 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Crea contatto"\n"utilizzando <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"selezionato"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"non selezionato"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Le applicazioni elencate richiedono l\'autorizzazione per accedere alle credenziali di accesso per l\'account <xliff:g id="ACCOUNT">%1$s</xliff:g> da <xliff:g id="APPLICATION">%2$s</xliff:g>. Concedere questa autorizzazione? In tal caso la tua risposta verrà memorizzata e questa domanda non ti verrà più posta."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Le applicazioni elencate richiedono l\'autorizzazione per accedere alle credenziali di accesso <xliff:g id="TYPE">%1$s</xliff:g> per l\'account <xliff:g id="ACCOUNT">%2$s</xliff:g> da <xliff:g id="APPLICATION">%3$s</xliff:g>. Concedere questa autorizzazione? In tal caso la tua risposta verrà memorizzata e questa domanda non ti verrà più posta."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Consenti"</string>
<string name="deny" msgid="2081879885755434506">"Nega"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorizzazione richiesta"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocollo di tunneling livello 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basata su chiave precondivisa"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basata su certificato"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 8c3836c..e26ece2 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g>を使って"\n"連絡先を新規登録"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"オン"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"オフ"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"リストされているアプリケーションが、アカウント<xliff:g id="ACCOUNT">%1$s</xliff:g>のログイン認証情報に<xliff:g id="APPLICATION">%2$s</xliff:g>からアクセスする権限をリクエストしています。この権限を許可しますか?許可すると、入力が記録され、次回以降はこのメッセージが表示されなくなります。"</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"リストされているアプリケーションが、アカウント<xliff:g id="ACCOUNT">%2$s</xliff:g>の<xliff:g id="TYPE">%1$s</xliff:g>ログイン認証情報に<xliff:g id="APPLICATION">%3$s</xliff:g>からアクセスする権限をリクエストしています。この権限を許可しますか?許可すると、入力は記録され、次回以降はこのメッセージが表示されなくなります。"</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"許可"</string>
<string name="deny" msgid="2081879885755434506">"拒否"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"リクエスト済み権限"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"レイヤー2トンネリングプロトコル"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPNベースの事前共有鍵"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPNベースの証明書"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 840fa99..fc156b1 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"전화번호부에"\n"<xliff:g id="NUMBER">%s</xliff:g> 추가"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"선택함"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"선택 안함"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"나열된 응용프로그램이 <xliff:g id="APPLICATION">%2$s</xliff:g>에서 <xliff:g id="ACCOUNT">%1$s</xliff:g> 계정의 로그인 자격증명에 액세스할 수 있는 권한을 요청 중입니다. 권한을 부여하시겠습니까? 권한을 부여하면 응답 내용이 저장되며 메시지가 다시 표시되지 않습니다."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"나열된 응용프로그램이 <xliff:g id="APPLICATION">%3$s</xliff:g>에서 <xliff:g id="ACCOUNT">%2$s</xliff:g> 계정의 <xliff:g id="TYPE">%1$s</xliff:g> 로그인 자격증명에 액세스할 수 있는 권한을 요청 중입니다. 권한을 부여하시겠습니까? 권한을 부여하면 응답 내용이 저장되며 메시지가 다시 표시되지 않습니다."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"허용"</string>
<string name="deny" msgid="2081879885755434506">"거부"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"권한 요청"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"L2TP(Layer 2 Tunneling Protocol)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"사전 공유 키 기반 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"인증서 기반 L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index ced4f16..c0aae98 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Lag kontakt"\n"med nummeret <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"valgt"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"ikke valgt"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Det nevnte programmet ber om tilgangstillatelse til påloggingsopplysningene for konto <xliff:g id="ACCOUNT">%1$s</xliff:g> fra <xliff:g id="APPLICATION">%2$s</xliff:g>. Vil du gi denne tillatelsen? I så fall vil svaret ditt bli lagret, og du vil ikke bli spurt flere ganger."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Det nevnte programmet ber om tilgangstillatelse til <xliff:g id="TYPE">%1$s</xliff:g>-påloggingsopplysningene for konto <xliff:g id="ACCOUNT">%2$s</xliff:g> fra <xliff:g id="APPLICATION">%3$s</xliff:g>. Vil du gi denne tillatelsen? I så fall vil svaret ditt bli lagret, og du vil ikke bli spurt flere ganger."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Tillat"</string>
<string name="deny" msgid="2081879885755434506">"Avslå"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Tillatelse forespurt"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Lag 2-tunneleringsprotokoll"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Passordbasert L2TP/IPSec-VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sertifikatbasert L2TP/IPSec-VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index c5f5a4b..9b95c09 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -782,8 +782,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Contact maken"\n"met <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"aangevinkt"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"niet aangevinkt"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"De weergegeven toepassingen vragen toestemming voor toegang tot de aanmeldingsgegevens voor account \'<xliff:g id="ACCOUNT">%1$s</xliff:g>\' van <xliff:g id="APPLICATION">%2$s</xliff:g>. Wilt u deze toestemming verlenen? Als u dit wilt doen, wordt uw antwoord onthouden en wordt dit niet opnieuw gevraagd."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"De weergegeven toepassingen vragen toestemming voor toegang tot de aanmeldingsgegevens voor <xliff:g id="TYPE">%1$s</xliff:g> van het account \'<xliff:g id="ACCOUNT">%2$s</xliff:g>\' van <xliff:g id="APPLICATION">%3$s</xliff:g>. Wilt u deze toestemming verlenen? Als u dit wilt doen, wordt uw antwoord onthouden en wordt dit niet opnieuw gevraagd."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Toestaan"</string>
<string name="deny" msgid="2081879885755434506">"Weigeren"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Toestemming gevraagd"</string>
@@ -801,4 +805,6 @@
<skip />
<!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) -->
<skip />
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 388a9e7..6ee52ed 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Utwórz kontakt"\n"dla numeru <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"zaznaczone"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"niezaznaczone"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Wymienione aplikacje żądają pozwolenia na dostęp do danych logowania dla konta <xliff:g id="ACCOUNT">%1$s</xliff:g> powiązanego z aplikacją <xliff:g id="APPLICATION">%2$s</xliff:g>. Czy chcesz udzielić takiego pozwolenia? Jeśli tak, odpowiedź zostanie zapamiętana i to pytanie nie będzie już wyświetlane."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Wymienione aplikacje żądają pozwolenia na dostęp do danych logowania dotyczących funkcji <xliff:g id="TYPE">%1$s</xliff:g> dla konta <xliff:g id="ACCOUNT">%2$s</xliff:g> powiązanego z aplikacją <xliff:g id="APPLICATION">%3$s</xliff:g>. Czy chcesz udzielić takiego pozwolenia? Jeśli tak, odpowiedź zostanie zapamiętana i to pytanie nie będzie już wyświetlane."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Zezwól"</string>
<string name="deny" msgid="2081879885755434506">"Odmów"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Żądane pozwolenie"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protokół L2TP"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Sieć VPN L2TP/IPSec z kluczem PSK"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sieć VPN L2TP/IPSec z certyfikatem"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index b2edaae..c86fbdb 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Criar contacto"\n"utilizando <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"verificado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"não verificado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"As aplicações listadas estão a pedir autorização para aceder às credenciais de início de sessão da conta <xliff:g id="ACCOUNT">%1$s</xliff:g> a partir de <xliff:g id="APPLICATION">%2$s</xliff:g>. Pretende conceder esta autorização? Em caso afirmativo, a sua resposta será memorizada e não terá de responder a esta questão novamente."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"As aplicações listadas estão a pedir autorização para aceder às credenciais de início de sessão <xliff:g id="TYPE">%1$s</xliff:g> para a conta <xliff:g id="ACCOUNT">%2$s</xliff:g> a partir de <xliff:g id="APPLICATION">%3$s</xliff:g>. Pretende conceder esta autorização? Em caso afirmativo, a sua resposta será memorizada e não terá de responder a esta questão novamente."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Recusar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorização Solicitada"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de camada 2 (L2TP)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec baseada em chave pré- partilhada"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec baseada em certificado"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 725053c..70f82ee 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Criar contato "\n"usando <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"selecionado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"não selecionado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Os aplicativos listados estão solicitando autorização para acessar as credenciais de login para a conta <xliff:g id="ACCOUNT">%1$s</xliff:g> do <xliff:g id="APPLICATION">%2$s</xliff:g>. Deseja conceder essa autorização? Em caso afirmativo, sua resposta será lembrada e essa pergunta não será feita novamente."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Os aplicativos listados estão solicitando autorização para acessar as credenciais de login <xliff:g id="TYPE">%1$s</xliff:g> para a conta <xliff:g id="ACCOUNT">%2$s</xliff:g> do <xliff:g id="APPLICATION">%3$s</xliff:g>. Deseja conceder essa autorização? Em caso afirmativo, sua resposta será lembrada e essa pergunta não será feita novamente."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Negar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorização solicitada"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de encapsulamento de camada 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec com base em chave pré-compartilhada"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec com base em certificado"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 1e1500b..56b75fc 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Создать контакт"\n"с номером <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"отмечено"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"не проверено"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Перечисленные приложения запрашивают разрешение на доступ к регистрационном данным аккаунта <xliff:g id="ACCOUNT">%1$s</xliff:g> из <xliff:g id="APPLICATION">%2$s</xliff:g>. Разрешить доступ? Если да, ответ будет сохранен и это сообщение больше не будет выводиться."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Перечисленные приложения запрашивают разрешение на доступ к регистрационном данным <xliff:g id="TYPE">%1$s</xliff:g> аккаунта <xliff:g id="ACCOUNT">%2$s</xliff:g> из <xliff:g id="APPLICATION">%3$s</xliff:g>. Разрешить доступ? Если да, ответ будет сохранен и это сообщение больше не будет выводиться."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Разрешить"</string>
<string name="deny" msgid="2081879885755434506">"Отклонить"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Разрешение запрошено"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Протокол L2TP"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN (на основе предв. общ. ключа)"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN (на основе сертификата)"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 823ba22..3e32b15 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"Skapa kontakt"\n"med <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"markerad"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"inte markerad"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Programmen begär åtkomst till inloggningsuppgifterna för kontot <xliff:g id="ACCOUNT">%1$s</xliff:g> från <xliff:g id="APPLICATION">%2$s</xliff:g>. Vill du bevilja behörighet? Om du gör det kommer vi ihåg det och du blir inte tillfrågad igen."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Programmen begär åtkomst till inloggningsuppgifterna <xliff:g id="TYPE">%1$s</xliff:g> för kontot <xliff:g id="ACCOUNT">%2$s</xliff:g> från <xliff:g id="APPLICATION">%3$s</xliff:g>. Vill du bevilja behörighet? Om du gör det kommer vi ihåg det och du blir inte tillfrågad igen."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"Tillåt"</string>
<string name="deny" msgid="2081879885755434506">"Neka"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Begärd behörighet"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"I förväg delad L2TP/IPSec VPN-nyckel"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatsbaserad L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f95fc0a..cae0c03 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g>"\n" ile kişi oluştur"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"seçildi"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"seçilmedi"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Listelenen uygulamalar, <xliff:g id="APPLICATION">%2$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%1$s</xliff:g> hesabı için giriş bilgilerine erişim izni istiyor. Bu izni vermek istiyor musunuz? İstiyorsanız yanıtınız kaydedilecek ve tekrar sorulmayacaktır."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Listelenen uygulamalar, <xliff:g id="APPLICATION">%3$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabı için <xliff:g id="TYPE">%1$s</xliff:g> girişi bilgilerine erişim izni istiyor. Bu izni vermek istiyor musunuz? İstiyorsanız yanıtınız kaydedilecek ve tekrar sorulmayacaktır."</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"İzin Ver"</string>
<string name="deny" msgid="2081879885755434506">"Reddet"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"İzin İstendi"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Katman 2 Tünel Protokolü"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN temelli önceden paylaşılmış anahtar"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN temelli sertifika"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index ab5554b..6620a60 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"创建电话号码为"\n"<xliff:g id="NUMBER">%s</xliff:g> 的联系人"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"已选中"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"未选中"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"所列应用程序正在请求相应的权限,以便从<xliff:g id="APPLICATION">%2$s</xliff:g>访问 <xliff:g id="ACCOUNT">%1$s</xliff:g> 帐户的登录凭据。是否要授予这种权限?如果授予,系统会记住您所做的选择,且不再提示。"</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"所列应用程序正在请求相应的权限,以便从<xliff:g id="APPLICATION">%3$s</xliff:g>访问 <xliff:g id="ACCOUNT">%2$s</xliff:g> 帐户的<xliff:g id="TYPE">%1$s</xliff:g>登录凭据。是否要授予这种权限?如果授予,系统会记住您所做的选择,且不再提示。"</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"允许"</string>
<string name="deny" msgid="2081879885755434506">"拒绝"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"已请求权限"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"第 2 层隧道协议"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"基于预共享密钥的 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"基于证书的 L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index da02fe7..b9f344e 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -777,8 +777,12 @@
<string name="create_contact_using" msgid="4947405226788104538">"建立手機號碼為 <xliff:g id="NUMBER">%s</xliff:g>"\n"的聯絡人"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"已勾選"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"未勾選"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"這些應用程式要求存取「<xliff:g id="APPLICATION">%2$s</xliff:g>」的 <xliff:g id="ACCOUNT">%1$s</xliff:g> 帳戶登入認證的權限,您要授予此權限嗎?如果確定授予,系統會記住您的選擇,不會再次詢問您。"</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"這些應用程式要求存取「<xliff:g id="APPLICATION">%3$s</xliff:g>」的 <xliff:g id="ACCOUNT">%2$s</xliff:g> 帳戶「<xliff:g id="TYPE">%1$s</xliff:g>」登入認證的權限,您要授予此權限嗎?如果確定授予,系統會記住您的選擇,不會再次詢問您。"</string>
+ <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+ <skip />
+ <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+ <skip />
+ <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+ <skip />
<string name="allow" msgid="7225948811296386551">"允許"</string>
<string name="deny" msgid="2081879885755434506">"拒絕"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"已要求權限"</string>
@@ -792,4 +796,6 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"第二層通道通訊協定"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"採用預先共用金鑰的 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"採用憑證的 L2TP/IPSec VPN"</string>
+ <!-- no translation found for upload_file (2897957172366730416) -->
+ <skip />
</resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5d3069b..a506c9a 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -78,6 +78,7 @@
<!-- For security permissions -->
<color name="perms_dangerous_grp_color">#dd6826</color>
<color name="perms_dangerous_perm_color">#dd6826</color>
+ <color name="shadow">#cc222222</color>
<!-- For search-related UIs -->
<color name="search_url_text_normal">#7fa87f</color>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index de30fe7..83a6dc5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2067,16 +2067,9 @@
<!-- Title for the unselected state of a CompoundButton. -->
<string name="accessibility_compound_button_unselected">not checked</string>
- <string name="grant_credentials_permission_message_desc">The
- listed applications are requesting permission to access the login credentials for account <xliff:g id="account" example="foo@gmail.com">%1$s</xliff:g> from
- <xliff:g id="application" example="Google Apps">%2$s</xliff:g>. Do you wish to grant this permission? If so, your answer will be remembered and you will not be prompted
- again.</string>
-
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc">The
- listed applications are requesting permission to access the <xliff:g id="type" example="Contacts">%1$s</xliff:g> login credentials for account <xliff:g id="account" example="foo@gmail.com">%2$s</xliff:g> from
- <xliff:g id="application" example="Google Apps">%3$s</xliff:g>. Do you wish to grant this permission? If so, your answer will be remembered and you will not be prompted
- again.</string>
-
+ <string name="grant_credentials_permission_message_header">The following one or more applications request permission to access your account, now and in the future.</string>
+ <string name="grant_credentials_permission_message_footer">Do you want to allow this request?</string>
+ <string name="grant_permissions_header_text">Access Request</string>
<string name="allow">Allow</string>
<string name="deny">Deny</string>
<string name="permission_request_notification_title">Permission Requested</string>
@@ -2100,4 +2093,6 @@
<string name="l2tp_vpn_description">Layer 2 Tunneling Protocol</string>
<string name="l2tp_ipsec_psk_vpn_description">Pre-shared key based L2TP/IPSec VPN</string>
<string name="l2tp_ipsec_crt_vpn_description">Certificate based L2TP/IPSec VPN</string>
+ <!-- Label for button in a WebView that will open a chooser to choose a file to upload -->
+ <string name="upload_file">Choose file</string>
</resources>
diff --git a/graphics/java/android/graphics/utils/BoundaryPatch.java b/graphics/java/android/graphics/utils/BoundaryPatch.java
new file mode 100644
index 0000000..1cd5e13
--- /dev/null
+++ b/graphics/java/android/graphics/utils/BoundaryPatch.java
@@ -0,0 +1,173 @@
+/*
+ * 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 android.graphics.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.Xfermode;
+
+/**
+ * @hide
+ */
+public class BoundaryPatch {
+ private Paint mPaint;
+ private Bitmap mTexture;
+ private int mRows;
+ private int mCols;
+ private float[] mCubicPoints;
+ private boolean mDirty;
+ // these are the computed output of the native code
+ private float[] mVerts;
+ private short[] mIndices;
+
+ public BoundaryPatch() {
+ mRows = mCols = 2; // default minimum
+ mCubicPoints = new float[24];
+ mPaint = new Paint();
+ mPaint.setDither(true);
+ mPaint.setFilterBitmap(true);
+ mDirty = true;
+ }
+
+ /**
+ * Set the boundary to be 4 cubics. This takes a single array of floats,
+ * and picks up the 12 pairs starting at offset, and treats them as
+ * the x,y coordinates of the cubic control points. The points wrap around
+ * a patch, as follows. For documentation purposes, pts[i] will mean the
+ * x,y pair of floats, as if pts[] were an array of "points".
+ *
+ * Top: pts[0..3]
+ * Right: pts[3..6]
+ * Bottom: pts[6..9]
+ * Right: pts[9..11], pts[0]
+ *
+ * The coordinates are copied from the input array, so subsequent changes
+ * to pts[] will not be reflected in the boundary.
+ *
+ * @param pts The src array of x,y pairs for the boundary cubics
+ * @param offset The index into pts of the first pair
+ * @param rows The number of points across to approximate the boundary.
+ * Must be >= 2, though very large values may slow down drawing
+ * @param cols The number of points down to approximate the boundary.
+ * Must be >= 2, though very large values may slow down drawing
+ */
+ public void setCubicBoundary(float[] pts, int offset, int rows, int cols) {
+ if (rows < 2 || cols < 2) {
+ throw new RuntimeException("rows and cols must be >= 2");
+ }
+ System.arraycopy(pts, offset, mCubicPoints, 0, 24);
+ if (mRows != rows || mCols != cols) {
+ mRows = rows;
+ mCols = cols;
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Reference a bitmap texture to be mapped onto the patch.
+ */
+ public void setTexture(Bitmap texture) {
+ if (mTexture != texture) {
+ if (mTexture == null ||
+ mTexture.getWidth() != texture.getWidth() ||
+ mTexture.getHeight() != texture.getHeight()) {
+ // need to recompute texture coordinates
+ mDirty = true;
+ }
+ mTexture = texture;
+ mPaint.setShader(new BitmapShader(texture,
+ Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP));
+ }
+ }
+
+ /**
+ * Return the paint flags for the patch
+ */
+ public int getPaintFlags() {
+ return mPaint.getFlags();
+ }
+
+ /**
+ * Set the paint flags for the patch
+ */
+ public void setPaintFlags(int flags) {
+ mPaint.setFlags(flags);
+ }
+
+ /**
+ * Set the xfermode for the patch
+ */
+ public void setXfermode(Xfermode mode) {
+ mPaint.setXfermode(mode);
+ }
+
+ /**
+ * Set the alpha for the patch
+ */
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ /**
+ * Draw the patch onto the canvas.
+ *
+ * setCubicBoundary() and setTexture() must be called before drawing.
+ */
+ public void draw(Canvas canvas) {
+ if (mDirty) {
+ buildCache();
+ mDirty = false;
+ }
+
+ // cut the count in half, since mVerts.length is really the length of
+ // the verts[] and tex[] arrays combined
+ // (tex[] are stored after verts[])
+ int vertCount = mVerts.length >> 1;
+ canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertCount,
+ mVerts, 0, mVerts, vertCount, null, 0,
+ mIndices, 0, mIndices.length,
+ mPaint);
+ }
+
+ private void buildCache() {
+ // we need mRows * mCols points, for verts and another set for textures
+ // so *2 for going from points -> floats, and *2 for verts and textures
+ int vertCount = mRows * mCols * 4;
+ if (mVerts == null || mVerts.length != vertCount) {
+ mVerts = new float[vertCount];
+ }
+
+ int indexCount = (mRows - 1) * (mCols - 1) * 6;
+ if (mIndices == null || mIndices.length != indexCount) {
+ mIndices = new short[indexCount];
+ }
+
+ nativeComputeCubicPatch(mCubicPoints,
+ mTexture.getWidth(), mTexture.getHeight(),
+ mRows, mCols, mVerts, mIndices);
+ }
+
+ private static native
+ void nativeComputeCubicPatch(float[] cubicPoints,
+ int texW, int texH, int rows, int cols,
+ float[] verts, short[] indices);
+}
+
diff --git a/include/media/stagefright/CachingDataSource.h b/include/media/stagefright/CachingDataSource.h
index e35e19e..b0fc4b2 100644
--- a/include/media/stagefright/CachingDataSource.h
+++ b/include/media/stagefright/CachingDataSource.h
@@ -29,9 +29,9 @@
CachingDataSource(
const sp<DataSource> &source, size_t pageSize, int numPages);
- status_t InitCheck() const;
+ virtual status_t initCheck() const;
- virtual ssize_t read_at(off_t offset, void *data, size_t size);
+ virtual ssize_t readAt(off_t offset, void *data, size_t size);
protected:
virtual ~CachingDataSource();
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index 7042e1a..ff3ea05 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -26,12 +26,11 @@
namespace android {
-class ICamera;
-class ICameraClient;
class IMemory;
+class ISurface;
+class Camera;
-class CameraSource : public MediaSource,
- public MediaBufferObserver {
+class CameraSource : public MediaSource {
public:
static CameraSource *Create();
@@ -45,24 +44,25 @@
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL);
- virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2);
- virtual void dataCallback(int32_t msgType, const sp<IMemory>& data);
-
- virtual void signalBufferReturned(MediaBuffer *buffer);
-
private:
- CameraSource(const sp<ICamera> &camera, const sp<ICameraClient> &client);
+ friend class CameraSourceListener;
- sp<ICamera> mCamera;
- sp<ICameraClient> mCameraClient;
+ sp<Camera> mCamera;
Mutex mLock;
Condition mFrameAvailableCondition;
List<sp<IMemory> > mFrames;
+ List<int64_t> mFrameTimes;
- int mNumFrames;
+ int mWidth, mHeight;
+ int64_t mFirstFrameTimeUs;
+ int32_t mNumFrames;
bool mStarted;
+ CameraSource(const sp<Camera> &camera);
+
+ void dataCallback(int32_t msgType, const sp<IMemory> &data);
+
CameraSource(const CameraSource &);
CameraSource &operator=(const CameraSource &);
};
diff --git a/include/media/stagefright/ColorConverter.h b/include/media/stagefright/ColorConverter.h
new file mode 100644
index 0000000..1e341b9
--- /dev/null
+++ b/include/media/stagefright/ColorConverter.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef COLOR_CONVERTER_H_
+
+#define COLOR_CONVERTER_H_
+
+#include <sys/types.h>
+
+#include <stdint.h>
+
+#include <OMX_Video.h>
+
+namespace android {
+
+struct ColorConverter {
+ ColorConverter(OMX_COLOR_FORMATTYPE from, OMX_COLOR_FORMATTYPE to);
+ ~ColorConverter();
+
+ bool isValid() const;
+
+ void convert(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip);
+
+private:
+ OMX_COLOR_FORMATTYPE mSrcFormat, mDstFormat;
+ uint8_t *mClip;
+
+ uint8_t *initClip();
+
+ void convertCbYCrY(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip);
+
+ void convertYUV420Planar(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip);
+
+ void convertQCOMYUV420SemiPlanar(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip);
+
+ ColorConverter(const ColorConverter &);
+ ColorConverter &operator=(const ColorConverter &);
+};
+
+} // namespace android
+
+#endif // COLOR_CONVERTER_H_
diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h
index f46f0af..b843cd9 100644
--- a/include/media/stagefright/DataSource.h
+++ b/include/media/stagefright/DataSource.h
@@ -33,7 +33,9 @@
public:
DataSource() {}
- virtual ssize_t read_at(off_t offset, void *data, size_t size) = 0;
+ virtual status_t initCheck() const = 0;
+
+ virtual ssize_t readAt(off_t offset, void *data, size_t size) = 0;
// Convenience methods:
bool getUInt16(off_t offset, uint16_t *x);
diff --git a/include/media/stagefright/FileSource.h b/include/media/stagefright/FileSource.h
index ccbe0ef..d7b42c3 100644
--- a/include/media/stagefright/FileSource.h
+++ b/include/media/stagefright/FileSource.h
@@ -29,12 +29,14 @@
class FileSource : public DataSource {
public:
FileSource(const char *filename);
+
+ virtual status_t initCheck() const;
+
+ virtual ssize_t readAt(off_t offset, void *data, size_t size);
+
+protected:
virtual ~FileSource();
- status_t InitCheck() const;
-
- virtual ssize_t read_at(off_t offset, void *data, size_t size);
-
private:
FILE *mFile;
Mutex mLock;
diff --git a/include/media/stagefright/HTTPDataSource.h b/include/media/stagefright/HTTPDataSource.h
index 0587c7c..d5dc9e6 100644
--- a/include/media/stagefright/HTTPDataSource.h
+++ b/include/media/stagefright/HTTPDataSource.h
@@ -19,28 +19,29 @@
#define HTTP_DATASOURCE_H_
#include <media/stagefright/DataSource.h>
-#include <media/stagefright/HTTPStream.h>
namespace android {
+class HTTPStream;
+
class HTTPDataSource : public DataSource {
public:
HTTPDataSource(const char *host, int port, const char *path);
HTTPDataSource(const char *uri);
+ virtual status_t initCheck() const;
+
+ virtual ssize_t readAt(off_t offset, void *data, size_t size);
+
+protected:
virtual ~HTTPDataSource();
- // XXXandih
- status_t InitCheck() const { return OK; }
-
- virtual ssize_t read_at(off_t offset, void *data, size_t size);
-
private:
enum {
kBufferSize = 64 * 1024
};
- HTTPStream mHttp;
+ HTTPStream *mHttp;
char *mHost;
int mPort;
char *mPath;
@@ -49,6 +50,8 @@
size_t mBufferLength;
off_t mBufferOffset;
+ status_t mInitCheck;
+
HTTPDataSource(const HTTPDataSource &);
HTTPDataSource &operator=(const HTTPDataSource &);
};
diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h
index feb66e3..1efeb92 100644
--- a/include/media/stagefright/MediaDefs.h
+++ b/include/media/stagefright/MediaDefs.h
@@ -34,6 +34,7 @@
extern const char *MEDIA_MIMETYPE_AUDIO_RAW;
extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG4;
+extern const char *MEDIA_MIMETYPE_CONTAINER_WAV;
} // namespace android
diff --git a/include/media/stagefright/MediaErrors.h b/include/media/stagefright/MediaErrors.h
index 2bb0ed6..73d0f77 100644
--- a/include/media/stagefright/MediaErrors.h
+++ b/include/media/stagefright/MediaErrors.h
@@ -36,6 +36,9 @@
ERROR_BUFFER_TOO_SMALL = MEDIA_ERROR_BASE - 9,
ERROR_UNSUPPORTED = MEDIA_ERROR_BASE - 10,
ERROR_END_OF_STREAM = MEDIA_ERROR_BASE - 11,
+
+ // Not technically an error.
+ INFO_FORMAT_CHANGED = MEDIA_ERROR_BASE - 12,
};
} // namespace android
diff --git a/include/media/stagefright/MediaExtractor.h b/include/media/stagefright/MediaExtractor.h
index 67e45bd..d56d4b3 100644
--- a/include/media/stagefright/MediaExtractor.h
+++ b/include/media/stagefright/MediaExtractor.h
@@ -31,9 +31,17 @@
static sp<MediaExtractor> Create(
const sp<DataSource> &source, const char *mime = NULL);
+ static sp<MediaExtractor> CreateFromURI(
+ const char *uri, const char *mime = NULL);
+
virtual size_t countTracks() = 0;
virtual sp<MediaSource> getTrack(size_t index) = 0;
- virtual sp<MetaData> getTrackMetaData(size_t index) = 0;
+
+ enum GetTrackMetaDataFlags {
+ kIncludeExtensiveMetaData = 1
+ };
+ virtual sp<MetaData> getTrackMetaData(
+ size_t index, uint32_t flags = 0) = 0;
protected:
MediaExtractor() {}
diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h
index d1fa114..96d57e7 100644
--- a/include/media/stagefright/MediaSource.h
+++ b/include/media/stagefright/MediaSource.h
@@ -51,6 +51,9 @@
// buffer is available, an error is encountered of the end of the stream
// is reached.
// End of stream is signalled by a result of ERROR_END_OF_STREAM.
+ // A result of INFO_FORMAT_CHANGED indicates that the format of this
+ // MediaSource has changed mid-stream, the client can continue reading
+ // but should be prepared for buffers of the new configuration.
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL) = 0;
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index abb45a9..c2d8f98 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -27,25 +27,26 @@
namespace android {
+// The following keys map to int32_t data unless indicated otherwise.
enum {
- kKeyMIMEType = 'mime',
+ kKeyMIMEType = 'mime', // cstring
kKeyWidth = 'widt',
kKeyHeight = 'heig',
kKeyChannelCount = '#chn',
kKeySampleRate = 'srte',
kKeyBitRate = 'brte',
- kKeyESDS = 'esds',
- kKeyAVCC = 'avcc',
- kKeyTimeUnits = '#tim',
- kKeyTimeScale = 'scal',
+ kKeyESDS = 'esds', // raw data
+ kKeyAVCC = 'avcc', // raw data
kKeyWantsNALFragments = 'NALf',
kKeyIsSyncFrame = 'sync',
- kKeyDuration = 'dura',
+ kKeyTime = 'time', // int64_t (usecs)
+ kKeyDuration = 'dura', // int64_t (usecs)
kKeyColorFormat = 'colf',
- kKeyPlatformPrivate = 'priv',
- kKeyDecoderComponent = 'decC',
+ kKeyPlatformPrivate = 'priv', // pointer
+ kKeyDecoderComponent = 'decC', // cstring
kKeyBufferID = 'bfID',
kKeyMaxInputSize = 'inpS',
+ kKeyThumbnailTime = 'thbT', // int64_t (usecs)
};
enum {
@@ -62,6 +63,7 @@
TYPE_NONE = 'none',
TYPE_C_STRING = 'cstr',
TYPE_INT32 = 'in32',
+ TYPE_INT64 = 'in64',
TYPE_FLOAT = 'floa',
TYPE_POINTER = 'ptr ',
};
@@ -71,11 +73,13 @@
bool setCString(uint32_t key, const char *value);
bool setInt32(uint32_t key, int32_t value);
+ bool setInt64(uint32_t key, int64_t value);
bool setFloat(uint32_t key, float value);
bool setPointer(uint32_t key, void *value);
bool findCString(uint32_t key, const char **value);
bool findInt32(uint32_t key, int32_t *value);
+ bool findInt64(uint32_t key, int64_t *value);
bool findFloat(uint32_t key, float *value);
bool findPointer(uint32_t key, void **value);
diff --git a/include/media/stagefright/MmapSource.h b/include/media/stagefright/MmapSource.h
index a8bd57f..1b39d53 100644
--- a/include/media/stagefright/MmapSource.h
+++ b/include/media/stagefright/MmapSource.h
@@ -30,13 +30,14 @@
// Assumes ownership of "fd".
MmapSource(int fd, int64_t offset, int64_t length);
- virtual ~MmapSource();
+ virtual status_t initCheck() const;
- status_t InitCheck() const;
-
- virtual ssize_t read_at(off_t offset, void *data, size_t size);
+ virtual ssize_t readAt(off_t offset, void *data, size_t size);
virtual status_t getSize(off_t *size);
+protected:
+ virtual ~MmapSource();
+
private:
int mFd;
void *mBase;
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 3f3dcf9..7890883 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -30,11 +30,15 @@
struct OMXCodec : public MediaSource,
public MediaBufferObserver {
+ enum CreationFlags {
+ kPreferSoftwareCodecs = 1,
+ };
static sp<OMXCodec> Create(
const sp<IOMX> &omx,
const sp<MetaData> &meta, bool createEncoder,
const sp<MediaSource> &source,
- const char *matchComponentName = NULL);
+ const char *matchComponentName = NULL,
+ uint32_t flags = 0);
static void setComponentRole(
const sp<IOMX> &omx, IOMX::node_id node, bool isEncoder,
@@ -90,7 +94,6 @@
kRequiresFlushCompleteEmulation = 16,
kRequiresAllocateBufferOnOutputPorts = 32,
kRequiresFlushBeforeShutdown = 64,
- kOutputDimensionsAre16Aligned = 128,
};
struct BufferInfo {
@@ -107,7 +110,6 @@
sp<IOMX> mOMX;
IOMX::node_id mNode;
- sp<OMXCodecObserver> mObserver;
uint32_t mQuirks;
bool mIsEncoder;
char *mMIME;
@@ -125,6 +127,7 @@
bool mInitialBufferSubmit;
bool mSignalledEOS;
bool mNoMoreOutputData;
+ bool mOutputPortSettingsHaveChanged;
int64_t mSeekTimeUs;
Mutex mLock;
@@ -155,6 +158,8 @@
void setVideoInputFormat(
const char *mime, OMX_U32 width, OMX_U32 height);
+ status_t setupMPEG4EncoderParameters();
+
void setVideoOutputFormat(
const char *mime, OMX_U32 width, OMX_U32 height);
@@ -208,6 +213,14 @@
void dumpPortStatus(OMX_U32 portIndex);
+ static uint32_t getComponentQuirks(const char *componentName);
+
+ static void findMatchingCodecs(
+ const char *mime,
+ bool createEncoder, const char *matchComponentName,
+ uint32_t flags,
+ Vector<String8> *matchingCodecs);
+
OMXCodec(const OMXCodec &);
OMXCodec &operator=(const OMXCodec &);
};
diff --git a/include/media/stagefright/ShoutcastSource.h b/include/media/stagefright/ShoutcastSource.h
index 352857a..bc67156 100644
--- a/include/media/stagefright/ShoutcastSource.h
+++ b/include/media/stagefright/ShoutcastSource.h
@@ -31,7 +31,6 @@
public:
// Assumes ownership of "http".
ShoutcastSource(HTTPStream *http);
- virtual ~ShoutcastSource();
virtual status_t start(MetaData *params = NULL);
virtual status_t stop();
@@ -41,6 +40,9 @@
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL);
+protected:
+ virtual ~ShoutcastSource();
+
private:
HTTPStream *mHttp;
size_t mMetaDataOffset;
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index e80d8aa..1713324 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -137,11 +137,17 @@
cursor = res.query(uri, MEDIA_COLUMNS, null, null, null);
}
- if (cursor != null && cursor.getCount() == 1) {
- cursor.moveToFirst();
- return cursor.getString(2);
- } else {
- title = uri.getLastPathSegment();
+ try {
+ if (cursor != null && cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ return cursor.getString(2);
+ } else {
+ title = uri.getLastPathSegment();
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
}
}
}
diff --git a/media/java/android/media/ThumbnailUtil.java b/media/java/android/media/ThumbnailUtil.java
index f9d69fb..0cf4e76 100644
--- a/media/java/android/media/ThumbnailUtil.java
+++ b/media/java/android/media/ThumbnailUtil.java
@@ -33,10 +33,9 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.media.MediaMetadataRetriever;
+import android.media.MediaFile.MediaFileType;
-import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
@@ -295,7 +294,7 @@
* @param uri URI of original image
* @param origId image id
* @param kind either MINI_KIND or MICRO_KIND
- * @param saveImage Whether to save MINI_KIND thumbnail obtained in this method.
+ * @param saveMini Whether to save MINI_KIND thumbnail obtained in this method.
* @return Bitmap
*/
public static Bitmap createImageThumbnail(ContentResolver cr, String filePath, Uri uri,
@@ -305,16 +304,12 @@
ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE;
int maxPixels = wantMini ?
ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS;
- byte[] thumbData = createThumbnailFromEXIF(filePath, targetSize);
+ SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
Bitmap bitmap = null;
-
- if (thumbData != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = computeSampleSize(options, targetSize, maxPixels);
- options.inDither = false;
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- options.inJustDecodeBounds = false;
- bitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options);
+ MediaFileType fileType = MediaFile.getFileType(filePath);
+ if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) {
+ createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
+ bitmap = sizedThumbnailBitmap.mBitmap;
}
if (bitmap == null) {
@@ -326,9 +321,11 @@
}
if (saveMini) {
- if (thumbData != null) {
- ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(),
- bitmap.getHeight());
+ if (sizedThumbnailBitmap.mThumbnailData != null) {
+ ThumbnailUtil.storeThumbnail(cr, origId,
+ sizedThumbnailBitmap.mThumbnailData,
+ sizedThumbnailBitmap.mThumbnailWidth,
+ sizedThumbnailBitmap.mThumbnailHeight);
} else {
ThumbnailUtil.storeThumbnail(cr, origId, bitmap);
}
@@ -454,6 +451,7 @@
Cursor c = cr.query(thumbUri, THUMB_PROJECTION,
Thumbnails.IMAGE_ID + "=?",
new String[]{String.valueOf(origId)}, null);
+ if (c == null) return null;
try {
if (c.moveToNext()) {
return ContentUris.withAppendedId(thumbUri, c.getLong(0));
@@ -482,6 +480,7 @@
if (thumb == null) return false;
try {
Uri uri = getImageThumbnailUri(cr, origId, thumb.getWidth(), thumb.getHeight());
+ if (uri == null) return false;
OutputStream thumbOut = cr.openOutputStream(uri);
thumb.compress(Bitmap.CompressFormat.JPEG, 85, thumbOut);
thumbOut.close();
@@ -514,31 +513,69 @@
}
}
- // Extract thumbnail in image that meets the targetSize criteria.
- static byte[] createThumbnailFromEXIF(String filePath, int targetSize) {
- if (filePath == null) return null;
+ // SizedThumbnailBitmap contains the bitmap, which is downsampled either from
+ // the thumbnail in exif or the full image.
+ // mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail is not null.
+ // The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.
+ private static class SizedThumbnailBitmap {
+ public byte[] mThumbnailData;
+ public Bitmap mBitmap;
+ public int mThumbnailWidth;
+ public int mThumbnailHeight;
+ }
+ // Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.
+ // The functions returns a SizedThumbnailBitmap,
+ // which contains a downsampled bitmap and the thumbnail data in EXIF if exists.
+ private static void createThumbnailFromEXIF(String filePath, int targetSize,
+ int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {
+ if (filePath == null) return;
+
+ ExifInterface exif = null;
+ byte [] thumbData = null;
try {
- ExifInterface exif = new ExifInterface(filePath);
- if (exif == null) return null;
- byte [] thumbData = exif.getThumbnail();
- if (thumbData == null) return null;
- // Sniff the size of the EXIF thumbnail before decoding it. Photos
- // from the device will pass, but images that are side loaded from
- // other cameras may not.
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options);
-
- int width = options.outWidth;
- int height = options.outHeight;
-
- if (width >= targetSize && height >= targetSize) {
- return thumbData;
+ exif = new ExifInterface(filePath);
+ if (exif != null) {
+ thumbData = exif.getThumbnail();
}
} catch (IOException ex) {
Log.w(TAG, ex);
}
- return null;
+
+ BitmapFactory.Options fullOptions = new BitmapFactory.Options();
+ BitmapFactory.Options exifOptions = new BitmapFactory.Options();
+ int exifThumbWidth = 0;
+ int fullThumbWidth = 0;
+
+ // Compute exifThumbWidth.
+ if (thumbData != null) {
+ exifOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);
+ exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);
+ exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;
+ }
+
+ // Compute fullThumbWidth.
+ fullOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(filePath, fullOptions);
+ fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);
+ fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;
+
+ // Choose the larger thumbnail as the returning sizedThumbBitmap.
+ if (exifThumbWidth >= fullThumbWidth) {
+ int width = exifOptions.outWidth;
+ int height = exifOptions.outHeight;
+ exifOptions.inJustDecodeBounds = false;
+ sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,
+ thumbData.length, exifOptions);
+ if (sizedThumbBitmap.mBitmap != null) {
+ sizedThumbBitmap.mThumbnailData = thumbData;
+ sizedThumbBitmap.mThumbnailWidth = width;
+ sizedThumbBitmap.mThumbnailHeight = height;
+ }
+ } else {
+ fullOptions.inJustDecodeBounds = false;
+ sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
+ }
}
}
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index 88a7064..76a9e7d 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -284,7 +284,7 @@
data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
data.writeIntPtr((intptr_t)node);
data.writeIntPtr((intptr_t)buffer);
- remote()->transact(FILL_BUFFER, data, &reply, IBinder::FLAG_ONEWAY);
+ remote()->transact(FILL_BUFFER, data, &reply);
return reply.readInt32();
}
@@ -302,7 +302,7 @@
data.writeInt32(range_length);
data.writeInt32(flags);
data.writeInt64(timestamp);
- remote()->transact(EMPTY_BUFFER, data, &reply, IBinder::FLAG_ONEWAY);
+ remote()->transact(EMPTY_BUFFER, data, &reply);
return reply.readInt32();
}
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index fb569da..6fc9fa2 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -18,8 +18,9 @@
ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
-LOCAL_SRC_FILES += \
- StagefrightPlayer.cpp
+LOCAL_SRC_FILES += \
+ StagefrightPlayer.cpp \
+ StagefrightMetadataRetriever.cpp
LOCAL_CFLAGS += -DBUILD_WITH_FULL_STAGEFRIGHT=1
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 0a6c365..b81684b 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -366,11 +366,44 @@
size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
extern "C" void free_malloc_leak_info(uint8_t* info);
+// Use the String-class below instead of String8 to allocate all memory
+// beforehand and not reenter the heap while we are examining it...
+struct MyString8 {
+ static const size_t MAX_SIZE = 256 * 1024;
+
+ MyString8()
+ : mPtr((char *)malloc(MAX_SIZE)) {
+ *mPtr = '\0';
+ }
+
+ ~MyString8() {
+ free(mPtr);
+ }
+
+ void append(const char *s) {
+ strcat(mPtr, s);
+ }
+
+ const char *string() const {
+ return mPtr;
+ }
+
+ size_t size() const {
+ return strlen(mPtr);
+ }
+
+private:
+ char *mPtr;
+
+ MyString8(const MyString8 &);
+ MyString8 &operator=(const MyString8 &);
+};
+
void memStatus(int fd, const Vector<String16>& args)
{
const size_t SIZE = 256;
char buffer[SIZE];
- String8 result;
+ MyString8 result;
typedef struct {
size_t size;
diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
index 2cdc351..866c7bd 100644
--- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp
+++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
@@ -38,6 +38,7 @@
#include "VorbisMetadataRetriever.h"
#include "MidiMetadataRetriever.h"
#include "MetadataRetrieverClient.h"
+#include "StagefrightMetadataRetriever.h"
/* desktop Linux needs a little help with gettid() */
#if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS)
@@ -118,9 +119,15 @@
LOGV("create midi metadata retriever");
p = new MidiMetadataRetriever();
break;
+#if BUILD_WITH_FULL_STAGEFRIGHT
+ case STAGEFRIGHT_PLAYER:
+ LOGV("create StagefrightMetadataRetriever");
+ p = new StagefrightMetadataRetriever;
+ break;
+#endif
default:
// TODO:
- // support for STAGEFRIGHT_PLAYER and TEST_PLAYER
+ // support for TEST_PLAYER
LOGE("player type %d is not supported", playerType);
break;
}
@@ -138,12 +145,6 @@
return UNKNOWN_ERROR;
}
player_type playerType = getPlayerType(url);
-#if !defined(NO_OPENCORE) && defined(BUILD_WITH_FULL_STAGEFRIGHT)
- if (playerType == STAGEFRIGHT_PLAYER) {
- // Stagefright doesn't support metadata in this branch yet.
- playerType = PV_PLAYER;
- }
-#endif
LOGV("player type = %d", playerType);
sp<MediaMetadataRetrieverBase> p = createRetriever(playerType);
if (p == NULL) return NO_INIT;
@@ -182,12 +183,6 @@
}
player_type playerType = getPlayerType(fd, offset, length);
-#if !defined(NO_OPENCORE) && defined(BUILD_WITH_FULL_STAGEFRIGHT)
- if (playerType == STAGEFRIGHT_PLAYER) {
- // Stagefright doesn't support metadata in this branch yet.
- playerType = PV_PLAYER;
- }
-#endif
LOGV("player type = %d", playerType);
sp<MediaMetadataRetrieverBase> p = createRetriever(playerType);
if (p == NULL) {
diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
new file mode 100644
index 0000000..7a3aee8
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
@@ -0,0 +1,197 @@
+/*
+**
+** Copyright 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.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "StagefrightMetadataRetriever"
+#include <utils/Log.h>
+
+#include "StagefrightMetadataRetriever.h"
+
+#include <media/stagefright/CachingDataSource.h>
+#include <media/stagefright/ColorConverter.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/HTTPDataSource.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MmapSource.h>
+#include <media/stagefright/OMXCodec.h>
+
+namespace android {
+
+StagefrightMetadataRetriever::StagefrightMetadataRetriever() {
+ LOGV("StagefrightMetadataRetriever()");
+
+ DataSource::RegisterDefaultSniffers();
+ CHECK_EQ(mClient.connect(), OK);
+}
+
+StagefrightMetadataRetriever::~StagefrightMetadataRetriever() {
+ LOGV("~StagefrightMetadataRetriever()");
+ mClient.disconnect();
+}
+
+status_t StagefrightMetadataRetriever::setDataSource(const char *uri) {
+ LOGV("setDataSource(%s)", uri);
+
+ mExtractor = MediaExtractor::CreateFromURI(uri);
+
+ return mExtractor.get() != NULL ? OK : UNKNOWN_ERROR;
+}
+
+status_t StagefrightMetadataRetriever::setDataSource(
+ int fd, int64_t offset, int64_t length) {
+ LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
+
+ mExtractor = MediaExtractor::Create(
+ new MmapSource(fd, offset, length));
+
+ return OK;
+}
+
+VideoFrame *StagefrightMetadataRetriever::captureFrame() {
+ LOGV("captureFrame");
+
+ if (mExtractor.get() == NULL) {
+ LOGV("no extractor.");
+ return NULL;
+ }
+
+ size_t n = mExtractor->countTracks();
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ sp<MetaData> meta = mExtractor->getTrackMetaData(i);
+
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ if (!strncasecmp(mime, "video/", 6)) {
+ break;
+ }
+ }
+
+ if (i == n) {
+ LOGV("no video track found.");
+ return NULL;
+ }
+
+ sp<MetaData> trackMeta = mExtractor->getTrackMetaData(
+ i, MediaExtractor::kIncludeExtensiveMetaData);
+
+ sp<MediaSource> source = mExtractor->getTrack(i);
+
+ if (source.get() == NULL) {
+ LOGV("unable to instantiate video track.");
+ return NULL;
+ }
+
+ sp<MetaData> meta = source->getFormat();
+
+ sp<MediaSource> decoder =
+ OMXCodec::Create(
+ mClient.interface(), meta, false, source,
+ NULL, OMXCodec::kPreferSoftwareCodecs);
+
+ if (decoder.get() == NULL) {
+ LOGV("unable to instantiate video decoder.");
+
+ return NULL;
+ }
+
+ decoder->start();
+
+ // Read one output buffer, ignore format change notifications
+ // and spurious empty buffers.
+
+ MediaSource::ReadOptions options;
+ int64_t thumbNailTime;
+ if (trackMeta->findInt64(kKeyThumbnailTime, &thumbNailTime)) {
+ options.setSeekTo(thumbNailTime);
+ }
+
+ MediaBuffer *buffer = NULL;
+ status_t err;
+ do {
+ if (buffer != NULL) {
+ buffer->release();
+ buffer = NULL;
+ }
+ err = decoder->read(&buffer, &options);
+ options.clearSeekTo();
+ } while (err == INFO_FORMAT_CHANGED
+ || (buffer != NULL && buffer->range_length() == 0));
+
+ if (err != OK) {
+ CHECK_EQ(buffer, NULL);
+
+ LOGV("decoding frame failed.");
+ decoder->stop();
+
+ return NULL;
+ }
+
+ LOGV("successfully decoded video frame.");
+
+ meta = decoder->getFormat();
+
+ int32_t width, height;
+ CHECK(meta->findInt32(kKeyWidth, &width));
+ CHECK(meta->findInt32(kKeyHeight, &height));
+
+ VideoFrame *frame = new VideoFrame;
+ frame->mWidth = width;
+ frame->mHeight = height;
+ frame->mDisplayWidth = width;
+ frame->mDisplayHeight = height;
+ frame->mSize = width * height * 2;
+ frame->mData = new uint8_t[frame->mSize];
+
+ int32_t srcFormat;
+ CHECK(meta->findInt32(kKeyColorFormat, &srcFormat));
+
+ ColorConverter converter(
+ (OMX_COLOR_FORMATTYPE)srcFormat, OMX_COLOR_Format16bitRGB565);
+ CHECK(converter.isValid());
+
+ converter.convert(
+ width, height,
+ (const uint8_t *)buffer->data() + buffer->range_offset(),
+ 0,
+ frame->mData, width * 2);
+
+ buffer->release();
+ buffer = NULL;
+
+ decoder->stop();
+
+ return frame;
+}
+
+MediaAlbumArt *StagefrightMetadataRetriever::extractAlbumArt() {
+ LOGV("extractAlbumArt (extractor: %s)", mExtractor.get() != NULL ? "YES" : "NO");
+
+ return NULL;
+}
+
+const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {
+ LOGV("extractMetadata %d (extractor: %s)",
+ keyCode, mExtractor.get() != NULL ? "YES" : "NO");
+
+ return NULL;
+}
+
+} // namespace android
diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.h b/media/libmediaplayerservice/StagefrightMetadataRetriever.h
new file mode 100644
index 0000000..16127d7
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.h
@@ -0,0 +1,53 @@
+/*
+**
+** Copyright 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.
+*/
+
+#ifndef STAGEFRIGHT_METADATA_RETRIEVER_H_
+
+#define STAGEFRIGHT_METADATA_RETRIEVER_H_
+
+#include <media/MediaMetadataRetrieverInterface.h>
+
+#include <media/stagefright/OMXClient.h>
+
+namespace android {
+
+class MediaExtractor;
+
+struct StagefrightMetadataRetriever : public MediaMetadataRetrieverInterface {
+ StagefrightMetadataRetriever();
+ virtual ~StagefrightMetadataRetriever();
+
+ virtual status_t setDataSource(const char *url);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+
+ virtual VideoFrame *captureFrame();
+ virtual MediaAlbumArt *extractAlbumArt();
+ virtual const char *extractMetadata(int keyCode);
+
+private:
+ OMXClient mClient;
+ sp<MediaExtractor> mExtractor;
+
+ StagefrightMetadataRetriever(const StagefrightMetadataRetriever &);
+
+ StagefrightMetadataRetriever &operator=(
+ const StagefrightMetadataRetriever &);
+};
+
+} // namespace android
+
+#endif // STAGEFRIGHT_METADATA_RETRIEVER_H_
diff --git a/media/libmediaplayerservice/TestPlayerStub.cpp b/media/libmediaplayerservice/TestPlayerStub.cpp
index 8627708..aa49429 100644
--- a/media/libmediaplayerservice/TestPlayerStub.cpp
+++ b/media/libmediaplayerservice/TestPlayerStub.cpp
@@ -176,7 +176,7 @@
mContentUrl = NULL;
if (mPlayer) {
- LOG_ASSERT(mDeletePlayer != NULL);
+ LOG_ASSERT(mDeletePlayer != NULL, "mDeletePlayer is null");
(*mDeletePlayer)(mPlayer);
mPlayer = NULL;
}
diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp
index 8d85ce2..1e3c5a4 100644
--- a/media/libstagefright/AMRExtractor.cpp
+++ b/media/libstagefright/AMRExtractor.cpp
@@ -18,7 +18,8 @@
#define LOG_TAG "AMRExtractor"
#include <utils/Log.h>
-#include <media/stagefright/AMRExtractor.h>
+#include "include/AMRExtractor.h"
+
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
@@ -86,7 +87,7 @@
return new AMRSource(mDataSource, mIsWide);
}
-sp<MetaData> AMRExtractor::getTrackMetaData(size_t index) {
+sp<MetaData> AMRExtractor::getTrackMetaData(size_t index, uint32_t flags) {
if (mInitCheck != OK || index != 0) {
return NULL;
}
@@ -155,7 +156,7 @@
*out = NULL;
uint8_t header;
- ssize_t n = mDataSource->read_at(mOffset, &header, 1);
+ ssize_t n = mDataSource->readAt(mOffset, &header, 1);
if (n < 1) {
return ERROR_IO;
@@ -191,7 +192,7 @@
// Round up bits to bytes and add 1 for the header byte.
frameSize = (frameSize + 7) / 8 + 1;
- n = mDataSource->read_at(mOffset, buffer->data(), frameSize);
+ n = mDataSource->readAt(mOffset, buffer->data(), frameSize);
if (n != (ssize_t)frameSize) {
buffer->release();
@@ -201,10 +202,7 @@
}
buffer->set_range(0, frameSize);
- buffer->meta_data()->setInt32(
- kKeyTimeUnits, (mCurrentTimeUs + 500) / 1000);
- buffer->meta_data()->setInt32(
- kKeyTimeScale, 1000);
+ buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
mOffset += frameSize;
mCurrentTimeUs += 20000; // Each frame is 20ms
@@ -220,7 +218,7 @@
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
char header[9];
- if (source->read_at(0, header, sizeof(header)) != sizeof(header)) {
+ if (source->readAt(0, header, sizeof(header)) != sizeof(header)) {
return false;
}
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 9f71dae..c36e769 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -16,24 +16,26 @@
LOCAL_SRC_FILES += \
AMRExtractor.cpp \
+ AudioPlayer.cpp \
CachingDataSource.cpp \
+ CameraSource.cpp \
DataSource.cpp \
FileSource.cpp \
HTTPDataSource.cpp \
HTTPStream.cpp \
JPEGSource.cpp \
- MediaExtractor.cpp \
MP3Extractor.cpp \
MPEG4Extractor.cpp \
MPEG4Writer.cpp \
+ MediaExtractor.cpp \
MediaPlayerImpl.cpp \
MmapSource.cpp \
SampleTable.cpp \
ShoutcastSource.cpp \
TimeSource.cpp \
TimedEventQueue.cpp \
- AudioPlayer.cpp \
- stagefright_string.cpp
+ WAVExtractor.cpp \
+ string.cpp
endif
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
index 538facb..d7e3f66 100644
--- a/media/libstagefright/AudioPlayer.cpp
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -210,15 +210,9 @@
break;
}
- int32_t units, scale;
- bool success =
- mInputBuffer->meta_data()->findInt32(kKeyTimeUnits, &units);
- success = success &&
- mInputBuffer->meta_data()->findInt32(kKeyTimeScale, &scale);
- CHECK(success);
-
Mutex::Autolock autoLock(mLock);
- mPositionTimeMediaUs = (int64_t)units * 1000000 / scale;
+ CHECK(mInputBuffer->meta_data()->findInt64(
+ kKeyTime, &mPositionTimeMediaUs));
mPositionTimeRealUs =
((mNumFramesPlayed + size_done / mFrameSize) * 1000000)
diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp
index fd00576..23f4897 100644
--- a/media/libstagefright/CachingDataSource.cpp
+++ b/media/libstagefright/CachingDataSource.cpp
@@ -61,11 +61,11 @@
mData = NULL;
}
-status_t CachingDataSource::InitCheck() const {
- return OK;
+status_t CachingDataSource::initCheck() const {
+ return mSource->initCheck();
}
-ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) {
+ssize_t CachingDataSource::readAt(off_t offset, void *data, size_t size) {
Mutex::Autolock autoLock(mLock);
size_t total = 0;
@@ -82,7 +82,7 @@
if (page == NULL) {
page = allocate_page();
page->mOffset = offset - offset % mPageSize;
- ssize_t n = mSource->read_at(page->mOffset, page->mData, mPageSize);
+ ssize_t n = mSource->readAt(page->mOffset, page->mData, mPageSize);
if (n < 0) {
page->mLength = 0;
} else {
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 596ab67..e9d8557 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -21,120 +21,142 @@
#include <binder/IServiceManager.h>
#include <media/stagefright/CameraSource.h>
#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
-#include <ui/ICameraClient.h>
-#include <ui/ICameraService.h>
+#include <ui/Camera.h>
+#include <ui/CameraParameters.h>
+#include <ui/GraphicBuffer.h>
+#include <ui/ISurface.h>
#include <ui/Overlay.h>
-#include <utils/String16.h>
+#include <utils/String8.h>
namespace android {
-class CameraBuffer : public MediaBuffer {
-public:
- CameraBuffer(const sp<IMemory> &frame)
- : MediaBuffer(frame->pointer(), frame->size()),
- mFrame(frame) {
- }
+static int64_t getNowUs() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
- sp<IMemory> releaseFrame() {
- sp<IMemory> frame = mFrame;
- mFrame.clear();
- return frame;
- }
+ return (int64_t)tv.tv_usec + tv.tv_sec * 1000000;
+}
-private:
- sp<IMemory> mFrame;
-};
-
-class CameraSourceClient : public BnCameraClient {
-public:
- CameraSourceClient()
- : mSource(NULL) {
- }
-
- virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) {
- CHECK(mSource != NULL);
- mSource->notifyCallback(msgType, ext1, ext2);
- }
-
- virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) {
- CHECK(mSource != NULL);
- mSource->dataCallback(msgType, data);
- }
-
- void setCameraSource(CameraSource *source) {
- mSource = source;
- }
-
-private:
- CameraSource *mSource;
-};
-
-class DummySurface : public BnSurface {
-public:
+struct DummySurface : public BnSurface {
DummySurface() {}
+ virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage) {
+ return NULL;
+ }
+
virtual status_t registerBuffers(const BufferHeap &buffers) {
return OK;
}
- virtual void postBuffer(ssize_t offset) {
- }
+ virtual void postBuffer(ssize_t offset) {}
+ virtual void unregisterBuffers() {}
- virtual void unregisterBuffers() {
- }
-
virtual sp<OverlayRef> createOverlay(
uint32_t w, uint32_t h, int32_t format) {
return NULL;
}
+
+protected:
+ virtual ~DummySurface() {}
+
+ DummySurface(const DummySurface &);
+ DummySurface &operator=(const DummySurface &);
};
+struct CameraSourceListener : public CameraListener {
+ CameraSourceListener(const sp<CameraSource> &source);
+
+ virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
+ virtual void postData(int32_t msgType, const sp<IMemory> &dataPtr);
+
+ virtual void postDataTimestamp(
+ nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
+
+protected:
+ virtual ~CameraSourceListener();
+
+private:
+ wp<CameraSource> mSource;
+
+ CameraSourceListener(const CameraSourceListener &);
+ CameraSourceListener &operator=(const CameraSourceListener &);
+};
+
+CameraSourceListener::CameraSourceListener(const sp<CameraSource> &source)
+ : mSource(source) {
+}
+
+CameraSourceListener::~CameraSourceListener() {
+}
+
+void CameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) {
+ LOGV("notify(%d, %d, %d)", msgType, ext1, ext2);
+}
+
+void CameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr) {
+ LOGV("postData(%d, ptr:%p, size:%d)",
+ msgType, dataPtr->pointer(), dataPtr->size());
+
+ sp<CameraSource> source = mSource.promote();
+ if (source.get() != NULL) {
+ source->dataCallback(msgType, dataPtr);
+ }
+}
+
+void CameraSourceListener::postDataTimestamp(
+ nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
+ LOGV("postDataTimestamp(%lld, %d, ptr:%p, size:%d)",
+ timestamp, msgType, dataPtr->pointer(), dataPtr->size());
+}
+
// static
CameraSource *CameraSource::Create() {
- sp<IServiceManager> sm = defaultServiceManager();
+ sp<Camera> camera = Camera::connect();
- sp<ICameraService> service =
- interface_cast<ICameraService>(
- sm->getService(String16("media.camera")));
+ if (camera.get() == NULL) {
+ return NULL;
+ }
- sp<CameraSourceClient> client = new CameraSourceClient;
- sp<ICamera> camera = service->connect(client);
-
- CameraSource *source = new CameraSource(camera, client);
- client->setCameraSource(source);
-
- return source;
+ return new CameraSource(camera);
}
-CameraSource::CameraSource(
- const sp<ICamera> &camera, const sp<ICameraClient> &client)
+CameraSource::CameraSource(const sp<Camera> &camera)
: mCamera(camera),
- mCameraClient(client),
+ mWidth(0),
+ mHeight(0),
+ mFirstFrameTimeUs(0),
mNumFrames(0),
mStarted(false) {
- printf("params: \"%s\"\n", mCamera->getParameters().string());
+ String8 s = mCamera->getParameters();
+ printf("params: \"%s\"\n", s.string());
+
+ CameraParameters params(s);
+ params.getPreviewSize(&mWidth, &mHeight);
}
CameraSource::~CameraSource() {
if (mStarted) {
stop();
}
-
- mCamera->disconnect();
}
status_t CameraSource::start(MetaData *) {
CHECK(!mStarted);
- status_t err = mCamera->lock();
+ mCamera->setListener(new CameraSourceListener(this));
+
+ sp<ISurface> dummy = new DummySurface;
+ status_t err = mCamera->setPreviewDisplay(dummy);
CHECK_EQ(err, OK);
- err = mCamera->setPreviewDisplay(new DummySurface);
- CHECK_EQ(err, OK);
- mCamera->setPreviewCallbackFlag(1);
- mCamera->startPreview();
+ mCamera->setPreviewCallbackFlags(
+ FRAME_CALLBACK_FLAG_ENABLE_MASK
+ | FRAME_CALLBACK_FLAG_COPY_OUT_MASK);
+
+ err = mCamera->startPreview();
CHECK_EQ(err, OK);
mStarted = true;
@@ -146,7 +168,6 @@
CHECK(mStarted);
mCamera->stopPreview();
- mCamera->unlock();
mStarted = false;
@@ -157,8 +178,8 @@
sp<MetaData> meta = new MetaData;
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420SemiPlanar);
- meta->setInt32(kKeyWidth, 480);
- meta->setInt32(kKeyHeight, 320);
+ meta->setInt32(kKeyWidth, mWidth);
+ meta->setInt32(kKeyHeight, mHeight);
return meta;
}
@@ -175,6 +196,7 @@
}
sp<IMemory> frame;
+ int64_t frameTime;
{
Mutex::Autolock autoLock(mLock);
@@ -184,41 +206,33 @@
frame = *mFrames.begin();
mFrames.erase(mFrames.begin());
+
+ frameTime = *mFrameTimes.begin();
+ mFrameTimes.erase(mFrameTimes.begin());
}
- int count = mNumFrames++;
-
- *buffer = new CameraBuffer(frame);
+ *buffer = new MediaBuffer(frame->size());
+ memcpy((*buffer)->data(), frame->pointer(), frame->size());
+ (*buffer)->set_range(0, frame->size());
(*buffer)->meta_data()->clear();
- (*buffer)->meta_data()->setInt32(kKeyTimeScale, 15);
- (*buffer)->meta_data()->setInt32(kKeyTimeUnits, count);
-
- (*buffer)->add_ref();
- (*buffer)->setObserver(this);
+ (*buffer)->meta_data()->setInt64(kKeyTime, frameTime);
return OK;
}
-void CameraSource::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) {
- printf("notifyCallback %d, %d, %d\n", msgType, ext1, ext2);
-}
-
void CameraSource::dataCallback(int32_t msgType, const sp<IMemory> &data) {
Mutex::Autolock autoLock(mLock);
+ int64_t nowUs = getNowUs();
+ if (mNumFrames == 0) {
+ mFirstFrameTimeUs = nowUs;
+ }
+ ++mNumFrames;
+
mFrames.push_back(data);
+ mFrameTimes.push_back(nowUs - mFirstFrameTimeUs);
mFrameAvailableCondition.signal();
}
-void CameraSource::signalBufferReturned(MediaBuffer *_buffer) {
- CameraBuffer *buffer = static_cast<CameraBuffer *>(_buffer);
-
- mCamera->releaseRecordingFrame(buffer->releaseFrame());
-
- buffer->setObserver(NULL);
- buffer->release();
- buffer = NULL;
-}
-
} // namespace android
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index daac539..2a6dbc4 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-#include <media/stagefright/AMRExtractor.h>
+#include "include/AMRExtractor.h"
+#include "include/MP3Extractor.h"
+#include "include/MPEG4Extractor.h"
+#include "include/WAVExtractor.h"
+
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MP3Extractor.h>
-#include <media/stagefright/MPEG4Extractor.h>
#include <utils/String8.h>
namespace android {
@@ -27,7 +29,7 @@
*x = 0;
uint8_t byte[2];
- if (read_at(offset, byte, 2) != 2) {
+ if (readAt(offset, byte, 2) != 2) {
return false;
}
@@ -86,6 +88,7 @@
RegisterSniffer(SniffMP3);
RegisterSniffer(SniffMPEG4);
RegisterSniffer(SniffAMR);
+ RegisterSniffer(SniffWAV);
}
} // namespace android
diff --git a/media/libstagefright/ESDS.cpp b/media/libstagefright/ESDS.cpp
index 53b92a0..28d338c 100644
--- a/media/libstagefright/ESDS.cpp
+++ b/media/libstagefright/ESDS.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <media/stagefright/ESDS.h>
+#include "include/ESDS.h"
#include <string.h>
diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp
index f6b90b2..f318ee3 100644
--- a/media/libstagefright/FileSource.cpp
+++ b/media/libstagefright/FileSource.cpp
@@ -30,11 +30,11 @@
}
}
-status_t FileSource::InitCheck() const {
+status_t FileSource::initCheck() const {
return mFile != NULL ? OK : NO_INIT;
}
-ssize_t FileSource::read_at(off_t offset, void *data, size_t size) {
+ssize_t FileSource::readAt(off_t offset, void *data, size_t size) {
Mutex::Autolock autoLock(mLock);
int err = fseeko(mFile, offset, SEEK_SET);
diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp
index 4dedebd..5536801 100644
--- a/media/libstagefright/HTTPDataSource.cpp
+++ b/media/libstagefright/HTTPDataSource.cpp
@@ -14,17 +14,19 @@
* limitations under the License.
*/
+#include "include/stagefright_string.h"
+#include "include/HTTPStream.h"
+
#include <stdlib.h>
#include <media/stagefright/HTTPDataSource.h>
-#include <media/stagefright/HTTPStream.h>
#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/stagefright_string.h>
namespace android {
HTTPDataSource::HTTPDataSource(const char *uri)
- : mHost(NULL),
+ : mHttp(new HTTPStream),
+ mHost(NULL),
mPort(0),
mPath(NULL),
mBuffer(malloc(kBufferSize)),
@@ -65,33 +67,40 @@
mPort = port;
mPath = strdup(path.c_str());
- status_t err = mHttp.connect(mHost, mPort);
- CHECK_EQ(err, OK);
+ mInitCheck = mHttp->connect(mHost, mPort);
}
HTTPDataSource::HTTPDataSource(const char *host, int port, const char *path)
- : mHost(strdup(host)),
+ : mHttp(new HTTPStream),
+ mHost(strdup(host)),
mPort(port),
mPath(strdup(path)),
mBuffer(malloc(kBufferSize)),
mBufferLength(0),
mBufferOffset(0) {
- status_t err = mHttp.connect(mHost, mPort);
- CHECK_EQ(err, OK);
+ mInitCheck = mHttp->connect(mHost, mPort);
+}
+
+status_t HTTPDataSource::initCheck() const {
+ return mInitCheck;
}
HTTPDataSource::~HTTPDataSource() {
- mHttp.disconnect();
+ mHttp->disconnect();
free(mBuffer);
mBuffer = NULL;
free(mPath);
mPath = NULL;
+
+ delete mHttp;
+ mHttp = NULL;
}
-ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) {
- if (offset >= mBufferOffset && offset < mBufferOffset + mBufferLength) {
+ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) {
+ if (offset >= mBufferOffset
+ && offset < (off_t)(mBufferOffset + mBufferLength)) {
size_t num_bytes_available = mBufferLength - (offset - mBufferOffset);
size_t copy = num_bytes_available;
@@ -119,19 +128,19 @@
status_t err;
int attempt = 1;
for (;;) {
- if ((err = mHttp.send("GET ")) != OK
- || (err = mHttp.send(mPath)) != OK
- || (err = mHttp.send(" HTTP/1.1\r\n")) != OK
- || (err = mHttp.send(host)) != OK
- || (err = mHttp.send(range)) != OK
- || (err = mHttp.send("\r\n")) != OK
- || (err = mHttp.receive_header(&http_status)) != OK) {
+ if ((err = mHttp->send("GET ")) != OK
+ || (err = mHttp->send(mPath)) != OK
+ || (err = mHttp->send(" HTTP/1.1\r\n")) != OK
+ || (err = mHttp->send(host)) != OK
+ || (err = mHttp->send(range)) != OK
+ || (err = mHttp->send("\r\n")) != OK
+ || (err = mHttp->receive_header(&http_status)) != OK) {
if (attempt == 3) {
return err;
}
- mHttp.connect(mHost, mPort);
+ mHttp->connect(mHost, mPort);
++attempt;
} else {
break;
@@ -143,14 +152,14 @@
}
string value;
- if (!mHttp.find_header_value("Content-Length", &value)) {
+ if (!mHttp->find_header_value("Content-Length", &value)) {
return UNKNOWN_ERROR;
}
char *end;
unsigned long contentLength = strtoul(value.c_str(), &end, 10);
- ssize_t num_bytes_received = mHttp.receive(mBuffer, contentLength);
+ ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength);
if (num_bytes_received <= 0) {
return num_bytes_received;
diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp
index 6af7df9..02f9439 100644
--- a/media/libstagefright/HTTPStream.cpp
+++ b/media/libstagefright/HTTPStream.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "include/HTTPStream.h"
+
#include <sys/socket.h>
#include <arpa/inet.h>
@@ -25,7 +27,6 @@
#include <string.h>
#include <unistd.h>
-#include <media/stagefright/HTTPStream.h>
#include <media/stagefright/MediaDebug.h>
namespace android {
diff --git a/media/libstagefright/JPEGSource.cpp b/media/libstagefright/JPEGSource.cpp
index d1dfd83..a4be2dd 100644
--- a/media/libstagefright/JPEGSource.cpp
+++ b/media/libstagefright/JPEGSource.cpp
@@ -119,7 +119,7 @@
MediaBuffer *buffer;
mGroup->acquire_buffer(&buffer);
- ssize_t n = mSource->read_at(mOffset, buffer->data(), mSize - mOffset);
+ ssize_t n = mSource->readAt(mOffset, buffer->data(), mSize - mOffset);
if (n <= 0) {
buffer->release();
@@ -156,13 +156,13 @@
for (;;) {
uint8_t marker;
- if (mSource->read_at(i++, &marker, 1) != 1) {
+ if (mSource->readAt(i++, &marker, 1) != 1) {
return ERROR_IO;
}
CHECK_EQ(marker, 0xff);
- if (mSource->read_at(i++, &marker, 1) != 1) {
+ if (mSource->readAt(i++, &marker, 1) != 1) {
return ERROR_IO;
}
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 7fd699f..8dd8ea9 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -18,8 +18,9 @@
#define LOG_TAG "MP3Extractor"
#include <utils/Log.h>
+#include "include/MP3Extractor.h"
+
#include <media/stagefright/DataSource.h>
-#include <media/stagefright/MP3Extractor.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
@@ -147,7 +148,12 @@
*out_bitrate = bitrate;
}
- *frame_size = 144000 * bitrate / sampling_rate + padding;
+ if (version == 3 /* V1 */) {
+ *frame_size = 144000 * bitrate / sampling_rate + padding;
+ } else {
+ // V2 or V2.5
+ *frame_size = 72000 * bitrate / sampling_rate + padding;
+ }
}
if (out_sampling_rate) {
@@ -166,6 +172,33 @@
static bool Resync(
const sp<DataSource> &source, uint32_t match_header,
off_t *inout_pos, uint32_t *out_header) {
+ if (*inout_pos == 0) {
+ // Skip an optional ID3 header if syncing at the very beginning
+ // of the datasource.
+
+ uint8_t id3header[10];
+ if (source->readAt(0, id3header, sizeof(id3header))
+ < (ssize_t)sizeof(id3header)) {
+ // If we can't even read these 10 bytes, we might as well bail out,
+ // even if there _were_ 10 bytes of valid mp3 audio data...
+ return false;
+ }
+
+ if (id3header[0] == 'I' && id3header[1] == 'D' && id3header[2] == '3') {
+ // Skip the ID3v2 header.
+
+ size_t len =
+ ((id3header[6] & 0x7f) << 21)
+ | ((id3header[7] & 0x7f) << 14)
+ | ((id3header[8] & 0x7f) << 7)
+ | (id3header[9] & 0x7f);
+
+ len += 10;
+
+ *inout_pos += len;
+ }
+ }
+
// Everything must match except for
// protection, bitrate, padding, private bits and mode extension.
const uint32_t kMask = 0xfffe0ccf;
@@ -195,7 +228,7 @@
buffer_length = buffer_length - buffer_offset;
buffer_offset = 0;
- ssize_t n = source->read_at(
+ ssize_t n = source->readAt(
pos, &buffer[buffer_length], kMaxFrameSize - buffer_length);
if (n <= 0) {
@@ -232,7 +265,7 @@
valid = true;
for (int j = 0; j < 3; ++j) {
uint8_t tmp[4];
- if (source->read_at(test_pos, tmp, 4) < 4) {
+ if (source->readAt(test_pos, tmp, 4) < 4) {
valid = false;
break;
}
@@ -338,10 +371,9 @@
off_t fileSize;
if (mDataSource->getSize(&fileSize) == OK) {
- mMeta->setInt32(
+ mMeta->setInt64(
kKeyDuration,
- 8 * (fileSize - mFirstFramePos) / bitrate);
- mMeta->setInt32(kKeyTimeScale, 1000);
+ 8000LL * (fileSize - mFirstFramePos) / bitrate);
}
}
}
@@ -362,7 +394,7 @@
mMeta, mDataSource, mFirstFramePos, mFixedHeader);
}
-sp<MetaData> MP3Extractor::getTrackMetaData(size_t index) {
+sp<MetaData> MP3Extractor::getTrackMetaData(size_t index, uint32_t flags) {
if (mFirstFramePos < 0 || index != 0) {
return NULL;
}
@@ -448,7 +480,7 @@
size_t frame_size;
for (;;) {
- ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), 4);
+ ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), 4);
if (n < 4) {
buffer->release();
buffer = NULL;
@@ -482,7 +514,7 @@
CHECK(frame_size <= buffer->size());
- ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), frame_size);
+ ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), frame_size);
if (n < (ssize_t)frame_size) {
buffer->release();
buffer = NULL;
@@ -492,8 +524,7 @@
buffer->set_range(0, frame_size);
- buffer->meta_data()->setInt32(kKeyTimeUnits, mCurrentTimeUs / 1000);
- buffer->meta_data()->setInt32(kKeyTimeScale, 1000);
+ buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
mCurrentPos += frame_size;
mCurrentTimeUs += 1152 * 1000000 / 44100;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 9174d19..9d17064 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -17,6 +17,9 @@
#define LOG_TAG "MPEG4Extractor"
#include <utils/Log.h>
+#include "include/MPEG4Extractor.h"
+#include "include/SampleTable.h"
+
#include <arpa/inet.h>
#include <ctype.h>
@@ -25,14 +28,12 @@
#include <string.h>
#include <media/stagefright/DataSource.h>
-#include <media/stagefright/MPEG4Extractor.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
-#include <media/stagefright/SampleTable.h>
#include <media/stagefright/Utils.h>
#include <utils/String8.h>
@@ -43,6 +44,7 @@
// Caller retains ownership of both "dataSource" and "sampleTable".
MPEG4Source(const sp<MetaData> &format,
const sp<DataSource> &dataSource,
+ int32_t timeScale,
const sp<SampleTable> &sampleTable);
virtual status_t start(MetaData *params = NULL);
@@ -177,7 +179,8 @@
return n;
}
-sp<MetaData> MPEG4Extractor::getTrackMetaData(size_t index) {
+sp<MetaData> MPEG4Extractor::getTrackMetaData(
+ size_t index, uint32_t flags) {
status_t err;
if ((err = readMetaData()) != OK) {
return NULL;
@@ -197,6 +200,25 @@
return NULL;
}
+ if ((flags & kIncludeExtensiveMetaData)
+ && !track->includes_expensive_metadata) {
+ track->includes_expensive_metadata = true;
+
+ const char *mime;
+ CHECK(track->meta->findCString(kKeyMIMEType, &mime));
+ if (!strncasecmp("video/", mime, 6)) {
+ uint32_t sampleIndex;
+ uint32_t sampleTime;
+ if (track->sampleTable->findThumbnailSample(&sampleIndex) == OK
+ && track->sampleTable->getDecodingTime(
+ sampleIndex, &sampleTime) == OK) {
+ track->meta->setInt64(
+ kKeyThumbnailTime,
+ ((int64_t)sampleTime * 1000000) / track->timescale);
+ }
+ }
+ }
+
return track->meta;
}
@@ -227,7 +249,7 @@
status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
uint32_t hdr[2];
- if (mDataSource->read_at(*offset, hdr, 8) < 8) {
+ if (mDataSource->readAt(*offset, hdr, 8) < 8) {
return ERROR_IO;
}
uint64_t chunk_size = ntohl(hdr[0]);
@@ -235,7 +257,7 @@
off_t data_offset = *offset + 8;
if (chunk_size == 1) {
- if (mDataSource->read_at(*offset + 8, &chunk_size, 8) < 8) {
+ if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
return ERROR_IO;
}
chunk_size = ntoh64(chunk_size);
@@ -252,7 +274,7 @@
char buffer[256];
if (chunk_size <= sizeof(buffer)) {
- if (mDataSource->read_at(*offset, buffer, chunk_size) < chunk_size) {
+ if (mDataSource->readAt(*offset, buffer, chunk_size) < chunk_size) {
return ERROR_IO;
}
@@ -298,7 +320,7 @@
CHECK(chunk_data_size >= 4);
uint8_t version;
- if (mDataSource->read_at(data_offset, &version, 1) < 1) {
+ if (mDataSource->readAt(data_offset, &version, 1) < 1) {
return ERROR_IO;
}
@@ -312,7 +334,7 @@
}
uint8_t buffer[36 + 60];
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
return ERROR_IO;
}
@@ -329,7 +351,7 @@
}
uint8_t buffer[24 + 60];
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
return ERROR_IO;
}
@@ -351,6 +373,7 @@
mLastTrack = track;
track->meta = new MetaData;
+ track->includes_expensive_metadata = false;
track->timescale = 0;
track->sampleTable = new SampleTable(mDataSource);
track->meta->setCString(kKeyMIMEType, "application/octet-stream");
@@ -366,7 +389,7 @@
}
uint8_t version;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, &version, sizeof(version))
< (ssize_t)sizeof(version)) {
return ERROR_IO;
@@ -383,18 +406,17 @@
}
uint32_t timescale;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
timescale_offset, ×cale, sizeof(timescale))
< (ssize_t)sizeof(timescale)) {
return ERROR_IO;
}
mLastTrack->timescale = ntohl(timescale);
- mLastTrack->meta->setInt32(kKeyTimeScale, mLastTrack->timescale);
int64_t duration;
if (version == 1) {
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
timescale_offset + 4, &duration, sizeof(duration))
< (ssize_t)sizeof(duration)) {
return ERROR_IO;
@@ -402,14 +424,15 @@
duration = ntoh64(duration);
} else {
int32_t duration32;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
timescale_offset + 4, &duration32, sizeof(duration32))
< (ssize_t)sizeof(duration32)) {
return ERROR_IO;
}
duration = ntohl(duration32);
}
- mLastTrack->meta->setInt32(kKeyDuration, duration);
+ mLastTrack->meta->setInt64(
+ kKeyDuration, (duration * 1000000) / mLastTrack->timescale);
*offset += chunk_size;
break;
@@ -422,7 +445,7 @@
}
uint8_t buffer[24];
- if (mDataSource->read_at(data_offset, buffer, 24) < 24) {
+ if (mDataSource->readAt(data_offset, buffer, 24) < 24) {
return ERROR_IO;
}
@@ -449,7 +472,7 @@
uint8_t buffer[8];
CHECK(chunk_data_size >= (off_t)sizeof(buffer));
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, buffer, 8) < 8) {
return ERROR_IO;
}
@@ -492,7 +515,7 @@
return ERROR_MALFORMED;
}
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
return ERROR_IO;
}
@@ -544,7 +567,7 @@
return ERROR_MALFORMED;
}
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
return ERROR_IO;
}
@@ -655,7 +678,7 @@
return ERROR_BUFFER_TOO_SMALL;
}
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, buffer, chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
@@ -679,7 +702,7 @@
return ERROR_BUFFER_TOO_SMALL;
}
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, buffer, chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
@@ -722,7 +745,7 @@
}
return new MPEG4Source(
- track->meta, mDataSource, track->sampleTable);
+ track->meta, mDataSource, track->timescale, track->sampleTable);
}
////////////////////////////////////////////////////////////////////////////////
@@ -730,10 +753,11 @@
MPEG4Source::MPEG4Source(
const sp<MetaData> &format,
const sp<DataSource> &dataSource,
+ int32_t timeScale,
const sp<SampleTable> &sampleTable)
: mFormat(format),
mDataSource(dataSource),
- mTimescale(0),
+ mTimescale(timeScale),
mSampleTable(sampleTable),
mCurrentSampleIndex(0),
mIsAVC(false),
@@ -746,9 +770,6 @@
bool success = mFormat->findCString(kKeyMIMEType, &mime);
CHECK(success);
- success = mFormat->findInt32(kKeyTimeScale, &mTimescale);
- CHECK(success);
-
mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
}
@@ -868,7 +889,7 @@
if (!mIsAVC || mWantsNALFragments) {
if (newBuffer) {
ssize_t num_bytes_read =
- mDataSource->read_at(offset, (uint8_t *)mBuffer->data(), size);
+ mDataSource->readAt(offset, (uint8_t *)mBuffer->data(), size);
if (num_bytes_read < (ssize_t)size) {
mBuffer->release();
@@ -879,8 +900,8 @@
mBuffer->set_range(0, size);
mBuffer->meta_data()->clear();
- mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts);
- mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale);
+ mBuffer->meta_data()->setInt64(
+ kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
++mCurrentSampleIndex;
}
@@ -923,7 +944,7 @@
// the start code (0x00 00 00 01).
ssize_t num_bytes_read =
- mDataSource->read_at(offset, mSrcBuffer, size);
+ mDataSource->readAt(offset, mSrcBuffer, size);
if (num_bytes_read < (ssize_t)size) {
mBuffer->release();
@@ -959,8 +980,8 @@
mBuffer->set_range(0, dstOffset);
mBuffer->meta_data()->clear();
- mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts);
- mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale);
+ mBuffer->meta_data()->setInt64(
+ kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
++mCurrentSampleIndex;
*out = mBuffer;
@@ -974,13 +995,14 @@
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
uint8_t header[8];
- ssize_t n = source->read_at(4, header, sizeof(header));
+ ssize_t n = source->readAt(4, header, sizeof(header));
if (n < (ssize_t)sizeof(header)) {
return false;
}
if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8)
- || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)) {
+ || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)
+ || !memcmp(header, "ftypM4A ", 8)) {
*mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
*confidence = 0.1;
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index fa35768..9a7a873 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -399,15 +399,11 @@
info.size = buffer->range_length();
info.offset = offset;
- int32_t units, scale;
- bool success =
- buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
- CHECK(success);
- success =
- buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
- CHECK(success);
+ int64_t timestampUs;
+ CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs));
- info.timestamp = (int64_t)units * 1000 / scale;
+ // Our timestamp is in ms.
+ info.timestamp = (timestampUs + 500) / 1000;
mSampleInfos.push_back(info);
diff --git a/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/MediaBuffer.cpp
index f3c0e73..b973745 100644
--- a/media/libstagefright/MediaBuffer.cpp
+++ b/media/libstagefright/MediaBuffer.cpp
@@ -108,10 +108,10 @@
}
void MediaBuffer::set_range(size_t offset, size_t length) {
- if (offset < 0 || offset + length > mSize) {
+ if (offset + length > mSize) {
LOGE("offset = %d, length = %d, mSize = %d", offset, length, mSize);
}
- CHECK(offset >= 0 && offset + length <= mSize);
+ CHECK(offset + length <= mSize);
mRangeOffset = offset;
mRangeLength = length;
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 87b5b24..04b1454 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -32,5 +32,6 @@
const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw";
const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4";
+const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav";
} // namespace android
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index 8535f52..19a1f85 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -18,12 +18,17 @@
#define LOG_TAG "MediaExtractor"
#include <utils/Log.h>
-#include <media/stagefright/AMRExtractor.h>
+#include "include/AMRExtractor.h"
+#include "include/MP3Extractor.h"
+#include "include/MPEG4Extractor.h"
+#include "include/WAVExtractor.h"
+
+#include <media/stagefright/CachingDataSource.h>
#include <media/stagefright/DataSource.h>
+#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaDefs.h>
-#include <media/stagefright/MP3Extractor.h>
-#include <media/stagefright/MPEG4Extractor.h>
#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MmapSource.h>
#include <utils/String8.h>
namespace android {
@@ -41,7 +46,7 @@
}
mime = tmp.string();
- LOGI("Autodetected media content as '%s' with confidence %.2f",
+ LOGV("Autodetected media content as '%s' with confidence %.2f",
mime, confidence);
}
@@ -53,9 +58,32 @@
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
return new AMRExtractor(source);
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
+ return new WAVExtractor(source);
}
return NULL;
}
+// static
+sp<MediaExtractor> MediaExtractor::CreateFromURI(
+ const char *uri, const char *mime) {
+ sp<DataSource> source;
+ if (!strncasecmp("file://", uri, 7)) {
+ source = new MmapSource(uri + 7);
+ } else if (!strncasecmp("http://", uri, 7)) {
+ source = new HTTPDataSource(uri);
+ source = new CachingDataSource(source, 64 * 1024, 10);
+ } else {
+ // Assume it's a filename.
+ source = new MmapSource(uri);
+ }
+
+ if (source == NULL || source->initCheck() != OK) {
+ return NULL;
+ }
+
+ return Create(source, mime);
+}
+
} // namespace android
diff --git a/media/libstagefright/MediaPlayerImpl.cpp b/media/libstagefright/MediaPlayerImpl.cpp
index 622ea7e..eb9fe7f 100644
--- a/media/libstagefright/MediaPlayerImpl.cpp
+++ b/media/libstagefright/MediaPlayerImpl.cpp
@@ -18,16 +18,17 @@
#define LOG_TAG "MediaPlayerImpl"
#include "utils/Log.h"
+#include "include/stagefright_string.h"
+#include "include/HTTPStream.h"
+
#include <OMX_Component.h>
#include <unistd.h>
#include <media/stagefright/AudioPlayer.h>
-#include <media/stagefright/CachingDataSource.h>
// #include <media/stagefright/CameraSource.h>
-#include <media/stagefright/HTTPDataSource.h>
-#include <media/stagefright/HTTPStream.h>
#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/MediaPlayerImpl.h>
#include <media/stagefright/MetaData.h>
@@ -69,18 +70,7 @@
mVideoDecoder = CameraSource::Create();
#endif
} else {
- sp<DataSource> source;
- if (!strncasecmp("file://", uri, 7)) {
- source = new MmapSource(uri + 7);
- } else if (!strncasecmp("http://", uri, 7)) {
- source = new HTTPDataSource(uri);
- source = new CachingDataSource(source, 64 * 1024, 10);
- } else {
- // Assume it's a filename.
- source = new MmapSource(uri);
- }
-
- mExtractor = MediaExtractor::Create(source);
+ mExtractor = MediaExtractor::CreateFromURI(uri);
if (mExtractor == NULL) {
return;
@@ -258,6 +248,13 @@
status_t err = mVideoDecoder->read(&buffer, &options);
CHECK((err == OK && buffer != NULL) || (err != OK && buffer == NULL));
+ if (err == INFO_FORMAT_CHANGED) {
+ LOGI("format changed.");
+ depopulateISurface();
+ populateISurface();
+ continue;
+ }
+
if (err == ERROR_END_OF_STREAM || err != OK) {
eof = true;
continue;
@@ -269,15 +266,9 @@
continue;
}
- int32_t units, scale;
- bool success =
- buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
- CHECK(success);
- success =
- buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
- CHECK(success);
+ int64_t pts_us;
+ CHECK(buffer->meta_data()->findInt64(kKeyTime, &pts_us));
- int64_t pts_us = (int64_t)units * 1000000 / scale;
{
Mutex::Autolock autoLock(mLock);
mVideoPosition = pts_us;
@@ -391,12 +382,10 @@
sp<MediaSource> source = mExtractor->getTrack(i);
- int32_t units, scale;
- if (meta->findInt32(kKeyDuration, &units)
- && meta->findInt32(kKeyTimeScale, &scale)) {
- int64_t duration_us = (int64_t)units * 1000000 / scale;
- if (duration_us > mDuration) {
- mDuration = duration_us;
+ int64_t durationUs;
+ if (meta->findInt64(kKeyDuration, &durationUs)) {
+ if (durationUs > mDuration) {
+ mDuration = durationUs;
}
}
@@ -415,8 +404,15 @@
sp<MetaData> meta = source->getFormat();
- mAudioDecoder = OMXCodec::Create(
- mClient.interface(), meta, false /* createEncoder */, source);
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) {
+ mAudioDecoder = source;
+ } else {
+ mAudioDecoder = OMXCodec::Create(
+ mClient.interface(), meta, false /* createEncoder */, source);
+ }
}
void MediaPlayerImpl::setVideoSource(const sp<MediaSource> &source) {
@@ -621,6 +617,9 @@
success = success && meta->findInt32(kKeyHeight, &decodedHeight);
CHECK(success);
+ LOGI("mVideoWidth=%d, mVideoHeight=%d, decodedWidth=%d, decodedHeight=%d",
+ mVideoWidth, mVideoHeight, decodedWidth, decodedHeight);
+
if (mSurface.get() != NULL) {
mVideoRenderer =
mClient.interface()->createRenderer(
diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp
index 6b067cb..63b476e 100644
--- a/media/libstagefright/MetaData.cpp
+++ b/media/libstagefright/MetaData.cpp
@@ -58,6 +58,10 @@
return setData(key, TYPE_INT32, &value, sizeof(value));
}
+bool MetaData::setInt64(uint32_t key, int64_t value) {
+ return setData(key, TYPE_INT64, &value, sizeof(value));
+}
+
bool MetaData::setFloat(uint32_t key, float value) {
return setData(key, TYPE_FLOAT, &value, sizeof(value));
}
@@ -94,6 +98,21 @@
return true;
}
+bool MetaData::findInt64(uint32_t key, int64_t *value) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!findData(key, &type, &data, &size) || type != TYPE_INT64) {
+ return false;
+ }
+
+ CHECK_EQ(size, sizeof(*value));
+
+ *value = *(int64_t *)data;
+
+ return true;
+}
+
bool MetaData::findFloat(uint32_t key, float *value) {
uint32_t type;
const void *data;
diff --git a/media/libstagefright/MmapSource.cpp b/media/libstagefright/MmapSource.cpp
index 47d95f9..42749cf 100644
--- a/media/libstagefright/MmapSource.cpp
+++ b/media/libstagefright/MmapSource.cpp
@@ -34,7 +34,10 @@
mBase(NULL),
mSize(0) {
LOGV("MmapSource '%s'", filename);
- CHECK(mFd >= 0);
+
+ if (mFd < 0) {
+ return;
+ }
off_t size = lseek(mFd, 0, SEEK_END);
mSize = (size_t)size;
@@ -78,12 +81,12 @@
}
}
-status_t MmapSource::InitCheck() const {
+status_t MmapSource::initCheck() const {
return mFd == -1 ? NO_INIT : OK;
}
-ssize_t MmapSource::read_at(off_t offset, void *data, size_t size) {
- LOGV("read_at offset:%ld data:%p size:%d", offset, data, size);
+ssize_t MmapSource::readAt(off_t offset, void *data, size_t size) {
+ LOGV("readAt offset:%ld data:%p size:%d", offset, data, size);
CHECK(offset >= 0);
size_t avail = 0;
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index ebf1e0c..6c0367a 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -18,11 +18,12 @@
#define LOG_TAG "OMXCodec"
#include <utils/Log.h>
+#include "include/ESDS.h"
+
#include <binder/IServiceManager.h>
#include <binder/MemoryDealer.h>
#include <binder/ProcessState.h>
#include <media/IMediaPlayerService.h>
-#include <media/stagefright/ESDS.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
@@ -39,6 +40,8 @@
namespace android {
+static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
+
struct CodecInfo {
const char *mime;
const char *codec;
@@ -169,52 +172,37 @@
params->nVersion.s.nStep = 0;
}
-// static
-sp<OMXCodec> OMXCodec::Create(
- const sp<IOMX> &omx,
- const sp<MetaData> &meta, bool createEncoder,
- const sp<MediaSource> &source,
- const char *matchComponentName) {
- const char *mime;
- bool success = meta->findCString(kKeyMIMEType, &mime);
- CHECK(success);
-
- const char *componentName = NULL;
- sp<OMXCodecObserver> observer = new OMXCodecObserver;
- IOMX::node_id node = 0;
- for (int index = 0;; ++index) {
- if (createEncoder) {
- componentName = GetCodec(
- kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]),
- mime, index);
- } else {
- componentName = GetCodec(
- kDecoderInfo, sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),
- mime, index);
- }
-
- if (!componentName) {
- return NULL;
- }
-
- // If a specific codec is requested, skip the non-matching ones.
- if (matchComponentName && strcmp(componentName, matchComponentName)) {
- continue;
- }
-
- LOGV("Attempting to allocate OMX node '%s'", componentName);
-
- status_t err = omx->allocateNode(componentName, observer, &node);
- if (err == OK) {
- LOGI("Successfully allocated OMX node '%s'", componentName);
- break;
- }
+static bool IsSoftwareCodec(const char *componentName) {
+ if (!strncmp("OMX.PV.", componentName, 7)) {
+ return true;
}
+ return false;
+}
+
+static int CompareSoftwareCodecsFirst(
+ const String8 *elem1, const String8 *elem2) {
+ bool isSoftwareCodec1 = IsSoftwareCodec(elem1->string());
+ bool isSoftwareCodec2 = IsSoftwareCodec(elem2->string());
+
+ if (isSoftwareCodec1) {
+ if (isSoftwareCodec2) { return 0; }
+ return -1;
+ }
+
+ if (isSoftwareCodec2) {
+ return 1;
+ }
+
+ return 0;
+}
+
+// static
+uint32_t OMXCodec::getComponentQuirks(const char *componentName) {
uint32_t quirks = 0;
+
if (!strcmp(componentName, "OMX.PV.avcdec")) {
quirks |= kWantsNALFragments;
- quirks |= kOutputDimensionsAre16Aligned;
}
if (!strcmp(componentName, "OMX.TI.MP3.decode")) {
quirks |= kNeedsFlushBeforeDisable;
@@ -222,20 +210,15 @@
if (!strcmp(componentName, "OMX.TI.AAC.decode")) {
quirks |= kNeedsFlushBeforeDisable;
quirks |= kRequiresFlushCompleteEmulation;
-
- // The following is currently necessary for proper shutdown
- // behaviour, but NOT enabled by default in order to make the
- // bug reproducible...
- // quirks |= kRequiresFlushBeforeShutdown;
}
if (!strncmp(componentName, "OMX.qcom.video.encoder.", 23)) {
quirks |= kRequiresLoadedToIdleAfterAllocation;
quirks |= kRequiresAllocateBufferOnInputPorts;
+ quirks |= kRequiresAllocateBufferOnOutputPorts;
}
if (!strncmp(componentName, "OMX.qcom.video.decoder.", 23)) {
// XXX Required on P....on only.
quirks |= kRequiresAllocateBufferOnOutputPorts;
- quirks |= kOutputDimensionsAre16Aligned;
}
if (!strncmp(componentName, "OMX.TI.", 7)) {
@@ -248,8 +231,94 @@
quirks |= kRequiresAllocateBufferOnOutputPorts;
}
+ return quirks;
+}
+
+// static
+void OMXCodec::findMatchingCodecs(
+ const char *mime,
+ bool createEncoder, const char *matchComponentName,
+ uint32_t flags,
+ Vector<String8> *matchingCodecs) {
+ matchingCodecs->clear();
+
+ for (int index = 0;; ++index) {
+ const char *componentName;
+
+ if (createEncoder) {
+ componentName = GetCodec(
+ kEncoderInfo,
+ sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]),
+ mime, index);
+ } else {
+ componentName = GetCodec(
+ kDecoderInfo,
+ sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),
+ mime, index);
+ }
+
+ if (!componentName) {
+ break;
+ }
+
+ // If a specific codec is requested, skip the non-matching ones.
+ if (matchComponentName && strcmp(componentName, matchComponentName)) {
+ continue;
+ }
+
+ matchingCodecs->push(String8(componentName));
+ }
+
+ if (flags & kPreferSoftwareCodecs) {
+ matchingCodecs->sort(CompareSoftwareCodecsFirst);
+ }
+}
+
+// static
+sp<OMXCodec> OMXCodec::Create(
+ const sp<IOMX> &omx,
+ const sp<MetaData> &meta, bool createEncoder,
+ const sp<MediaSource> &source,
+ const char *matchComponentName,
+ uint32_t flags) {
+ const char *mime;
+ bool success = meta->findCString(kKeyMIMEType, &mime);
+ CHECK(success);
+
+ Vector<String8> matchingCodecs;
+ findMatchingCodecs(
+ mime, createEncoder, matchComponentName, flags, &matchingCodecs);
+
+ if (matchingCodecs.isEmpty()) {
+ return NULL;
+ }
+
+ sp<OMXCodecObserver> observer = new OMXCodecObserver;
+ IOMX::node_id node = 0;
+ success = false;
+
+ const char *componentName;
+ for (size_t i = 0; i < matchingCodecs.size(); ++i) {
+ componentName = matchingCodecs[i].string();
+
+ LOGV("Attempting to allocate OMX node '%s'", componentName);
+
+ status_t err = omx->allocateNode(componentName, observer, &node);
+ if (err == OK) {
+ LOGV("Successfully allocated OMX node '%s'", componentName);
+
+ success = true;
+ break;
+ }
+ }
+
+ if (!success) {
+ return NULL;
+ }
+
sp<OMXCodec> codec = new OMXCodec(
- omx, node, quirks, createEncoder, mime, componentName,
+ omx, node, getComponentQuirks(componentName),
+ createEncoder, mime, componentName,
source);
observer->setCodec(codec);
@@ -331,7 +400,7 @@
size -= length;
}
- LOGI("AVC profile = %d (%s), level = %d",
+ LOGV("AVC profile = %d (%s), level = %d",
(int)profile, AVCProfileToString(profile), (int)level / 10);
if (!strcmp(componentName, "OMX.TI.Video.Decoder")
@@ -452,7 +521,7 @@
// CHECK_EQ(format.nIndex, index);
#if 1
- CODEC_LOGI("portIndex: %ld, index: %ld, eCompressionFormat=%d eColorFormat=%d",
+ CODEC_LOGV("portIndex: %ld, index: %ld, eCompressionFormat=%d eColorFormat=%d",
portIndex,
index, format.eCompressionFormat, format.eColorFormat);
#endif
@@ -493,9 +562,25 @@
return err;
}
+static size_t getFrameSize(
+ OMX_COLOR_FORMATTYPE colorFormat, int32_t width, int32_t height) {
+ switch (colorFormat) {
+ case OMX_COLOR_FormatYCbYCr:
+ case OMX_COLOR_FormatCbYCrY:
+ return width * height * 2;
+
+ case OMX_COLOR_FormatYUV420SemiPlanar:
+ return (width * height * 3) / 2;
+
+ default:
+ CHECK(!"Should not be here. Unsupported color format.");
+ break;
+ }
+}
+
void OMXCodec::setVideoInputFormat(
const char *mime, OMX_U32 width, OMX_U32 height) {
- CODEC_LOGI("setVideoInputFormat width=%ld, height=%ld", width, height);
+ CODEC_LOGV("setVideoInputFormat width=%ld, height=%ld", width, height);
OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused;
if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
@@ -516,12 +601,13 @@
colorFormat = OMX_COLOR_FormatYUV420SemiPlanar;
}
- setVideoPortFormatType(
+ CHECK_EQ(setVideoPortFormatType(
kPortIndexInput, OMX_VIDEO_CodingUnused,
- colorFormat);
+ colorFormat), OK);
- setVideoPortFormatType(
- kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused);
+ CHECK_EQ(setVideoPortFormatType(
+ kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused),
+ OK);
OMX_PARAM_PORTDEFINITIONTYPE def;
InitOMXParams(&def);
@@ -554,8 +640,8 @@
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
- def.nBufferSize = (width * height * 2); // (width * height * 3) / 2;
- CODEC_LOGI("Setting nBufferSize = %ld", def.nBufferSize);
+ def.nBufferSize = getFrameSize(colorFormat, width, height);
+ CODEC_LOGV("Setting nBufferSize = %ld", def.nBufferSize);
CHECK_EQ(def.eDomain, OMX_PortDomainVideo);
@@ -564,14 +650,108 @@
video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
video_def->eColorFormat = colorFormat;
+ video_def->xFramerate = 24 << 16; // XXX crucial!
+
err = mOMX->setParameter(
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
+
+ switch (compressionFormat) {
+ case OMX_VIDEO_CodingMPEG4:
+ {
+ CHECK_EQ(setupMPEG4EncoderParameters(), OK);
+ break;
+ }
+
+ case OMX_VIDEO_CodingH263:
+ break;
+
+ default:
+ CHECK(!"Support for this compressionFormat to be implemented.");
+ break;
+ }
+}
+
+status_t OMXCodec::setupMPEG4EncoderParameters() {
+ OMX_VIDEO_PARAM_MPEG4TYPE mpeg4type;
+ InitOMXParams(&mpeg4type);
+ mpeg4type.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+ CHECK_EQ(err, OK);
+
+ mpeg4type.nSliceHeaderSpacing = 0;
+ mpeg4type.bSVH = OMX_FALSE;
+ mpeg4type.bGov = OMX_FALSE;
+
+ mpeg4type.nAllowedPictureTypes =
+ OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+ mpeg4type.nPFrames = 23;
+ mpeg4type.nBFrames = 0;
+
+ mpeg4type.nIDCVLCThreshold = 0;
+ mpeg4type.bACPred = OMX_TRUE;
+ mpeg4type.nMaxPacketSize = 256;
+ mpeg4type.nTimeIncRes = 1000;
+ mpeg4type.nHeaderExtension = 0;
+ mpeg4type.bReversibleVLC = OMX_FALSE;
+
+ mpeg4type.eProfile = OMX_VIDEO_MPEG4ProfileCore;
+ mpeg4type.eLevel = OMX_VIDEO_MPEG4Level2;
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+ CHECK_EQ(err, OK);
+
+ // ----------------
+
+ OMX_VIDEO_PARAM_BITRATETYPE bitrateType;
+ InitOMXParams(&bitrateType);
+ bitrateType.nPortIndex = kPortIndexOutput;
+
+ err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoBitrate,
+ &bitrateType, sizeof(bitrateType));
+ CHECK_EQ(err, OK);
+
+ bitrateType.eControlRate = OMX_Video_ControlRateVariable;
+ bitrateType.nTargetBitrate = 1000000;
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamVideoBitrate,
+ &bitrateType, sizeof(bitrateType));
+ CHECK_EQ(err, OK);
+
+ // ----------------
+
+ OMX_VIDEO_PARAM_ERRORCORRECTIONTYPE errorCorrectionType;
+ InitOMXParams(&errorCorrectionType);
+ errorCorrectionType.nPortIndex = kPortIndexOutput;
+
+ err = mOMX->getParameter(
+ mNode, OMX_IndexParamVideoErrorCorrection,
+ &errorCorrectionType, sizeof(errorCorrectionType));
+ CHECK_EQ(err, OK);
+
+ errorCorrectionType.bEnableHEC = OMX_FALSE;
+ errorCorrectionType.bEnableResync = OMX_TRUE;
+ errorCorrectionType.nResynchMarkerSpacing = 256;
+ errorCorrectionType.bEnableDataPartitioning = OMX_FALSE;
+ errorCorrectionType.bEnableRVLC = OMX_FALSE;
+
+ err = mOMX->setParameter(
+ mNode, OMX_IndexParamVideoErrorCorrection,
+ &errorCorrectionType, sizeof(errorCorrectionType));
+ CHECK_EQ(err, OK);
+
+ return OK;
}
void OMXCodec::setVideoOutputFormat(
const char *mime, OMX_U32 width, OMX_U32 height) {
- CODEC_LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height);
+ CODEC_LOGV("setVideoOutputFormat width=%ld, height=%ld", width, height);
OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused;
if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
@@ -639,6 +819,7 @@
video_def->nFrameWidth = width;
video_def->nFrameHeight = height;
+ video_def->eCompressionFormat = compressionFormat;
video_def->eColorFormat = OMX_COLOR_FormatUnused;
err = mOMX->setParameter(
@@ -687,6 +868,7 @@
mInitialBufferSubmit(true),
mSignalledEOS(false),
mNoMoreOutputData(false),
+ mOutputPortSettingsHaveChanged(false),
mSeekTimeUs(-1) {
mPortStatus[kPortIndexInput] = ENABLED;
mPortStatus[kPortIndexOutput] = ENABLED;
@@ -990,12 +1172,8 @@
buffer->meta_data()->clear();
- buffer->meta_data()->setInt32(
- kKeyTimeUnits,
- (msg.u.extended_buffer_data.timestamp + 500) / 1000);
-
- buffer->meta_data()->setInt32(
- kKeyTimeScale, 1000);
+ buffer->meta_data()->setInt64(
+ kKeyTime, msg.u.extended_buffer_data.timestamp);
if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_SYNCFRAME) {
buffer->meta_data()->setInt32(kKeyIsSyncFrame, true);
@@ -1064,6 +1242,71 @@
}
}
+// Has the format changed in any way that the client would have to be aware of?
+static bool formatHasNotablyChanged(
+ const sp<MetaData> &from, const sp<MetaData> &to) {
+ if (from.get() == NULL && to.get() == NULL) {
+ return false;
+ }
+
+ if ((from.get() == NULL && to.get() != NULL)
+ || (from.get() != NULL && to.get() == NULL)) {
+ return true;
+ }
+
+ const char *mime_from, *mime_to;
+ CHECK(from->findCString(kKeyMIMEType, &mime_from));
+ CHECK(to->findCString(kKeyMIMEType, &mime_to));
+
+ if (strcasecmp(mime_from, mime_to)) {
+ return true;
+ }
+
+ if (!strcasecmp(mime_from, MEDIA_MIMETYPE_VIDEO_RAW)) {
+ int32_t colorFormat_from, colorFormat_to;
+ CHECK(from->findInt32(kKeyColorFormat, &colorFormat_from));
+ CHECK(to->findInt32(kKeyColorFormat, &colorFormat_to));
+
+ if (colorFormat_from != colorFormat_to) {
+ return true;
+ }
+
+ int32_t width_from, width_to;
+ CHECK(from->findInt32(kKeyWidth, &width_from));
+ CHECK(to->findInt32(kKeyWidth, &width_to));
+
+ if (width_from != width_to) {
+ return true;
+ }
+
+ int32_t height_from, height_to;
+ CHECK(from->findInt32(kKeyHeight, &height_from));
+ CHECK(to->findInt32(kKeyHeight, &height_to));
+
+ if (height_from != height_to) {
+ return true;
+ }
+ } else if (!strcasecmp(mime_from, MEDIA_MIMETYPE_AUDIO_RAW)) {
+ int32_t numChannels_from, numChannels_to;
+ CHECK(from->findInt32(kKeyChannelCount, &numChannels_from));
+ CHECK(to->findInt32(kKeyChannelCount, &numChannels_to));
+
+ if (numChannels_from != numChannels_to) {
+ return true;
+ }
+
+ int32_t sampleRate_from, sampleRate_to;
+ CHECK(from->findInt32(kKeySampleRate, &sampleRate_from));
+ CHECK(to->findInt32(kKeySampleRate, &sampleRate_to));
+
+ if (sampleRate_from != sampleRate_to) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) {
switch (cmd) {
case OMX_CommandStateSet:
@@ -1086,6 +1329,15 @@
if (mState == RECONFIGURING) {
CHECK_EQ(portIndex, kPortIndexOutput);
+ sp<MetaData> oldOutputFormat = mOutputFormat;
+ initOutputFormat(mSource->getFormat());
+
+ // Don't notify clients if the output port settings change
+ // wasn't of importance to them, i.e. it may be that just the
+ // number of buffers has changed and nothing else.
+ mOutputPortSettingsHaveChanged =
+ formatHasNotablyChanged(oldOutputFormat, mOutputFormat);
+
enablePortAsync(portIndex);
status_t err = allocateBuffersOnPort(portIndex);
@@ -1443,7 +1695,7 @@
}
OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
- OMX_TICKS timestamp = 0;
+ OMX_TICKS timestampUs = 0;
size_t srcLength = 0;
if (err != OK) {
@@ -1463,15 +1715,11 @@
(const uint8_t *)srcBuffer->data() + srcBuffer->range_offset(),
srcLength);
- int32_t units, scale;
- if (srcBuffer->meta_data()->findInt32(kKeyTimeUnits, &units)
- && srcBuffer->meta_data()->findInt32(kKeyTimeScale, &scale)) {
- timestamp = ((OMX_TICKS)units * 1000000) / scale;
-
- CODEC_LOGV("Calling empty_buffer on buffer %p (length %d)",
+ if (srcBuffer->meta_data()->findInt64(kKeyTime, ×tampUs)) {
+ CODEC_LOGV("Calling emptyBuffer on buffer %p (length %d)",
info->mBuffer, srcLength);
- CODEC_LOGV("Calling empty_buffer with timestamp %lld us (%.2f secs)",
- timestamp, timestamp / 1E6);
+ CODEC_LOGV("Calling emptyBuffer with timestamp %lld us (%.2f secs)",
+ timestampUs, timestampUs / 1E6);
}
}
@@ -1482,7 +1730,7 @@
err = mOMX->emptyBuffer(
mNode, info->mBuffer, 0, srcLength,
- flags, timestamp);
+ flags, timestampUs);
if (err != OK) {
setState(ERROR);
@@ -1794,6 +2042,7 @@
mInitialBufferSubmit = true;
mSignalledEOS = false;
mNoMoreOutputData = false;
+ mOutputPortSettingsHaveChanged = false;
mSeekTimeUs = -1;
mFilledBuffers.clear();
@@ -1864,6 +2113,8 @@
}
sp<MetaData> OMXCodec::getFormat() {
+ Mutex::Autolock autoLock(mLock);
+
return mOutputFormat;
}
@@ -1941,6 +2192,12 @@
return ERROR_END_OF_STREAM;
}
+ if (mOutputPortSettingsHaveChanged) {
+ mOutputPortSettingsHaveChanged = false;
+
+ return INFO_FORMAT_CHANGED;
+ }
+
size_t index = *mFilledBuffers.begin();
mFilledBuffers.erase(mFilledBuffers.begin());
@@ -2041,8 +2298,6 @@
size_t numNames = sizeof(kNames) / sizeof(kNames[0]);
- static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
-
if (type == OMX_QCOM_COLOR_FormatYVU420SemiPlanar) {
return "OMX_QCOM_COLOR_FormatYVU420SemiPlanar";
} else if (type < 0 || (size_t)type >= numNames) {
@@ -2410,7 +2665,7 @@
CHECK(!"Unknown compression format.");
}
- if (mQuirks & kOutputDimensionsAre16Aligned) {
+ if (!strcmp(mComponentName, "OMX.PV.avcdec")) {
// This component appears to be lying to me.
mOutputFormat->setInt32(
kKeyWidth, (video_def->nFrameWidth + 15) & -16);
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 8efa7c7..4aec0e9 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -17,11 +17,12 @@
#define LOG_TAG "SampleTable"
#include <utils/Log.h>
+#include "include/SampleTable.h"
+
#include <arpa/inet.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/SampleTable.h>
#include <media/stagefright/Utils.h>
namespace android {
@@ -54,7 +55,7 @@
}
status_t SampleTable::setChunkOffsetParams(
- uint32_t type, off_t data_offset, off_t data_size) {
+ uint32_t type, off_t data_offset, size_t data_size) {
if (mChunkOffsetOffset >= 0) {
return ERROR_MALFORMED;
}
@@ -69,7 +70,7 @@
}
uint8_t header[8];
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
return ERROR_IO;
}
@@ -95,7 +96,7 @@
}
status_t SampleTable::setSampleToChunkParams(
- off_t data_offset, off_t data_size) {
+ off_t data_offset, size_t data_size) {
if (mSampleToChunkOffset >= 0) {
return ERROR_MALFORMED;
}
@@ -107,7 +108,7 @@
}
uint8_t header[8];
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
return ERROR_IO;
}
@@ -127,7 +128,7 @@
}
status_t SampleTable::setSampleSizeParams(
- uint32_t type, off_t data_offset, off_t data_size) {
+ uint32_t type, off_t data_offset, size_t data_size) {
if (mSampleSizeOffset >= 0) {
return ERROR_MALFORMED;
}
@@ -141,7 +142,7 @@
}
uint8_t header[12];
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
return ERROR_IO;
}
@@ -187,13 +188,13 @@
}
status_t SampleTable::setTimeToSampleParams(
- off_t data_offset, off_t data_size) {
+ off_t data_offset, size_t data_size) {
if (mTimeToSample != NULL || data_size < 8) {
return ERROR_MALFORMED;
}
uint8_t header[8];
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
return ERROR_IO;
}
@@ -207,7 +208,7 @@
mTimeToSample = new uint32_t[mTimeToSampleCount * 2];
size_t size = sizeof(uint32_t) * mTimeToSampleCount * 2;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset + 8, mTimeToSample, size) < (ssize_t)size) {
return ERROR_IO;
}
@@ -219,7 +220,7 @@
return OK;
}
-status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) {
+status_t SampleTable::setSyncSampleParams(off_t data_offset, size_t data_size) {
if (mSyncSampleOffset >= 0 || data_size < 8) {
return ERROR_MALFORMED;
}
@@ -227,7 +228,7 @@
mSyncSampleOffset = data_offset;
uint8_t header[8];
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
return ERROR_IO;
}
@@ -263,7 +264,7 @@
if (mChunkOffsetType == kChunkOffsetType32) {
uint32_t offset32;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
mChunkOffsetOffset + 8 + 4 * chunk_index,
&offset32,
sizeof(offset32)) < (ssize_t)sizeof(offset32)) {
@@ -275,7 +276,7 @@
CHECK_EQ(mChunkOffsetType, kChunkOffsetType64);
uint64_t offset64;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
mChunkOffsetOffset + 8 + 8 * chunk_index,
&offset64,
sizeof(offset64)) < (ssize_t)sizeof(offset64)) {
@@ -312,7 +313,7 @@
uint32_t index = 0;
while (index < mNumSampleToChunkOffsets) {
uint8_t buffer[12];
- if (mDataSource->read_at(mSampleToChunkOffset + 8 + index * 12,
+ if (mDataSource->readAt(mSampleToChunkOffset + 8 + index * 12,
buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
return ERROR_IO;
}
@@ -361,7 +362,7 @@
switch (mSampleSizeFieldSize) {
case 32:
{
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
mSampleSizeOffset + 12 + 4 * sample_index,
sample_size, sizeof(*sample_size)) < (ssize_t)sizeof(*sample_size)) {
return ERROR_IO;
@@ -374,7 +375,7 @@
case 16:
{
uint16_t x;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
mSampleSizeOffset + 12 + 2 * sample_index,
&x, sizeof(x)) < (ssize_t)sizeof(x)) {
return ERROR_IO;
@@ -387,7 +388,7 @@
case 8:
{
uint8_t x;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
mSampleSizeOffset + 12 + sample_index,
&x, sizeof(x)) < (ssize_t)sizeof(x)) {
return ERROR_IO;
@@ -402,7 +403,7 @@
CHECK_EQ(mSampleSizeFieldSize, 4);
uint8_t x;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
mSampleSizeOffset + 12 + sample_index / 2,
&x, sizeof(x)) < (ssize_t)sizeof(x)) {
return ERROR_IO;
@@ -553,7 +554,7 @@
uint32_t right = mNumSyncSamples;
while (left < right) {
uint32_t mid = (left + right) / 2;
- if (mDataSource->read_at(
+ if (mDataSource->readAt(
mSyncSampleOffset + 8 + (mid - 1) * 4, &x, 4) != 4) {
return ERROR_IO;
}
@@ -574,5 +575,52 @@
return OK;
}
+status_t SampleTable::findThumbnailSample(uint32_t *sample_index) {
+ if (mSyncSampleOffset < 0) {
+ // All samples are sync-samples.
+ *sample_index = 0;
+ return OK;
+ }
+
+ uint32_t bestSampleIndex = 0;
+ size_t maxSampleSize = 0;
+
+ static const size_t kMaxNumSyncSamplesToScan = 20;
+
+ // Consider the first kMaxNumSyncSamplesToScan sync samples and
+ // pick the one with the largest (compressed) size as the thumbnail.
+
+ size_t numSamplesToScan = mNumSyncSamples;
+ if (numSamplesToScan > kMaxNumSyncSamplesToScan) {
+ numSamplesToScan = kMaxNumSyncSamplesToScan;
+ }
+
+ for (size_t i = 0; i < numSamplesToScan; ++i) {
+ uint32_t x;
+ if (mDataSource->readAt(
+ mSyncSampleOffset + 8 + i * 4, &x, 4) != 4) {
+ return ERROR_IO;
+ }
+ x = ntohl(x);
+ --x;
+
+ // Now x is a sample index.
+ size_t sampleSize;
+ status_t err = getSampleSize(x, &sampleSize);
+ if (err != OK) {
+ return err;
+ }
+
+ if (i == 0 || sampleSize > maxSampleSize) {
+ bestSampleIndex = x;
+ maxSampleSize = sampleSize;
+ }
+ }
+
+ *sample_index = bestSampleIndex;
+
+ return OK;
+}
+
} // namespace android
diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp
index 346b5aa..ec25430 100644
--- a/media/libstagefright/ShoutcastSource.cpp
+++ b/media/libstagefright/ShoutcastSource.cpp
@@ -14,16 +14,17 @@
* limitations under the License.
*/
+#include "include/stagefright_string.h"
+#include "include/HTTPStream.h"
+
#include <stdlib.h>
-#include <media/stagefright/HTTPStream.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/ShoutcastSource.h>
-#include <media/stagefright/stagefright_string.h>
namespace android {
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
index 3d85f75..dd8005c 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -22,10 +22,11 @@
#define LOG_TAG "TimedEventQueue"
#include <utils/Log.h>
+#include "include/TimedEventQueue.h"
+
#include <sys/time.h>
#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/TimedEventQueue.h>
namespace android {
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
new file mode 100644
index 0000000..542c764
--- /dev/null
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -0,0 +1,317 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "WAVExtractor"
+#include <utils/Log.h>
+
+#include "include/WAVExtractor.h"
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/String8.h>
+
+namespace android {
+
+static uint16_t WAVE_FORMAT_PCM = 1;
+
+static uint32_t U32_LE_AT(const uint8_t *ptr) {
+ return ptr[3] << 24 | ptr[2] << 16 | ptr[1] << 8 | ptr[0];
+}
+
+static uint16_t U16_LE_AT(const uint8_t *ptr) {
+ return ptr[1] << 8 | ptr[0];
+}
+
+struct WAVSource : public MediaSource {
+ WAVSource(
+ const sp<DataSource> &dataSource,
+ int32_t sampleRate, int32_t numChannels,
+ off_t offset, size_t size);
+
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+protected:
+ virtual ~WAVSource();
+
+private:
+ static const size_t kMaxFrameSize;
+
+ sp<DataSource> mDataSource;
+ int32_t mSampleRate;
+ int32_t mNumChannels;
+ off_t mOffset;
+ size_t mSize;
+ bool mStarted;
+ MediaBufferGroup *mGroup;
+ off_t mCurrentPos;
+
+ WAVSource(const WAVSource &);
+ WAVSource &operator=(const WAVSource &);
+};
+
+WAVExtractor::WAVExtractor(const sp<DataSource> &source)
+ : mDataSource(source),
+ mValidFormat(false) {
+ mInitCheck = init();
+}
+
+WAVExtractor::~WAVExtractor() {
+}
+
+size_t WAVExtractor::countTracks() {
+ return mInitCheck == OK ? 1 : 0;
+}
+
+sp<MediaSource> WAVExtractor::getTrack(size_t index) {
+ if (mInitCheck != OK || index > 0) {
+ return NULL;
+ }
+
+ return new WAVSource(
+ mDataSource, mSampleRate, mNumChannels, mDataOffset, mDataSize);
+}
+
+sp<MetaData> WAVExtractor::getTrackMetaData(
+ size_t index, uint32_t flags) {
+ if (mInitCheck != OK || index > 0) {
+ return NULL;
+ }
+
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+ meta->setInt32(kKeyChannelCount, mNumChannels);
+ meta->setInt32(kKeySampleRate, mSampleRate);
+
+ int64_t durationUs =
+ 1000000LL * (mDataSize / (mNumChannels * 2)) / mSampleRate;
+
+ meta->setInt64(kKeyDuration, durationUs);
+
+ return meta;
+}
+
+status_t WAVExtractor::init() {
+ uint8_t header[12];
+ if (mDataSource->readAt(
+ 0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return NO_INIT;
+ }
+
+ if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) {
+ return NO_INIT;
+ }
+
+ size_t totalSize = U32_LE_AT(&header[4]);
+
+ off_t offset = 12;
+ size_t remainingSize = totalSize;
+ while (remainingSize >= 8) {
+ uint8_t chunkHeader[8];
+ if (mDataSource->readAt(offset, chunkHeader, 8) < 8) {
+ return NO_INIT;
+ }
+
+ remainingSize -= 8;
+ offset += 8;
+
+ uint32_t chunkSize = U32_LE_AT(&chunkHeader[4]);
+
+ if (chunkSize > remainingSize) {
+ return NO_INIT;
+ }
+
+ if (!memcmp(chunkHeader, "fmt ", 4)) {
+ if (chunkSize < 16) {
+ return NO_INIT;
+ }
+
+ uint8_t formatSpec[16];
+ if (mDataSource->readAt(offset, formatSpec, 16) < 16) {
+ return NO_INIT;
+ }
+
+ uint16_t format = U16_LE_AT(formatSpec);
+ if (format != WAVE_FORMAT_PCM) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mNumChannels = U16_LE_AT(&formatSpec[2]);
+ if (mNumChannels != 1 && mNumChannels != 2) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mSampleRate = U32_LE_AT(&formatSpec[4]);
+
+ if (U16_LE_AT(&formatSpec[14]) != 16) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ mValidFormat = true;
+ } else if (!memcmp(chunkHeader, "data", 4)) {
+ if (mValidFormat) {
+ mDataOffset = offset;
+ mDataSize = chunkSize;
+
+ return OK;
+ }
+ }
+
+ offset += chunkSize;
+ }
+
+ return NO_INIT;
+}
+
+const size_t WAVSource::kMaxFrameSize = 32768;
+
+WAVSource::WAVSource(
+ const sp<DataSource> &dataSource,
+ int32_t sampleRate, int32_t numChannels,
+ off_t offset, size_t size)
+ : mDataSource(dataSource),
+ mSampleRate(sampleRate),
+ mNumChannels(numChannels),
+ mOffset(offset),
+ mSize(size),
+ mStarted(false),
+ mGroup(NULL) {
+}
+
+WAVSource::~WAVSource() {
+ if (mStarted) {
+ stop();
+ }
+}
+
+status_t WAVSource::start(MetaData *params) {
+ LOGV("WAVSource::start");
+
+ CHECK(!mStarted);
+
+ mGroup = new MediaBufferGroup;
+ mGroup->add_buffer(new MediaBuffer(kMaxFrameSize));
+
+ mCurrentPos = mOffset;
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t WAVSource::stop() {
+ LOGV("WAVSource::stop");
+
+ CHECK(mStarted);
+
+ delete mGroup;
+ mGroup = NULL;
+
+ mStarted = false;
+
+ return OK;
+}
+
+sp<MetaData> WAVSource::getFormat() {
+ LOGV("WAVSource::getFormat");
+
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+ meta->setInt32(kKeyChannelCount, mNumChannels);
+ meta->setInt32(kKeySampleRate, mSampleRate);
+
+ int64_t durationUs =
+ 1000000LL * (mSize / (mNumChannels * 2)) / mSampleRate;
+
+ meta->setInt64(kKeyDuration, durationUs);
+
+ return meta;
+}
+
+status_t WAVSource::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ *out = NULL;
+
+ int64_t seekTimeUs;
+ if (options != NULL && options->getSeekTo(&seekTimeUs)) {
+ int64_t pos = (seekTimeUs * mSampleRate) / 1000000 * mNumChannels * 2;
+ if (pos > mSize) {
+ pos = mSize;
+ }
+ mCurrentPos = pos + mOffset;
+ }
+
+ MediaBuffer *buffer;
+ status_t err = mGroup->acquire_buffer(&buffer);
+ if (err != OK) {
+ return err;
+ }
+
+ ssize_t n = mDataSource->readAt(
+ mCurrentPos, buffer->data(), kMaxFrameSize);
+
+ if (n <= 0) {
+ buffer->release();
+ buffer = NULL;
+
+ return ERROR_END_OF_STREAM;
+ }
+
+ mCurrentPos += n;
+
+ buffer->set_range(0, n);
+ buffer->meta_data()->setInt64(
+ kKeyTime,
+ 1000000LL * (mCurrentPos - mOffset)
+ / (mNumChannels * 2) / mSampleRate);
+
+
+ *out = buffer;
+
+ return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool SniffWAV(
+ const sp<DataSource> &source, String8 *mimeType, float *confidence) {
+ char header[12];
+ if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return false;
+ }
+
+ if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) {
+ return false;
+ }
+
+ *mimeType = MEDIA_MIMETYPE_CONTAINER_WAV;
+ *confidence = 0.3f;
+
+ return true;
+}
+
+} // namespace android
+
diff --git a/include/media/stagefright/AMRExtractor.h b/media/libstagefright/include/AMRExtractor.h
similarity index 94%
rename from include/media/stagefright/AMRExtractor.h
rename to media/libstagefright/include/AMRExtractor.h
index c8710d3..debf006 100644
--- a/include/media/stagefright/AMRExtractor.h
+++ b/media/libstagefright/include/AMRExtractor.h
@@ -30,7 +30,7 @@
virtual size_t countTracks();
virtual sp<MediaSource> getTrack(size_t index);
- virtual sp<MetaData> getTrackMetaData(size_t index);
+ virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
static sp<MetaData> makeAMRFormat(bool isWide);
diff --git a/include/media/stagefright/ESDS.h b/media/libstagefright/include/ESDS.h
similarity index 100%
rename from include/media/stagefright/ESDS.h
rename to media/libstagefright/include/ESDS.h
diff --git a/include/media/stagefright/HTTPStream.h b/media/libstagefright/include/HTTPStream.h
similarity index 96%
rename from include/media/stagefright/HTTPStream.h
rename to media/libstagefright/include/HTTPStream.h
index 72e796c..43ef614 100644
--- a/include/media/stagefright/HTTPStream.h
+++ b/media/libstagefright/include/HTTPStream.h
@@ -18,10 +18,11 @@
#define HTTP_STREAM_H_
+#include "stagefright_string.h"
+
#include <sys/types.h>
#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/stagefright_string.h>
#include <utils/KeyedVector.h>
namespace android {
diff --git a/include/media/stagefright/MP3Extractor.h b/media/libstagefright/include/MP3Extractor.h
similarity index 94%
rename from include/media/stagefright/MP3Extractor.h
rename to media/libstagefright/include/MP3Extractor.h
index 11ba01d..074973b 100644
--- a/include/media/stagefright/MP3Extractor.h
+++ b/media/libstagefright/include/MP3Extractor.h
@@ -32,7 +32,7 @@
virtual size_t countTracks();
virtual sp<MediaSource> getTrack(size_t index);
- virtual sp<MetaData> getTrackMetaData(size_t index);
+ virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
protected:
virtual ~MP3Extractor();
diff --git a/include/media/stagefright/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
similarity index 93%
rename from include/media/stagefright/MPEG4Extractor.h
rename to media/libstagefright/include/MPEG4Extractor.h
index 932e30f..ce4736d 100644
--- a/include/media/stagefright/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -33,7 +33,7 @@
size_t countTracks();
sp<MediaSource> getTrack(size_t index);
- sp<MetaData> getTrackMetaData(size_t index);
+ sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
protected:
virtual ~MPEG4Extractor();
@@ -44,6 +44,7 @@
sp<MetaData> meta;
uint32_t timescale;
sp<SampleTable> sampleTable;
+ bool includes_expensive_metadata;
};
sp<DataSource> mDataSource;
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index d0bd61e..a4b62b2 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -99,7 +99,7 @@
OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2,
OMX_IN OMX_PTR pEventData);
-
+
OMX_ERRORTYPE OnEmptyBufferDone(
node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer);
diff --git a/include/media/stagefright/QComHardwareRenderer.h b/media/libstagefright/include/QComHardwareRenderer.h
similarity index 100%
rename from include/media/stagefright/QComHardwareRenderer.h
rename to media/libstagefright/include/QComHardwareRenderer.h
diff --git a/include/media/stagefright/SampleTable.h b/media/libstagefright/include/SampleTable.h
similarity index 86%
rename from include/media/stagefright/SampleTable.h
rename to media/libstagefright/include/SampleTable.h
index 808d142..ead3431 100644
--- a/include/media/stagefright/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -35,17 +35,17 @@
// type can be 'stco' or 'co64'.
status_t setChunkOffsetParams(
- uint32_t type, off_t data_offset, off_t data_size);
+ uint32_t type, off_t data_offset, size_t data_size);
- status_t setSampleToChunkParams(off_t data_offset, off_t data_size);
+ status_t setSampleToChunkParams(off_t data_offset, size_t data_size);
// type can be 'stsz' or 'stz2'.
status_t setSampleSizeParams(
- uint32_t type, off_t data_offset, off_t data_size);
+ uint32_t type, off_t data_offset, size_t data_size);
- status_t setTimeToSampleParams(off_t data_offset, off_t data_size);
+ status_t setTimeToSampleParams(off_t data_offset, size_t data_size);
- status_t setSyncSampleParams(off_t data_offset, off_t data_size);
+ status_t setSyncSampleParams(off_t data_offset, size_t data_size);
////////////////////////////////////////////////////////////////////////////
@@ -75,6 +75,8 @@
status_t findClosestSyncSample(
uint32_t start_sample_index, uint32_t *sample_index);
+ status_t findThumbnailSample(uint32_t *sample_index);
+
protected:
~SampleTable();
diff --git a/include/media/stagefright/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h
similarity index 86%
rename from include/media/stagefright/SoftwareRenderer.h
rename to media/libstagefright/include/SoftwareRenderer.h
index 1545493..9eed089 100644
--- a/include/media/stagefright/SoftwareRenderer.h
+++ b/media/libstagefright/include/SoftwareRenderer.h
@@ -18,7 +18,7 @@
#define SOFTWARE_RENDERER_H_
-#include <OMX_Video.h>
+#include <media/stagefright/ColorConverter.h>
#include <media/stagefright/VideoRenderer.h>
#include <utils/RefBase.h>
@@ -41,13 +41,8 @@
const void *data, size_t size, void *platformPrivate);
private:
- uint8_t *initClip();
-
- void renderCbYCrY(const void *data, size_t size);
- void renderYUV420Planar(const void *data, size_t size);
- void renderQCOMYUV420SemiPlanar(const void *data, size_t size);
-
OMX_COLOR_FORMATTYPE mColorFormat;
+ ColorConverter mConverter;
sp<ISurface> mISurface;
size_t mDisplayWidth, mDisplayHeight;
size_t mDecodedWidth, mDecodedHeight;
@@ -55,8 +50,6 @@
sp<MemoryHeapBase> mMemoryHeap;
int mIndex;
- uint8_t *mClip;
-
SoftwareRenderer(const SoftwareRenderer &);
SoftwareRenderer &operator=(const SoftwareRenderer &);
};
diff --git a/include/media/stagefright/TIHardwareRenderer.h b/media/libstagefright/include/TIHardwareRenderer.h
similarity index 100%
rename from include/media/stagefright/TIHardwareRenderer.h
rename to media/libstagefright/include/TIHardwareRenderer.h
diff --git a/include/media/stagefright/TimedEventQueue.h b/media/libstagefright/include/TimedEventQueue.h
similarity index 100%
rename from include/media/stagefright/TimedEventQueue.h
rename to media/libstagefright/include/TimedEventQueue.h
diff --git a/include/media/stagefright/MP3Extractor.h b/media/libstagefright/include/WAVExtractor.h
similarity index 65%
copy from include/media/stagefright/MP3Extractor.h
copy to media/libstagefright/include/WAVExtractor.h
index 11ba01d..10b9700 100644
--- a/include/media/stagefright/MP3Extractor.h
+++ b/media/libstagefright/include/WAVExtractor.h
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#ifndef MP3_EXTRACTOR_H_
+#ifndef WAV_EXTRACTOR_H_
-#define MP3_EXTRACTOR_H_
+#define WAV_EXTRACTOR_H_
#include <media/stagefright/MediaExtractor.h>
@@ -25,31 +25,37 @@
class DataSource;
class String8;
-class MP3Extractor : public MediaExtractor {
+class WAVExtractor : public MediaExtractor {
public:
// Extractor assumes ownership of "source".
- MP3Extractor(const sp<DataSource> &source);
+ WAVExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
virtual sp<MediaSource> getTrack(size_t index);
- virtual sp<MetaData> getTrackMetaData(size_t index);
+ virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
protected:
- virtual ~MP3Extractor();
+ virtual ~WAVExtractor();
private:
sp<DataSource> mDataSource;
- off_t mFirstFramePos;
- sp<MetaData> mMeta;
- uint32_t mFixedHeader;
+ status_t mInitCheck;
+ bool mValidFormat;
+ uint16_t mNumChannels;
+ uint32_t mSampleRate;
+ off_t mDataOffset;
+ size_t mDataSize;
- MP3Extractor(const MP3Extractor &);
- MP3Extractor &operator=(const MP3Extractor &);
+ status_t init();
+
+ WAVExtractor(const WAVExtractor &);
+ WAVExtractor &operator=(const WAVExtractor &);
};
-bool SniffMP3(
+bool SniffWAV(
const sp<DataSource> &source, String8 *mimeType, float *confidence);
} // namespace android
-#endif // MP3_EXTRACTOR_H_
+#endif // WAV_EXTRACTOR_H_
+
diff --git a/include/media/stagefright/stagefright_string.h b/media/libstagefright/include/stagefright_string.h
similarity index 92%
rename from include/media/stagefright/stagefright_string.h
rename to media/libstagefright/include/stagefright_string.h
index 1ed4c86..5dc7116 100644
--- a/include/media/stagefright/stagefright_string.h
+++ b/media/libstagefright/include/stagefright_string.h
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#ifndef STAGEFRIGHT_STRING_H_
+#ifndef STRING_H_
-#define STAGEFRIGHT_STRING_H_
+#define STRING_H_
#include <utils/String8.h>
@@ -51,4 +51,4 @@
} // namespace android
-#endif // STAGEFRIGHT_STRING_H_
+#endif // STRING_H_
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
index 20fb4f3..edbc04e 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -10,6 +10,7 @@
LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
LOCAL_SRC_FILES:= \
+ ColorConverter.cpp \
OMX.cpp \
OMXNodeInstance.cpp \
QComHardwareRenderer.cpp \
diff --git a/media/libstagefright/omx/ColorConverter.cpp b/media/libstagefright/omx/ColorConverter.cpp
new file mode 100644
index 0000000..e74782f
--- /dev/null
+++ b/media/libstagefright/omx/ColorConverter.cpp
@@ -0,0 +1,297 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/ColorConverter.h>
+#include <media/stagefright/MediaDebug.h>
+
+namespace android {
+
+static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
+
+ColorConverter::ColorConverter(
+ OMX_COLOR_FORMATTYPE from, OMX_COLOR_FORMATTYPE to)
+ : mSrcFormat(from),
+ mDstFormat(to),
+ mClip(NULL) {
+}
+
+ColorConverter::~ColorConverter() {
+ delete[] mClip;
+ mClip = NULL;
+}
+
+bool ColorConverter::isValid() const {
+ if (mDstFormat != OMX_COLOR_Format16bitRGB565) {
+ return false;
+ }
+
+ switch (mSrcFormat) {
+ case OMX_COLOR_FormatYUV420Planar:
+ case OMX_COLOR_FormatCbYCrY:
+ case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+void ColorConverter::convert(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip) {
+ CHECK_EQ(mDstFormat, OMX_COLOR_Format16bitRGB565);
+
+ switch (mSrcFormat) {
+ case OMX_COLOR_FormatYUV420Planar:
+ convertYUV420Planar(
+ width, height, srcBits, srcSkip, dstBits, dstSkip);
+ break;
+
+ case OMX_COLOR_FormatCbYCrY:
+ convertCbYCrY(
+ width, height, srcBits, srcSkip, dstBits, dstSkip);
+ break;
+
+ case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
+ convertQCOMYUV420SemiPlanar(
+ width, height, srcBits, srcSkip, dstBits, dstSkip);
+ break;
+
+ default:
+ {
+ CHECK(!"Should not be here. Unknown color conversion.");
+ break;
+ }
+ }
+}
+
+void ColorConverter::convertCbYCrY(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip) {
+ CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats.
+ CHECK(dstSkip >= width * 2);
+ CHECK((dstSkip & 3) == 0);
+
+ uint8_t *kAdjustedClip = initClip();
+
+ uint32_t *dst_ptr = (uint32_t *)dstBits;
+
+ const uint8_t *src = (const uint8_t *)srcBits;
+
+ for (size_t y = 0; y < height; ++y) {
+ for (size_t x = 0; x < width; x += 2) {
+ signed y1 = (signed)src[2 * x + 1] - 16;
+ signed y2 = (signed)src[2 * x + 3] - 16;
+ signed u = (signed)src[2 * x] - 128;
+ signed v = (signed)src[2 * x + 2] - 128;
+
+ signed u_b = u * 517;
+ signed u_g = -u * 100;
+ signed v_g = -v * 208;
+ signed v_r = v * 409;
+
+ signed tmp1 = y1 * 298;
+ signed b1 = (tmp1 + u_b) / 256;
+ signed g1 = (tmp1 + v_g + u_g) / 256;
+ signed r1 = (tmp1 + v_r) / 256;
+
+ signed tmp2 = y2 * 298;
+ signed b2 = (tmp2 + u_b) / 256;
+ signed g2 = (tmp2 + v_g + u_g) / 256;
+ signed r2 = (tmp2 + v_r) / 256;
+
+ uint32_t rgb1 =
+ ((kAdjustedClip[r1] >> 3) << 11)
+ | ((kAdjustedClip[g1] >> 2) << 5)
+ | (kAdjustedClip[b1] >> 3);
+
+ uint32_t rgb2 =
+ ((kAdjustedClip[r2] >> 3) << 11)
+ | ((kAdjustedClip[g2] >> 2) << 5)
+ | (kAdjustedClip[b2] >> 3);
+
+ dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ }
+
+ src += width * 2;
+ dst_ptr += dstSkip / 4;
+ }
+}
+
+void ColorConverter::convertYUV420Planar(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip) {
+ CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats.
+ CHECK(dstSkip >= width * 2);
+ CHECK((dstSkip & 3) == 0);
+
+ uint8_t *kAdjustedClip = initClip();
+
+ uint32_t *dst_ptr = (uint32_t *)dstBits;
+ const uint8_t *src_y = (const uint8_t *)srcBits;
+
+ const uint8_t *src_u =
+ (const uint8_t *)src_y + width * height;
+
+ const uint8_t *src_v =
+ (const uint8_t *)src_u + (width / 2) * (height / 2);
+
+ for (size_t y = 0; y < height; ++y) {
+ for (size_t x = 0; x < width; x += 2) {
+ // B = 1.164 * (Y - 16) + 2.018 * (U - 128)
+ // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128)
+ // R = 1.164 * (Y - 16) + 1.596 * (V - 128)
+
+ // B = 298/256 * (Y - 16) + 517/256 * (U - 128)
+ // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128)
+ // R = .................. + 409/256 * (V - 128)
+
+ // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277
+ // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172
+ // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223
+
+ // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534
+ // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432
+ // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481
+
+ // clip range -278 .. 535
+
+ signed y1 = (signed)src_y[x] - 16;
+ signed y2 = (signed)src_y[x + 1] - 16;
+
+ signed u = (signed)src_u[x / 2] - 128;
+ signed v = (signed)src_v[x / 2] - 128;
+
+ signed u_b = u * 517;
+ signed u_g = -u * 100;
+ signed v_g = -v * 208;
+ signed v_r = v * 409;
+
+ signed tmp1 = y1 * 298;
+ signed b1 = (tmp1 + u_b) / 256;
+ signed g1 = (tmp1 + v_g + u_g) / 256;
+ signed r1 = (tmp1 + v_r) / 256;
+
+ signed tmp2 = y2 * 298;
+ signed b2 = (tmp2 + u_b) / 256;
+ signed g2 = (tmp2 + v_g + u_g) / 256;
+ signed r2 = (tmp2 + v_r) / 256;
+
+ uint32_t rgb1 =
+ ((kAdjustedClip[r1] >> 3) << 11)
+ | ((kAdjustedClip[g1] >> 2) << 5)
+ | (kAdjustedClip[b1] >> 3);
+
+ uint32_t rgb2 =
+ ((kAdjustedClip[r2] >> 3) << 11)
+ | ((kAdjustedClip[g2] >> 2) << 5)
+ | (kAdjustedClip[b2] >> 3);
+
+ dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ }
+
+ src_y += width;
+
+ if (y & 1) {
+ src_u += width / 2;
+ src_v += width / 2;
+ }
+
+ dst_ptr += dstSkip / 4;
+ }
+}
+
+void ColorConverter::convertQCOMYUV420SemiPlanar(
+ size_t width, size_t height,
+ const void *srcBits, size_t srcSkip,
+ void *dstBits, size_t dstSkip) {
+ CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats.
+ CHECK(dstSkip >= width * 2);
+ CHECK((dstSkip & 3) == 0);
+
+ uint8_t *kAdjustedClip = initClip();
+
+ uint32_t *dst_ptr = (uint32_t *)dstBits;
+ const uint8_t *src_y = (const uint8_t *)srcBits;
+
+ const uint8_t *src_u =
+ (const uint8_t *)src_y + width * height;
+
+ for (size_t y = 0; y < height; ++y) {
+ for (size_t x = 0; x < width; x += 2) {
+ signed y1 = (signed)src_y[x] - 16;
+ signed y2 = (signed)src_y[x + 1] - 16;
+
+ signed u = (signed)src_u[x & ~1] - 128;
+ signed v = (signed)src_u[(x & ~1) + 1] - 128;
+
+ signed u_b = u * 517;
+ signed u_g = -u * 100;
+ signed v_g = -v * 208;
+ signed v_r = v * 409;
+
+ signed tmp1 = y1 * 298;
+ signed b1 = (tmp1 + u_b) / 256;
+ signed g1 = (tmp1 + v_g + u_g) / 256;
+ signed r1 = (tmp1 + v_r) / 256;
+
+ signed tmp2 = y2 * 298;
+ signed b2 = (tmp2 + u_b) / 256;
+ signed g2 = (tmp2 + v_g + u_g) / 256;
+ signed r2 = (tmp2 + v_r) / 256;
+
+ uint32_t rgb1 =
+ ((kAdjustedClip[b1] >> 3) << 11)
+ | ((kAdjustedClip[g1] >> 2) << 5)
+ | (kAdjustedClip[r1] >> 3);
+
+ uint32_t rgb2 =
+ ((kAdjustedClip[b2] >> 3) << 11)
+ | ((kAdjustedClip[g2] >> 2) << 5)
+ | (kAdjustedClip[r2] >> 3);
+
+ dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ }
+
+ src_y += width;
+
+ if (y & 1) {
+ src_u += width;
+ }
+
+ dst_ptr += dstSkip / 4;
+ }
+}
+
+uint8_t *ColorConverter::initClip() {
+ static const signed kClipMin = -278;
+ static const signed kClipMax = 535;
+
+ if (mClip == NULL) {
+ mClip = new uint8_t[kClipMax - kClipMin + 1];
+
+ for (signed i = kClipMin; i <= kClipMax; ++i) {
+ mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i;
+ }
+ }
+
+ return &mClip[-kClipMin];
+}
+
+} // namespace android
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 9ac0d44..e361018 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -24,12 +24,12 @@
#include "pv_omxcore.h"
#include "../include/OMXNodeInstance.h"
+#include "../include/QComHardwareRenderer.h"
+#include "../include/SoftwareRenderer.h"
+#include "../include/TIHardwareRenderer.h"
#include <binder/IMemory.h>
#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/QComHardwareRenderer.h>
-#include <media/stagefright/SoftwareRenderer.h>
-#include <media/stagefright/TIHardwareRenderer.h>
#include <media/stagefright/VideoRenderer.h>
#include <OMX_Component.h>
@@ -235,7 +235,7 @@
&OMXNodeInstance::kCallbacks);
if (err != OMX_ErrorNone) {
- LOGE("FAILED to allocate omx component '%s'", name);
+ LOGV("FAILED to allocate omx component '%s'", name);
instance->onGetHandleFailed();
diff --git a/media/libstagefright/omx/QComHardwareRenderer.cpp b/media/libstagefright/omx/QComHardwareRenderer.cpp
index c65d1f3..837b6a4 100644
--- a/media/libstagefright/omx/QComHardwareRenderer.cpp
+++ b/media/libstagefright/omx/QComHardwareRenderer.cpp
@@ -14,10 +14,11 @@
* limitations under the License.
*/
+#include "../include/QComHardwareRenderer.h"
+
#include <binder/MemoryHeapBase.h>
#include <binder/MemoryHeapPmem.h>
#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/QComHardwareRenderer.h>
#include <ui/ISurface.h>
namespace android {
diff --git a/media/libstagefright/omx/SoftwareRenderer.cpp b/media/libstagefright/omx/SoftwareRenderer.cpp
index 4ed6869..ef6ede0 100644
--- a/media/libstagefright/omx/SoftwareRenderer.cpp
+++ b/media/libstagefright/omx/SoftwareRenderer.cpp
@@ -17,21 +17,21 @@
#define LOG_TAG "SoftwareRenderer"
#include <utils/Log.h>
+#include "../include/SoftwareRenderer.h"
+
#include <binder/MemoryHeapBase.h>
#include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/SoftwareRenderer.h>
#include <ui/ISurface.h>
namespace android {
-#define QCOM_YUV 0
-
SoftwareRenderer::SoftwareRenderer(
OMX_COLOR_FORMATTYPE colorFormat,
const sp<ISurface> &surface,
size_t displayWidth, size_t displayHeight,
size_t decodedWidth, size_t decodedHeight)
: mColorFormat(colorFormat),
+ mConverter(colorFormat, OMX_COLOR_Format16bitRGB565),
mISurface(surface),
mDisplayWidth(displayWidth),
mDisplayHeight(displayHeight),
@@ -39,12 +39,12 @@
mDecodedHeight(decodedHeight),
mFrameSize(mDecodedWidth * mDecodedHeight * 2), // RGB565
mMemoryHeap(new MemoryHeapBase(2 * mFrameSize)),
- mIndex(0),
- mClip(NULL) {
+ mIndex(0) {
CHECK(mISurface.get() != NULL);
CHECK(mDecodedWidth > 0);
CHECK(mDecodedHeight > 0);
CHECK(mMemoryHeap->heapID() >= 0);
+ CHECK(mConverter.isValid());
ISurface::BufferHeap bufferHeap(
mDisplayWidth, mDisplayHeight,
@@ -58,278 +58,19 @@
SoftwareRenderer::~SoftwareRenderer() {
mISurface->unregisterBuffers();
-
- delete[] mClip;
- mClip = NULL;
}
void SoftwareRenderer::render(
const void *data, size_t size, void *platformPrivate) {
- static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
-
- switch (mColorFormat) {
- case OMX_COLOR_FormatYUV420Planar:
- return renderYUV420Planar(data, size);
-
- case OMX_COLOR_FormatCbYCrY:
- return renderCbYCrY(data, size);
-
- case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
- return renderQCOMYUV420SemiPlanar(data, size);
-
- default:
- {
- LOGW("Cannot render color format %ld", mColorFormat);
- break;
- }
- }
-}
-
-void SoftwareRenderer::renderYUV420Planar(
- const void *data, size_t size) {
- if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) {
- LOGE("size is %d, expected %d",
- size, (mDecodedHeight * mDecodedWidth * 3) / 2);
- }
- CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2);
-
- uint8_t *kAdjustedClip = initClip();
-
size_t offset = mIndex * mFrameSize;
-
void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
- uint32_t *dst_ptr = (uint32_t *)dst;
-
- const uint8_t *src_y = (const uint8_t *)data;
-
- const uint8_t *src_u =
- (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight;
-
-#if !QCOM_YUV
- const uint8_t *src_v =
- (const uint8_t *)src_u + (mDecodedWidth / 2) * (mDecodedHeight / 2);
-#endif
-
- for (size_t y = 0; y < mDecodedHeight; ++y) {
- for (size_t x = 0; x < mDecodedWidth; x += 2) {
- // B = 1.164 * (Y - 16) + 2.018 * (U - 128)
- // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128)
- // R = 1.164 * (Y - 16) + 1.596 * (V - 128)
-
- // B = 298/256 * (Y - 16) + 517/256 * (U - 128)
- // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128)
- // R = .................. + 409/256 * (V - 128)
-
- // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277
- // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172
- // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223
-
- // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534
- // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432
- // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481
-
- // clip range -278 .. 535
-
- signed y1 = (signed)src_y[x] - 16;
- signed y2 = (signed)src_y[x + 1] - 16;
-
-#if QCOM_YUV
- signed u = (signed)src_u[x & ~1] - 128;
- signed v = (signed)src_u[(x & ~1) + 1] - 128;
-#else
- signed u = (signed)src_u[x / 2] - 128;
- signed v = (signed)src_v[x / 2] - 128;
-#endif
-
- signed u_b = u * 517;
- signed u_g = -u * 100;
- signed v_g = -v * 208;
- signed v_r = v * 409;
-
- signed tmp1 = y1 * 298;
- signed b1 = (tmp1 + u_b) / 256;
- signed g1 = (tmp1 + v_g + u_g) / 256;
- signed r1 = (tmp1 + v_r) / 256;
-
- signed tmp2 = y2 * 298;
- signed b2 = (tmp2 + u_b) / 256;
- signed g2 = (tmp2 + v_g + u_g) / 256;
- signed r2 = (tmp2 + v_r) / 256;
-
- uint32_t rgb1 =
- ((kAdjustedClip[r1] >> 3) << 11)
- | ((kAdjustedClip[g1] >> 2) << 5)
- | (kAdjustedClip[b1] >> 3);
-
- uint32_t rgb2 =
- ((kAdjustedClip[r2] >> 3) << 11)
- | ((kAdjustedClip[g2] >> 2) << 5)
- | (kAdjustedClip[b2] >> 3);
-
- dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
- }
-
- src_y += mDecodedWidth;
-
- if (y & 1) {
-#if QCOM_YUV
- src_u += mDecodedWidth;
-#else
- src_u += mDecodedWidth / 2;
- src_v += mDecodedWidth / 2;
-#endif
- }
-
- dst_ptr += mDecodedWidth / 2;
- }
+ mConverter.convert(
+ mDecodedWidth, mDecodedHeight,
+ data, 0, dst, 2 * mDecodedWidth);
mISurface->postBuffer(offset);
mIndex = 1 - mIndex;
}
-void SoftwareRenderer::renderCbYCrY(
- const void *data, size_t size) {
- if (size != (mDecodedHeight * mDecodedWidth * 2)) {
- LOGE("size is %d, expected %d",
- size, (mDecodedHeight * mDecodedWidth * 2));
- }
- CHECK(size >= (mDecodedWidth * mDecodedHeight * 2));
-
- uint8_t *kAdjustedClip = initClip();
-
- size_t offset = mIndex * mFrameSize;
- void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
- uint32_t *dst_ptr = (uint32_t *)dst;
-
- const uint8_t *src = (const uint8_t *)data;
-
- for (size_t y = 0; y < mDecodedHeight; ++y) {
- for (size_t x = 0; x < mDecodedWidth; x += 2) {
- signed y1 = (signed)src[2 * x + 1] - 16;
- signed y2 = (signed)src[2 * x + 3] - 16;
- signed u = (signed)src[2 * x] - 128;
- signed v = (signed)src[2 * x + 2] - 128;
-
- signed u_b = u * 517;
- signed u_g = -u * 100;
- signed v_g = -v * 208;
- signed v_r = v * 409;
-
- signed tmp1 = y1 * 298;
- signed b1 = (tmp1 + u_b) / 256;
- signed g1 = (tmp1 + v_g + u_g) / 256;
- signed r1 = (tmp1 + v_r) / 256;
-
- signed tmp2 = y2 * 298;
- signed b2 = (tmp2 + u_b) / 256;
- signed g2 = (tmp2 + v_g + u_g) / 256;
- signed r2 = (tmp2 + v_r) / 256;
-
- uint32_t rgb1 =
- ((kAdjustedClip[r1] >> 3) << 11)
- | ((kAdjustedClip[g1] >> 2) << 5)
- | (kAdjustedClip[b1] >> 3);
-
- uint32_t rgb2 =
- ((kAdjustedClip[r2] >> 3) << 11)
- | ((kAdjustedClip[g2] >> 2) << 5)
- | (kAdjustedClip[b2] >> 3);
-
- dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
- }
-
- src += mDecodedWidth * 2;
- dst_ptr += mDecodedWidth / 2;
- }
-
- mISurface->postBuffer(offset);
- mIndex = 1 - mIndex;
-}
-
-void SoftwareRenderer::renderQCOMYUV420SemiPlanar(
- const void *data, size_t size) {
- if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) {
- LOGE("size is %d, expected %d",
- size, (mDecodedHeight * mDecodedWidth * 3) / 2);
- }
- CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2);
-
- uint8_t *kAdjustedClip = initClip();
-
- size_t offset = mIndex * mFrameSize;
-
- void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
-
- uint32_t *dst_ptr = (uint32_t *)dst;
-
- const uint8_t *src_y = (const uint8_t *)data;
-
- const uint8_t *src_u =
- (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight;
-
- for (size_t y = 0; y < mDecodedHeight; ++y) {
- for (size_t x = 0; x < mDecodedWidth; x += 2) {
- signed y1 = (signed)src_y[x] - 16;
- signed y2 = (signed)src_y[x + 1] - 16;
-
- signed u = (signed)src_u[x & ~1] - 128;
- signed v = (signed)src_u[(x & ~1) + 1] - 128;
-
- signed u_b = u * 517;
- signed u_g = -u * 100;
- signed v_g = -v * 208;
- signed v_r = v * 409;
-
- signed tmp1 = y1 * 298;
- signed b1 = (tmp1 + u_b) / 256;
- signed g1 = (tmp1 + v_g + u_g) / 256;
- signed r1 = (tmp1 + v_r) / 256;
-
- signed tmp2 = y2 * 298;
- signed b2 = (tmp2 + u_b) / 256;
- signed g2 = (tmp2 + v_g + u_g) / 256;
- signed r2 = (tmp2 + v_r) / 256;
-
- uint32_t rgb1 =
- ((kAdjustedClip[b1] >> 3) << 11)
- | ((kAdjustedClip[g1] >> 2) << 5)
- | (kAdjustedClip[r1] >> 3);
-
- uint32_t rgb2 =
- ((kAdjustedClip[b2] >> 3) << 11)
- | ((kAdjustedClip[g2] >> 2) << 5)
- | (kAdjustedClip[r2] >> 3);
-
- dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
- }
-
- src_y += mDecodedWidth;
-
- if (y & 1) {
- src_u += mDecodedWidth;
- }
-
- dst_ptr += mDecodedWidth / 2;
- }
-
- mISurface->postBuffer(offset);
- mIndex = 1 - mIndex;
-}
-
-uint8_t *SoftwareRenderer::initClip() {
- static const signed kClipMin = -278;
- static const signed kClipMax = 535;
-
- if (mClip == NULL) {
- mClip = new uint8_t[kClipMax - kClipMin + 1];
-
- for (signed i = kClipMin; i <= kClipMax; ++i) {
- mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i;
- }
- }
-
- return &mClip[-kClipMin];
-}
-
} // namespace android
diff --git a/media/libstagefright/omx/TIHardwareRenderer.cpp b/media/libstagefright/omx/TIHardwareRenderer.cpp
index ebade4a..6dde86a 100644
--- a/media/libstagefright/omx/TIHardwareRenderer.cpp
+++ b/media/libstagefright/omx/TIHardwareRenderer.cpp
@@ -17,7 +17,8 @@
#define LOG_TAG "TIHardwareRenderer"
#include <utils/Log.h>
-#include <media/stagefright/TIHardwareRenderer.h>
+#include "../include/TIHardwareRenderer.h"
+
#include <media/stagefright/MediaDebug.h>
#include <ui/ISurface.h>
#include <ui/Overlay.h>
diff --git a/media/libstagefright/stagefright_string.cpp b/media/libstagefright/string.cpp
similarity index 97%
rename from media/libstagefright/stagefright_string.cpp
rename to media/libstagefright/string.cpp
index 2aedb80..bd6204b 100644
--- a/media/libstagefright/stagefright_string.cpp
+++ b/media/libstagefright/string.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <media/stagefright/stagefright_string.h>
+#include "include/stagefright_string.h"
namespace android {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
index e66e560..fa0986a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
@@ -226,8 +226,7 @@
* Test case 1: Take a picture and verify all the callback
* functions are called properly.
*/
- // TODO: add this back to LargeTest once bug 2141755 is fixed
- // @LargeTest
+ @LargeTest
public void testTakePicture() throws Exception {
synchronized (lock) {
initializeMessageLooper();
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index ac27a2d..f11123e 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -18,7 +18,6 @@
import java.io.Writer;
import java.util.ArrayList;
-import java.util.concurrent.Semaphore;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
@@ -30,6 +29,8 @@
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
@@ -682,7 +683,10 @@
}
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] num_config = new int[1];
- egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config);
+ if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
+ num_config)) {
+ throw new IllegalArgumentException("eglChooseConfig failed");
+ }
int numConfigs = num_config[0];
@@ -692,8 +696,10 @@
}
EGLConfig[] configs = new EGLConfig[numConfigs];
- egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
- num_config);
+ if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
+ num_config)) {
+ throw new IllegalArgumentException("eglChooseConfig#2 failed");
+ }
EGLConfig config = chooseConfig(egl, display, configs);
if (config == null) {
throw new IllegalArgumentException("No config chosen");
@@ -818,11 +824,17 @@
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+ throw new RuntimeException("eglGetDisplay failed");
+ }
+
/*
* We can now initialize EGL for that display
*/
int[] version = new int[2];
- mEgl.eglInitialize(mEglDisplay, version);
+ if(!mEgl.eglInitialize(mEglDisplay, version)) {
+ throw new RuntimeException("eglInitialize failed");
+ }
mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
/*
@@ -1059,6 +1071,7 @@
}
if (changed) {
gl = (GL10) mEglHelper.createSurface(getHolder());
+ sGLThreadManager.checkGLDriver(gl);
tellRendererSurfaceChanged = true;
}
if (tellRendererSurfaceCreated) {
@@ -1272,37 +1285,76 @@
}
}
- static class GLThreadManager {
- public boolean shouldQuit(GLThread thread) {
- synchronized(this) {
- return thread != mMostRecentGLThread;
- }
- }
+ private static class GLThreadManager {
public void start(GLThread thread) throws InterruptedException {
+ if (! mGLESVersionCheckComplete) {
+ mGLESVersion = SystemProperties.getInt(
+ "ro.opengles.version",
+ ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
+ if (mGLESVersion >= kGLES_20) {
+ mMultipleGLESContextsAllowed = true;
+ }
+ mGLESVersionCheckComplete = true;
+ }
+
GLThread oldThread = null;
synchronized(this) {
oldThread = mMostRecentGLThread;
mMostRecentGLThread = thread;
}
- if (oldThread != null) {
+ if (oldThread != null && ! mMultipleGLESContextsAllowed) {
synchronized(oldThread) {
oldThread.notifyAll();
}
}
- sEglSemaphore.acquire();
- }
- public void end(GLThread thread) {
- sEglSemaphore.release();
+
synchronized(this) {
- if (mMostRecentGLThread == thread) {
- mMostRecentGLThread = null;
+ while ((! mMultipleGLESContextsAllowed)
+ && mGLContextCount > 0) {
+ wait();
}
+
+ mGLContextCount++;
}
}
- private GLThread mMostRecentGLThread;
- }
- private static final Semaphore sEglSemaphore = new Semaphore(1);
+ public synchronized void end(GLThread thread) {
+ mGLContextCount--;
+ notifyAll();
+ if (mMostRecentGLThread == thread) {
+ mMostRecentGLThread = null;
+ }
+ }
+
+ public synchronized void checkGLDriver(GL10 gl) {
+ if (! mGLESDriverCheckComplete) {
+ if (mGLESVersion < kGLES_20) {
+ String renderer = gl.glGetString(GL10.GL_RENDERER);
+ mMultipleGLESContextsAllowed =
+ ! renderer.startsWith(kMSM7K_RENDERER_PREFIX);
+ notifyAll();
+ }
+ mGLESDriverCheckComplete = true;
+ }
+ }
+
+ public boolean shouldQuit(GLThread thread) {
+ synchronized(this) {
+ return thread != mMostRecentGLThread;
+ }
+ }
+
+ private boolean mGLESVersionCheckComplete;
+ private int mGLESVersion;
+ private GLThread mMostRecentGLThread;
+ private boolean mGLESDriverCheckComplete;
+ private boolean mMultipleGLESContextsAllowed;
+ private int mGLContextCount;
+ private static final int kGLES_20 = 0x20000;
+ private static final String kMSM7K_RENDERER_PREFIX =
+ "Q3Dimension MSM7500 ";
+ };
+
private static final GLThreadManager sGLThreadManager = new GLThreadManager();
private boolean mSizeChanged = true;
diff --git a/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java b/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java
index 2dae090..72b1dfb 100644
--- a/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java
+++ b/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java
@@ -56,19 +56,22 @@
*/
class GL2JNIView extends GLSurfaceView {
private static String TAG = "GL2JNIView";
- GL2JNIView(Context context) {
+
+ public GL2JNIView(Context context) {
super(context);
- init();
+ init(false, 0, 0);
}
- public GL2JNIView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
+ public GL2JNIView(Context context, boolean translucent, int depth, int stencil) {
+ super(context);
+ init(translucent, depth, stencil);
}
- private void init() {
+ private void init(boolean translucent, int depth, int stencil) {
setEGLContextFactory(new ContextFactory());
- setEGLConfigChooser(new ConfigChooser());
+ setEGLConfigChooser( translucent ?
+ new ConfigChooser(8,8,8,8, depth, stencil) :
+ new ConfigChooser(5,6,5,0, depth, stencil));
setRenderer(new Renderer());
}
@@ -105,6 +108,16 @@
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_NONE
};
+
+ public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
+ mRedSize = r;
+ mGreenSize = g;
+ mBlueSize = b;
+ mAlphaSize = a;
+ mDepthSize = depth;
+ mStencilSize = stencil;
+ }
+
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] num_config = new int[1];
@@ -112,14 +125,158 @@
int numConfigs = num_config[0];
- Log.w(TAG, String.format("Found %d configurations", numConfigs));
if (numConfigs <= 0) {
throw new IllegalArgumentException("No configs match configSpec");
}
EGLConfig[] configs = new EGLConfig[numConfigs];
egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
- return configs[0];
+ // printConfigs(egl, display, configs);
+ return chooseConfig(egl, display, configs);
}
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ EGLConfig closestConfig = null;
+ int closestDistance = 1000;
+ for(EGLConfig config : configs) {
+ int d = findConfigAttrib(egl, display, config,
+ EGL10.EGL_DEPTH_SIZE, 0);
+ int s = findConfigAttrib(egl, display, config,
+ EGL10.EGL_STENCIL_SIZE, 0);
+ if (d >= mDepthSize && s>= mStencilSize) {
+ int r = findConfigAttrib(egl, display, config,
+ EGL10.EGL_RED_SIZE, 0);
+ int g = findConfigAttrib(egl, display, config,
+ EGL10.EGL_GREEN_SIZE, 0);
+ int b = findConfigAttrib(egl, display, config,
+ EGL10.EGL_BLUE_SIZE, 0);
+ int a = findConfigAttrib(egl, display, config,
+ EGL10.EGL_ALPHA_SIZE, 0);
+ int distance = Math.abs(r - mRedSize)
+ + Math.abs(g - mGreenSize)
+ + Math.abs(b - mBlueSize)
+ + Math.abs(a - mAlphaSize);
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestConfig = config;
+ }
+ }
+ }
+ return closestConfig;
+ }
+
+ private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+ EGLConfig config, int attribute, int defaultValue) {
+
+ if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+ return mValue[0];
+ }
+ return defaultValue;
+ }
+
+ private void printConfigs(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ int numConfigs = configs.length;
+ Log.w(TAG, String.format("%d configurations", numConfigs));
+ for (int i = 0; i < numConfigs; i++) {
+ Log.w(TAG, String.format("Configuration %d:\n", i));
+ printConfig(egl, display, configs[i]);
+ }
+ }
+
+ private void printConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig config) {
+ int[] attributes = {
+ EGL10.EGL_BUFFER_SIZE,
+ EGL10.EGL_ALPHA_SIZE,
+ EGL10.EGL_BLUE_SIZE,
+ EGL10.EGL_GREEN_SIZE,
+ EGL10.EGL_RED_SIZE,
+ EGL10.EGL_DEPTH_SIZE,
+ EGL10.EGL_STENCIL_SIZE,
+ EGL10.EGL_CONFIG_CAVEAT,
+ EGL10.EGL_CONFIG_ID,
+ EGL10.EGL_LEVEL,
+ EGL10.EGL_MAX_PBUFFER_HEIGHT,
+ EGL10.EGL_MAX_PBUFFER_PIXELS,
+ EGL10.EGL_MAX_PBUFFER_WIDTH,
+ EGL10.EGL_NATIVE_RENDERABLE,
+ EGL10.EGL_NATIVE_VISUAL_ID,
+ EGL10.EGL_NATIVE_VISUAL_TYPE,
+ 0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+ EGL10.EGL_SAMPLES,
+ EGL10.EGL_SAMPLE_BUFFERS,
+ EGL10.EGL_SURFACE_TYPE,
+ EGL10.EGL_TRANSPARENT_TYPE,
+ EGL10.EGL_TRANSPARENT_RED_VALUE,
+ EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+ EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+ 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+ 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+ 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+ 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+ EGL10.EGL_LUMINANCE_SIZE,
+ EGL10.EGL_ALPHA_MASK_SIZE,
+ EGL10.EGL_COLOR_BUFFER_TYPE,
+ EGL10.EGL_RENDERABLE_TYPE,
+ 0x3042 // EGL10.EGL_CONFORMANT
+ };
+ String[] names = {
+ "EGL_BUFFER_SIZE",
+ "EGL_ALPHA_SIZE",
+ "EGL_BLUE_SIZE",
+ "EGL_GREEN_SIZE",
+ "EGL_RED_SIZE",
+ "EGL_DEPTH_SIZE",
+ "EGL_STENCIL_SIZE",
+ "EGL_CONFIG_CAVEAT",
+ "EGL_CONFIG_ID",
+ "EGL_LEVEL",
+ "EGL_MAX_PBUFFER_HEIGHT",
+ "EGL_MAX_PBUFFER_PIXELS",
+ "EGL_MAX_PBUFFER_WIDTH",
+ "EGL_NATIVE_RENDERABLE",
+ "EGL_NATIVE_VISUAL_ID",
+ "EGL_NATIVE_VISUAL_TYPE",
+ "EGL_PRESERVED_RESOURCES",
+ "EGL_SAMPLES",
+ "EGL_SAMPLE_BUFFERS",
+ "EGL_SURFACE_TYPE",
+ "EGL_TRANSPARENT_TYPE",
+ "EGL_TRANSPARENT_RED_VALUE",
+ "EGL_TRANSPARENT_GREEN_VALUE",
+ "EGL_TRANSPARENT_BLUE_VALUE",
+ "EGL_BIND_TO_TEXTURE_RGB",
+ "EGL_BIND_TO_TEXTURE_RGBA",
+ "EGL_MIN_SWAP_INTERVAL",
+ "EGL_MAX_SWAP_INTERVAL",
+ "EGL_LUMINANCE_SIZE",
+ "EGL_ALPHA_MASK_SIZE",
+ "EGL_COLOR_BUFFER_TYPE",
+ "EGL_RENDERABLE_TYPE",
+ "EGL_CONFORMANT"
+ };
+ int[] value = new int[1];
+ for (int i = 0; i < attributes.length; i++) {
+ int attribute = attributes[i];
+ String name = names[i];
+ if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
+ Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
+ } else {
+ // Log.w(TAG, String.format(" %s: failed\n", name));
+ while (egl.eglGetError() != EGL10.EGL_SUCCESS);
+ }
+ }
+ }
+
+ // Subclasses can adjust these values:
+ protected int mRedSize;
+ protected int mGreenSize;
+ protected int mBlueSize;
+ protected int mAlphaSize;
+ protected int mDepthSize;
+ protected int mStencilSize;
+ private int[] mValue = new int[1];
}
private static class Renderer implements GLSurfaceView.Renderer {
diff --git a/opengl/tests/gldual/Android.mk b/opengl/tests/gldual/Android.mk
new file mode 100644
index 0000000..e73c249
--- /dev/null
+++ b/opengl/tests/gldual/Android.mk
@@ -0,0 +1,51 @@
+#########################################################################
+# OpenGL ES JNI sample
+# This makefile builds both an activity and a shared library.
+#########################################################################
+ifneq ($(TARGET_SIMULATOR),true) # not 64 bit clean
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := GLDual
+
+LOCAL_JNI_SHARED_LIBRARIES := libgldualjni
+
+include $(BUILD_PACKAGE)
+
+#########################################################################
+# Build JNI Shared Library
+#########################################################################
+
+LOCAL_PATH:= $(LOCAL_PATH)/jni
+
+include $(CLEAR_VARS)
+
+# Optional tag would mean it doesn't get installed by default
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS := -Werror
+
+LOCAL_SRC_FILES:= \
+ gl_code.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libutils \
+ libEGL \
+ libGLESv2
+
+LOCAL_MODULE := libgldualjni
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
+
+endif # TARGET_SIMULATOR
diff --git a/opengl/tests/gldual/AndroidManifest.xml b/opengl/tests/gldual/AndroidManifest.xml
new file mode 100644
index 0000000..06f4c4d
--- /dev/null
+++ b/opengl/tests/gldual/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.gldual">
+ <application
+ android:label="@string/gldual_activity">
+ <activity android:name="GLDualActivity"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+ android:launchMode="singleTask"
+ android:configChanges="orientation|keyboardHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/opengl/tests/gldual/jni/gl_code.cpp b/opengl/tests/gldual/jni/gl_code.cpp
new file mode 100644
index 0000000..f1f0a1f
--- /dev/null
+++ b/opengl/tests/gldual/jni/gl_code.cpp
@@ -0,0 +1,165 @@
+// OpenGL ES 2.0 code
+
+#include <nativehelper/jni.h>
+#define LOG_TAG "GL2JNI gl_code.cpp"
+#include <utils/Log.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+static void printGLString(const char *name, GLenum s) {
+ const char *v = (const char *) glGetString(s);
+ LOGI("GL %s = %s\n", name, v);
+}
+
+static void checkGlError(const char* op) {
+ for (GLint error = glGetError(); error; error
+ = glGetError()) {
+ LOGI("after %s() glError (0x%x)\n", op, error);
+ }
+}
+
+static const char gVertexShader[] = "attribute vec4 vPosition;\n"
+ "void main() {\n"
+ " gl_Position = vPosition;\n"
+ "}\n";
+
+static const char gFragmentShader[] = "precision mediump float;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+ "}\n";
+
+GLuint loadShader(GLenum shaderType, const char* pSource) {
+ GLuint shader = glCreateShader(shaderType);
+ if (shader) {
+ glShaderSource(shader, 1, &pSource, NULL);
+ glCompileShader(shader);
+ GLint compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (!compiled) {
+ GLint infoLen = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ if (infoLen) {
+ char* buf = (char*) malloc(infoLen);
+ if (buf) {
+ glGetShaderInfoLog(shader, infoLen, NULL, buf);
+ LOGE("Could not compile shader %d:\n%s\n",
+ shaderType, buf);
+ free(buf);
+ }
+ glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ }
+ return shader;
+}
+
+GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) {
+ GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
+ if (!vertexShader) {
+ return 0;
+ }
+
+ GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
+ if (!pixelShader) {
+ return 0;
+ }
+
+ GLuint program = glCreateProgram();
+ if (program) {
+ glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ glLinkProgram(program);
+ GLint linkStatus = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus != GL_TRUE) {
+ GLint bufLength = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+ if (bufLength) {
+ char* buf = (char*) malloc(bufLength);
+ if (buf) {
+ glGetProgramInfoLog(program, bufLength, NULL, buf);
+ LOGE("Could not link program:\n%s\n", buf);
+ free(buf);
+ }
+ }
+ glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+}
+
+GLuint gProgram;
+GLuint gvPositionHandle;
+
+bool setupGraphics(int w, int h) {
+ printGLString("Version", GL_VERSION);
+ printGLString("Vendor", GL_VENDOR);
+ printGLString("Renderer", GL_RENDERER);
+ printGLString("Extensions", GL_EXTENSIONS);
+
+ LOGI("setupGraphics(%d, %d)", w, h);
+ gProgram = createProgram(gVertexShader, gFragmentShader);
+ if (!gProgram) {
+ LOGE("Could not create program.");
+ return false;
+ }
+ gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
+ checkGlError("glGetAttribLocation");
+ LOGI("glGetAttribLocation(\"vPosition\") = %d\n",
+ gvPositionHandle);
+
+ glViewport(0, 0, w, h);
+ checkGlError("glViewport");
+ return true;
+}
+
+const GLfloat gTriangleVertices[] = { 0.0f, 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f };
+
+void renderFrame() {
+ static float grey;
+ grey += 0.01f;
+ if (grey > 1.0f) {
+ grey = 0.0f;
+ }
+ glClearColor(grey, grey, grey, 1.0f);
+ checkGlError("glClearColor");
+ glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+ checkGlError("glClear");
+
+ glUseProgram(gProgram);
+ checkGlError("glUseProgram");
+
+ glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices);
+ checkGlError("glVertexAttribPointer");
+ glEnableVertexAttribArray(gvPositionHandle);
+ checkGlError("glEnableVertexAttribArray");
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+ checkGlError("glDrawArrays");
+}
+
+extern "C" {
+ JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_init(JNIEnv * env, jobject obj, jint width, jint height);
+ JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_step(JNIEnv * env, jobject obj);
+};
+
+JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_init(JNIEnv * env, jobject obj, jint width, jint height)
+{
+ setupGraphics(width, height);
+}
+
+JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_step(JNIEnv * env, jobject obj)
+{
+ renderFrame();
+}
+
diff --git a/opengl/tests/gldual/res/layout/gldual_activity.xml b/opengl/tests/gldual/res/layout/gldual_activity.xml
new file mode 100644
index 0000000..f2d59c7
--- /dev/null
+++ b/opengl/tests/gldual/res/layout/gldual_activity.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <android.opengl.GLSurfaceView android:id="@+id/gl1"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+ <com.android.gldual.GLDualGL2View android:id="@+id/gl2"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+</LinearLayout>
diff --git a/opengl/tests/gldual/res/values/strings.xml b/opengl/tests/gldual/res/values/strings.xml
new file mode 100644
index 0000000..4267dff
--- /dev/null
+++ b/opengl/tests/gldual/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2006, 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.
+*/
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+ them to be changed based on the locale and options. -->
+
+<resources>
+ <!-- Simple strings. -->
+ <string name="gldual_activity">GLDual</string>
+
+</resources>
+
diff --git a/opengl/tests/gldual/src/com/android/gldual/GLDualActivity.java b/opengl/tests/gldual/src/com/android/gldual/GLDualActivity.java
new file mode 100644
index 0000000..9d88f64
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/GLDualActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2007 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.gldual;
+
+import android.app.Activity;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.LinearLayout;
+
+
+public class GLDualActivity extends Activity {
+
+ GLSurfaceView mGLView;
+ GLDualGL2View mGL2View;
+
+ @Override protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ View root = getLayoutInflater().inflate(R.layout.gldual_activity, null);
+ mGLView = (GLSurfaceView) root.findViewById(R.id.gl1);
+ mGLView.setEGLConfigChooser(5,6,5,0,0,0);
+ mGLView.setRenderer(new TriangleRenderer());
+ mGL2View = (GLDualGL2View) root.findViewById(R.id.gl2);
+ setContentView(root);
+ }
+
+ @Override protected void onPause() {
+ super.onPause();
+ mGLView.onPause();
+ mGL2View.onPause();
+ }
+
+ @Override protected void onResume() {
+ super.onResume();
+ mGLView.onResume();
+ mGL2View.onResume();
+ }
+}
diff --git a/opengl/tests/gldual/src/com/android/gldual/GLDualGL2View.java b/opengl/tests/gldual/src/com/android/gldual/GLDualGL2View.java
new file mode 100644
index 0000000..8f5e347
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/GLDualGL2View.java
@@ -0,0 +1,299 @@
+/*
+ * 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.gldual;
+/*
+ * Copyright (C) 2008 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.
+ */
+
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying an OpenGL animation. This allows the animation to run in a
+ * separate thread, without requiring that it be driven by the update mechanism
+ * of the view hierarchy.
+ *
+ * The application-specific rendering code is delegated to a GLView.Renderer
+ * instance.
+ */
+class GLDualGL2View extends GLSurfaceView {
+ private static String TAG = "GLDualGL2View";
+
+ public GLDualGL2View(Context context) {
+ super(context);
+ init(false, 0, 0);
+ }
+
+ public GLDualGL2View(Context context, AttributeSet set) {
+ super(context, set);
+ init(false, 0, 0);
+ }
+
+ public GLDualGL2View(Context context, boolean translucent, int depth, int stencil) {
+ super(context);
+ init(translucent, depth, stencil);
+ }
+
+ private void init(boolean translucent, int depth, int stencil) {
+ setEGLContextFactory(new ContextFactory());
+ setEGLConfigChooser( translucent ?
+ new ConfigChooser(8,8,8,8, depth, stencil) :
+ new ConfigChooser(5,6,5,0, depth, stencil));
+ setRenderer(new Renderer());
+ }
+
+ private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
+ private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
+ Log.w(TAG, "creating OpenGL ES 2.0 context");
+ checkEglError("Before eglCreateContext", egl);
+ int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+ EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+ checkEglError("After eglCreateContext", egl);
+ return context;
+ }
+
+ public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
+ egl.eglDestroyContext(display, context);
+ }
+ }
+
+ private static void checkEglError(String prompt, EGL10 egl) {
+ int error;
+ while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
+ Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
+ }
+ }
+
+ private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
+ private static int EGL_OPENGL_ES2_BIT = 4;
+ private static int[] s_configAttribs2 =
+ {
+ EGL10.EGL_RED_SIZE, 4,
+ EGL10.EGL_GREEN_SIZE, 4,
+ EGL10.EGL_BLUE_SIZE, 4,
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_NONE
+ };
+
+ public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
+ mRedSize = r;
+ mGreenSize = g;
+ mBlueSize = b;
+ mAlphaSize = a;
+ mDepthSize = depth;
+ mStencilSize = stencil;
+ }
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+
+ int[] num_config = new int[1];
+ egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
+
+ int numConfigs = num_config[0];
+
+ if (numConfigs <= 0) {
+ throw new IllegalArgumentException("No configs match configSpec");
+ }
+ EGLConfig[] configs = new EGLConfig[numConfigs];
+ egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
+ // printConfigs(egl, display, configs);
+ return chooseConfig(egl, display, configs);
+ }
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ EGLConfig closestConfig = null;
+ int closestDistance = 1000;
+ for(EGLConfig config : configs) {
+ int d = findConfigAttrib(egl, display, config,
+ EGL10.EGL_DEPTH_SIZE, 0);
+ int s = findConfigAttrib(egl, display, config,
+ EGL10.EGL_STENCIL_SIZE, 0);
+ if (d >= mDepthSize && s>= mStencilSize) {
+ int r = findConfigAttrib(egl, display, config,
+ EGL10.EGL_RED_SIZE, 0);
+ int g = findConfigAttrib(egl, display, config,
+ EGL10.EGL_GREEN_SIZE, 0);
+ int b = findConfigAttrib(egl, display, config,
+ EGL10.EGL_BLUE_SIZE, 0);
+ int a = findConfigAttrib(egl, display, config,
+ EGL10.EGL_ALPHA_SIZE, 0);
+ int distance = Math.abs(r - mRedSize)
+ + Math.abs(g - mGreenSize)
+ + Math.abs(b - mBlueSize)
+ + Math.abs(a - mAlphaSize);
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestConfig = config;
+ }
+ }
+ }
+ return closestConfig;
+ }
+
+ private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+ EGLConfig config, int attribute, int defaultValue) {
+
+ if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+ return mValue[0];
+ }
+ return defaultValue;
+ }
+
+ private void printConfigs(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ int numConfigs = configs.length;
+ Log.w(TAG, String.format("%d configurations", numConfigs));
+ for (int i = 0; i < numConfigs; i++) {
+ Log.w(TAG, String.format("Configuration %d:\n", i));
+ printConfig(egl, display, configs[i]);
+ }
+ }
+
+ private void printConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig config) {
+ int[] attributes = {
+ EGL10.EGL_BUFFER_SIZE,
+ EGL10.EGL_ALPHA_SIZE,
+ EGL10.EGL_BLUE_SIZE,
+ EGL10.EGL_GREEN_SIZE,
+ EGL10.EGL_RED_SIZE,
+ EGL10.EGL_DEPTH_SIZE,
+ EGL10.EGL_STENCIL_SIZE,
+ EGL10.EGL_CONFIG_CAVEAT,
+ EGL10.EGL_CONFIG_ID,
+ EGL10.EGL_LEVEL,
+ EGL10.EGL_MAX_PBUFFER_HEIGHT,
+ EGL10.EGL_MAX_PBUFFER_PIXELS,
+ EGL10.EGL_MAX_PBUFFER_WIDTH,
+ EGL10.EGL_NATIVE_RENDERABLE,
+ EGL10.EGL_NATIVE_VISUAL_ID,
+ EGL10.EGL_NATIVE_VISUAL_TYPE,
+ 0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+ EGL10.EGL_SAMPLES,
+ EGL10.EGL_SAMPLE_BUFFERS,
+ EGL10.EGL_SURFACE_TYPE,
+ EGL10.EGL_TRANSPARENT_TYPE,
+ EGL10.EGL_TRANSPARENT_RED_VALUE,
+ EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+ EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+ 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+ 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+ 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+ 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+ EGL10.EGL_LUMINANCE_SIZE,
+ EGL10.EGL_ALPHA_MASK_SIZE,
+ EGL10.EGL_COLOR_BUFFER_TYPE,
+ EGL10.EGL_RENDERABLE_TYPE,
+ 0x3042 // EGL10.EGL_CONFORMANT
+ };
+ String[] names = {
+ "EGL_BUFFER_SIZE",
+ "EGL_ALPHA_SIZE",
+ "EGL_BLUE_SIZE",
+ "EGL_GREEN_SIZE",
+ "EGL_RED_SIZE",
+ "EGL_DEPTH_SIZE",
+ "EGL_STENCIL_SIZE",
+ "EGL_CONFIG_CAVEAT",
+ "EGL_CONFIG_ID",
+ "EGL_LEVEL",
+ "EGL_MAX_PBUFFER_HEIGHT",
+ "EGL_MAX_PBUFFER_PIXELS",
+ "EGL_MAX_PBUFFER_WIDTH",
+ "EGL_NATIVE_RENDERABLE",
+ "EGL_NATIVE_VISUAL_ID",
+ "EGL_NATIVE_VISUAL_TYPE",
+ "EGL_PRESERVED_RESOURCES",
+ "EGL_SAMPLES",
+ "EGL_SAMPLE_BUFFERS",
+ "EGL_SURFACE_TYPE",
+ "EGL_TRANSPARENT_TYPE",
+ "EGL_TRANSPARENT_RED_VALUE",
+ "EGL_TRANSPARENT_GREEN_VALUE",
+ "EGL_TRANSPARENT_BLUE_VALUE",
+ "EGL_BIND_TO_TEXTURE_RGB",
+ "EGL_BIND_TO_TEXTURE_RGBA",
+ "EGL_MIN_SWAP_INTERVAL",
+ "EGL_MAX_SWAP_INTERVAL",
+ "EGL_LUMINANCE_SIZE",
+ "EGL_ALPHA_MASK_SIZE",
+ "EGL_COLOR_BUFFER_TYPE",
+ "EGL_RENDERABLE_TYPE",
+ "EGL_CONFORMANT"
+ };
+ int[] value = new int[1];
+ for (int i = 0; i < attributes.length; i++) {
+ int attribute = attributes[i];
+ String name = names[i];
+ if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
+ Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
+ } else {
+ // Log.w(TAG, String.format(" %s: failed\n", name));
+ while (egl.eglGetError() != EGL10.EGL_SUCCESS);
+ }
+ }
+ }
+
+ // Subclasses can adjust these values:
+ protected int mRedSize;
+ protected int mGreenSize;
+ protected int mBlueSize;
+ protected int mAlphaSize;
+ protected int mDepthSize;
+ protected int mStencilSize;
+ private int[] mValue = new int[1];
+ }
+
+ private static class Renderer implements GLSurfaceView.Renderer {
+ public void onDrawFrame(GL10 gl) {
+ GLDualLib.step();
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ GLDualLib.init(width, height);
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ // Do nothing.
+ }
+ }
+}
+
diff --git a/opengl/tests/gldual/src/com/android/gldual/GLDualLib.java b/opengl/tests/gldual/src/com/android/gldual/GLDualLib.java
new file mode 100644
index 0000000..d8f765e
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/GLDualLib.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 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.gldual;
+
+// Wrapper for native library
+
+public class GLDualLib {
+
+ static {
+ System.loadLibrary("gldualjni");
+ }
+
+ /**
+ * @param width the current view width
+ * @param height the current view height
+ */
+ public static native void init(int width, int height);
+ public static native void step();
+}
diff --git a/opengl/tests/gldual/src/com/android/gldual/TriangleRenderer.java b/opengl/tests/gldual/src/com/android/gldual/TriangleRenderer.java
new file mode 100644
index 0000000..098c4d2
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/TriangleRenderer.java
@@ -0,0 +1,149 @@
+package com.android.gldual;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLSurfaceView;
+import android.opengl.GLU;
+import android.os.SystemClock;
+
+public class TriangleRenderer implements GLSurfaceView.Renderer{
+
+ public TriangleRenderer() {
+ mTriangle = new Triangle();
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ /*
+ * By default, OpenGL enables features that improve quality
+ * but reduce performance. One might want to tweak that
+ * especially on software renderer.
+ */
+ gl.glDisable(GL10.GL_DITHER);
+
+ /*
+ * Some one-time OpenGL initialization can be made here
+ * probably based on features of this particular context
+ */
+ gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+ GL10.GL_FASTEST);
+
+ gl.glClearColor(.5f, .5f, .5f, 1);
+ gl.glShadeModel(GL10.GL_SMOOTH);
+ }
+
+ public void onDrawFrame(GL10 gl) {
+ /*
+ * By default, OpenGL enables features that improve quality
+ * but reduce performance. One might want to tweak that
+ * especially on software renderer.
+ */
+ gl.glDisable(GL10.GL_DITHER);
+
+ /*
+ * Usually, the first thing one might want to do is to clear
+ * the screen. The most efficient way of doing this is to use
+ * glClear().
+ */
+
+ gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+ /*
+ * Now we're ready to draw some 3D objects
+ */
+
+ gl.glMatrixMode(GL10.GL_MODELVIEW);
+ gl.glLoadIdentity();
+
+ GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+
+ long time = SystemClock.uptimeMillis() % 4000L;
+ float angle = 0.090f * ((int) time);
+
+ gl.glRotatef(angle, 0, 0, 1.0f);
+
+ mTriangle.draw(gl);
+ }
+
+ public void onSurfaceChanged(GL10 gl, int w, int h) {
+ gl.glViewport(0, 0, w, h);
+
+ /*
+ * Set our projection matrix. This doesn't have to be done
+ * each time we draw, but usually a new projection needs to
+ * be set when the viewport is resized.
+ */
+
+ float ratio = (float) w / h;
+ gl.glMatrixMode(GL10.GL_PROJECTION);
+ gl.glLoadIdentity();
+ gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
+
+ }
+
+ private Triangle mTriangle;
+}
+
+class Triangle {
+ public Triangle() {
+
+ // Buffers to be passed to gl*Pointer() functions
+ // must be direct, i.e., they must be placed on the
+ // native heap where the garbage collector cannot
+ // move them.
+ //
+ // Buffers with multi-byte datatypes (e.g., short, int, float)
+ // must have their byte order set to native order
+
+ ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
+ vbb.order(ByteOrder.nativeOrder());
+ mFVertexBuffer = vbb.asFloatBuffer();
+
+ ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4);
+ tbb.order(ByteOrder.nativeOrder());
+
+ ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
+ ibb.order(ByteOrder.nativeOrder());
+ mIndexBuffer = ibb.asShortBuffer();
+
+ // A unit-sided equalateral triangle centered on the origin.
+ float[] coords = {
+ // X, Y, Z
+ -0.5f, -0.25f, 0,
+ 0.5f, -0.25f, 0,
+ 0.0f, 0.559016994f, 0
+ };
+
+ for (int i = 0; i < VERTS; i++) {
+ for(int j = 0; j < 3; j++) {
+ mFVertexBuffer.put(coords[i*3+j] * 2.0f);
+ }
+ }
+
+ for(int i = 0; i < VERTS; i++) {
+ mIndexBuffer.put((short) i);
+ }
+
+ mFVertexBuffer.position(0);
+ mIndexBuffer.position(0);
+ }
+
+ public void draw(GL10 gl) {
+ gl.glFrontFace(GL10.GL_CCW);
+ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
+ gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, VERTS,
+ GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
+ }
+
+ private final static int VERTS = 3;
+
+ private FloatBuffer mFVertexBuffer;
+ private ShortBuffer mIndexBuffer;
+}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 78215b0..a91635e 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -572,6 +572,8 @@
// javadoc from interface
public int stopUsingNetworkFeature(int networkType, String feature) {
+ enforceChangePermission();
+
int pid = getCallingPid();
int uid = getCallingUid();
@@ -611,7 +613,7 @@
Log.d(TAG, "stopUsingNetworkFeature for net " + networkType +
": " + feature);
}
- enforceChangePermission();
+
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
return -1;
}
diff --git a/services/java/com/android/server/DropBoxService.java b/services/java/com/android/server/DropBoxService.java
new file mode 100644
index 0000000..f4e5ebc
--- /dev/null
+++ b/services/java/com/android/server/DropBoxService.java
@@ -0,0 +1,695 @@
+/*
+ * 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.server;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.DropBox;
+import android.os.ParcelFileDescriptor;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxService;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Implementation of {@link IDropBoxService} using the filesystem.
+ * Clients use {@link DropBox} to access this service.
+ *
+ * {@hide}
+ */
+public final class DropBoxService extends IDropBoxService.Stub {
+ private static final String TAG = "DropBoxService";
+ private static final int DEFAULT_RESERVE_PERCENT = 10;
+ private static final int DEFAULT_QUOTA_PERCENT = 10;
+ private static final int DEFAULT_QUOTA_KB = 5 * 1024;
+ private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
+ private static final int QUOTA_RESCAN_MILLIS = 5000;
+
+ // TODO: This implementation currently uses one file per entry, which is
+ // inefficient for smallish entries -- consider using a single queue file
+ // per tag (or even globally) instead.
+
+ // The cached context and derived objects
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final File mDropBoxDir;
+
+ // Accounting of all currently written log files (set in init()).
+
+ private FileList mAllFiles = null;
+ private HashMap<String, FileList> mFilesByTag = null;
+
+ // Various bits of disk information
+
+ private StatFs mStatFs = null;
+ private int mBlockSize = 0;
+ private int mCachedQuotaBlocks = 0; // Space we can use: computed from free space, etc.
+ private long mCachedQuotaUptimeMillis = 0;
+
+ // Ensure that all log entries have a unique timestamp
+ private long mLastTimestamp = 0;
+
+ /** Receives events that might indicate a need to clean up files. */
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mCachedQuotaUptimeMillis = 0; // Force a re-check of quota size
+ try {
+ init();
+ trimToFit();
+ } catch (IOException e) {
+ Log.e(TAG, "Can't init", e);
+ }
+ }
+ };
+
+ /**
+ * Creates an instance of managed drop box storage. Normally there is one of these
+ * run by the system, but others can be created for testing and other purposes.
+ *
+ * @param context to use for receiving free space & gservices intents
+ * @param path to store drop box entries in
+ */
+ public DropBoxService(Context context, File path) {
+ mDropBoxDir = path;
+
+ // Set up intent receivers
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ context.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+ context.registerReceiver(mReceiver, new IntentFilter(Settings.Gservices.CHANGED_ACTION));
+
+ // The real work gets done lazily in init() -- that way service creation always
+ // succeeds, and things like disk problems cause individual method failures.
+ }
+
+ /** Unregisters broadcast receivers and any other hooks -- for test instances */
+ public void stop() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ public void add(DropBox.Entry entry) {
+ File temp = null;
+ OutputStream output = null;
+ final String tag = entry.getTag();
+ try {
+ int flags = entry.getFlags();
+ if ((flags & DropBox.IS_EMPTY) != 0) throw new IllegalArgumentException();
+
+ init();
+ if (!isTagEnabled(tag)) return;
+ long max = trimToFit();
+ long lastTrim = System.currentTimeMillis();
+
+ byte[] buffer = new byte[mBlockSize];
+ InputStream input = entry.getInputStream();
+
+ // First, accumulate up to one block worth of data in memory before
+ // deciding whether to compress the data or not.
+
+ int read = 0;
+ while (read < buffer.length) {
+ int n = input.read(buffer, read, buffer.length - read);
+ if (n <= 0) break;
+ read += n;
+ }
+
+ // If we have at least one block, compress it -- otherwise, just write
+ // the data in uncompressed form.
+
+ temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
+ output = new FileOutputStream(temp);
+ if (read == buffer.length && ((flags & DropBox.IS_GZIPPED) == 0)) {
+ output = new GZIPOutputStream(output);
+ flags = flags | DropBox.IS_GZIPPED;
+ }
+
+ do {
+ output.write(buffer, 0, read);
+
+ long now = System.currentTimeMillis();
+ if (now - lastTrim > 30 * 1000) {
+ max = trimToFit(); // In case data dribbles in slowly
+ lastTrim = now;
+ }
+
+ read = input.read(buffer);
+ if (read <= 0) {
+ output.close(); // Get a final size measurement
+ output = null;
+ } else {
+ output.flush(); // So the size measurement is pseudo-reasonable
+ }
+
+ long len = temp.length();
+ if (len > max) {
+ Log.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)");
+ temp.delete();
+ temp = null; // Pass temp = null to createEntry() to leave a tombstone
+ break;
+ }
+ } while (read > 0);
+
+ createEntry(temp, tag, flags);
+ temp = null;
+ } catch (IOException e) {
+ Log.e(TAG, "Can't write: " + tag, e);
+ } finally {
+ try { if (output != null) output.close(); } catch (IOException e) {}
+ entry.close();
+ if (temp != null) temp.delete();
+ }
+ }
+
+ public boolean isTagEnabled(String tag) {
+ return !"disabled".equals(Settings.Gservices.getString(
+ mContentResolver, Settings.Gservices.DROPBOX_TAG_PREFIX + tag));
+ }
+
+ public synchronized DropBox.Entry getNextEntry(String tag, long millis) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.READ_LOGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("READ_LOGS permission required");
+ }
+
+ try {
+ init();
+ } catch (IOException e) {
+ Log.e(TAG, "Can't init", e);
+ return null;
+ }
+
+ FileList list = tag == null ? mAllFiles : mFilesByTag.get(tag);
+ if (list == null) return null;
+
+ for (EntryFile entry : list.contents.tailSet(new EntryFile(millis + 1))) {
+ if (entry.tag == null) continue;
+ if ((entry.flags & DropBox.IS_EMPTY) != 0) {
+ return new DropBox.Entry(entry.tag, entry.timestampMillis);
+ }
+ try {
+ return new DropBox.Entry(entry.tag, entry.timestampMillis, entry.file, entry.flags);
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read: " + entry.file, e);
+ // Continue to next file
+ }
+ }
+
+ return null;
+ }
+
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: Can't dump DropBoxService");
+ return;
+ }
+
+ try {
+ init();
+ } catch (IOException e) {
+ pw.println("Can't initialize: " + e);
+ Log.e(TAG, "Can't init", e);
+ return;
+ }
+
+ boolean doPrint = false, doFile = false;
+ ArrayList<String> searchArgs = new ArrayList<String>();
+ for (int i = 0; args != null && i < args.length; i++) {
+ if (args[i].equals("-p") || args[i].equals("--print")) {
+ doPrint = true;
+ } else if (args[i].equals("-f") || args[i].equals("--file")) {
+ doFile = true;
+ } else if (args[i].startsWith("-")) {
+ pw.print("Unknown argument: ");
+ pw.println(args[i]);
+ } else {
+ searchArgs.add(args[i]);
+ }
+ }
+
+ pw.format("Drop box contents: %d entries", mAllFiles.contents.size());
+ pw.println();
+
+ if (!searchArgs.isEmpty()) {
+ pw.print("Searching for:");
+ for (String a : searchArgs) pw.format(" %s", a);
+ pw.println();
+ }
+
+ int numFound = 0;
+ pw.println();
+ for (EntryFile entry : mAllFiles.contents) {
+ String date = new Formatter().format("%s.%03d",
+ DateFormat.format("yyyy-MM-dd kk:mm:ss", entry.timestampMillis),
+ entry.timestampMillis % 1000).toString();
+
+ boolean match = true;
+ for (String a: searchArgs) match = match && (date.contains(a) || a.equals(entry.tag));
+ if (!match) continue;
+
+ numFound++;
+ pw.print(date);
+ pw.print(" ");
+ pw.print(entry.tag == null ? "(no tag)" : entry.tag);
+ if (entry.file == null) {
+ pw.println(" (no file)");
+ continue;
+ } else if ((entry.flags & DropBox.IS_EMPTY) != 0) {
+ pw.println(" (contents lost)");
+ continue;
+ } else {
+ pw.print((entry.flags & DropBox.IS_GZIPPED) != 0 ? " (comopressed " : " (");
+ pw.print((entry.flags & DropBox.IS_TEXT) != 0 ? "text" : "data");
+ pw.format(", %d bytes)", entry.file.length());
+ pw.println();
+ }
+
+ if (doFile || (doPrint && (entry.flags & DropBox.IS_TEXT) == 0)) {
+ if (!doPrint) pw.print(" ");
+ pw.println(entry.file.getPath());
+ }
+
+ if ((entry.flags & DropBox.IS_TEXT) != 0 && (doPrint || !doFile)) {
+ DropBox.Entry dbe = null;
+ try {
+ dbe = new DropBox.Entry(
+ entry.tag, entry.timestampMillis, entry.file, entry.flags);
+
+ if (doPrint) {
+ InputStreamReader r = new InputStreamReader(dbe.getInputStream());
+ char[] buf = new char[4096];
+ boolean newline = false;
+ for (;;) {
+ int n = r.read(buf);
+ if (n <= 0) break;
+ pw.write(buf, 0, n);
+ newline = (buf[n - 1] == '\n');
+ }
+ if (!newline) pw.println();
+ } else {
+ String text = dbe.getText(70);
+ boolean truncated = (text.length() == 70);
+ pw.print(" ");
+ pw.print(text.trim().replace('\n', '/'));
+ if (truncated) pw.print(" ...");
+ pw.println();
+ }
+ } catch (IOException e) {
+ pw.print("*** ");
+ pw.println(e.toString());
+ Log.e(TAG, "Can't read: " + entry.file, e);
+ } finally {
+ if (dbe != null) dbe.close();
+ }
+ }
+
+ if (doPrint) pw.println();
+ }
+
+ if (numFound == 0) pw.println("(No entries found.)");
+
+ if (args == null || args.length == 0) {
+ if (!doPrint) pw.println();
+ pw.println("Usage: dumpsys dropbox [--print|--file] [YYYY-mm-dd] [HH:MM:SS.SSS] [tag]");
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ /** Chronologically sorted list of {@link #EntryFile} */
+ private static final class FileList implements Comparable<FileList> {
+ public int blocks = 0;
+ public final TreeSet<EntryFile> contents = new TreeSet<EntryFile>();
+
+ /** Sorts bigger FileList instances before smaller ones. */
+ public final int compareTo(FileList o) {
+ if (blocks != o.blocks) return o.blocks - blocks;
+ if (this == o) return 0;
+ if (hashCode() < o.hashCode()) return -1;
+ if (hashCode() > o.hashCode()) return 1;
+ return 0;
+ }
+ }
+
+ /** Metadata describing an on-disk log file. */
+ private static final class EntryFile implements Comparable<EntryFile> {
+ public final String tag;
+ public final long timestampMillis;
+ public final int flags;
+ public final File file;
+ public final int blocks;
+
+ /** Sorts earlier EntryFile instances before later ones. */
+ public final int compareTo(EntryFile o) {
+ if (timestampMillis < o.timestampMillis) return -1;
+ if (timestampMillis > o.timestampMillis) return 1;
+ if (file != null && o.file != null) return file.compareTo(o.file);
+ if (o.file != null) return -1;
+ if (file != null) return 1;
+ if (this == o) return 0;
+ if (hashCode() < o.hashCode()) return -1;
+ if (hashCode() > o.hashCode()) return 1;
+ return 0;
+ }
+
+ /**
+ * Moves an existing temporary file to a new log filename.
+ * @param temp file to rename
+ * @param dir to store file in
+ * @param tag to use for new log file name
+ * @param timestampMillis of log entry
+ * @param flags for the entry data
+ * @param blockSize to use for space accounting
+ * @throws IOException if the file can't be moved
+ */
+ public EntryFile(File temp, File dir, String tag,long timestampMillis,
+ int flags, int blockSize) throws IOException {
+ if ((flags & DropBox.IS_EMPTY) != 0) throw new IllegalArgumentException();
+
+ this.tag = tag;
+ this.timestampMillis = timestampMillis;
+ this.flags = flags;
+ this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis +
+ ((flags & DropBox.IS_TEXT) != 0 ? ".txt" : ".dat") +
+ ((flags & DropBox.IS_GZIPPED) != 0 ? ".gz" : ""));
+
+ if (!temp.renameTo(this.file)) {
+ throw new IOException("Can't rename " + temp + " to " + this.file);
+ }
+ this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+ }
+
+ /**
+ * Creates a zero-length tombstone for a file whose contents were lost.
+ * @param dir to store file in
+ * @param tag to use for new log file name
+ * @param timestampMillis of log entry
+ * @throws IOException if the file can't be created.
+ */
+ public EntryFile(File dir, String tag, long timestampMillis) throws IOException {
+ this.tag = tag;
+ this.timestampMillis = timestampMillis;
+ this.flags = DropBox.IS_EMPTY;
+ this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ".lost");
+ this.blocks = 0;
+ new FileOutputStream(this.file).close();
+ }
+
+ /**
+ * Extracts metadata from an existing on-disk log filename.
+ * @param file name of existing log file
+ * @param blockSize to use for space accounting
+ */
+ public EntryFile(File file, int blockSize) {
+ this.file = file;
+ this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+
+ String name = file.getName();
+ int at = name.lastIndexOf('@');
+ if (at < 0) {
+ this.tag = null;
+ this.timestampMillis = 0;
+ this.flags = DropBox.IS_EMPTY;
+ return;
+ }
+
+ int flags = 0;
+ this.tag = Uri.decode(name.substring(0, at));
+ if (name.endsWith(".gz")) {
+ flags |= DropBox.IS_GZIPPED;
+ name = name.substring(0, name.length() - 3);
+ }
+ if (name.endsWith(".lost")) {
+ flags |= DropBox.IS_EMPTY;
+ name = name.substring(at + 1, name.length() - 5);
+ } else if (name.endsWith(".txt")) {
+ flags |= DropBox.IS_TEXT;
+ name = name.substring(at + 1, name.length() - 4);
+ } else if (name.endsWith(".dat")) {
+ name = name.substring(at + 1, name.length() - 4);
+ } else {
+ this.flags = DropBox.IS_EMPTY;
+ this.timestampMillis = 0;
+ return;
+ }
+ this.flags = flags;
+
+ long millis;
+ try { millis = Long.valueOf(name); } catch (NumberFormatException e) { millis = 0; }
+ this.timestampMillis = millis;
+ }
+
+ /**
+ * Creates a EntryFile object with only a timestamp for comparison purposes.
+ * @param timestampMillis to compare with.
+ */
+ public EntryFile(long millis) {
+ this.tag = null;
+ this.timestampMillis = millis;
+ this.flags = DropBox.IS_EMPTY;
+ this.file = null;
+ this.blocks = 0;
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ /** If never run before, scans disk contents to build in-memory tracking data. */
+ private synchronized void init() throws IOException {
+ if (mStatFs == null) {
+ if (!mDropBoxDir.isDirectory() && !mDropBoxDir.mkdirs()) {
+ throw new IOException("Can't mkdir: " + mDropBoxDir);
+ }
+ try {
+ mStatFs = new StatFs(mDropBoxDir.getPath());
+ mBlockSize = mStatFs.getBlockSize();
+ } catch (IllegalArgumentException e) { // StatFs throws this on error
+ throw new IOException("Can't statfs: " + mDropBoxDir);
+ }
+ }
+
+ if (mAllFiles == null) {
+ File[] files = mDropBoxDir.listFiles();
+ if (files == null) throw new IOException("Can't list files: " + mDropBoxDir);
+
+ mAllFiles = new FileList();
+ mFilesByTag = new HashMap<String, FileList>();
+
+ // Scan pre-existing files.
+ for (File file : files) {
+ if (file.getName().endsWith(".tmp")) {
+ Log.i(TAG, "Cleaning temp file: " + file);
+ file.delete();
+ continue;
+ }
+
+ EntryFile entry = new EntryFile(file, mBlockSize);
+ if (entry.tag == null) {
+ Log.w(TAG, "Unrecognized file: " + file);
+ continue;
+ } else if (entry.timestampMillis == 0) {
+ Log.w(TAG, "Invalid filename: " + file);
+ file.delete();
+ continue;
+ }
+
+ enrollEntry(entry);
+ }
+ }
+ }
+
+ /** Adds a disk log file to in-memory tracking for accounting and enumeration. */
+ private synchronized void enrollEntry(EntryFile entry) {
+ mAllFiles.contents.add(entry);
+ mAllFiles.blocks += entry.blocks;
+
+ // mFilesByTag is used for trimming, so don't list empty files.
+ // (Zero-length/lost files are trimmed by date from mAllFiles.)
+
+ if (entry.tag != null && entry.file != null && entry.blocks > 0) {
+ FileList tagFiles = mFilesByTag.get(entry.tag);
+ if (tagFiles == null) {
+ tagFiles = new FileList();
+ mFilesByTag.put(entry.tag, tagFiles);
+ }
+ tagFiles.contents.add(entry);
+ tagFiles.blocks += entry.blocks;
+ }
+ }
+
+ /** Moves a temporary file to a final log filename and enrolls it. */
+ private synchronized void createEntry(File temp, String tag, int flags) throws IOException {
+ long t = System.currentTimeMillis();
+
+ // Require each entry to have a unique timestamp; if there are entries
+ // >10sec in the future (due to clock skew), drag them back to avoid
+ // keeping them around forever.
+
+ SortedSet<EntryFile> tail = mAllFiles.contents.tailSet(new EntryFile(t + 10000));
+ EntryFile[] future = null;
+ if (!tail.isEmpty()) {
+ future = tail.toArray(new EntryFile[tail.size()]);
+ tail.clear(); // Remove from mAllFiles
+ }
+
+ if (!mAllFiles.contents.isEmpty()) {
+ t = Math.max(t, mAllFiles.contents.last().timestampMillis + 1);
+ }
+
+ if (future != null) {
+ for (EntryFile late : future) {
+ mAllFiles.blocks -= late.blocks;
+ FileList tagFiles = mFilesByTag.get(late.tag);
+ if (tagFiles.contents.remove(late)) tagFiles.blocks -= late.blocks;
+ if ((late.flags & DropBox.IS_EMPTY) == 0) {
+ enrollEntry(new EntryFile(
+ late.file, mDropBoxDir, late.tag, t++, late.flags, mBlockSize));
+ } else {
+ enrollEntry(new EntryFile(mDropBoxDir, late.tag, t++));
+ }
+ }
+ }
+
+ if (temp == null) {
+ enrollEntry(new EntryFile(mDropBoxDir, tag, t));
+ } else {
+ enrollEntry(new EntryFile(temp, mDropBoxDir, tag, t, flags, mBlockSize));
+ }
+ }
+
+ /**
+ * Trims the files on disk to make sure they aren't using too much space.
+ * @return the overall quota for storage (in bytes)
+ */
+ private synchronized long trimToFit() {
+ // Expunge aged items (including tombstones marking deleted data).
+
+ int ageSeconds = Settings.Gservices.getInt(mContentResolver,
+ Settings.Gservices.DROPBOX_AGE_SECONDS, DEFAULT_AGE_SECONDS);
+ long cutoffMillis = System.currentTimeMillis() - ageSeconds * 1000;
+ while (!mAllFiles.contents.isEmpty()) {
+ EntryFile entry = mAllFiles.contents.first();
+ if (entry.timestampMillis > cutoffMillis) break;
+
+ FileList tag = mFilesByTag.get(entry.tag);
+ if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks;
+ if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
+ if (entry.file != null) entry.file.delete();
+ }
+
+ // Compute overall quota (a fraction of available free space) in blocks.
+ // The quota changes dynamically based on the amount of free space;
+ // that way when lots of data is available we can use it, but we'll get
+ // out of the way if storage starts getting tight.
+
+ long uptimeMillis = SystemClock.uptimeMillis();
+ if (uptimeMillis > mCachedQuotaUptimeMillis + QUOTA_RESCAN_MILLIS) {
+ int quotaPercent = Settings.Gservices.getInt(mContentResolver,
+ Settings.Gservices.DROPBOX_QUOTA_PERCENT, DEFAULT_QUOTA_PERCENT);
+ int reservePercent = Settings.Gservices.getInt(mContentResolver,
+ Settings.Gservices.DROPBOX_RESERVE_PERCENT, DEFAULT_RESERVE_PERCENT);
+ int quotaKb = Settings.Gservices.getInt(mContentResolver,
+ Settings.Gservices.DROPBOX_QUOTA_KB, DEFAULT_QUOTA_KB);
+
+ mStatFs.restat(mDropBoxDir.getPath());
+ int available = mStatFs.getAvailableBlocks();
+ int nonreserved = available - mStatFs.getBlockCount() * reservePercent / 100;
+ int maximum = quotaKb * 1024 / mBlockSize;
+ mCachedQuotaBlocks = Math.min(maximum, Math.max(0, nonreserved * quotaPercent / 100));
+ mCachedQuotaUptimeMillis = uptimeMillis;
+ }
+
+ // If we're using too much space, delete old items to make room.
+ //
+ // We trim each tag independently (this is why we keep per-tag lists).
+ // Space is "fairly" shared between tags -- they are all squeezed
+ // equally until enough space is reclaimed.
+ //
+ // A single circular buffer (a la logcat) would be simpler, but this
+ // way we can handle fat/bursty data (like 1MB+ bugreports, 300KB+
+ // kernel crash dumps, and 100KB+ ANR reports) without swamping small,
+ // well-behaved data // streams (event statistics, profile data, etc).
+ //
+ // Deleted files are replaced with zero-length tombstones to mark what
+ // was lost. Tombstones are expunged by age (see above).
+
+ if (mAllFiles.blocks > mCachedQuotaBlocks) {
+ Log.i(TAG, "Usage (" + mAllFiles.blocks + ") > Quota (" + mCachedQuotaBlocks + ")");
+
+ // Find a fair share amount of space to limit each tag
+ int unsqueezed = mAllFiles.blocks, squeezed = 0;
+ TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values());
+ for (FileList tag : tags) {
+ if (squeezed > 0 && tag.blocks <= (mCachedQuotaBlocks - unsqueezed) / squeezed) {
+ break;
+ }
+ unsqueezed -= tag.blocks;
+ squeezed++;
+ }
+ int tagQuota = (mCachedQuotaBlocks - unsqueezed) / squeezed;
+
+ // Remove old items from each tag until it meets the per-tag quota.
+ for (FileList tag : tags) {
+ if (mAllFiles.blocks < mCachedQuotaBlocks) break;
+ while (tag.blocks > tagQuota && !tag.contents.isEmpty()) {
+ EntryFile entry = tag.contents.first();
+ if (tag.contents.remove(entry)) tag.blocks -= entry.blocks;
+ if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
+
+ try {
+ if (entry.file != null) entry.file.delete();
+ enrollEntry(new EntryFile(mDropBoxDir, entry.tag, entry.timestampMillis));
+ } catch (IOException e) {
+ Log.e(TAG, "Can't write tombstone file", e);
+ }
+ }
+ }
+ }
+
+ return mCachedQuotaBlocks * mBlockSize;
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b8cf844..5f30710 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -43,6 +43,7 @@
import android.util.Log;
import android.accounts.AccountManagerService;
+import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
@@ -294,6 +295,14 @@
}
try {
+ Log.i(TAG, "DropBox Service");
+ ServiceManager.addService(Context.DROPBOX_SERVICE,
+ new DropBoxService(context, new File("/data/system/dropbox")));
+ } catch (Throwable e) {
+ Log.e(TAG, "Failure starting DropBox Service", e);
+ }
+
+ try {
Log.i(TAG, "Checkin Service");
Intent intent = new Intent().setComponent(new ComponentName(
"com.google.android.server.checkin",
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 32ad6c6..0ea832b 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -142,6 +142,7 @@
private static final int MESSAGE_STOP_WIFI = 2;
private static final int MESSAGE_START_WIFI = 3;
private static final int MESSAGE_RELEASE_WAKELOCK = 4;
+ private static final int MESSAGE_UPDATE_STATE = 5;
private final WifiHandler mWifiHandler;
@@ -542,7 +543,7 @@
value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
if (!TextUtils.isEmpty(value)) {
- config.SSID = value;
+ config.SSID = removeDoubleQuotes(value);
} else {
config.SSID = null;
}
@@ -675,11 +676,21 @@
value = WifiNative.getNetworkVariableCommand(netId,
field.varName());
if (!TextUtils.isEmpty(value)) {
+ if (field != config.eap) value = removeDoubleQuotes(value);
field.setValue(value);
}
}
}
+ private static String removeDoubleQuotes(String string) {
+ if (string.length() <= 2) return "";
+ return string.substring(1, string.length() - 1);
+ }
+
+ private static String convertToQuotedString(String string) {
+ return "\"" + string + "\"";
+ }
+
/**
* see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
* @return the supplicant-assigned identifier for the new or updated
@@ -731,7 +742,7 @@
!WifiNative.setNetworkVariableCommand(
netId,
WifiConfiguration.ssidVarName,
- config.SSID)) {
+ convertToQuotedString(config.SSID))) {
if (DBG) {
Log.d(TAG, "failed to set SSID: "+config.SSID);
}
@@ -894,18 +905,22 @@
: config.enterpriseFields) {
String varName = field.varName();
String value = field.value();
- if ((value != null) && !WifiNative.setNetworkVariableCommand(
- netId,
- varName,
- value)) {
- if (DBG) {
- Log.d(TAG, config.SSID + ": failed to set " + varName +
- ": " + value);
+ if (value != null) {
+ if (field != config.eap) {
+ value = convertToQuotedString(value);
}
- break setVariables;
+ if (!WifiNative.setNetworkVariableCommand(
+ netId,
+ varName,
+ value)) {
+ if (DBG) {
+ Log.d(TAG, config.SSID + ": failed to set " + varName +
+ ": " + value);
+ }
+ break setVariables;
+ }
}
}
-
return netId;
}
@@ -1432,6 +1447,11 @@
}
private void updateWifiState() {
+ // send a message so it's all serialized
+ Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget();
+ }
+
+ private void doUpdateWifiState() {
boolean wifiEnabled = getPersistedWifiEnabled();
boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden;
boolean lockHeld = mLocks.hasLocks();
@@ -1537,6 +1557,10 @@
sWakeLock.release();
break;
+ case MESSAGE_UPDATE_STATE:
+ doUpdateWifiState();
+ break;
+
case MESSAGE_DISABLE_WIFI:
// a non-zero msg.arg1 value means the "enabled" setting
// should be persisted
@@ -1815,6 +1839,19 @@
}
}
+ public void initializeMulticastFiltering() {
+ enforceMulticastChangePermission();
+
+ synchronized (mMulticasters) {
+ // if anybody had requested filters be off, leave off
+ if (mMulticasters.size() != 0) {
+ return;
+ } else {
+ WifiNative.startPacketFiltering();
+ }
+ }
+ }
+
public void acquireMulticastLock(IBinder binder, String tag) {
enforceMulticastChangePermission();
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 9acc97a..c92039a 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -9303,15 +9303,6 @@
& WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
wallpaperMayChange = true;
}
- if (changed && !forceHiding
- && (mCurrentFocus == null)
- && (mFocusedApp != null)) {
- // It's possible that the last focus recalculation left no
- // current focused window even though the app has come to the
- // foreground already. In this case, we make sure to recalculate
- // focus when we show a window.
- focusMayChange = true;
- }
}
mPolicy.animatingWindowLw(w, attrs);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 1aa1c76..586f63f 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -207,6 +207,42 @@
}
/**
+ * Extracts the network address portion and canonicalize.
+ *
+ * This function is equivalent to extractNetworkPortion(), except
+ * for allowing the PLUS character to occur at arbitrary positions
+ * in the address portion, not just the first position.
+ *
+ * @hide
+ */
+ public static String extractNetworkPortionAlt(String phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
+
+ int len = phoneNumber.length();
+ StringBuilder ret = new StringBuilder(len);
+ boolean haveSeenPlus = false;
+
+ for (int i = 0; i < len; i++) {
+ char c = phoneNumber.charAt(i);
+ if (c == '+') {
+ if (haveSeenPlus) {
+ continue;
+ }
+ haveSeenPlus = true;
+ }
+ if (isDialable(c)) {
+ ret.append(c);
+ } else if (isStartsPostDial (c)) {
+ break;
+ }
+ }
+
+ return ret.toString();
+ }
+
+ /**
* Strips separators from a phone number string.
* @param phoneNumber phone number to strip.
* @return phone string stripped of separators.
@@ -342,6 +378,8 @@
compareLoosely(String a, String b) {
int ia, ib;
int matched;
+ int numNonDialableCharsInA = 0;
+ int numNonDialableCharsInB = 0;
if (a == null || b == null) return a == b;
@@ -362,6 +400,7 @@
if (!isDialable(ca)) {
ia--;
skipCmp = true;
+ numNonDialableCharsInA++;
}
cb = b.charAt(ib);
@@ -369,6 +408,7 @@
if (!isDialable(cb)) {
ib--;
skipCmp = true;
+ numNonDialableCharsInB++;
}
if (!skipCmp) {
@@ -380,13 +420,16 @@
}
if (matched < MIN_MATCH) {
- int aLen = a.length();
+ int effectiveALen = a.length() - numNonDialableCharsInA;
+ int effectiveBLen = b.length() - numNonDialableCharsInB;
- // if the input strings match, but their lengths < MIN_MATCH,
- // treat them as equal.
- if (aLen == b.length() && aLen == matched) {
+
+ // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH,
+ // treat them as equal (i.e. 404-04 and 40404)
+ if (effectiveALen == effectiveBLen && effectiveALen == matched) {
return true;
}
+
return false;
}
@@ -590,7 +633,7 @@
*/
public static String
toCallerIDMinMatch(String phoneNumber) {
- String np = extractNetworkPortion(phoneNumber);
+ String np = extractNetworkPortionAlt(phoneNumber);
return internalGetStrippedReversed(np, MIN_MATCH);
}
@@ -603,7 +646,7 @@
*/
public static String
getStrippedReversed(String phoneNumber) {
- String np = extractNetworkPortion(phoneNumber);
+ String np = extractNetworkPortionAlt(phoneNumber);
if (np == null) return null;
@@ -1017,8 +1060,8 @@
* Breaks the given number down and formats it according to the rules
* for the country the number is from.
*
- * @param source the phone number to format
- * @return a locally acceptable formatting of the input, or the raw input if
+ * @param source The phone number to format
+ * @return A locally acceptable formatting of the input, or the raw input if
* formatting rules aren't known for the number
*/
public static String formatNumber(String source) {
@@ -1028,10 +1071,27 @@
}
/**
+ * Formats the given number with the given formatting type. Currently
+ * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type.
+ *
+ * @param source the phone number to format
+ * @param defaultFormattingType The default formatting rules to apply if the number does
+ * not begin with +<country_code>
+ * @return The phone number formatted with the given formatting type.
+ *
+ * @hide TODO:Shuold be unhidden.
+ */
+ public static String formatNumber(String source, int defaultFormattingType) {
+ SpannableStringBuilder text = new SpannableStringBuilder(source);
+ formatNumber(text, defaultFormattingType);
+ return text.toString();
+ }
+
+ /**
* Returns the phone number formatting type for the given locale.
*
* @param locale The locale of interest, usually {@link Locale#getDefault()}
- * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
+ * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
* rules are not known for the given locale
*/
public static int getFormatTypeForLocale(Locale locale) {
@@ -1041,7 +1101,8 @@
}
/**
- * Formats a phone number in-place. Currently only supports NANP formatting.
+ * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP}
+ * is supported as a second argument.
*
* @param text The number to be formatted, will be modified with the formatting
* @param defaultFormattingType The default formatting rules to apply if the number does
@@ -1247,7 +1308,7 @@
// Strip the separators from the number before comparing it
// to the list.
- number = extractNetworkPortion(number);
+ number = extractNetworkPortionAlt(number);
// retrieve the list of emergency numbers
String numbers = SystemProperties.get("ro.ril.ecclist");
@@ -1290,7 +1351,7 @@
// Strip the separators from the number before comparing it
// to the list.
- number = extractNetworkPortion(number);
+ number = extractNetworkPortionAlt(number);
// compare tolerates null so we need to make sure that we
// don't return true when both are null.
diff --git a/telephony/java/com/android/internal/telephony/DriverCall.java b/telephony/java/com/android/internal/telephony/DriverCall.java
index 0d9a60f..66f6b9c 100644
--- a/telephony/java/com/android/internal/telephony/DriverCall.java
+++ b/telephony/java/com/android/internal/telephony/DriverCall.java
@@ -73,8 +73,7 @@
if (p.hasMore()) {
// Some lame implementations return strings
// like "NOT AVAILABLE" in the CLCC line
- ret.number = PhoneNumberUtils.extractNetworkPortion(
- p.nextString());
+ ret.number = PhoneNumberUtils.extractNetworkPortionAlt(p.nextString());
if (ret.number.length() == 0) {
ret.number = null;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 3548cad..04a03b2 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -261,12 +261,6 @@
}
public ServiceState getServiceState() {
- int roamInd = mSST.ss.getCdmaRoamingIndicator();
- int defRoamInd = mSST.ss.getCdmaDefaultRoamingIndicator();
-
- mSST.ss.setCdmaEriIconIndex(mEriManager.getCdmaEriIconIndex(roamInd, defRoamInd));
- mSST.ss.setCdmaEriIconMode(mEriManager.getCdmaEriIconMode(roamInd, defRoamInd));
-
return mSST.ss;
}
@@ -808,10 +802,6 @@
}
void notifyServiceStateChanged(ServiceState ss) {
- // TODO this seems really inefficient. Can't we calc this when the fundamentals change and store in the
- // service state?
- ss.setCdmaEriIconIndex(this.getCdmaEriIconIndex());
- ss.setCdmaEriIconMode(this.getCdmaEriIconMode());
super.notifyServiceStateChangedP(ss);
}
@@ -1341,7 +1331,7 @@
@Override
public boolean isOtaSpNumber(String dialStr){
boolean isOtaSpNum = false;
- String dialableStr = PhoneNumberUtils.extractNetworkPortion(dialStr);
+ String dialableStr = PhoneNumberUtils.extractNetworkPortionAlt(dialStr);
if (dialableStr != null) {
isOtaSpNum = isIs683OtaSpDialStr(dialableStr);
if (isOtaSpNum == false) {
@@ -1354,9 +1344,7 @@
@Override
public int getCdmaEriIconIndex() {
- int roamInd = getServiceState().getCdmaRoamingIndicator();
- int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator();
- return mEriManager.getCdmaEriIconIndex(roamInd, defRoamInd);
+ return getServiceState().getCdmaEriIconIndex();
}
/**
@@ -1366,9 +1354,7 @@
*/
@Override
public int getCdmaEriIconMode() {
- int roamInd = getServiceState().getCdmaRoamingIndicator();
- int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator();
- return mEriManager.getCdmaEriIconMode(roamInd, defRoamInd);
+ return getServiceState().getCdmaEriIconMode();
}
/**
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java b/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java
index f637d33..08946d2 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java
@@ -154,7 +154,7 @@
dialString = formatDialString(dialString);
Log.d(LOG_TAG, "[CDMAConn] CdmaConnection:formated dialString=" + dialString);
- this.address = PhoneNumberUtils.extractNetworkPortion(dialString);
+ this.address = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
index = -1;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 3051e2f..03acf5c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -811,6 +811,12 @@
}
}
+ int roamingIndicator = newSS.getCdmaRoamingIndicator();
+ newSS.setCdmaEriIconIndex(phone.mEriManager.getCdmaEriIconIndex(roamingIndicator,
+ mDefaultRoamingIndicator));
+ newSS.setCdmaEriIconMode(phone.mEriManager.getCdmaEriIconMode(roamingIndicator,
+ mDefaultRoamingIndicator));
+
// NOTE: Some operator may require overriding mCdmaRoaming
// (set by the modem), depending on the mRoamingIndicator.
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index 5614c12..56499a8 100755
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -729,7 +729,7 @@
}
// Only look at the Network portion for mmi
- String networkPortion = PhoneNumberUtils.extractNetworkPortion(newDialString);
+ String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this);
if (LOCAL_DEBUG) Log.d(LOG_TAG,
"dialing w/ mmi '" + mmi + "'...");
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java b/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java
index 445be39..4788a01 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java
@@ -144,7 +144,7 @@
this.dialString = dialString;
- this.address = PhoneNumberUtils.extractNetworkPortion(dialString);
+ this.address = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
index = -1;
diff --git a/test-runner/android/test/mock/MockCursor.java b/test-runner/android/test/mock/MockCursor.java
new file mode 100644
index 0000000..9b1c0ef
--- /dev/null
+++ b/test-runner/android/test/mock/MockCursor.java
@@ -0,0 +1,264 @@
+/*
+ * 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 android.test.mock;
+
+import android.content.ContentResolver;
+import android.database.CharArrayBuffer;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * <P>
+ * A mock {@link android.database.Cursor} class that isolates the test code from real
+ * Cursor implementation.
+ * </P>
+ * <P>
+ * All methods including ones related to querying the state of the cursor are
+ * are non-functional and throw {@link java.lang.UnsupportedOperationException}.
+ * </P>
+ */
+public class MockCursor implements Cursor {
+ public int getColumnCount() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public int getColumnIndex(String columnName) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public int getColumnIndexOrThrow(String columnName) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public String getColumnName(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public String[] getColumnNames() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public int getCount() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean isNull(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public int getInt(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public long getLong(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public short getShort(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public float getFloat(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public double getDouble(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public byte[] getBlob(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public String getString(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public Bundle getExtras() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public int getPosition() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean isAfterLast() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean isBeforeFirst() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean isFirst() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean isLast() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean move(int offset) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean moveToFirst() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean moveToLast() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean moveToNext() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean moveToPrevious() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean moveToPosition(int position) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public void deactivate() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public void close() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean isClosed() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean requery() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public void registerContentObserver(ContentObserver observer) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public void registerDataSetObserver(DataSetObserver observer) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public Bundle respond(Bundle extras) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public boolean getWantsAllOnMoveCalls() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean commitUpdates() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean hasUpdates() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public void setNotificationUri(ContentResolver cr, Uri uri) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean supportsUpdates() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean deleteRow() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public void unregisterContentObserver(ContentObserver observer) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateBlob(int columnIndex, byte[] value) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateDouble(int columnIndex, double value) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateFloat(int columnIndex, float value) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateInt(int columnIndex, int value) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateLong(int columnIndex, long value) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateShort(int columnIndex, short value) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateString(int columnIndex, String value) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean updateToNull(int columnIndex) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("deprecation")
+ public void abortUpdates() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/Android.mk b/tests/AndroidTests/Android.mk
index ced796a..757044f 100644
--- a/tests/AndroidTests/Android.mk
+++ b/tests/AndroidTests/Android.mk
@@ -3,7 +3,7 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner
+LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner services
LOCAL_STATIC_JAVA_LIBRARIES := googlelogin-client
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index d94327a..786178c 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -35,27 +35,27 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
- <uses-permission android:name="android.permission.READ_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_SETTINGS" />
- <uses-permission android:name="android.permission.READ_SMS"/>
- <uses-permission android:name="android.permission.WRITE_SMS"/>
- <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
<uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
+ <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.WRITE_GSERVICES" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
-
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_GSERVICES" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SMS"/>
+ <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
+ <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
- <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
+
<!-- InstrumentationTestRunner for AndroidTests -->
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.unit_tests"
diff --git a/tests/AndroidTests/res/raw/v21_org_before_title.vcf b/tests/AndroidTests/res/raw/v21_org_before_title.vcf
new file mode 100644
index 0000000..8ff1190
--- /dev/null
+++ b/tests/AndroidTests/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/tests/AndroidTests/res/raw/v21_pref_handling.vcf b/tests/AndroidTests/res/raw/v21_pref_handling.vcf
new file mode 100644
index 0000000..5105310
--- /dev/null
+++ b/tests/AndroidTests/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/tests/AndroidTests/res/raw/v21_title_before_org.vcf b/tests/AndroidTests/res/raw/v21_title_before_org.vcf
new file mode 100644
index 0000000..9fdc738
--- /dev/null
+++ b/tests/AndroidTests/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/tests/AndroidTests/res/raw/v30_comma_separated.vcf b/tests/AndroidTests/res/raw/v30_comma_separated.vcf
new file mode 100644
index 0000000..98a7f20
--- /dev/null
+++ b/tests/AndroidTests/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/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java b/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java
new file mode 100644
index 0000000..286f702
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java
@@ -0,0 +1,540 @@
+/*
+ * 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.unit_tests;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.DropBox;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+
+import com.android.server.DropBoxService;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.util.Random;
+import java.util.zip.GZIPOutputStream;
+
+/** Test {@link DropBox} functionality. */
+public class DropBoxTest extends AndroidTestCase {
+ public void tearDown() throws Exception {
+ Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+ override.putExtra(Settings.Gservices.DROPBOX_AGE_SECONDS, "");
+ override.putExtra(Settings.Gservices.DROPBOX_QUOTA_KB, "");
+ override.putExtra(Settings.Gservices.DROPBOX_TAG_PREFIX + "DropBoxTest", "");
+ waitForBroadcast(override);
+ }
+
+ public void testAddText() throws Exception {
+ DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+ long before = System.currentTimeMillis();
+ Thread.sleep(5);
+ dropbox.addText("DropBoxTest", "TEST0");
+ Thread.sleep(5);
+ long between = System.currentTimeMillis();
+ Thread.sleep(5);
+ dropbox.addText("DropBoxTest", "TEST1");
+ dropbox.addText("DropBoxTest", "TEST2");
+ Thread.sleep(5);
+ long after = System.currentTimeMillis();
+
+ DropBox.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+ DropBox.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+ DropBox.Entry e2 = dropbox.getNextEntry("DropBoxTest", e1.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest", e2.getTimeMillis()));
+
+ assertTrue(e0.getTimeMillis() > before);
+ assertTrue(e0.getTimeMillis() < between);
+ assertTrue(e1.getTimeMillis() > between);
+ assertTrue(e1.getTimeMillis() < e2.getTimeMillis());
+ assertTrue(e2.getTimeMillis() < after);
+
+ assertEquals("TEST0", e0.getText(80));
+ assertEquals("TEST1", e1.getText(80));
+ assertEquals("TES", e2.getText(3));
+
+ e0.close();
+ e1.close();
+ e2.close();
+ }
+
+ public void testAddData() throws Exception {
+ DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+ long before = System.currentTimeMillis();
+ dropbox.addData("DropBoxTest", "TEST".getBytes(), 0);
+ long after = System.currentTimeMillis();
+
+ DropBox.Entry e = dropbox.getNextEntry("DropBoxTest", before);
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest", e.getTimeMillis()));
+
+ assertEquals("DropBoxTest", e.getTag());
+ assertTrue(e.getTimeMillis() >= before);
+ assertEquals(0, e.getFlags());
+ assertTrue(null == e.getText(80));
+
+ byte[] buf = new byte[80];
+ assertEquals("TEST", new String(buf, 0, e.getInputStream().read(buf)));
+
+ e.close();
+ }
+
+ public void testAddFile() throws Exception {
+ File dir = getEmptyDir("testAddFile");
+ long before = System.currentTimeMillis();
+
+ File f0 = new File(dir, "f0.txt");
+ File f1 = new File(dir, "f1.txt.gz");
+ File f2 = new File(dir, "f2.dat");
+ File f3 = new File(dir, "f2.dat.gz");
+
+ FileWriter w0 = new FileWriter(f0);
+ GZIPOutputStream gz1 = new GZIPOutputStream(new FileOutputStream(f1));
+ FileOutputStream os2 = new FileOutputStream(f2);
+ GZIPOutputStream gz3 = new GZIPOutputStream(new FileOutputStream(f3));
+
+ w0.write("FILE0");
+ gz1.write("FILE1".getBytes());
+ os2.write("DATA2".getBytes());
+ gz3.write("DATA3".getBytes());
+
+ w0.close();
+ gz1.close();
+ os2.close();
+ gz3.close();
+
+ DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+ int mode = ParcelFileDescriptor.MODE_READ_ONLY;
+
+ ParcelFileDescriptor pfd0 = ParcelFileDescriptor.open(f0, mode);
+ ParcelFileDescriptor pfd1 = ParcelFileDescriptor.open(f1, mode);
+ ParcelFileDescriptor pfd2 = ParcelFileDescriptor.open(f2, mode);
+ ParcelFileDescriptor pfd3 = ParcelFileDescriptor.open(f3, mode);
+
+ dropbox.addFile("DropBoxTest", pfd0, DropBox.IS_TEXT);
+ dropbox.addFile("DropBoxTest", pfd1, DropBox.IS_TEXT | DropBox.IS_GZIPPED);
+ dropbox.addFile("DropBoxTest", pfd2, 0);
+ dropbox.addFile("DropBoxTest", pfd3, DropBox.IS_GZIPPED);
+
+ pfd0.close();
+ pfd1.close();
+ pfd2.close();
+ pfd3.close();
+
+ DropBox.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+ DropBox.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+ DropBox.Entry e2 = dropbox.getNextEntry("DropBoxTest", e1.getTimeMillis());
+ DropBox.Entry e3 = dropbox.getNextEntry("DropBoxTest", e2.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest", e3.getTimeMillis()));
+
+ assertTrue(e0.getTimeMillis() > before);
+ assertTrue(e1.getTimeMillis() > e0.getTimeMillis());
+ assertTrue(e2.getTimeMillis() > e1.getTimeMillis());
+ assertTrue(e3.getTimeMillis() > e2.getTimeMillis());
+
+ assertEquals(DropBox.IS_TEXT, e0.getFlags());
+ assertEquals(DropBox.IS_TEXT, e1.getFlags());
+ assertEquals(0, e2.getFlags());
+ assertEquals(0, e3.getFlags());
+
+ assertEquals("FILE0", e0.getText(80));
+
+ byte[] buf1 = new byte[80];
+ assertEquals("FILE1", new String(buf1, 0, e1.getInputStream().read(buf1)));
+
+ assertTrue(null == e2.getText(80));
+ byte[] buf2 = new byte[80];
+ assertEquals("DATA2", new String(buf2, 0, e2.getInputStream().read(buf2)));
+
+ assertTrue(null == e3.getText(80));
+ byte[] buf3 = new byte[80];
+ assertEquals("DATA3", new String(buf3, 0, e3.getInputStream().read(buf3)));
+
+ e0.close();
+ e1.close();
+ e2.close();
+ e3.close();
+ }
+
+ public void testAddEntriesInTheFuture() throws Exception {
+ File dir = getEmptyDir("testAddEntriesInTheFuture");
+ long before = System.currentTimeMillis();
+
+ // Near future: should be allowed to persist
+ FileWriter w0 = new FileWriter(new File(dir, "DropBoxTest@" + (before + 5000) + ".txt"));
+ w0.write("FUTURE0");
+ w0.close();
+
+ // Far future: should be collapsed
+ FileWriter w1 = new FileWriter(new File(dir, "DropBoxTest@" + (before + 100000) + ".txt"));
+ w1.write("FUTURE1");
+ w1.close();
+
+ // Another far future item, this one gzipped
+ File f2 = new File(dir, "DropBoxTest@" + (before + 100001) + ".txt.gz");
+ GZIPOutputStream gz2 = new GZIPOutputStream(new FileOutputStream(f2));
+ gz2.write("FUTURE2".getBytes());
+ gz2.close();
+
+ // Tombstone in the far future
+ new FileOutputStream(new File(dir, "DropBoxTest@" + (before + 100002) + ".lost")).close();
+
+ DropBoxService service = new DropBoxService(getContext(), dir);
+ DropBox dropbox = new DropBox(service);
+
+ // Until a write, the timestamps are taken at face value
+ DropBox.Entry e0 = dropbox.getNextEntry(null, before);
+ DropBox.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+ DropBox.Entry e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+ DropBox.Entry e3 = dropbox.getNextEntry(null, e2.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry(null, e3.getTimeMillis()));
+
+ assertEquals("FUTURE0", e0.getText(80));
+ assertEquals("FUTURE1", e1.getText(80));
+ assertEquals("FUTURE2", e2.getText(80));
+ assertEquals(null, e3.getText(80));
+
+ assertEquals(before + 5000, e0.getTimeMillis());
+ assertEquals(before + 100000, e1.getTimeMillis());
+ assertEquals(before + 100001, e2.getTimeMillis());
+ assertEquals(before + 100002, e3.getTimeMillis());
+
+ e0.close();
+ e1.close();
+ e2.close();
+ e3.close();
+
+ // Write something to force a collapse
+ dropbox.addText("NotDropBoxTest", "FUTURE");
+ e0 = dropbox.getNextEntry(null, before);
+ e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+ e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+ e3 = dropbox.getNextEntry(null, e2.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest", e3.getTimeMillis()));
+
+ assertEquals("FUTURE0", e0.getText(80));
+ assertEquals("FUTURE1", e1.getText(80));
+ assertEquals("FUTURE2", e2.getText(80));
+ assertEquals(null, e3.getText(80));
+
+ assertEquals(before + 5000, e0.getTimeMillis());
+ assertEquals(before + 5001, e1.getTimeMillis());
+ assertEquals(before + 5002, e2.getTimeMillis());
+ assertEquals(before + 5003, e3.getTimeMillis());
+
+ e0.close();
+ e1.close();
+ e2.close();
+ e3.close();
+ service.stop();
+ }
+
+ public void testIsTagEnabled() throws Exception {
+ DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+ long before = System.currentTimeMillis();
+ dropbox.addText("DropBoxTest", "TEST-ENABLED");
+ assertTrue(dropbox.isTagEnabled("DropBoxTest"));
+
+ Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+ override.putExtra(Settings.Gservices.DROPBOX_TAG_PREFIX + "DropBoxTest", "disabled");
+ waitForBroadcast(override);
+
+ dropbox.addText("DropBoxTest", "TEST-DISABLED");
+ assertFalse(dropbox.isTagEnabled("DropBoxTest"));
+
+ override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+ override.putExtra(Settings.Gservices.DROPBOX_TAG_PREFIX + "DropBoxTest", "");
+ waitForBroadcast(override);
+
+ dropbox.addText("DropBoxTest", "TEST-ENABLED-AGAIN");
+ assertTrue(dropbox.isTagEnabled("DropBoxTest"));
+
+ DropBox.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+ DropBox.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest", e1.getTimeMillis()));
+
+ assertEquals("TEST-ENABLED", e0.getText(80));
+ assertEquals("TEST-ENABLED-AGAIN", e1.getText(80));
+
+ e0.close();
+ e1.close();
+ }
+
+ public void testGetNextEntry() throws Exception {
+ File dir = getEmptyDir("testGetNextEntry");
+ DropBoxService service = new DropBoxService(getContext(), dir);
+ DropBox dropbox = new DropBox(service);
+
+ long before = System.currentTimeMillis();
+ dropbox.addText("DropBoxTest.A", "A0");
+ dropbox.addText("DropBoxTest.B", "B0");
+ dropbox.addText("DropBoxTest.A", "A1");
+
+ DropBox.Entry a0 = dropbox.getNextEntry("DropBoxTest.A", before);
+ DropBox.Entry a1 = dropbox.getNextEntry("DropBoxTest.A", a0.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest.A", a1.getTimeMillis()));
+
+ DropBox.Entry b0 = dropbox.getNextEntry("DropBoxTest.B", before);
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest.B", b0.getTimeMillis()));
+
+ DropBox.Entry x0 = dropbox.getNextEntry(null, before);
+ DropBox.Entry x1 = dropbox.getNextEntry(null, x0.getTimeMillis());
+ DropBox.Entry x2 = dropbox.getNextEntry(null, x1.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry(null, x2.getTimeMillis()));
+
+ assertEquals("DropBoxTest.A", a0.getTag());
+ assertEquals("DropBoxTest.A", a1.getTag());
+ assertEquals("A0", a0.getText(80));
+ assertEquals("A1", a1.getText(80));
+
+ assertEquals("DropBoxTest.B", b0.getTag());
+ assertEquals("B0", b0.getText(80));
+
+ assertEquals("DropBoxTest.A", x0.getTag());
+ assertEquals("DropBoxTest.B", x1.getTag());
+ assertEquals("DropBoxTest.A", x2.getTag());
+ assertEquals("A0", x0.getText(80));
+ assertEquals("B0", x1.getText(80));
+ assertEquals("A1", x2.getText(80));
+
+ a0.close();
+ a1.close();
+ b0.close();
+ x0.close();
+ x1.close();
+ x2.close();
+ service.stop();
+ }
+
+ public void testSizeLimits() throws Exception {
+ File dir = getEmptyDir("testSizeLimits");
+ int blockSize = new StatFs(dir.getPath()).getBlockSize();
+
+ // Limit storage to 10 blocks
+ int kb = blockSize * 10 / 1024;
+ Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+ override.putExtra(Settings.Gservices.DROPBOX_QUOTA_KB, Integer.toString(kb));
+ waitForBroadcast(override);
+
+ // Three tags using a total of 12 blocks:
+ // DropBoxTest0 [ ][ ]
+ // DropBoxTest1 [x][ ][ ][ ][xxx(20 blocks)xxx]
+ // DropBoxTest2 [xxxxxxxxxx][ ][ ]
+ //
+ // The blocks marked "x" will be removed due to storage restrictions.
+ // Use random fill (so it doesn't compress), subtract a little for gzip overhead
+
+ final int overhead = 64;
+ long before = System.currentTimeMillis();
+ DropBoxService service = new DropBoxService(getContext(), dir);
+ DropBox dropbox = new DropBox(service);
+
+ addRandomEntry(dropbox, "DropBoxTest0", blockSize - overhead);
+ addRandomEntry(dropbox, "DropBoxTest0", blockSize - overhead);
+
+ addRandomEntry(dropbox, "DropBoxTest1", blockSize - overhead);
+ addRandomEntry(dropbox, "DropBoxTest1", blockSize - overhead);
+ addRandomEntry(dropbox, "DropBoxTest1", blockSize * 2 - overhead);
+ addRandomEntry(dropbox, "DropBoxTest1", blockSize - overhead);
+ addRandomEntry(dropbox, "DropBoxTest1", blockSize * 20 - overhead);
+
+ addRandomEntry(dropbox, "DropBoxTest2", blockSize * 4 - overhead);
+ addRandomEntry(dropbox, "DropBoxTest2", blockSize - overhead);
+ addRandomEntry(dropbox, "DropBoxTest2", blockSize - overhead);
+
+ DropBox.Entry e0 = dropbox.getNextEntry(null, before);
+ DropBox.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+ DropBox.Entry e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+ DropBox.Entry e3 = dropbox.getNextEntry(null, e2.getTimeMillis());
+ DropBox.Entry e4 = dropbox.getNextEntry(null, e3.getTimeMillis());
+ DropBox.Entry e5 = dropbox.getNextEntry(null, e4.getTimeMillis());
+ DropBox.Entry e6 = dropbox.getNextEntry(null, e5.getTimeMillis());
+ DropBox.Entry e7 = dropbox.getNextEntry(null, e6.getTimeMillis());
+ DropBox.Entry e8 = dropbox.getNextEntry(null, e7.getTimeMillis());
+ DropBox.Entry e9 = dropbox.getNextEntry(null, e8.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry(null, e9.getTimeMillis()));
+
+ assertEquals("DropBoxTest0", e0.getTag());
+ assertEquals("DropBoxTest0", e1.getTag());
+ assertEquals(blockSize - overhead, getEntrySize(e0));
+ assertEquals(blockSize - overhead, getEntrySize(e1));
+
+ assertEquals("DropBoxTest1", e2.getTag());
+ assertEquals("DropBoxTest1", e3.getTag());
+ assertEquals("DropBoxTest1", e4.getTag());
+ assertEquals("DropBoxTest1", e5.getTag());
+ assertEquals("DropBoxTest1", e6.getTag());
+ assertEquals(-1, getEntrySize(e2)); // Tombstone
+ assertEquals(blockSize - overhead, getEntrySize(e3));
+ assertEquals(blockSize * 2 - overhead, getEntrySize(e4));
+ assertEquals(blockSize - overhead, getEntrySize(e5));
+ assertEquals(-1, getEntrySize(e6));
+
+ assertEquals("DropBoxTest2", e7.getTag());
+ assertEquals("DropBoxTest2", e8.getTag());
+ assertEquals("DropBoxTest2", e9.getTag());
+ assertEquals(-1, getEntrySize(e7)); // Tombstone
+ assertEquals(blockSize - overhead, getEntrySize(e8));
+ assertEquals(blockSize - overhead, getEntrySize(e9));
+
+ e0.close();
+ e1.close();
+ e2.close();
+ e3.close();
+ e4.close();
+ e5.close();
+ e6.close();
+ e7.close();
+ e8.close();
+ e9.close();
+
+ // Specifying a tag name skips tombstone records.
+
+ DropBox.Entry t0 = dropbox.getNextEntry("DropBoxTest1", before);
+ DropBox.Entry t1 = dropbox.getNextEntry("DropBoxTest1", t0.getTimeMillis());
+ DropBox.Entry t2 = dropbox.getNextEntry("DropBoxTest1", t1.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest1", t2.getTimeMillis()));
+
+ assertEquals("DropBoxTest1", t0.getTag());
+ assertEquals("DropBoxTest1", t1.getTag());
+ assertEquals("DropBoxTest1", t2.getTag());
+
+ assertEquals(blockSize - overhead, getEntrySize(t0));
+ assertEquals(blockSize * 2 - overhead, getEntrySize(t1));
+ assertEquals(blockSize - overhead, getEntrySize(t2));
+
+ t0.close();
+ t1.close();
+ t2.close();
+ service.stop();
+ }
+
+ public void testAgeLimits() throws Exception {
+ File dir = getEmptyDir("testAgeLimits");
+ int blockSize = new StatFs(dir.getPath()).getBlockSize();
+
+ // Limit storage to 10 blocks with an expiration of 1 second
+ int kb = blockSize * 10 / 1024;
+ Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+ override.putExtra(Settings.Gservices.DROPBOX_AGE_SECONDS, "1");
+ override.putExtra(Settings.Gservices.DROPBOX_QUOTA_KB, Integer.toString(kb));
+ waitForBroadcast(override);
+
+ // Write one normal entry and another so big that it is instantly tombstoned
+ long before = System.currentTimeMillis();
+ DropBoxService service = new DropBoxService(getContext(), dir);
+ DropBox dropbox = new DropBox(service);
+
+ dropbox.addText("DropBoxTest", "TEST");
+ addRandomEntry(dropbox, "DropBoxTest", blockSize * 20);
+
+ // Verify that things are as expected
+ DropBox.Entry e0 = dropbox.getNextEntry(null, before);
+ DropBox.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+ assertTrue(null == dropbox.getNextEntry(null, e1.getTimeMillis()));
+
+ assertEquals("TEST", e0.getText(80));
+ assertEquals(null, e1.getText(80));
+ assertEquals(-1, getEntrySize(e1));
+
+ e0.close();
+ e1.close();
+
+ // Wait a second and write another entry -- old ones should be expunged
+ Thread.sleep(2000);
+ dropbox.addText("DropBoxTest", "TEST1");
+
+ e0 = dropbox.getNextEntry(null, before);
+ assertTrue(null == dropbox.getNextEntry(null, e0.getTimeMillis()));
+ assertEquals("TEST1", e0.getText(80));
+ e0.close();
+ }
+
+ public void testCreateDropBoxWithInvalidDirectory() throws Exception {
+ // If created with an invalid directory, the DropBox should suffer quietly
+ // and fail all operations (this is how it survives a full disk).
+ // Once the directory becomes possible to create, it will start working.
+
+ File dir = new File(getEmptyDir("testCreateDropBoxWith"), "InvalidDirectory");
+ new FileOutputStream(dir).close(); // Create an empty file
+ DropBoxService service = new DropBoxService(getContext(), dir);
+ DropBox dropbox = new DropBox(service);
+
+ dropbox.addText("DropBoxTest", "should be ignored");
+ dropbox.addData("DropBoxTest", "should be ignored".getBytes(), 0);
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest", 0));
+
+ dir.delete(); // Remove the file so a directory can be created
+ dropbox.addText("DropBoxTest", "TEST");
+ DropBox.Entry e = dropbox.getNextEntry("DropBoxTest", 0);
+ assertTrue(null == dropbox.getNextEntry("DropBoxTest", e.getTimeMillis()));
+ assertEquals("DropBoxTest", e.getTag());
+ assertEquals("TEST", e.getText(80));
+ e.close();
+ service.stop();
+ }
+
+ private void addRandomEntry(DropBox dropbox, String tag, int size) throws Exception {
+ byte[] bytes = new byte[size];
+ new Random(System.currentTimeMillis()).nextBytes(bytes);
+
+ File f = new File(getEmptyDir("addRandomEntry"), "random.dat");
+ FileOutputStream os = new FileOutputStream(f);
+ os.write(bytes);
+ os.close();
+
+ ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ dropbox.addFile(tag, fd, 0);
+ fd.close();
+ }
+
+ private int getEntrySize(DropBox.Entry e) throws Exception {
+ InputStream is = e.getInputStream();
+ if (is == null) return -1;
+ int length = 0;
+ while (is.read() != -1) length++;
+ return length;
+ }
+
+ private void waitForBroadcast(Intent intent) throws InterruptedException {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ public synchronized void onReceive(Context context, Intent intent) { notify(); }
+ };
+
+ getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
+ synchronized (receiver) { receiver.wait(); }
+ }
+
+ private void recursiveDelete(File file) {
+ if (!file.delete() && file.isDirectory()) {
+ for (File f : file.listFiles()) recursiveDelete(f);
+ file.delete();
+ }
+ }
+
+ private File getEmptyDir(String name) {
+ File dir = getContext().getDir("DropBoxTest." + name, 0);
+ for (File f : dir.listFiles()) recursiveDelete(f);
+ assertTrue(dir.listFiles().length == 0);
+ return dir;
+ }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/ContentValuesBuilder.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/ContentValuesBuilder.java
new file mode 100644
index 0000000..e99e4cb
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/ContentValuesBuilder.java
@@ -0,0 +1,81 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentValues;
+
+/**
+ * ContentValues-like class which enables users to chain put() methods and restricts
+ * the other methods.
+ */
+/* package */ 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/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
index 0ee74df..d93a41b 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
@@ -16,19 +16,21 @@
package com.android.unit_tests.vcard;
import android.content.ContentValues;
-
-import org.apache.commons.codec.binary.Base64;
+import android.pim.vcard.ContactStruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.Map.Entry;
-import java.util.regex.Pattern;
/**
- * @hide old class just for test
+ * Previously used in main vCard handling code but now exists only for testing.
+ *
+ * Especially useful for testing parser code (VCardParser), since all properties can be
+ * checked via this class unlike {@link ContactStruct}, which only emits the result of
+ * interpretation of the content of each vCard. We cannot know whether vCard parser or
+ * ContactStruct is wrong withouth this class.
*/
public class PropertyNode {
public String propName;
@@ -101,6 +103,15 @@
}
@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;
@@ -159,164 +170,4 @@
builder.append(propValue);
return builder.toString();
}
-
- /**
- * Encode this object into a string which can be decoded.
- */
- public String encode() {
- // PropertyNode#toString() is for reading, not for parsing in the future.
- // We construct appropriate String here.
- StringBuilder builder = new StringBuilder();
- if (propName.length() > 0) {
- builder.append("propName:[");
- builder.append(propName);
- builder.append("],");
- }
- int size = propGroupSet.size();
- if (size > 0) {
- Set<String> set = propGroupSet;
- builder.append("propGroup:[");
- int i = 0;
- for (String group : set) {
- // We do not need to double quote groups.
- // group = 1*(ALPHA / DIGIT / "-")
- builder.append(group);
- if (i < size - 1) {
- builder.append(",");
- }
- i++;
- }
- builder.append("],");
- }
-
- if (paramMap.size() > 0 || paramMap_TYPE.size() > 0) {
- ContentValues values = paramMap;
- builder.append("paramMap:[");
- size = paramMap.size();
- int i = 0;
- for (Entry<String, Object> entry : values.valueSet()) {
- // Assuming param-key does not contain NON-ASCII nor symbols.
- //
- // According to vCard 3.0:
- // param-name = iana-token / x-name
- builder.append(entry.getKey());
-
- // param-value may contain any value including NON-ASCIIs.
- // We use the following replacing rule.
- // \ -> \\
- // , -> \,
- // In String#replaceAll(), "\\\\" means a single backslash.
- builder.append("=");
- builder.append(entry.getValue().toString()
- .replaceAll("\\\\", "\\\\\\\\")
- .replaceAll(",", "\\\\,"));
- if (i < size -1) {
- builder.append(",");
- }
- i++;
- }
-
- Set<String> set = paramMap_TYPE;
- size = paramMap_TYPE.size();
- if (i > 0 && size > 0) {
- builder.append(",");
- }
- i = 0;
- for (String type : set) {
- builder.append("TYPE=");
- builder.append(type
- .replaceAll("\\\\", "\\\\\\\\")
- .replaceAll(",", "\\\\,"));
- if (i < size - 1) {
- builder.append(",");
- }
- i++;
- }
- builder.append("],");
- }
-
- size = propValue_vector.size();
- if (size > 0) {
- builder.append("propValue:[");
- List<String> list = propValue_vector;
- for (int i = 0; i < size; i++) {
- builder.append(list.get(i)
- .replaceAll("\\\\", "\\\\\\\\")
- .replaceAll(",", "\\\\,"));
- if (i < size -1) {
- builder.append(",");
- }
- }
- builder.append("],");
- }
-
- return builder.toString();
- }
-
- public static PropertyNode decode(String encodedString) {
- PropertyNode propertyNode = new PropertyNode();
- String trimed = encodedString.trim();
- if (trimed.length() == 0) {
- return propertyNode;
- }
- String[] elems = trimed.split("],");
-
- for (String elem : elems) {
- int index = elem.indexOf('[');
- String name = elem.substring(0, index - 1);
- Pattern pattern = Pattern.compile("(?<!\\\\),");
- String[] values = pattern.split(elem.substring(index + 1), -1);
- if (name.equals("propName")) {
- propertyNode.propName = values[0];
- } else if (name.equals("propGroupSet")) {
- for (String value : values) {
- propertyNode.propGroupSet.add(value);
- }
- } else if (name.equals("paramMap")) {
- ContentValues paramMap = propertyNode.paramMap;
- Set<String> paramMap_TYPE = propertyNode.paramMap_TYPE;
- for (String value : values) {
- String[] tmp = value.split("=", 2);
- String mapKey = tmp[0];
- // \, -> ,
- // \\ -> \
- // In String#replaceAll(), "\\\\" means a single backslash.
- String mapValue =
- tmp[1].replaceAll("\\\\,", ",").replaceAll("\\\\\\\\", "\\\\");
- if (mapKey.equalsIgnoreCase("TYPE")) {
- paramMap_TYPE.add(mapValue);
- } else {
- paramMap.put(mapKey, mapValue);
- }
- }
- } else if (name.equals("propValue")) {
- StringBuilder builder = new StringBuilder();
- List<String> list = propertyNode.propValue_vector;
- int length = values.length;
- for (int i = 0; i < length; i++) {
- String normValue = values[i]
- .replaceAll("\\\\,", ",")
- .replaceAll("\\\\\\\\", "\\\\");
- list.add(normValue);
- builder.append(normValue);
- if (i < length - 1) {
- builder.append(";");
- }
- }
- propertyNode.propValue = builder.toString();
- }
- }
-
- // At this time, QUOTED-PRINTABLE is already decoded to Java String.
- // We just need to decode BASE64 String to binary.
- String encoding = propertyNode.paramMap.getAsString("ENCODING");
- if (encoding != null &&
- (encoding.equalsIgnoreCase("BASE64") ||
- encoding.equalsIgnoreCase("B"))) {
- propertyNode.propValue_bytes =
- Base64.decodeBase64(propertyNode.propValue_vector.get(0).getBytes());
- }
-
- return propertyNode;
- }
}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java
new file mode 100644
index 0000000..917b18e
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java
@@ -0,0 +1,344 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+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) {
+ 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(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30(true); // use StrictParsing
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ try {
+ vCardParser.parse(is, this);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void endRecord() {
+ super.endRecord();
+ mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
+ mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
+ mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
+ mIndex++;
+ }
+}
+
+/**
+ * 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.
+ */
+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 addNodeWithOrder(String propName, String propValue) {
+ return addNodeWithOrder(propName, propValue, null, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+ ContentValues contentValues) {
+ return addNodeWithOrder(propName, propValue, null, null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+ List<String> propValueList) {
+ return addNodeWithOrder(propName, propValue, propValueList, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithOrder(String propName, List<String> propValueList) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addNodeWithOrder(propName, propValue.toString(), propValueList,
+ null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+ TypeSet paramMap_TYPE) {
+ return addNodeWithOrder(propName, propValue, null, null, null, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithOrder(String propName,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addNodeWithOrder(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addNodeWithOrder(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+ List<String> propValueList, byte[] propValue_bytes,
+ ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+ 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 addNodeWithoutOrder(String propName, String propValue) {
+ return addNodeWithoutOrder(propName, propValue, null, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+ ContentValues contentValues) {
+ return addNodeWithoutOrder(propName, propValue, null, null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+ List<String> propValueList) {
+ return addNodeWithoutOrder(propName, propValue, propValueList, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, List<String> propValueList) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addNodeWithoutOrder(propName, propValue, propValueList,
+ null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+ TypeSet paramMap_TYPE) {
+ return addNodeWithoutOrder(propName, propValue, null, null, null, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithoutOrder(String propName,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addNodeWithoutOrder(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addNodeWithoutOrder(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+ List<String> propValueList, byte[] propValue_bytes,
+ ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+ 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/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
new file mode 100644
index 0000000..727d33b
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
@@ -0,0 +1,892 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+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.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.unit_tests.vcard.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() {
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, V21);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithoutOrder("FN", "Roid Ando")
+ .addNodeWithoutOrder("N", "Ando;Roid;;;", Arrays.asList("Ando", "Roid", "", "", ""));
+ verifier.verify();
+ }
+
+ private void testStructuredNameBasic(int version) {
+ final boolean isV30 = VCardConfig.isV30(version);
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ resolver.buildContactEntry().buildData(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");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addNodeWithoutOrder("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addNodeWithoutOrder("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addNodeWithoutOrder("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addNodeWithoutOrder("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ + "AppropriatePhoneticFamily");
+ }
+
+ verifier.verify();
+ }
+
+ 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 version) {
+ final boolean isV30 = (version == V30);
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(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.buildData(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.buildData(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);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addNodeWithoutOrder("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addNodeWithoutOrder("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addNodeWithoutOrder("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addNodeWithoutOrder("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ + "AppropriatePhoneticFamily");
+ }
+
+ verifier.verify();
+ }
+
+ 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 version) {
+ final boolean isV30 = (version == V30);
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(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.buildData(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.buildData(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.buildData(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);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addNodeWithoutOrder("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addNodeWithoutOrder("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addNodeWithoutOrder("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addNodeWithoutOrder("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
+ + " AppropriatePhoneticFamily");
+ }
+
+ verifier.verify();
+ }
+
+ public void testStructuredNameUseSuperPrimaryV21() {
+ testStructuredNameUseSuperPrimaryCommon(V21);
+ }
+
+ public void testStructuredNameUseSuperPrimaryV30() {
+ testStructuredNameUseSuperPrimaryCommon(V30);
+ }
+
+ public void testNickNameV30() {
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, V30);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithOrder("NICKNAME", "Nicky");
+
+ verifier.verify();
+ }
+
+ private void testPhoneBasicCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ resolver.buildContactEntry().buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("TEL", "1", new TypeSet("HOME"));
+
+ verifier.verify();
+ }
+
+ public void testPhoneBasicV21() {
+ testPhoneBasicCommon(V21);
+ }
+
+ public void testPhoneBasicV30() {
+ testPhoneBasicCommon(V30);
+ }
+
+ /**
+ * Tests that vCard composer emits corresponding type param which we expect.
+ */
+ private void testPhoneVariousTypeSupport(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "10")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "20")
+ .put(Phone.TYPE, Phone.TYPE_WORK);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "30")
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "40")
+ .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "50")
+ .put(Phone.TYPE, Phone.TYPE_MOBILE);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "60")
+ .put(Phone.TYPE, Phone.TYPE_PAGER);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "70")
+ .put(Phone.TYPE, Phone.TYPE_OTHER);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "80")
+ .put(Phone.TYPE, Phone.TYPE_CAR);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "90")
+ .put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "100")
+ .put(Phone.TYPE, Phone.TYPE_ISDN);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "110")
+ .put(Phone.TYPE, Phone.TYPE_MAIN);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "120")
+ .put(Phone.TYPE, Phone.TYPE_OTHER_FAX);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "130")
+ .put(Phone.TYPE, Phone.TYPE_TELEX);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "140")
+ .put(Phone.TYPE, Phone.TYPE_WORK_MOBILE);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "150")
+ .put(Phone.TYPE, Phone.TYPE_WORK_PAGER);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "160")
+ .put(Phone.TYPE, Phone.TYPE_MMS);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("TEL", "10", new TypeSet("HOME"))
+ .addNodeWithoutOrder("TEL", "20", new TypeSet("WORK"))
+ .addNodeWithoutOrder("TEL", "30", new TypeSet("HOME", "FAX"))
+ .addNodeWithoutOrder("TEL", "40", new TypeSet("WORK", "FAX"))
+ .addNodeWithoutOrder("TEL", "50", new TypeSet("CELL"))
+ .addNodeWithoutOrder("TEL", "60", new TypeSet("PAGER"))
+ .addNodeWithoutOrder("TEL", "70", new TypeSet("VOICE"))
+ .addNodeWithoutOrder("TEL", "80", new TypeSet("CAR"))
+ .addNodeWithoutOrder("TEL", "90", new TypeSet("WORK", "PREF"))
+ .addNodeWithoutOrder("TEL", "100", new TypeSet("ISDN"))
+ .addNodeWithoutOrder("TEL", "110", new TypeSet("PREF"))
+ .addNodeWithoutOrder("TEL", "120", new TypeSet("FAX"))
+ .addNodeWithoutOrder("TEL", "130", new TypeSet("TLX"))
+ .addNodeWithoutOrder("TEL", "140", new TypeSet("WORK", "CELL"))
+ .addNodeWithoutOrder("TEL", "150", new TypeSet("WORK", "PAGER"))
+ .addNodeWithoutOrder("TEL", "160", new TypeSet("MSG"));
+ verifier.verify();
+ }
+
+ public void testPhoneVariousTypeSupportV21() {
+ testPhoneVariousTypeSupport(V21);
+ }
+
+ public void testPhoneVariousTypeSupportV30() {
+ testPhoneVariousTypeSupport(V30);
+ }
+
+ /**
+ * Tests that "PREF"s are emitted appropriately.
+ */
+ private void testPhonePrefHandlingCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "2")
+ .put(Phone.TYPE, Phone.TYPE_WORK)
+ .put(Phone.IS_PRIMARY, 1);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "3")
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
+ .put(Phone.IS_PRIMARY, 1);
+ entry.buildData(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "4")
+ .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("TEL", "4", new TypeSet("WORK", "FAX"))
+ .addNodeWithoutOrder("TEL", "3", new TypeSet("HOME", "FAX", "PREF"))
+ .addNodeWithoutOrder("TEL", "2", new TypeSet("WORK", "PREF"))
+ .addNodeWithoutOrder("TEL", "1", new TypeSet("HOME"));
+ verifier.verify();
+ }
+
+ public void testPhonePrefHandlingV21() {
+ testPhonePrefHandlingCommon(V21);
+ }
+
+ public void testPhonePrefHandlingV30() {
+ testPhonePrefHandlingCommon(V30);
+ }
+
+ private void testEmailBasicCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "sample@example.com");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("EMAIL", "sample@example.com");
+
+ verifier.verify();
+ }
+
+ public void testEmailBasicV21() {
+ testEmailBasicCommon(V21);
+ }
+
+ public void testEmailBasicV30() {
+ testEmailBasicCommon(V30);
+ }
+
+ private void testEmailVariousTypeSupportCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_home@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME);
+ entry.buildData(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_work@example.com")
+ .put(Email.TYPE, Email.TYPE_WORK);
+ entry.buildData(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_mobile@example.com")
+ .put(Email.TYPE, Email.TYPE_MOBILE);
+ entry.buildData(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_other@example.com")
+ .put(Email.TYPE, Email.TYPE_OTHER);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("EMAIL", "type_home@example.com", new TypeSet("HOME"))
+ .addNodeWithoutOrder("EMAIL", "type_work@example.com", new TypeSet("WORK"))
+ .addNodeWithoutOrder("EMAIL", "type_mobile@example.com", new TypeSet("CELL"))
+ .addNodeWithoutOrder("EMAIL", "type_other@example.com");
+
+ verifier.verify();
+ }
+
+ public void testEmailVariousTypeSupportV21() {
+ testEmailVariousTypeSupportCommon(V21);
+ }
+
+ public void testEmailVariousTypeSupportV30() {
+ testEmailVariousTypeSupportCommon(V30);
+ }
+
+ private void testEmailPrefHandlingCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_home@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME)
+ .put(Email.IS_PRIMARY, 1);
+ entry.buildData(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_notype@example.com")
+ .put(Email.IS_PRIMARY, 1);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("EMAIL", "type_notype@example.com", new TypeSet("PREF"))
+ .addNodeWithoutOrder("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF"));
+
+ verifier.verify();
+ }
+
+ public void testEmailPrefHandlingV21() {
+ testEmailPrefHandlingCommon(V21);
+ }
+
+ public void testEmailPrefHandlingV30() {
+ testEmailPrefHandlingCommon(V30);
+ }
+
+ private void testPostalOnlyWithStructuredDataCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal Code,
+ // ; Country Name
+ resolver.buildContactEntry().buildData(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");
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("ADR", "Pobox;Neighborhood;Street;City;Region;100;Country",
+ Arrays.asList("Pobox", "Neighborhood", "Street", "City",
+ "Region", "100", "Country"), new TypeSet("HOME"));
+
+ verifier.verify();
+ }
+
+ public void testPostalOnlyWithStructuredDataV21() {
+ testPostalOnlyWithStructuredDataCommon(V21);
+ }
+
+ public void testPostalOnlyWithStructuredDataV30() {
+ testPostalOnlyWithStructuredDataCommon(V30);
+ }
+
+ private void testPostalOnlyWithFormattedAddressCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ resolver.buildContactEntry().buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "Formatted address CA 123-334 United Statue");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;",
+ Arrays.asList("", "Formatted address CA 123-334 United Statue",
+ "", "", "", "", ""), new TypeSet("HOME"));
+
+ verifier.verify();
+ }
+
+ 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 version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ resolver.buildContactEntry().buildData(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
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("ADR", "Pobox;;;;;;Country",
+ Arrays.asList("Pobox", "", "", "", "", "", "Country"),
+ new TypeSet("HOME"));
+
+ verifier.verify();
+ }
+
+ public void testPostalWithBothStructuredAndFormattedV21() {
+ testPostalWithBothStructuredAndFormattedCommon(V21);
+ }
+
+ public void testPostalWithBothStructuredAndFormattedV30() {
+ testPostalWithBothStructuredAndFormattedCommon(V30);
+ }
+
+ private void testOrganizationCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(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.buildData(Organization.CONTENT_ITEM_TYPE)
+ .putNull(Organization.COMPANY)
+ .put(Organization.DEPARTMENT, "DepartmentXX")
+ .putNull(Organization.TITLE);
+
+ entry.buildData(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "CompanyXYZ")
+ .putNull(Organization.DEPARTMENT)
+ .put(Organization.TITLE, "TitleXYZYX");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+ // Currently we do not use group but depend on the order.
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithOrder("ORG", "CompanyX;DepartmentY",
+ Arrays.asList("CompanyX", "DepartmentY"))
+ .addNodeWithOrder("TITLE", "TitleZ")
+ .addNodeWithOrder("ORG", "DepartmentXX")
+ .addNodeWithOrder("ORG", "CompanyXYZ")
+ .addNodeWithOrder("TITLE", "TitleXYZYX");
+
+ verifier.verify();
+ }
+
+ public void testOrganizationV21() {
+ testOrganizationCommon(V21);
+ }
+
+ public void testOrganizationV30() {
+ testOrganizationCommon(V30);
+ }
+
+ private void testImVariousTypeSupportCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_MSN)
+ .put(Im.DATA, "msn");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_YAHOO)
+ .put(Im.DATA, "yahoo");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_SKYPE)
+ .put(Im.DATA, "skype");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_QQ)
+ .put(Im.DATA, "qq");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
+ .put(Im.DATA, "google talk");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_ICQ)
+ .put(Im.DATA, "icq");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_JABBER)
+ .put(Im.DATA, "jabber");
+
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING)
+ .put(Im.DATA, "netmeeting");
+
+ // No determined way to express unknown type...
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("X-JABBER", "jabber")
+ .addNodeWithoutOrder("X-ICQ", "icq")
+ .addNodeWithoutOrder("X-GOOGLE-TALK", "google talk")
+ .addNodeWithoutOrder("X-QQ", "qq")
+ .addNodeWithoutOrder("X-SKYPE-USERNAME", "skype")
+ .addNodeWithoutOrder("X-YAHOO", "yahoo")
+ .addNodeWithoutOrder("X-MSN", "msn")
+ .addNodeWithoutOrder("X-NETMEETING", "netmeeting")
+ .addNodeWithoutOrder("X-AIM", "aim");
+
+ verifier.verify();
+ }
+
+ public void testImBasiV21() {
+ testImVariousTypeSupportCommon(V21);
+ }
+
+ public void testImBasicV30() {
+ testImVariousTypeSupportCommon(V30);
+ }
+
+ private void testImPrefHandlingCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim1");
+
+ entry.buildData(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);
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("X-AIM", "aim1")
+ .addNodeWithoutOrder("X-AIM", "aim2", new TypeSet("HOME", "PREF"));
+
+ verifier.verify();
+ }
+
+ public void testImPrefHandlingV21() {
+ testImPrefHandlingCommon(V21);
+ }
+
+ public void testImPrefHandlingV30() {
+ testImPrefHandlingCommon(V30);
+ }
+
+ private void testWebsiteCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Website.CONTENT_ITEM_TYPE)
+ .put(Website.URL, "http://website.example.android.com/index.html")
+ .put(Website.TYPE, Website.TYPE_BLOG);
+
+ entry.buildData(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.
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("URL", "ftp://ftp.example.android.com/index.html")
+ .addNodeWithoutOrder("URL", "http://website.example.android.com/index.html");
+ verifier.verify();
+ }
+
+ public void testWebsiteV21() {
+ testWebsiteCommon(V21);
+ }
+
+ public void testWebsiteV30() {
+ testWebsiteCommon(V30);
+ }
+
+ private void testEventCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_ANNIVERSARY)
+ .put(Event.START_DATE, "1982-06-16");
+ entry.buildData(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+ .put(Event.START_DATE, "2008-10-22");
+ entry.buildData(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_OTHER)
+ .put(Event.START_DATE, "2018-03-12");
+ entry.buildData(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.buildData(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+ .put(Event.START_DATE, "2009-05-19");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithoutOrder("BDAY", "2008-10-22");
+
+ verifier.verify();
+ }
+
+ public void testEventV21() {
+ testEventCommon(V21);
+ }
+
+ public void testEventV30() {
+ testEventCommon(V30);
+ }
+
+ private void testNoteCommon(int version) {
+ ExportTestResolver resolver = new ExportTestResolver();
+
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note1");
+ entry.buildData(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note2")
+ .put(Note.IS_PRIMARY, 1); // Just ignored.
+
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithOrder("NOTE", "note1")
+ .addNodeWithOrder("NOTE", "note2");
+
+ verifier.verify();
+ }
+
+ public void testNoteV21() {
+ testNoteCommon(V21);
+ }
+
+ public void testNoteV30() {
+ testNoteCommon(V30);
+ }
+
+ private void testPhotoCommon(int version) {
+ final boolean isV30 = version == V30;
+ ExportTestResolver resolver = new ExportTestResolver();
+ ContactEntry entry = resolver.buildContactEntry();
+ entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "PhotoTest");
+ entry.buildData(Photo.CONTENT_ITEM_TYPE)
+ .put(Photo.PHOTO, sPhotoByteArray);
+
+ ContentValues contentValuesForPhoto = new ContentValues();
+ contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithoutOrder("FN", "PhotoTest")
+ .addNodeWithoutOrder("N", "PhotoTest;;;;",
+ Arrays.asList("PhotoTest", "", "", "", ""))
+ .addNodeWithOrder("PHOTO", null, null, sPhotoByteArray,
+ contentValuesForPhoto, new TypeSet("JPEG"), null);
+
+ verifier.verify();
+ }
+
+ public void testPhotoV21() {
+ testPhotoCommon(V21);
+ }
+
+ public void testPhotoV30() {
+ testPhotoCommon(V30);
+ }
+
+ public void testV30HandleEscape() {
+ final int version = V30;
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(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}>]");
+ VCardVerifier verifier = new VCardVerifier(resolver, version);
+ // Verifies the vCard String correctly escapes each character which must be escaped.
+ verifier.addLineVerifier()
+ .addExpected("N:\\\\;\\;;\\,;\\n;")
+ .addExpected("FN:[<{Unescaped:Asciis}>]");
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithoutOrder("FN", "[<{Unescaped:Asciis}>]")
+ .addNodeWithoutOrder("N", Arrays.asList("\\", ";", ",", "\n", ""));
+
+ verifier.verify();
+ }
+
+ /**
+ * 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() {
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, V21);
+ verifier.addPropertyNodesVerifierWithEmptyName()
+ .addNodeWithOrder("X-ANDROID-CUSTOM",
+ Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;");
+ verifier.addImportVerifier()
+ .addExpected(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+ verifier.verify();
+ }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java
new file mode 100644
index 0000000..169ea71
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java
@@ -0,0 +1,1025 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.exception.VCardException;
+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.unit_tests.R;
+import com.android.unit_tests.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.io.IOException;
+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() throws IOException, VCardException {
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
+ verifier.verify(R.raw.v21_simple_1, V21);
+ }
+
+ public void testV21SimpleCase1_Type_Generic() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ verifier.addImportVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ .put(StructuredName.DISPLAY_NAME, "Roid Ando");
+ verifier.verify(R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ public void testV21SimpleCase1_Type_Japanese() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ verifier.addImportVerifierElem()
+ .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");
+ verifier.verify(R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ }
+
+ public void testV21SimpleCase2() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ verifier.addImportVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Ando Roid");
+ verifier.verify(R.raw.v21_simple_2, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ public void testV21SimpleCase3() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ verifier.addImportVerifierElem()
+ .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");
+ verifier.verify(R.raw.v21_simple_3, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ /**
+ * Tests ';' is properly handled by VCardParser implementation.
+ */
+ public void testV21BackslashCase_Parsing() throws IOException, VCardException {
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "2.1")
+ .addNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;",
+ Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
+ .addNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
+ verifier.verify(R.raw.v21_backslash, V21);
+ }
+
+ /**
+ * Tests ContactStruct correctly ignores redundant fields in "N" property values and
+ * inserts name related data.
+ */
+ public void testV21BackslashCase() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ verifier.addImportVerifierElem()
+ .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\\\\");
+ verifier.verify(R.raw.v21_backslash, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ public void testOrgBeforTitle() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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);
+ verifier.verify(R.raw.v21_org_before_title, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ public void testTitleBeforOrg() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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);
+ verifier.verify(R.raw.v21_title_before_org, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ /**
+ * 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() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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);
+ verifier.verify(R.raw.v21_pref_handling, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ /**
+ * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser.
+ */
+ public void testV21ComplicatedCase_Parsing() throws IOException, VCardException {
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "2.1")
+ .addNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao",
+ Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao"))
+ .addNodeWithOrder("FN", "Joe Due")
+ .addNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
+ Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper"))
+ .addNodeWithOrder("ROLE", "Fish Cake Keeper!")
+ .addNodeWithOrder("TITLE", "Shrimp Man")
+ .addNodeWithOrder("X-CLASS", "PUBLIC")
+ .addNodeWithOrder("TEL", "(111) 555-1212", new TypeSet("WORK", "VOICE"))
+ .addNodeWithOrder("TEL", "(404) 555-1212", new TypeSet("HOME", "VOICE"))
+ .addNodeWithOrder("TEL", "0311111111", new TypeSet("CELL"))
+ .addNodeWithOrder("TEL", "0322222222", new TypeSet("VIDEO"))
+ .addNodeWithOrder("TEL", "0333333333", new TypeSet("VOICE"))
+ .addNodeWithOrder("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)
+ .addNodeWithOrder("LABEL",
+ "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, mContentValuesForQP, new TypeSet("WORK"), null)
+ .addNodeWithOrder("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)
+ .addNodeWithOrder("LABEL",
+ "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, mContentValuesForQP,
+ new TypeSet("HOME"), null)
+ .addNodeWithOrder("EMAIL", "forrestgump@walladalla.com",
+ new TypeSet("PREF", "INTERNET"))
+ .addNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL"))
+ .addNodeWithOrder("NOTE", "The following note is the example from RFC 2045.")
+ .addNodeWithOrder("NOTE",
+ "Now's the time for all folk to come to the aid of their country.",
+ null, null, mContentValuesForQP, null, null)
+ .addNodeWithOrder("PHOTO", null,
+ null, sPhotoByteArrayForComplicatedCase, mContentValuesForBase64V21,
+ new TypeSet("JPEG"), null)
+ .addNodeWithOrder("X-ATTRIBUTE", "Some String")
+ .addNodeWithOrder("BDAY", "19800101")
+ .addNodeWithOrder("GEO", "35.6563854,139.6994233")
+ .addNodeWithOrder("URL", "http://www.example.com/")
+ .addNodeWithOrder("REV", "20080424T195243Z");
+ verifier.verify(R.raw.v21_complicated, V21);
+ }
+
+ /**
+ * Checks ContactStruct correctly inserts values in a complicated vCard
+ * into ContentResolver.
+ */
+ public void testV21ComplicatedCase() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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);
+ verifier.verify(R.raw.v21_complicated, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ public void testV30Simple_Parsing() throws IOException, VCardException {
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "3.0")
+ .addNodeWithOrder("FN", "And Roid")
+ .addNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", ""))
+ .addNodeWithOrder("ORG", "Open;Handset; Alliance",
+ Arrays.asList("Open", "Handset", " Alliance"))
+ .addNodeWithOrder("SORT-STRING", "android")
+ .addNodeWithOrder("TEL", "0300000000", new TypeSet("PREF", "VOICE"))
+ .addNodeWithOrder("CLASS", "PUBLIC")
+ .addNodeWithOrder("X-GNO", "0")
+ .addNodeWithOrder("X-GN", "group0")
+ .addNodeWithOrder("X-REDUCTION", "0")
+ .addNodeWithOrder("REV", "20081031T065854Z");
+ verifier.verify(R.raw.v30_simple, V30);
+ }
+
+ public void testV30Simple() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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);
+ verifier.verify(R.raw.v30_simple, VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+ }
+
+ public void testV21Japanese1_Parsing() throws IOException, VCardException {
+ // 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.
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
+ .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addNodeWithOrder("TEL", "0300000000", null, null, null,
+ new TypeSet("VOICE", "PREF"), null);
+ verifier.verify(R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ }
+
+ private void testV21Japanese1Common(int resId, int vcardType, boolean japanese)
+ throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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_FAMILY_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);
+ verifier.verify(resId, vcardType);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}.
+ */
+ public void testV21Japanese1_Type_Generic_Utf8() throws IOException, VCardException {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}.
+ */
+ public void testV21Japanese1_Type_Japanese_Sjis() throws IOException, VCardException {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}.
+ * since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
+ */
+ public void testV21Japanese1_Type_Japanese_Utf8() throws IOException, VCardException {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8, true);
+ }
+
+ public void testV21Japanese2_Parsing() throws IOException, VCardException {
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "2.1")
+ .addNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
+ Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
+ "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
+ null, null, mContentValuesForSJis, null, null)
+ .addNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addNodeWithOrder("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)
+ .addNodeWithOrder("NOTE", "\u30E1\u30E2", null, null,
+ mContentValuesForQPAndSJis, null, null);
+ verifier.verify(R.raw.v21_japanese_2, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ }
+
+ public void testV21Japanese2_Type_Generic_Utf8() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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.NEIGHBORHOOD,
+ "\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");
+ verifier.verify(R.raw.v21_japanese_2, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ }
+
+ public void testV21MultipleEntryCase_Parse() throws IOException, VCardException {
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "2.1")
+ .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addNodeWithOrder("TEL", "9", new TypeSet("X-NEC-SECRET"))
+ .addNodeWithOrder("TEL", "10", new TypeSet("X-NEC-HOTEL"))
+ .addNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL"))
+ .addNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME"));
+
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "2.1")
+ .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addNodeWithOrder("TEL", "13", new TypeSet("MODEM"))
+ .addNodeWithOrder("TEL", "14", new TypeSet("PAGER"))
+ .addNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY"))
+ .addNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL"));
+
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "2.1")
+ .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addNodeWithOrder("TEL", "17", new TypeSet("X-NEC-BOY"))
+ .addNodeWithOrder("TEL", "18", new TypeSet("X-NEC-FRIEND"))
+ .addNodeWithOrder("TEL", "19", new TypeSet("X-NEC-PHS"))
+ .addNodeWithOrder("TEL", "20", new TypeSet("X-NEC-RESTAURANT"));
+ verifier.verify(R.raw.v21_multiple_entry, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ }
+
+ public void testV21MultipleEntryCase() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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_FAMILY_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 = verifier.addImportVerifierElem();
+ 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_FAMILY_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 = verifier.addImportVerifierElem();
+ 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_FAMILY_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");
+ verifier.verify(R.raw.v21_multiple_entry, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ }
+
+ public void testPagerV30_Parse() throws IOException, VCardException {
+ PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithOrder("VERSION", "3.0")
+ .addNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
+ .addNodeWithOrder("TEL", "6101231234@pagersample.com",
+ new TypeSet("WORK", "MSG", "PAGER"));
+ verifier.verify(R.raw.v30_comma_separated, VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+ }
+
+ public void testPagerV30() throws IOException, VCardException {
+ ImportVerifier verifier = new ImportVerifier();
+ ImportVerifierElem elem = verifier.addImportVerifierElem();
+ 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");
+ verifier.verify(R.raw.v30_comma_separated, VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+ }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java
new file mode 100644
index 0000000..4b65008
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.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.unit_tests.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+
+import com.android.unit_tests.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+public class VCardJapanizationTests extends VCardTestsBase {
+ private void testNameUtf8Common(int vcardType) {
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(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");
+
+ VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
+ ContentValues contentValues =
+ (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithoutOrder("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+ contentValues)
+ .addNodeWithoutOrder("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);
+ verifier.verify();
+ }
+
+ public void testNameUtf8V21() {
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ }
+
+ public void testNameUtf8V30() {
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ }
+
+ public void testNameShiftJis() {
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(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");
+
+ VCardVerifier verifier = new VCardVerifier(resolver,
+ VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithoutOrder("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+ mContentValuesForSJis)
+ .addNodeWithoutOrder("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);
+ verifier.verify();
+ }
+
+ /**
+ * DoCoMo phones require all name elements should be in "family name" field.
+ */
+ public void testNameDoCoMo() {
+ ExportTestResolver resolver = new ExportTestResolver();
+ resolver.buildContactEntry().buildData(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");
+
+ VCardVerifier verifier = new VCardVerifier(resolver,
+ VCardConfig.VCARD_TYPE_DOCOMO);
+ final String fullName = "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D";
+ verifier.addPropertyNodesVerifierElem()
+ .addNodeWithoutOrder("N", fullName + ";;;;",
+ Arrays.asList(fullName, "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addNodeWithoutOrder("FN", fullName, mContentValuesForSJis)
+ .addNodeWithoutOrder("SOUND", ";;;;", new TypeSet("X-IRMC-N"))
+ .addNodeWithoutOrder("TEL", "", new TypeSet("HOME"))
+ .addNodeWithoutOrder("EMAIL", "", new TypeSet("HOME"))
+ .addNodeWithoutOrder("ADR", "", new TypeSet("HOME"))
+ .addNodeWithoutOrder("X-CLASS", "PUBLIC")
+ .addNodeWithoutOrder("X-REDUCTION", "")
+ .addNodeWithoutOrder("X-NO", "")
+ .addNodeWithoutOrder("X-DCM-HMN-MODE", "");
+ verifier.verify();
+ }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java
new file mode 100644
index 0000000..f3d1c5e
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.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.unit_tests.vcard;
+
+import com.android.unit_tests.AndroidTests;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+import junit.framework.TestSuite;
+
+public class VCardTestSuite extends TestSuite {
+ public static TestSuite suite() {
+ TestSuiteBuilder suiteBuilder = new TestSuiteBuilder(AndroidTests.class);
+ suiteBuilder.includeAllPackagesUnderHere();
+ return suiteBuilder.build();
+ }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java
deleted file mode 100644
index 7589ba8..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java
+++ /dev/null
@@ -1,923 +0,0 @@
-/*
- * 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.unit_tests.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.ContactStruct;
-import android.pim.vcard.EntryHandler;
-import android.pim.vcard.VCardParser_V21;
-import android.pim.vcard.VCardParser_V30;
-import android.pim.vcard.exception.VCardException;
-import android.test.AndroidTestCase;
-
-import com.android.unit_tests.R;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-
-public class VCardTests extends AndroidTestCase {
-
- // TODO: Use EntityIterator, which is added in Eclair.
- private static class EntryHolder implements EntryHandler {
- public List<ContactStruct> contacts = new ArrayList<ContactStruct>();
- public void onParsingStart() {
- }
- public void onEntryCreated(ContactStruct contactStruct) {
- contacts.add(contactStruct);
- }
- public void onParsingEnd() {
- }
- }
- /*
- static void verify(ContactStruct expected, ContactStruct actual) {
- if (!equalsString(expected.getName(), actual.getName())) {
- fail(String.format("Names do not equal: \"%s\" != \"%s\"",
- expected.getName(), actual.getName()));
- }
- if (!equalsString(
- expected.getPhoneticName(), actual.getPhoneticName())) {
- fail(String.format("Phonetic names do not equal: \"%s\" != \"%s\"",
- expected.getPhoneticName(), actual.getPhoneticName()));
- }
- {
- final byte[] expectedPhotoBytes = expected.getPhotoBytes();
- final byte[] actualPhotoBytes = actual.getPhotoBytes();
- if (!((expectedPhotoBytes == null && actualPhotoBytes == null) ||
- Arrays.equals(expectedPhotoBytes, actualPhotoBytes))) {
- fail("photoBytes is not equal.");
- }
- }
- verifyInternal(expected.getNotes(), actual.getNotes(), "notes");
- verifyInternal(expected.getPhoneList(), actual.getPhoneList(), "phones");
- verifyInternal(expected.getContactMethodList(), actual.getContactMethodList(),
- "contact lists");
- verifyInternal(expected.getOrganizationList(), actual.getOrganizationList(),
- "organizations");
- {
- final Map<String, List<String>> expectedMap =
- expected.getExtensionMap();
- final Map<String, List<String>> actualMap =
- actual.getExtensionMap();
- if (verifySize((expectedMap == null ? 0 : expectedMap.size()),
- (actualMap == null ? 0 : actualMap.size()), "extensions") > 0) {
- for (String key : expectedMap.keySet()) {
- if (!actualMap.containsKey(key)) {
- fail(String.format(
- "Actual does not have %s extension while expected has",
- key));
- }
- final List<String> expectedList = expectedMap.get(key);
- final List<String> actualList = actualMap.get(key);
- verifyInternal(expectedList, actualList,
- String.format("extension \"%s\"", key));
- }
- }
- }
- }
-
- 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);
- }
- }
-
- private static int verifySize(int expectedSize, int actualSize, String name) {
- if (expectedSize != actualSize) {
- fail(String.format("Size of %s is different: %d != %d",
- name, expectedSize, actualSize));
- }
- return expectedSize;
- }
-
- private static <T> void verifyInternal(final List<T> expected, final List<T> actual,
- String name) {
- if(verifySize((expected == null ? 0 : expected.size()),
- (actual == null ? 0 : actual.size()), name) > 0) {
- int size = expected.size();
- for (int i = 0; i < size; i++) {
- final T expectedObj = expected.get(i);
- final T actualObj = actual.get(i);
- if (!expected.equals(actual)) {
- fail(String.format("The %i %s are different: %s != %s",
- i, name, expectedObj, actualObj));
- }
- }
- }
- }*/
-
- private class PropertyNodesVerifier {
- private HashMap<String, ArrayList<PropertyNode>> mPropertyNodeMap;
- public PropertyNodesVerifier(PropertyNode... nodes) {
- mPropertyNodeMap = new HashMap<String, ArrayList<PropertyNode>>();
- for (PropertyNode propertyNode : nodes) {
- String propName = propertyNode.propName;
- ArrayList<PropertyNode> expectedNodes =
- mPropertyNodeMap.get(propName);
- if (expectedNodes == null) {
- expectedNodes = new ArrayList<PropertyNode>();
- mPropertyNodeMap.put(propName, expectedNodes);
- }
- expectedNodes.add(propertyNode);
- }
- }
-
- public void verify(VNode vnode) {
- for (PropertyNode propertyNode : vnode.propList) {
- String propName = propertyNode.propName;
- ArrayList<PropertyNode> nodes = mPropertyNodeMap.get(propName);
- if (nodes == null) {
- fail("Unexpected propName \"" + propName + "\" exists.");
- }
- boolean successful = false;
- int size = nodes.size();
- for (int i = 0; i < size; i++) {
- PropertyNode expectedNode = nodes.get(i);
- if (expectedNode.propName.equals(propName)) {
- if (expectedNode.equals(propertyNode)) {
- successful = true;
- nodes.remove(i);
- if (nodes.size() == 0) {
- mPropertyNodeMap.remove(propName);
- }
- break;
- } else {
- fail("Property \"" + propName + "\" has wrong value.\n"
- + "expected: " + expectedNode.toString()
- + "\n actual: " + propertyNode.toString());
- }
- }
- }
- if (!successful) {
- fail("Unexpected property \"" + propName + "\" exists.");
- }
- }
- if (mPropertyNodeMap.size() != 0) {
- ArrayList<String> expectedProps = new ArrayList<String>();
- for (ArrayList<PropertyNode> nodes : mPropertyNodeMap.values()) {
- for (PropertyNode node : nodes) {
- expectedProps.add(node.propName);
- }
- }
- fail("expected props " + Arrays.toString(expectedProps.toArray()) +
- " was not found");
- }
- }
- }
-
- /*
- public void testV21SimpleCase1_1() throws IOException, VCardException {
- VCardParser parser = new VCardParser_V21();
- VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH);
- EntryHolder holder = new EntryHolder();
- builder.addEntryHandler(holder);
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, holder.contacts.size());
- verify(new ContactStruct("Roid Ando", null,
- null, null, null, null, null, null),
- holder.contacts.get(0));
- }
-
- public void testV21SimpleCase1_2() throws IOException, VCardException {
- VCardParser parser = new VCardParser_V21();
- VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_JAPANESE);
- EntryHolder holder = new EntryHolder();
- builder.addEntryHandler(holder);
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, holder.contacts.size());
- verify(new ContactStruct("Ando Roid", null,
- null, null, null, null, null, null),
- holder.contacts.get(0));
- }
-
- public void testV21SimpleCase2() throws IOException, VCardException {
- VCardParser parser = new VCardParser_V21();
- VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH);
- EntryHolder holder = new EntryHolder();
- builder.addEntryHandler(holder);
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_2);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, holder.contacts.size());
- verify(new ContactStruct("Ando Roid", null,
- null, null, null, null, null, null),
- holder.contacts.get(0));
- }
-
- public void testV21SimpleCase3() throws IOException, VCardException {
- VCardParser parser = new VCardParser_V21();
- VCardDataBuilder builder1 = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH);
- EntryHolder holder = new EntryHolder();
- builder1.addEntryHandler(holder);
- VNodeBuilder builder2 = new VNodeBuilder();
- VCardBuilderCollection collection =
- new VCardBuilderCollection(
- new ArrayList<VCardBuilder>(Arrays.asList(builder1, builder2)));
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_3);
- assertEquals(true, parser.parse(is,"ISO-8859-1", collection));
- is.close();
-
- assertEquals(1, builder2.vNodeList.size());
- VNode vnode = builder2.vNodeList.get(0);
- PropertyNodesVerifier verifier = new PropertyNodesVerifier(
- new PropertyNode("N", "Ando;Roid;",
- Arrays.asList("Ando", "Roid", ""),
- null, null, null, null),
- new PropertyNode("FN", "Ando Roid",
- null, null, null, null, null));
- verifier.verify(vnode);
-
- // FN is prefered.
- assertEquals(1, holder.contacts.size());
- ContactStruct actual = holder.contacts.get(0);
- verify(new ContactStruct("Ando Roid", null,
- null, null, null, null, null, null),
- actual);
- }*/
-
- public void testV21BackslashCase() throws IOException, VCardException {
- VCardParser_V21 parser = new VCardParser_V21();
- VNodeBuilder builder = new VNodeBuilder();
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_backslash);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, builder.vNodeList.size());
- PropertyNodesVerifier verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "2.1",
- null, null, null, null, null),
- new PropertyNode("N", ";A;B\\;C\\;;D;:E;\\\\;",
- Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""),
- null, null, null, null),
- new PropertyNode("FN", "A;B\\C\\;D:E\\\\",
- null, null, null, null, null));
- verifier.verify(builder.vNodeList.get(0));
- }
-
- public void testV21ComplicatedCase() throws IOException, VCardException {
- VCardParser_V21 parser = new VCardParser_V21();
- VNodeBuilder builder = new VNodeBuilder();
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_complicated);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, builder.vNodeList.size());
- ContentValues contentValuesForQP = new ContentValues();
- contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
- ContentValues contentValuesForPhoto = new ContentValues();
- contentValuesForPhoto.put("ENCODING", "BASE64");
- // 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...
- int[] photoIntArray = {
- 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};
- int length = photoIntArray.length;
- byte[] photoByteArray = new byte[length];
- for (int i = 0; i < length; i++) {
- photoByteArray[i] = (byte)photoIntArray[i];
- }
- PropertyNodesVerifier verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "2.1",
- null, null, null, null, null),
- new PropertyNode("N", "Gump;Forrest;Hoge;Pos;Tao",
- Arrays.asList("Gump", "Forrest",
- "Hoge", "Pos", "Tao"),
- null, null, null, null),
- new PropertyNode("FN", "Joe Due",
- null, null, null, null, null),
- new PropertyNode("ORG",
- "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
- Arrays.asList("Gump Shrimp Co.",
- "Sales Dept.;Manager",
- "Fish keeper"),
- null, null, null, null),
- new PropertyNode("ROLE", "Fish Cake Keeper!",
- null, null, null, null, null),
- new PropertyNode("TITLE", "Shrimp Man",
- null, null, null, null, null),
- new PropertyNode("X-CLASS", "PUBLIC",
- null, null, null, null, null),
- new PropertyNode("TEL", "(111) 555-1212",
- null, null, null,
- new HashSet<String>(Arrays.asList("WORK", "VOICE")), null),
- new PropertyNode("TEL", "(404) 555-1212",
- null, null, null,
- new HashSet<String>(Arrays.asList("HOME", "VOICE")), null),
- new PropertyNode("TEL", "0311111111",
- null, null, null,
- new HashSet<String>(Arrays.asList("CELL")), null),
- new PropertyNode("TEL", "0322222222",
- null, null, null,
- new HashSet<String>(Arrays.asList("VIDEO")), null),
- new PropertyNode("TEL", "0333333333",
- null, null, null,
- new HashSet<String>(Arrays.asList("VOICE")), null),
- new PropertyNode("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 HashSet<String>(Arrays.asList("WORK")), null),
- new PropertyNode("LABEL",
- "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited States of America",
- null, null, contentValuesForQP,
- new HashSet<String>(Arrays.asList("WORK")), null),
- new PropertyNode("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 HashSet<String>(Arrays.asList("HOME")), null),
- new PropertyNode("LABEL",
- "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited States of America",
- null, null, contentValuesForQP,
- new HashSet<String>(Arrays.asList("HOME")), null),
- new PropertyNode("EMAIL", "forrestgump@walladalla.com",
- null, null, null,
- new HashSet<String>(Arrays.asList("PREF", "INTERNET")), null),
- new PropertyNode("EMAIL", "cell@example.com",
- null, null, null,
- new HashSet<String>(Arrays.asList("CELL")), null),
- new PropertyNode("NOTE", "The following note is the example from RFC 2045.",
- null, null, null, null, null),
- new PropertyNode("NOTE",
- "Now's the time for all folk to come to the aid of their country.",
- null, null, contentValuesForQP, null, null),
- new PropertyNode("PHOTO", null,
- null, photoByteArray, contentValuesForPhoto,
- new HashSet<String>(Arrays.asList("JPEG")), null),
- new PropertyNode("X-ATTRIBUTE", "Some String",
- null, null, null, null, null),
- new PropertyNode("BDAY", "19800101",
- null, null, null, null, null),
- new PropertyNode("GEO", "35.6563854,139.6994233",
- null, null, null, null, null),
- new PropertyNode("URL", "http://www.example.com/",
- null, null, null, null, null),
- new PropertyNode("REV", "20080424T195243Z",
- null, null, null, null, null));
- verifier.verify(builder.vNodeList.get(0));
- }
-
- public void testV21Japanese1() throws IOException, VCardException {
- VCardParser_V21 parser = new VCardParser_V21();
- VNodeBuilder builder = new VNodeBuilder();
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_1);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, builder.vNodeList.size());
- ContentValues contentValuesForShiftJis = new ContentValues();
- contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
- ContentValues contentValuesForQP = new ContentValues();
- contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
- contentValuesForQP.put("CHARSET", "SHIFT_JIS");
- // 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.
- PropertyNodesVerifier verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "2.1",
- null, null, null, null, null),
- new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
- Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
- null, contentValuesForShiftJis, null, null),
- new PropertyNode("SOUND",
- "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
- null, null, contentValuesForShiftJis,
- new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
- new PropertyNode("TEL", "0300000000",
- null, null, null,
- new HashSet<String>(Arrays.asList("VOICE", "PREF")), null));
- verifier.verify(builder.vNodeList.get(0));
- }
-
- public void testV21Japanese2() throws IOException, VCardException {
- VCardParser_V21 parser = new VCardParser_V21();
- VNodeBuilder builder = new VNodeBuilder();
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_2);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, builder.vNodeList.size());
- ContentValues contentValuesForShiftJis = new ContentValues();
- contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
- ContentValues contentValuesForQP = new ContentValues();
- contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
- contentValuesForQP.put("CHARSET", "SHIFT_JIS");
- PropertyNodesVerifier verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "2.1",
- null, null, null, null, null),
- new PropertyNode("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
- Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
- "", "", ""),
- null, contentValuesForShiftJis, null, null),
- new PropertyNode("FN",
- "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
- null, null, contentValuesForShiftJis, null, null),
- new PropertyNode("SOUND",
- ("\uFF71\uFF9D\uFF84\uFF9E\uFF73" +
- ";\uFF9B\uFF72\uFF84\uFF9E\u0031;;;"),
- null, null, contentValuesForShiftJis,
- new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
- new PropertyNode("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, contentValuesForQP,
- new HashSet<String>(Arrays.asList("HOME")), null),
- new PropertyNode("NOTE", "\u30E1\u30E2",
- null, null, contentValuesForQP, null, null));
- verifier.verify(builder.vNodeList.get(0));
- }
-
- public void testV21MultipleEntryCase() throws IOException, VCardException {
- VCardParser_V21 parser = new VCardParser_V21();
- VNodeBuilder builder = new VNodeBuilder();
- InputStream is = getContext().getResources().openRawResource(R.raw.v21_multiple_entry);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(3, builder.vNodeList.size());
- ContentValues contentValuesForShiftJis = new ContentValues();
- contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
- PropertyNodesVerifier verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "2.1",
- null, null, null, null, null),
- new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
- Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
- null, contentValuesForShiftJis, null, null),
- new PropertyNode("SOUND",
- "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
- null, null, contentValuesForShiftJis,
- new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
- new PropertyNode("TEL", "9",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-SECRET")), null),
- new PropertyNode("TEL", "10",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-HOTEL")), null),
- new PropertyNode("TEL", "11",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-SCHOOL")), null),
- new PropertyNode("TEL", "12",
- null, null, null,
- new HashSet<String>(Arrays.asList("FAX", "HOME")), null));
- verifier.verify(builder.vNodeList.get(0));
-
- verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "2.1",
- null, null, null, null, null),
- new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
- Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
- null, contentValuesForShiftJis, null, null),
- new PropertyNode("SOUND",
- "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
- null, null, contentValuesForShiftJis,
- new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
- new PropertyNode("TEL", "13",
- null, null, null,
- new HashSet<String>(Arrays.asList("MODEM")), null),
- new PropertyNode("TEL", "14",
- null, null, null,
- new HashSet<String>(Arrays.asList("PAGER")), null),
- new PropertyNode("TEL", "15",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-FAMILY")), null),
- new PropertyNode("TEL", "16",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-GIRL")), null));
- verifier.verify(builder.vNodeList.get(1));
- verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "2.1",
- null, null, null, null, null),
- new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
- Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
- null, contentValuesForShiftJis, null, null),
- new PropertyNode("SOUND",
- "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
- null, null, contentValuesForShiftJis,
- new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
- new PropertyNode("TEL", "17",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-BOY")), null),
- new PropertyNode("TEL", "18",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-FRIEND")), null),
- new PropertyNode("TEL", "19",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-PHS")), null),
- new PropertyNode("TEL", "20",
- null, null, null,
- new HashSet<String>(Arrays.asList("X-NEC-RESTAURANT")), null));
- verifier.verify(builder.vNodeList.get(2));
- }
-
- public void testV30SimpleCase() throws IOException, VCardException {
- VCardParser_V21 parser = new VCardParser_V30();
- VNodeBuilder builder = new VNodeBuilder();
- InputStream is = getContext().getResources().openRawResource(R.raw.v30_simple);
- assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
- is.close();
- assertEquals(1, builder.vNodeList.size());
- PropertyNodesVerifier verifier = new PropertyNodesVerifier(
- new PropertyNode("VERSION", "3.0",
- null, null, null, null, null),
- new PropertyNode("FN", "And Roid",
- null, null, null, null, null),
- new PropertyNode("N", "And;Roid;;;",
- Arrays.asList("And", "Roid", "", "", ""),
- null, null, null, null),
- new PropertyNode("ORG", "Open;Handset; Alliance",
- Arrays.asList("Open", "Handset", " Alliance"),
- null, null, null, null),
- new PropertyNode("SORT-STRING", "android", null, null, null, null, null),
- new PropertyNode("TEL", "0300000000",
- null, null, null,
- new HashSet<String>(Arrays.asList("PREF", "VOICE")), null),
- new PropertyNode("CLASS", "PUBLIC", null, null, null, null, null),
- new PropertyNode("X-GNO", "0", null, null, null, null, null),
- new PropertyNode("X-GN", "group0", null, null, null, null, null),
- new PropertyNode("X-REDUCTION", "0",
- null, null, null, null, null),
- new PropertyNode("REV", "20081031T065854Z",
- null, null, null, null, null));
- verifier.verify(builder.vNodeList.get(0));
- }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java
new file mode 100644
index 0000000..bd4d13a
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java
@@ -0,0 +1,970 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.pim.vcard.ContactStruct;
+import android.pim.vcard.EntryCommitter;
+import android.pim.vcard.EntryHandler;
+import android.pim.vcard.VCardBuilder;
+import android.pim.vcard.VCardBuilderCollection;
+import android.pim.vcard.VCardComposer;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardDataBuilder;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+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.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.mock.MockCursor;
+import android.text.TextUtils;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+/**
+ * Almost a dead copy of android.test.mock.MockContentProvider, but different in that this
+ * class extends ContentProvider, not implementing IContentProvider,
+ * so that MockContentResolver is able to accept this class :(
+ */
+class MockContentProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public int bulkInsert(Uri url, ContentValues[] initialValues) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public IBulkCursor bulkQuery(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder, IContentObserver observer,
+ CursorWindow window) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ @SuppressWarnings("unused")
+ public int delete(Uri url, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public String getType(Uri url) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri url, String mode) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public EntityIterator queryEntities(Uri url, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+}
+
+class CustomMockContext extends MockContext {
+ final ContentResolver mResolver;
+ public CustomMockContext(ContentResolver resolver) {
+ mResolver = resolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+}
+
+/**
+ * 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_UTF8;
+ public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8;
+
+ // 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;
+
+ public VCardTestsBase() {
+ super();
+ 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");
+ }
+
+ public class ImportTestResolver extends MockContentResolver {
+ ImportTestProvider mProvider = new ImportTestProvider();
+ @Override
+ public ContentProviderResult[] applyBatch(String authority,
+ ArrayList<ContentProviderOperation> operations) {
+ equalsString(authority, RawContacts.CONTENT_URI.toString());
+ return mProvider.applyBatch(operations);
+ }
+
+ public void addExpectedContentValues(ContentValues expectedContentValues) {
+ mProvider.addExpectedContentValues(expectedContentValues);
+ }
+
+ public void verify() {
+ mProvider.verify();
+ }
+ }
+
+ private static 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));
+
+ public class ImportTestProvider extends MockContentProvider {
+ final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
+
+ public ImportTestProvider() {
+ 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)) {
+ 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) {
+ 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)) {
+ assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
+ assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
+ } else if (uri.equals(Data.CONTENT_URI)) {
+ final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
+ if (!sKnownMimeTypeSet.contains(mimeType)) {
+ 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()) {
+ 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)) {
+ assertTrue(contentValuesCollection.remove(expectedContentValues));
+ checked = true;
+ break;
+ }
+ }
+ if (!checked) {
+ final String failMsg =
+ "Unexpected ContentValues for MimeType " + mimeType
+ + ": " + actualContentValues;
+ fail(failMsg);
+ }
+ } else {
+ fail("Unexpected Uri has come: " + uri);
+ }
+ } // for (int i = 0; i < size; i++) {
+ return null;
+ }
+
+ 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();
+ fail(failMsg);
+ }
+ }
+ }
+
+ class ImportVerifierElem {
+ private final ImportTestResolver mResolver;
+ private final EntryHandler mHandler;
+
+ public ImportVerifierElem() {
+ mResolver = new ImportTestResolver();
+ mHandler = new EntryCommitter(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(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(true); // use StrictParsing
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ VCardDataBuilder builder =
+ new VCardDataBuilder(null, null, false, 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.onParsingStart();
+ }
+
+ public void onEntryCreated(ContactStruct entry) {
+ mHandler.onEntryCreated(entry);
+ }
+
+ public void onParsingEnd() {
+ mHandler.onParsingEnd();
+ }
+ }
+
+ class ImportVerifier implements EntryHandler {
+ private List<ImportVerifierElem> mImportVerifierElemList =
+ new ArrayList<ImportVerifierElem>();
+ private int mIndex;
+
+ public ImportVerifierElem addImportVerifierElem() {
+ ImportVerifierElem importVerifier = new ImportVerifierElem();
+ mImportVerifierElemList.add(importVerifier);
+ return importVerifier;
+ }
+
+ public void verify(int resId, int vCardType) throws IOException, VCardException {
+ verify(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(true); // use StrictParsing
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ VCardDataBuilder builder =
+ new VCardDataBuilder(null, null, false, vCardType, null);
+ builder.addEntryHandler(this);
+ try {
+ vCardParser.parse(is, builder);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public void onParsingStart() {
+ for (ImportVerifierElem elem : mImportVerifierElemList) {
+ elem.onParsingStart();
+ }
+ }
+
+ public void onEntryCreated(ContactStruct entry) {
+ assertTrue(mIndex < mImportVerifierElemList.size());
+ mImportVerifierElemList.get(mIndex).onEntryCreated(entry);
+ mIndex++;
+ }
+
+ public void onParsingEnd() {
+ for (ImportVerifierElem elem : mImportVerifierElemList) {
+ elem.onParsingEnd();
+ elem.verifyResolver();
+ }
+ }
+ }
+
+ public class ExportTestResolver extends MockContentResolver {
+ ExportTestProvider mProvider = new ExportTestProvider();
+ public ExportTestResolver() {
+ addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
+ addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
+ }
+
+ public ContactEntry buildContactEntry() {
+ return mProvider.buildInputEntry();
+ }
+ }
+
+ public 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 reset() {
+ mIterator = mEntityList.iterator();
+ }
+
+ public void close() {
+ }
+ }
+
+ /**
+ * Represents one contact, which should contain multiple ContentValues like
+ * StructuredName, Email, etc.
+ */
+ static class ContactEntry {
+ private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
+
+ public ContentValuesBuilder buildData(String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Data.MIMETYPE, mimeType);
+ mContentValuesList.add(contentValues);
+ return new ContentValuesBuilder(contentValues);
+ }
+
+ public List<ContentValues> getList() {
+ return mContentValuesList;
+ }
+ }
+
+ class ExportTestProvider extends MockContentProvider {
+ ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
+
+ public ContactEntry buildInputEntry() {
+ ContactEntry contactEntry = new ContactEntry();
+ mContactEntryList.add(contactEntry);
+ return contactEntry;
+ }
+
+ @Override
+ public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
+ String sortOrder) {
+ assertTrue(uri != null);
+ assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
+ final String authority = uri.getAuthority();
+ assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
+ assertTrue((Data.CONTACT_ID + "=?").equals(selection));
+ assertEquals(1, selectionArgs.length);
+ int id = Integer.parseInt(selectionArgs[0]);
+ 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) {
+ assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
+ // In this test, following arguments are not supported.
+ assertNull(selection);
+ assertNull(selectionArgs);
+ 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) {
+ assertEquals(Contacts._ID, columnName);
+ return 0;
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ assertEquals(0, columnIndex);
+ assertTrue(mCurrentPosition >= 0
+ && mCurrentPosition < mContactEntryList.size());
+ return mCurrentPosition;
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ return String.valueOf(getInt(columnIndex));
+ }
+
+ @Override
+ public void close() {
+ }
+ };
+ }
+ }
+
+ class LineVerifierElem {
+ private final List<String> mExpectedLineList = new ArrayList<String>();
+ private final boolean mIsV30;
+
+ public LineVerifierElem(boolean isV30) {
+ mIsV30 = isV30;
+ }
+
+ 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;
+ final TestCase testCase = VCardTestsBase.this;
+ 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) {
+ testCase.fail("Multiple \"BEGIN:VCARD\" line found");
+ } else {
+ beginExists = true;
+ continue;
+ }
+ } else if ("END:VCARD".equalsIgnoreCase(line)) {
+ if (endExists) {
+ testCase.fail("Multiple \"END:VCARD\" line found");
+ } else {
+ endExists = true;
+ continue;
+ }
+ } else if (
+ (mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) {
+ if (versionExists) {
+ testCase.fail("Multiple VERSION line + found");
+ } else {
+ versionExists = true;
+ continue;
+ }
+ }
+
+ if (!beginExists) {
+ testCase.fail(
+ "Property other than BEGIN came before BEGIN property: " + line);
+ } else if (endExists) {
+ testCase.fail("Property other than END came after END property: " + line);
+ }
+
+ final int index = mExpectedLineList.indexOf(line);
+ if (index >= 0) {
+ mExpectedLineList.remove(index);
+ } else {
+ testCase.fail("Unexpected line: " + line);
+ }
+ }
+
+ if (!mExpectedLineList.isEmpty()) {
+ StringBuffer buffer = new StringBuffer();
+ for (String expectedLine : mExpectedLineList) {
+ buffer.append(expectedLine);
+ buffer.append("\n");
+ }
+
+ testCase.fail("Expected line(s) not found:" + buffer.toString());
+ }
+ }
+ }
+
+ class LineVerifier implements VCardComposer.OneEntryHandler {
+ private final ArrayList<LineVerifierElem> mLineVerifierElemList;
+ private final boolean mIsV30;
+ private int index;
+
+ public LineVerifier(final boolean isV30) {
+ mLineVerifierElemList = new ArrayList<LineVerifierElem>();
+ mIsV30 = isV30;
+ }
+
+ public LineVerifierElem addLineVerifierElem() {
+ LineVerifierElem lineVerifier = new LineVerifierElem(mIsV30);
+ mLineVerifierElemList.add(lineVerifier);
+ return lineVerifier;
+ }
+
+ public void verify(String vcard) {
+ if (index >= mLineVerifierElemList.size()) {
+ VCardTestsBase.this.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() {
+ }
+ }
+
+ class VCardVerifier {
+ 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 VCardVerifierInternal mVCardVerifierInternal;
+ private final ExportTestResolver mResolver;
+ private final int mVCardType;
+ private final boolean mIsV30;
+
+ // To allow duplication, use list instead of set.
+ // When null, we don't need to do the verification.
+ private PropertyNodesVerifier mPropertyNodesVerifier;
+ private LineVerifier mLineVerificationHandler;
+ private ImportVerifier mImportVerifier;
+
+ public VCardVerifier(ExportTestResolver resolver, int vcardType) {
+ mVCardVerifierInternal = new VCardVerifierInternal();
+ mResolver = resolver;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mVCardType = vcardType;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+ if (mPropertyNodesVerifier == null) {
+ mPropertyNodesVerifier = new PropertyNodesVerifier(VCardTestsBase.this);
+ }
+ PropertyNodesVerifierElem elem =
+ mPropertyNodesVerifier.addPropertyNodesVerifierElem();
+ elem.addNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
+
+ return elem;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierWithEmptyName() {
+ PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
+ if (mIsV30) {
+ elem.addNodeWithOrder("N", "").addNodeWithOrder("FN", "");
+ }
+ return elem;
+ }
+
+ public LineVerifierElem addLineVerifier() {
+ if (mLineVerificationHandler == null) {
+ mLineVerificationHandler = new LineVerifier(mIsV30);
+ }
+ return mLineVerificationHandler.addLineVerifierElem();
+ }
+
+ public ImportVerifierElem addImportVerifier() {
+ if (mImportVerifier == null) {
+ mImportVerifier = new ImportVerifier();
+ }
+
+ return mImportVerifier.addImportVerifierElem();
+ }
+
+ private void verifyOneVCard(final String vcard) {
+ final VCardBuilder builder;
+ if (mImportVerifier != null) {
+ final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
+ final VCardDataBuilder vcardDataBuilder = new VCardDataBuilder();
+ vcardDataBuilder.addEntryHandler(mImportVerifier);
+ if (mPropertyNodesVerifier != null) {
+ builder = new VCardBuilderCollection(Arrays.asList(
+ vcardDataBuilder, mPropertyNodesVerifier));
+ } else {
+ builder = vnodeBuilder;
+ }
+ } else {
+ if (mPropertyNodesVerifier != null) {
+ builder = mPropertyNodesVerifier;
+ } else {
+ return;
+ }
+ }
+
+ final VCardParser parser =
+ (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
+ final TestCase testCase = VCardTestsBase.this;
+
+ InputStream is = null;
+ try {
+ String charset =
+ (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
+ is = new ByteArrayInputStream(vcard.getBytes(charset));
+ testCase.assertEquals(true, parser.parse(is, null, builder));
+ } catch (IOException e) {
+ testCase.fail("Unexpected IOException: " + e.getMessage());
+ } catch (VCardException e) {
+ testCase.fail("Unexpected VCardException: " + e.getMessage());
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public void verify() {
+ VCardComposer composer =
+ new VCardComposer(new CustomMockContext(mResolver), mVCardType);
+ composer.addHandler(mLineVerificationHandler);
+ composer.addHandler(mVCardVerifierInternal);
+ if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
+ fail("init() failed. Reason: " + composer.getErrorReason());
+ }
+ assertFalse(composer.isAfterLast());
+ try {
+ while (!composer.isAfterLast()) {
+ assertTrue(composer.createOneEntry());
+ }
+ } finally {
+ composer.terminate();
+ }
+ }
+ }
+
+ /**
+ * Utility method to print ContentValues whose content is printed with sorted keys.
+ */
+ private static 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 {
+ 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);
+ }
+ 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)) {
+ return false;
+ }
+ } else if (!value.equals(actual.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
index 3eb827b..7587320 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
@@ -18,7 +18,7 @@
import java.util.ArrayList;
/**
- * @hide old class. Just for testing
+ * Previously used in main vCard handling code but now exists only for testing.
*/
public class VNode {
public String VName;
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
index 6d69223..ce4de03 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
@@ -36,7 +36,8 @@
* Maybe several vcard instance, so use vNodeList to store.
* VNode: standy by a vcard instance.
* PropertyNode: standy by a property line of a card.
- * @hide old class, just for testing use
+ *
+ * Previously used in main vCard handling code but now exists only for testing.
*/
public class VNodeBuilder implements VCardBuilder {
static private String LOG_TAG = "VDATABuilder";
@@ -189,6 +190,7 @@
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 =
diff --git a/tests/CoreTests/android/core/MathTest.java b/tests/CoreTests/android/core/MathTest.java
index 64c8c1e..50009db 100644
--- a/tests/CoreTests/android/core/MathTest.java
+++ b/tests/CoreTests/android/core/MathTest.java
@@ -687,8 +687,7 @@
/**
* @tests java.lang.Math#tanh(double)
*/
- // TODO: Known failure, temporarily remove from SmallSuite
- // @SmallTest
+ @SmallTest
public void testTanhD() {
// Test for special situations
assertTrue("Should return NaN", Double.isNaN(Math.tanh(Double.NaN)));
diff --git a/tests/CoreTests/android/core/StrictMathTest.java b/tests/CoreTests/android/core/StrictMathTest.java
index c98c91d..92e6cb6 100644
--- a/tests/CoreTests/android/core/StrictMathTest.java
+++ b/tests/CoreTests/android/core/StrictMathTest.java
@@ -709,8 +709,7 @@
/**
* @tests java.lang.StrictMath#tanh(double)
*/
- // TODO: Known failure, temporarily remove from small suite
- // @SmallTest
+ @SmallTest
public void testTanhD() {
// Test for special situations
assertTrue(Double.isNaN(StrictMath.tanh(Double.NaN)));
diff --git a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
index 20ea4d7..aa2981b 100644
--- a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -25,7 +25,7 @@
public class PhoneNumberUtilsTest extends TestCase {
@SmallTest
- public void testA() throws Exception {
+ public void testExtractNetworkPortion() throws Exception {
assertEquals(
"+17005554141",
PhoneNumberUtils.extractNetworkPortion("+17005554141")
@@ -181,6 +181,68 @@
}
@SmallTest
+ public void testExtractNetworkPortionAlt() throws Exception {
+ assertEquals(
+ "+17005554141",
+ PhoneNumberUtils.extractNetworkPortionAlt("+17005554141")
+ );
+
+ assertEquals(
+ "+17005554141",
+ PhoneNumberUtils.extractNetworkPortionAlt("+1 (700).555-4141")
+ );
+
+ assertEquals(
+ "17005554141",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141")
+ );
+
+ // This may seem wrong, but it's probably ok
+ assertEquals(
+ "17005554141*#",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141*#")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN,1234")
+ );
+
+ assertEquals(
+ "170055541NN",
+ PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN;1234")
+ );
+
+ // An MMI string is unperterbed, even though it contains a
+ // (valid in this case) embedded +
+ assertEquals(
+ "**21**+17005554141#",
+ PhoneNumberUtils.extractNetworkPortionAlt("**21**+17005554141#")
+ );
+
+ assertEquals(
+ "*31#+447966164208",
+ PhoneNumberUtils.extractNetworkPortionAlt("*31#+447966164208")
+ );
+
+ assertEquals(
+ "*31#+447966164208",
+ PhoneNumberUtils.extractNetworkPortionAlt("*31# (+44) 79 6616 4208")
+ );
+
+ assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(""));
+
+ assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(",1234"));
+
+ assertNull(PhoneNumberUtils.extractNetworkPortionAlt(null));
+ }
+
+ @SmallTest
public void testB() throws Exception {
assertEquals("", PhoneNumberUtils.extractPostDialPortion("+17005554141"));
assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-4141"));
@@ -252,6 +314,9 @@
// 444 is not a valid country code, but
// matchIntlPrefixAndCC doesnt know this
assertTrue(PhoneNumberUtils.compare("+444 207 792 3490", "0 207 792 3490"));
+
+ // compare SMS short code
+ assertTrue(PhoneNumberUtils.compare("404-04", "40404"));
}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 73dbb6f..f3738e3 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -70,6 +70,8 @@
boolean releaseWifiLock(IBinder lock);
+ void initializeMulticastFiltering();
+
boolean isMulticastEnabled();
void acquireMulticastLock(IBinder binder, String tag);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 27755ed..178f76e 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1056,4 +1056,17 @@
return false;
}
}
+
+ /**
+ * Initialize the multicast filtering to 'on'
+ * @hide no intent to publish
+ */
+ public boolean initializeMulticastFiltering() {
+ try {
+ mService.initializeMulticastFiltering();
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index fc750e2..1e322bd 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -255,6 +255,8 @@
mWifiStateTracker.notifyDriverStopped();
} else if (state.equals("STARTED")) {
mWifiStateTracker.notifyDriverStarted();
+ } else if (state.equals("HANGED")) {
+ mWifiStateTracker.notifyDriverHung();
}
}
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index b7d3a6e..66552e4 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -87,12 +87,19 @@
/**
* The driver is started or stopped. The object will be the state: true for
* started, false for stopped.
- */
+ */
private static final int EVENT_DRIVER_STATE_CHANGED = 12;
private static final int EVENT_PASSWORD_KEY_MAY_BE_INCORRECT = 13;
private static final int EVENT_MAYBE_START_SCAN_POST_DISCONNECT = 14;
/**
+ * The driver state indication.
+ */
+ private static final int DRIVER_STARTED = 0;
+ private static final int DRIVER_STOPPED = 1;
+ private static final int DRIVER_HUNG = 2;
+
+ /**
* Interval in milliseconds between polling for connection
* status items that are not sent via asynchronous events.
* An example is RSSI (signal strength).
@@ -556,7 +563,7 @@
mRunState = RUN_STATE_STOPPED;
// Send a driver stopped message to our handler
- Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, 0, 0).sendToTarget();
+ Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STOPPED, 0).sendToTarget();
}
/**
@@ -565,9 +572,17 @@
*/
void notifyDriverStarted() {
// Send a driver started message to our handler
- Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, 1, 0).sendToTarget();
+ Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STARTED, 0).sendToTarget();
}
-
+
+ /**
+ * Send the tracker a notification that the Wi-Fi driver has hung and needs restarting.
+ */
+ void notifyDriverHung() {
+ // Send a driver hanged message to our handler
+ Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_HUNG, 0).sendToTarget();
+ }
+
/**
* Set the interval timer for polling connection information
* that is not delivered asynchronously.
@@ -786,16 +801,8 @@
* Filter out multicast packets. This saves battery power, since
* the CPU doesn't have to spend time processing packets that
* are going to end up being thrown away.
- *
- * Note that rather than turn this off directly, we use the
- * public api - this keeps us all in sync - turn multicast on
- * first and then off.. if nobody else wants it on it'll be
- * off then and it's all synchronized within the API.
*/
- WifiManager.MulticastLock l =
- mWM.createMulticastLock("WifiStateTracker");
- l.acquire();
- l.release();
+ mWM.initializeMulticastFiltering();
if (mBluetoothA2dp == null) {
mBluetoothA2dp = new BluetoothA2dp(mContext);
@@ -1157,15 +1164,14 @@
break;
case EVENT_DRIVER_STATE_CHANGED:
- boolean driverStarted = msg.arg1 != 0;
-
// Wi-Fi driver state changed:
- // [31- 1] Reserved for future use
- // [ 0- 0] Driver start (1) or stopped (0)
- eventLogParam = driverStarted ? 1 : 0;
- EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, eventLogParam);
-
- if (driverStarted) {
+ // 0 STARTED
+ // 1 STOPPED
+ // 2 HUNG
+ EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, msg.arg1);
+
+ switch (msg.arg1) {
+ case DRIVER_STARTED:
/**
* Set the number of allowed radio channels according
* to the system setting, since it gets reset by the
@@ -1184,6 +1190,15 @@
}
}
}
+ break;
+ case DRIVER_HUNG:
+ Log.e(TAG, "Wifi Driver reports HUNG - reloading.");
+ /**
+ * restart the driver - toggle off and on
+ */
+ mWM.setWifiEnabled(false);
+ mWM.setWifiEnabled(true);
+ break;
}
noteRunState();
break;