diff options
219 files changed, 15726 insertions, 5751 deletions
@@ -136,6 +136,7 @@ LOCAL_SRC_FILES += \ 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 \ @@ -216,6 +217,7 @@ aidl_files := \ 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 b4161ae..43663bc 100644 --- a/api/current.xml +++ b/api/current.xml @@ -118927,6 +118927,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" @@ -118959,6 +118976,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" @@ -119384,6 +119422,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" @@ -119416,6 +119471,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" @@ -131872,6 +131948,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" @@ -143808,6 +144551,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 @@ status_t SineSource::read( 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 @@ public: } virtual status_t start(MetaData *params) { + mNumFramesOutput = 0; return OK; } @@ -61,6 +64,12 @@ public: 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 @@ public: 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 @@ private: MediaBufferGroup mGroup; int mWidth, mHeight; size_t mSize; + int64_t mNumFramesOutput;; DummySource(const DummySource &); DummySource &operator=(const DummySource &); @@ -88,8 +104,8 @@ private: 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 @@ int main(int argc, char **argv) { 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 @@ int main(int argc, char **argv) { 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 f8bb3c8..3b7cece 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -52,12 +52,8 @@ static int64_t getNowUs() { 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 = ((int64_t)durationUnits * 1000000) / timeScale; + int64_t durationUs; + CHECK(meta->findInt64(kKeyDuration, &durationUs)); sp<OMXCodec> decoder = OMXCodec::Create( client->interface(), meta, false /* createEncoder */, source); @@ -78,20 +74,24 @@ static void playSource(OMXClient *client, const sp<MediaSource> &source) { 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; } @@ -157,6 +157,11 @@ static void playSource(OMXClient *client, const sp<MediaSource> &source) { if (err != OK) { CHECK_EQ(buffer, NULL); + if (err == INFO_FORMAT_CHANGED) { + printf("format changed.\n"); + continue; + } + break; } @@ -283,7 +288,7 @@ int main(int argc, char **argv) { CHECK(service.get() != NULL); - sp<IOMX> omx = service->createOMX(); + sp<IOMX> omx = service->getOMX(); CHECK(omx.get() != NULL); const char *kMimeTypes[] = { @@ -329,11 +334,11 @@ int main(int argc, char **argv) { CHECK(service.get() != NULL); - sp<IOMX> omx = service->createOMX(); + sp<IOMX> omx = service->getOMX(); CHECK(omx.get() != NULL); List<String8> list; - omx->list_nodes(&list); + omx->listNodes(&list); for (List<String8>::iterator it = list.begin(); it != list.end(); ++it) { 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.IBinder; 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.HashMap; 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 @@ public class AccountManagerService // 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService } 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService private void unbind() { if (mAuthenticator != null) { mAuthenticator = null; - mBindHelper.unbind(this); + mContext.unbindService(this); } } @@ -1185,7 +1184,7 @@ public class AccountManagerService 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 class AccountManagerService } } - 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 class AccountManagerService } } + public abstract void run() throws RemoteException; + public void onTimedOut() { IAccountManagerResponse response = getResponseAndClose(); if (response != null) { @@ -1275,6 +1274,39 @@ public class AccountManagerService } } } + + /** + * 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 class AccountManagerService } 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 @@ public class AccountManagerService */ @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 @@ public class AccountManagerService } 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 @@ public class AccountManagerService + ": 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 @@ package android.accounts; 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 @@ public class GrantCredentialsPermissionActivity extends Activity implements View 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); + + findViewById(R.id.allow_button).setOnClickListener(this); + findViewById(R.id.deny_button).setOnClickListener(this); + + LinearLayout packagesListView = (LinearLayout) findViewById(R.id.packages_list); - String[] packageLabels = new String[packages.length]; 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)); + } + + ((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); } - ((ListView) findViewById(R.id.packages_list)).setAdapter( - new PackagesArrayAdapter(this, packageLabels)); + } + + 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 @@ public class GrantCredentialsPermissionActivity extends Activity implements View } /** - * 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.IWifiManager; 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.view.inputmethod.InputMethodManager; 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 @@ class ApplicationContext extends Context { 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 @@ class ApplicationContext extends Context { return getClipboardManager(); } else if (WALLPAPER_SERVICE.equals(name)) { return getWallpaperManager(); + } else if (DROPBOX_SERVICE.equals(name)) { + return getDropBox(); } return null; @@ -1045,7 +1051,7 @@ class ApplicationContext extends Context { } return mVibrator; } - + private AudioManager getAudioManager() { if (mAudioManager == null) { @@ -1054,6 +1060,17 @@ class ApplicationContext extends Context { 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 3fc676b..53bffc1 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -46,7 +46,7 @@ import java.util.UUID; */ 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 abstract class AbstractSyncableContentProvider extends SyncableContentPro 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.Bundle; 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 abstract class AbstractThreadedSyncAdapter { } } } + + 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 @@ public abstract class Context { * @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 @@ public abstract class Context { * @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 @@ oneway interface ISyncAdapter { * @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 abstract class SyncAdapter { 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 @@ public class SyncContext { 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 class SyncContext { 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 @@ class SyncManager implements OnAccountsUpdateListener { 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 @@ class SyncManager implements OnAccountsUpdateListener { 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 @@ abstract class Connection { */ private static final String HTTP_CONNECTION = "http.connection"; - RequestQueue.ConnectionManager mConnectionManager; RequestFeeder mRequestFeeder; /** @@ -104,11 +103,9 @@ abstract class Connection { 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 @@ abstract class Connection { * 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 @@ abstract class Connection { 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 @@ class ConnectionThread extends Thread { 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 @@ import org.apache.http.params.HttpConnectionParams; 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 @@ public class HttpsConnection extends Connection { */ 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 @@ public class HttpsConnection extends Connection { 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 @@ public class HttpsConnection extends Connection { 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 @@ public class RequestHandle { 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 @@ public class RequestHandle { } /** + * 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 @@ public class RequestHandle { 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 class RequestQueue implements RequestFeeder { } 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 @@ public class RequestQueue implements RequestFeeder { 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 @@ public class RequestQueue implements RequestFeeder { 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..052f329 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,29 @@ package android.pim.vcard; 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"; + + // For some historical reason, we often use the term "ATTR"/"attribute" especially toward + // what is called "param" in both vCard specs, while vCard, while vCard importer side uses + // "param". + // + // TODO: Confusing. Fix it. + public static final String ATTR_TYPE = "TYPE"; + // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0 // // e.g. @@ -59,12 +107,19 @@ package android.pim.vcard; public static final String ATTR_TYPE_VOICE = "VOICE"; public static final String ATTR_TYPE_INTERNET = "INTERNET"; + // Abbreviation of "prefered" according to vCard 2.1 specification. + // We interpret this value as "primary" property during import/export. + // + // Note: Both vCard specs does anything about the requirement about this attribute, + // 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 ATTR_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"; + public static final String ATTR_TYPE_TLX = "TLX"; // Telex // Phone types existing in vCard 2.1 but not known to ContactsContract. // TODO: should make parser make these TYPE_CUSTOM. @@ -73,22 +128,42 @@ package android.pim.vcard; public static final String ATTR_TYPE_BBS = "BBS"; public static final String ATTR_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"; + // Attribute for Phones, which are not formally valid in vCard (at least 2.1). + // These types are basically encoded to "X-" attributes when composing vCard. + // Parser passes these when "X-" is added to the attribute or not. + public static final String ATTR_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK"; + public static final String ATTR_PHONE_EXTRA_TYPE_RADIO = "RADIO"; + public static final String ATTR_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD"; + public static final String ATTR_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT"; + // vCard composer translates this type to "WORK" + "PREF". Just for parsing. + public static final String ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN"; + // vCard composer translates this type to "VOICE" Just for parsing. + public static final String ATTR_PHONE_EXTRA_TYPE_OTHER = "OTHER"; + + // Attribute for addresses. + public static final String ATTR_ADR_TYPE_PARCEL = "PARCEL"; + public static final String ATTR_ADR_TYPE_DOM = "DOM"; + public static final String ATTR_ADR_TYPE_INTL = "INTL"; + + // Attribute types not officially valid but used in some vCard exporter. + // Do not use in composer side. + public static final String ATTR_EXTRA_TYPE_COMPANY = "COMPANY"; // DoCoMo specific attribute. 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 interface ImportOnly { + public static final String PROPERTY_X_NICKNAME = "X-NICKNAME"; + // Some device emits this "X-" attribute 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() { } }
\ No newline at end of file diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java index 36e5e23..4069729 100644 --- a/core/java/android/pim/vcard/ContactStruct.java +++ b/core/java/android/pim/vcard/ContactStruct.java @@ -38,6 +38,7 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; import android.telephony.PhoneNumberUtils; +import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.Log; @@ -46,7 +47,6 @@ 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; @@ -55,11 +55,11 @@ import java.util.Map; */ 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 +68,10 @@ public class ContactStruct { 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 +88,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof PhoneData) { + if (!(obj instanceof PhoneData)) { return false; } PhoneData phoneData = (PhoneData)obj; @@ -125,7 +123,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof EmailData) { + if (!(obj instanceof EmailData)) { return false; } EmailData emailData = (EmailData)obj; @@ -202,7 +200,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof PostalData) { + if (!(obj instanceof PostalData)) { return false; } PostalData postalData = (PostalData)obj; @@ -251,87 +249,117 @@ public class ContactStruct { } } - /** - * @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 +370,6 @@ public class ContactStruct { private List<String> mPropertyValueList = new ArrayList<String>(); private byte[] mPropertyBytes; - public Property() { - clear(); - } - public void setPropertyName(final String propertyName) { mPropertyName = propertyName; } @@ -385,6 +409,7 @@ public class ContactStruct { mPropertyName = null; mParameterMap.clear(); mPropertyValueList.clear(); + mPropertyBytes = null; } } @@ -417,21 +442,13 @@ public class ContactStruct { 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 +461,6 @@ public class ContactStruct { } /** - * @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 @@ -643,10 +480,12 @@ public class ContactStruct { } } - PhoneData phoneData = new PhoneData(type, - PhoneNumberUtils.formatNumber(builder.toString()), - label, isPrimary); - + // Use NANP in default when there's no information about locale. + final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ? + PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP); + final String formattedPhoneNumber = + PhoneNumberUtils.formatNumber(builder.toString(), formattingType); + PhoneData phoneData = new PhoneData(type, formattedPhoneNumber, label, isPrimary); mPhoneList.add(phoneData); } @@ -666,24 +505,122 @@ public class ContactStruct { 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 +630,14 @@ public class ContactStruct { 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 +663,7 @@ public class ContactStruct { 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 +704,36 @@ public class ContactStruct { } 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_NICKNAME)) { 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")) { + } else if (propName.equals(Constants.PROPERTY_SOUND)) { Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) { - handlePhoneticNameFromSound(propValueList); + // 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) { @@ -836,23 +752,21 @@ public class ContactStruct { 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.ATTR_TYPE_PREF)) { isPrimary = true; } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) { type = StructuredPostal.TYPE_HOME; label = ""; } else if (typeString.equals(Constants.ATTR_TYPE_WORK) || - typeString.equalsIgnoreCase("COMPANY")) { + typeString.equalsIgnoreCase(Constants.ATTR_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.ATTR_ADR_TYPE_PARCEL) || + typeString.equals(Constants.ATTR_ADR_TYPE_DOM) || + typeString.equals(Constants.ATTR_ADR_TYPE_INTL)) { // We do not have any appropriate way to store this information. } else { if (typeString.startsWith("X-") && type < 0) { @@ -871,7 +785,7 @@ public class ContactStruct { } 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; @@ -879,9 +793,7 @@ public class ContactStruct { 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.ATTR_TYPE_PREF)) { isPrimary = true; } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) { type = Email.TYPE_HOME; @@ -905,50 +817,47 @@ public class ContactStruct { 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); 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.ATTR_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.ATTR_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.ATTR_TYPE); + final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection); final int type; final String label; if (typeObject instanceof Integer) { @@ -960,9 +869,7 @@ public class ContactStruct { } final boolean isPrimary; - if (!mPrefIsSet_Phone && typeCollection != null && - typeCollection.contains(Constants.ATTR_TYPE_PREF)) { - mPrefIsSet_Phone = true; + if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) { isPrimary = true; } else { isPrimary = false; @@ -975,48 +882,54 @@ public class ContactStruct { 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.ATTR_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; + int type = -1; final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); if (typeCollection != null) { for (String typeString : typeCollection) { if (typeString.equals(Constants.ATTR_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.ATTR_TYPE_HOME)) { + type = Im.TYPE_HOME; + } else if (typeString.equalsIgnoreCase(Constants.ATTR_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")) { + } 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("X-PHONETIC-MIDDLE-NAME")) { + } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) { mPhoneticMiddleName = propValue; - } else if (propName.equals("X-PHONETIC-LAST-NAME")) { + } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_LAST_NAME)) { mPhoneticFamilyName = propValue; - } else if (propName.equals("BDAY")) { - mBirthday = 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 +957,21 @@ public class ContactStruct { } } + 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 +1002,6 @@ public class ContactStruct { } } mDisplayName = builder.toString(); - } else if (!TextUtils.isEmpty(mFullName)) { - mDisplayName = mFullName; } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) && TextUtils.isEmpty(mPhoneticGivenName))) { mDisplayName = VCardUtils.constructNameFromElements(mVCardType, @@ -1097,33 +1018,18 @@ public class ContactStruct { 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"; @@ -1181,22 +1087,16 @@ public class ContactStruct { } 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 +1109,34 @@ public class ContactStruct { } 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 +1169,11 @@ public class ContactStruct { 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 +1185,19 @@ public class ContactStruct { 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 +1210,12 @@ public class ContactStruct { 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 +1225,36 @@ public class ContactStruct { 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); + } + } + + operationList.add(builder.build()); + i++; + } + } + } + if (myGroupsId != null) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0); @@ -1364,4 +1294,99 @@ public class ContactStruct { 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 c4711f8..6476e40 100644 --- a/core/java/android/pim/vcard/VCardComposer.java +++ b/core/java/android/pim/vcard/VCardComposer.java @@ -72,19 +72,38 @@ import java.util.Set; * 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 +114,83 @@ public class VCardComposer { 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_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 = "="; + + 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"; + + /** + * 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 +285,6 @@ public class VCardComposer { } } - 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,7 +301,9 @@ public class VCardComposer { 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; @@ -298,20 +315,7 @@ public class VCardComposer { 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 mIsCallLogComposer; private static final String[] sContactsProjection = new String[] { Contacts._ID, @@ -332,30 +336,24 @@ public class VCardComposer { private static final String FLAG_TIMEZONE_UTC = "Z"; public VCardComposer(Context context) { - this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false); + this(context, VCardConfig.VCARD_TYPE_DEFAULT, true); } - public VCardComposer(Context context, String vcardTypeStr, - boolean careHandlerErrors) { - this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), - careHandlerErrors, false); + public VCardComposer(Context context, int vcardType) { + this(context, vcardType, true); } - public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) { - this(context, vcardType, careHandlerErrors, false); + 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) { + public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) { mContext = context; mVCardType = vcardType; mCareHandlerErrors = careHandlerErrors; - mIsCallLogComposer = isCallLogComposer; mContentResolver = context.getContentResolver(); mIsV30 = VCardConfig.isV30(vcardType); @@ -370,7 +368,9 @@ public class VCardComposer { 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) { @@ -389,50 +389,32 @@ public class VCardComposer { } /** - * 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); } + /** + * @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); + } + /** - * @return Returns true when initialization is successful and all the other - * methods are available. Returns false otherwise. + * Note that this is unstable interface, may be deleted in the future. */ - public boolean init(final String selection, final String[] selectionArgs) { + 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()); @@ -451,13 +433,19 @@ public class VCardComposer { } } - 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; @@ -534,89 +522,6 @@ public class VCardComposer { 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>>(); @@ -633,8 +538,7 @@ public class VCardComposer { 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) { @@ -663,11 +567,11 @@ public class VCardComposer { } 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); @@ -681,16 +585,16 @@ public class VCardComposer { appendOrganizations(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(); } @@ -748,11 +652,11 @@ public class VCardComposer { 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, ""); } } @@ -762,21 +666,38 @@ public class VCardComposer { // 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 @@ -800,7 +721,7 @@ public class VCardComposer { final String encodedSuffix; final boolean reallyUseQuotedPrintableToName = - (mUsesQPToPrimaryProperties && + (!mRefrainsQPToPrimaryProperties && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) && VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) && VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) && @@ -821,10 +742,9 @@ public class VCardComposer { encodedSuffix = escapeCharacters(suffix); } - // N property. This order is specified by vCard spec and does not depend on countries. - builder.append(VCARD_PROPERTY_NAME); + builder.append(Constants.PROPERTY_N); if (shouldAppendCharsetAttribute(Arrays.asList( - familyName, givenName, middleName, prefix, suffix))) { + encodedFamily, encodedGiven, encodedMiddle, encodedPrefix, encodedSuffix))) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); } @@ -845,20 +765,26 @@ public class VCardComposer { builder.append(encodedSuffix); builder.append(VCARD_COL_SEPARATOR); - final String fullname = VCardUtils.constructNameFromElements( + final String formattedName; + if (!TextUtils.isEmpty(displayName)) { + formattedName = displayName; + } else { + formattedName = VCardUtils.constructNameFromElements( VCardConfig.getNameOrderType(mVCardType), encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix); + } + final boolean reallyUseQuotedPrintableToFullname = - mUsesQPToPrimaryProperties && - !VCardUtils.containsOnlyNonCrLfPrintableAscii(fullname); + !mRefrainsQPToPrimaryProperties && + !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName); final String encodedFullname = reallyUseQuotedPrintableToFullname ? - encodeQuotedPrintable(fullname) : - escapeCharacters(fullname); + encodeQuotedPrintable(formattedName) : + escapeCharacters(formattedName); // FN property - builder.append(VCARD_PROPERTY_FULL_NAME); + builder.append(Constants.PROPERTY_FN); if (shouldAppendCharsetAttribute(encodedFullname)) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); @@ -872,14 +798,14 @@ public class VCardComposer { builder.append(VCARD_COL_SEPARATOR); } 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); + builder.append(Constants.PROPERTY_N); if (shouldAppendCharsetAttribute(encodedDisplayName)) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); @@ -895,38 +821,54 @@ public class VCardComposer { builder.append(VCARD_ITEM_SEPARATOR); builder.append(VCARD_ITEM_SEPARATOR); builder.append(VCARD_COL_SEPARATOR); - } else if (mIsDoCoMo) { - appendVCardLine(builder, VCARD_PROPERTY_NAME, ""); + if (mIsV30) { + builder.append(Constants.PROPERTY_FN); + // TODO: Not allowed formally... + if (shouldAppendCharsetAttribute(encodedDisplayName)) { + builder.append(VCARD_ATTR_SEPARATOR); + builder.append(mVCardAttributeCharset); + } + builder.append(VCARD_DATA_SEPARATOR); + builder.append(encodedDisplayName); + builder.append(VCARD_COL_SEPARATOR); + } } 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); @@ -937,25 +879,26 @@ public class VCardComposer { builder.append(VCARD_DATA_SEPARATOR); builder.append(encodedSortString); builder.append(VCARD_COL_SEPARATOR); - } else { + } 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(Constants.PROPERTY_SOUND); builder.append(VCARD_ATTR_SEPARATOR); builder.append(Constants.ATTR_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; @@ -987,7 +930,7 @@ public class VCardComposer { builder.append(VCARD_COL_SEPARATOR); } } else if (mIsDoCoMo) { - builder.append(VCARD_PROPERTY_SOUND); + builder.append(Constants.PROPERTY_SOUND); builder.append(VCARD_ATTR_SEPARATOR); builder.append(Constants.ATTR_TYPE_X_IRMC_N); builder.append(VCARD_DATA_SEPARATOR); @@ -1001,15 +944,14 @@ public class VCardComposer { 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); + builder.append(Constants.PROPERTY_X_PHONETIC_FIRST_NAME); if (shouldAppendCharsetAttribute(encodedPhoneticGivenName)) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); @@ -1024,15 +966,14 @@ public class VCardComposer { } 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); + builder.append(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME); if (shouldAppendCharsetAttribute(encodedPhoneticMiddleName)) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); @@ -1047,15 +988,14 @@ public class VCardComposer { } 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); + builder.append(Constants.PROPERTY_X_PHONETIC_LAST_NAME); if (shouldAppendCharsetAttribute(encodedPhoneticFamilyName)) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); @@ -1075,45 +1015,31 @@ public class VCardComposer { 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; - } - - for (ContentValues contentValues : contentValuesList) { - final String nickname = contentValues.getAsString(Nickname.NAME); - if (TextUtils.isEmpty(nickname)) { - continue; - } + if (contentValuesList == null) { + return; + } - final String encodedNickname; - final boolean reallyUseQuotedPrintable = - (mUsesQuotedPrintable && - !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname)); - if (reallyUseQuotedPrintable) { - encodedNickname = encodeQuotedPrintable(nickname); - } else { - encodedNickname = escapeCharacters(nickname); - } + final boolean useAndroidProperty; + if (mIsV30) { + useAndroidProperty = false; + } else if (mUsesAndroidProperty) { + useAndroidProperty = true; + } else { + // There's no way to add this field. + return; + } - 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); + for (ContentValues contentValues : contentValuesList) { + final String nickname = contentValues.getAsString(Nickname.NAME); + if (TextUtils.isEmpty(nickname)) { + continue; + } + if (useAndroidProperty) { + appendAndroidSpecificProperty(builder, Nickname.CONTENT_ITEM_TYPE, + contentValues); + } else { + appendVCardLineWithCharsetAndQPDetection(builder, + Constants.PROPERTY_NICKNAME, nickname); } } } @@ -1128,6 +1054,9 @@ public class VCardComposer { 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(); @@ -1136,18 +1065,18 @@ public class VCardComposer { 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); } } @@ -1155,14 +1084,11 @@ public class VCardComposer { 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(); @@ -1170,16 +1096,23 @@ public class VCardComposer { 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); } } @@ -1194,7 +1127,7 @@ public class VCardComposer { appendPostalsForGeneric(builder, contentValuesList); } } else if (mIsDoCoMo) { - builder.append(VCARD_PROPERTY_ADR); + builder.append(Constants.PROPERTY_ADR); builder.append(VCARD_ATTR_SEPARATOR); builder.append(Constants.ATTR_TYPE_HOME); builder.append(VCARD_DATA_SEPARATOR); @@ -1236,7 +1169,10 @@ public class VCardComposer { 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; } } @@ -1246,11 +1182,18 @@ public class VCardComposer { 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); } } @@ -1258,24 +1201,63 @@ public class VCardComposer { 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.ATTR_TYPE_HOME; + break; + } + case Im.TYPE_WORK: { + typeAsString = Constants.ATTR_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; } - // TODO: add "X-GOOGLE TALK" case... } } + + List<String> attributeList = new ArrayList<String>(); + if (!TextUtils.isEmpty(typeAsString)) { + attributeList.add(typeAsString); + } + final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY); + final boolean isPrimary = (isPrimaryAsInteger != null ? + (isPrimaryAsInteger > 0) : false); + if (isPrimary) { + attributeList.add(Constants.ATTR_TYPE_PREF); + } + + appendVCardLineWithCharsetAndQPDetection( + builder, propertyName, attributeList, data); } } @@ -1283,39 +1265,83 @@ public class VCardComposer { 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 attribute 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); - if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) { - return; + 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; } - // 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 Integer eventType = contentValues.getAsInteger(Event.TYPE); + if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) { + continue; + } + final String birthdayCandidate = contentValues.getAsString(Event.START_DATE); + if (birthdayCandidate == null) { + continue; + } + final Integer isSuperPrimaryAsInteger = + contentValues.getAsInteger(Event.IS_SUPER_PRIMARY); + final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ? + (isSuperPrimaryAsInteger > 0) : false); + if (isSuperPrimary) { + // "super primary" birthday should the prefered one. + primaryBirthday = birthdayCandidate; + break; } - if (!TextUtils.isEmpty(birthday)) { - appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday); + 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, @@ -1324,25 +1350,37 @@ public class VCardComposer { .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))); @@ -1369,11 +1407,9 @@ public class VCardComposer { 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) { @@ -1420,7 +1456,7 @@ public class VCardComposer { 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) { @@ -1431,7 +1467,7 @@ public class VCardComposer { final boolean reallyUseQuotedPrintable = (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr)); - appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr, + appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr, shouldAppendCharsetInfo, reallyUseQuotedPrintable); } } @@ -1439,6 +1475,33 @@ public class VCardComposer { } } + 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. @@ -1454,7 +1517,7 @@ public class VCardComposer { 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('\\'); @@ -1465,7 +1528,7 @@ public class VCardComposer { if (i + 1 < length) { char nextChar = unescaped.charAt(i); if (nextChar == '\n') { - continue; + break; } else { // fall through } @@ -1517,7 +1580,7 @@ public class VCardComposer { private void appendVCardPhotoLine(final StringBuilder builder, final String encodedData, final String photoType) { StringBuilder tmpBuilder = new StringBuilder(); - tmpBuilder.append(VCARD_PROPERTY_PHOTO); + tmpBuilder.append(Constants.PROPERTY_PHOTO); tmpBuilder.append(VCARD_ATTR_SEPARATOR); if (mIsV30) { tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30); @@ -1547,59 +1610,126 @@ public class VCardComposer { builder.append(VCARD_COL_SEPARATOR); } - 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()); } - final int typeAsPrimitive; - if (typeAsObject == null) { - typeAsPrimitive = StructuredPostal.TYPE_OTHER; - } else { - typeAsPrimitive = typeAsObject; + 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; + } } - String typeAsString = null; - switch (typeAsPrimitive) { + List<String> attributeList = new ArrayList<String>(); + if (isPrimary) { + attributeList.add(Constants.ATTR_TYPE_PREF); + } + switch (type) { case StructuredPostal.TYPE_HOME: { - typeAsString = Constants.ATTR_TYPE_HOME; + attributeList.add(Constants.ATTR_TYPE_HOME); break; } case StructuredPostal.TYPE_WORK: { - typeAsString = Constants.ATTR_TYPE_WORK; + attributeList.add(Constants.ATTR_TYPE_WORK); break; } case StructuredPostal.TYPE_CUSTOM: { @@ -1609,9 +1739,7 @@ public class VCardComposer { // ("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); + attributeList.add("X-" + label); } break; } @@ -1619,82 +1747,56 @@ public class VCardComposer { 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_ATTR_SEPARATOR); + + // Attributes { boolean shouldAppendAttrSeparator = false; - if (typeAsString != null) { - appendTypeAttribute(builder, typeAsString); + if (!attributeList.isEmpty()) { + appendTypeAttributes(builder, attributeList); shouldAppendAttrSeparator = 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 attribute in vCard 3.0. + if (shouldAppendAttrSeparator) { + builder.append(VCARD_ATTR_SEPARATOR); } + builder.append(mVCardAttributeCharset); + shouldAppendAttrSeparator = true; + } - if (actuallyUseQuotedPrintable) { - if (shouldAppendAttrSeparator) { - builder.append(VCARD_ATTR_SEPARATOR); - } - builder.append(VCARD_ATTR_ENCODING_QP); - shouldAppendAttrSeparator = true; + if (reallyUseQuotedPrintable) { + if (shouldAppendAttrSeparator) { + builder.append(VCARD_ATTR_SEPARATOR); } + builder.append(VCARD_ATTR_ENCODING_QP); + shouldAppendAttrSeparator = 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(addressData); builder.append(VCARD_COL_SEPARATOR); } 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. @@ -1706,7 +1808,7 @@ public class VCardComposer { && VCardUtils.containsOnlyAlphaDigitHyphen(label)) { typeAsString = "X-" + label; } else { - typeAsString = DEFAULT_EMAIL_TYPE; + typeAsString = null; } break; } @@ -1719,7 +1821,7 @@ public class VCardComposer { break; } case Email.TYPE_OTHER: { - typeAsString = DEFAULT_EMAIL_TYPE; + typeAsString = null; break; } case Email.TYPE_MOBILE: { @@ -1727,23 +1829,28 @@ public class VCardComposer { 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> attributeList = new ArrayList<String>(); + if (isPrimary) { + attributeList.add(Constants.ATTR_TYPE_PREF); + } + if (!TextUtils.isEmpty(typeAsString)) { + attributeList.add(typeAsString); + } + + appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_EMAIL, + attributeList, rawData); } private void appendVCardTelephoneLine(final StringBuilder builder, final Integer typeAsObject, final String label, - String encodedData) { - builder.append(VCARD_PROPERTY_TEL); + final String encodedData, boolean isPrimary) { + builder.append(Constants.PROPERTY_TEL); builder.append(VCARD_ATTR_SEPARATOR); final int typeAsPrimitive; @@ -1753,53 +1860,102 @@ public class VCardComposer { typeAsPrimitive = typeAsObject; } + ArrayList<String> attributeList = new ArrayList<String>(); switch (typeAsPrimitive) { case Phone.TYPE_HOME: - appendTypeAttributes(builder, Arrays.asList( - Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE)); + attributeList.addAll( + Arrays.asList(Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE)); break; case Phone.TYPE_WORK: - appendTypeAttributes(builder, Arrays.asList( - Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE)); + attributeList.addAll( + Arrays.asList(Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE)); break; case Phone.TYPE_FAX_HOME: - appendTypeAttributes(builder, Arrays.asList( - Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX)); + attributeList.addAll( + Arrays.asList(Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX)); break; case Phone.TYPE_FAX_WORK: - appendTypeAttributes(builder, Arrays.asList( - Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX)); + attributeList.addAll( + Arrays.asList(Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX)); break; case Phone.TYPE_MOBILE: - builder.append(Constants.ATTR_TYPE_CELL); + attributeList.add(Constants.ATTR_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); + attributeList.add(Constants.ATTR_TYPE_VOICE); } else { - appendTypeAttribute(builder, Constants.ATTR_TYPE_PAGER); + attributeList.add(Constants.ATTR_TYPE_PAGER); } break; case Phone.TYPE_OTHER: - appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE); + attributeList.add(Constants.ATTR_TYPE_VOICE); + break; + case Phone.TYPE_CAR: + attributeList.add(Constants.ATTR_TYPE_CAR); + break; + case Phone.TYPE_COMPANY_MAIN: + // There's no relevant field in vCard (at least 2.1). + attributeList.add(Constants.ATTR_TYPE_WORK); + isPrimary = true; + break; + case Phone.TYPE_ISDN: + attributeList.add(Constants.ATTR_TYPE_ISDN); + break; + case Phone.TYPE_MAIN: + isPrimary = true; + break; + case Phone.TYPE_OTHER_FAX: + attributeList.add(Constants.ATTR_TYPE_FAX); + break; + case Phone.TYPE_TELEX: + attributeList.add(Constants.ATTR_TYPE_TLX); + break; + case Phone.TYPE_WORK_MOBILE: + attributeList.addAll( + Arrays.asList(Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_CELL)); + break; + case Phone.TYPE_WORK_PAGER: + attributeList.add(Constants.ATTR_TYPE_WORK); + // See above. + if (mIsDoCoMo) { + attributeList.add(Constants.ATTR_TYPE_VOICE); + } else { + attributeList.add(Constants.ATTR_TYPE_PAGER); + } + break; + case Phone.TYPE_MMS: + attributeList.add(Constants.ATTR_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-" attribute without + // "TYPE=" string. + attributeList.add("X-" + label); } else { // Just ignore the custom type. - appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE); + attributeList.add(Constants.ATTR_TYPE_VOICE); } break; + case Phone.TYPE_RADIO: + case Phone.TYPE_TTY_TDD: default: - appendUncommonPhoneType(builder, typeAsPrimitive); break; } + if (isPrimary) { + attributeList.add(Constants.ATTR_TYPE_PREF); + } + + if (attributeList.isEmpty()) { + appendUncommonPhoneType(builder, typeAsPrimitive); + } else { + appendTypeAttributes(builder, attributeList); + } + builder.append(VCARD_DATA_SEPARATOR); builder.append(encodedData); builder.append(VCARD_COL_SEPARATOR); @@ -1823,15 +1979,45 @@ public class VCardComposer { } } + // 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> attributeList, final String rawData) { + final boolean needCharset = + (mUsesQuotedPrintable && !VCardUtils.containsOnlyPrintableAscii(rawData)); + final boolean reallyUseQuotedPrintable = + !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData); + appendVCardLine(builder, propertyName, attributeList, + 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> attributeList, + final String rawData, final boolean needCharset, + boolean needQuotedPrintable) { + builder.append(propertyName); + if (attributeList != null && attributeList.size() > 0) { + builder.append(VCARD_ATTR_SEPARATOR); + appendTypeAttributes(builder, attributeList); + } if (needCharset) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); @@ -1853,6 +2039,90 @@ public class VCardComposer { builder.append(VCARD_COL_SEPARATOR); } + // 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> attributeList, 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, attributeList, + 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> attributeList, + final List<String> rawDataList, final boolean needCharset, + boolean needQuotedPrintable) { + builder.append(propertyName); + if (attributeList != null && attributeList.size() > 0) { + builder.append(VCARD_ATTR_SEPARATOR); + appendTypeAttributes(builder, attributeList); + } + if (needCharset) { + builder.append(VCARD_ATTR_SEPARATOR); + builder.append(mVCardAttributeCharset); + } + + builder.append(VCARD_DATA_SEPARATOR); + boolean first = true; + for (String rawData : rawDataList) { + final String encodedData; + if (needQuotedPrintable) { + builder.append(VCARD_ATTR_SEPARATOR); + builder.append(VCARD_ATTR_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_COL_SEPARATOR); + } + + /** + * VCARD_ATTR_SEPARATOR must be appended before this method being called. + */ private void appendTypeAttributes(final StringBuilder builder, final List<String> types) { // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future, @@ -1868,9 +2138,15 @@ public class VCardComposer { } } + /** + * VCARD_ATTR_SEPARATOR must be appended before this method being called. + */ private void appendTypeAttribute(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) { + if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) { builder.append(Constants.ATTR_TYPE).append(VCARD_ATTR_EQUAL); } builder.append(type); @@ -1961,4 +2237,116 @@ public class VCardComposer { 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_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, 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..9581c74 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 @@ public class VCardConfig { // 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 @@ public class VCardConfig { 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. + * <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_REFRAIN_QP_TO_PRIMARY_PROPERTIES = 0x10000000; + + /** + * <P> + * The flag indicating that phonetic name related fields must be converted to + * appropriate form. Note that "appropriate" is not defined in any vCard specification. + * This is Android-specific. + * </P> + * <P> + * One typical (and currently sole) example where we need this flag is the time when + * we need to emit Japanese phonetic names into vCard entries. The property values + * should be encoded into half-width katakana when the target importer is Japanese mobile + * phones', which are probably not able to parse full-width hiragana/katakana for + * historical reasons, while the vCard importers embedded to softwares for PC should be + * able to parse them as we expect. + * </P> + */ + public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 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 attributes (params) + * without "TYPE=" string (which should be rare though), please use this flag. * - * @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. + * XXX: Really rare? + * + * e.g. int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM); */ - public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000; - - // VCard types + 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. - * + * 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 in Japan, it should be "Prefix Family Middle Given Suffix". + * while it should be "Prefix Family Middle Given Suffix" in Japan. + * </P> */ - public static final int VCARD_TYPE_V21_GENERIC = + 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 @@ public class VCardConfig { /* 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 @@ public class VCardConfig { 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 @@ public class VCardConfig { 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 @@ public class VCardConfig { 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 @@ public class VCardDataBuilder implements VCardBuilder { 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 @@ public class VCardDataBuilder implements VCardBuilder { boolean strictLineBreakParsing, int vcardType, Account account) { this(null, charset, strictLineBreakParsing, vcardType, account); } - + /** * @hide */ @@ -127,6 +127,18 @@ public class VCardDataBuilder implements VCardBuilder { } /** + * 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_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 @@ public class VCardParser_V21 extends VCardParser { } } - 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 @@ public class VCardParser_V21 extends VCardParser { * / [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 @@ public class VCardParser_V21 extends VCardParser { } 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 @@ public class VCardParser_V21 extends VCardParser { 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 @@ public class VCardParser_V21 extends VCardParser { } 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 @@ public class VCardParser_V21 extends VCardParser { 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 @@ public class VCardParser_V21 extends VCardParser { @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 @@ public class VCardParser_V30 extends VCardParser_V21 { 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 int getVersion() { + return VCardConfig.FLAG_V30; + } + @Override - protected String getVersion() { + protected String getVersionString() { return Constants.VERSION_V30; } - + @Override protected boolean isValidPropertyName(String propertyName) { if (!(sAcceptablePropsWithParam.contains(propertyName) || @@ -199,7 +227,16 @@ public class VCardParser_V30 extends VCardParser_V21 { // 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 @@ public class VCardParser_V30 extends VCardParser_V21 { */ @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 @@ public class VCardParser_V30 extends VCardParser_V21 { 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..376327c 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -18,13 +18,16 @@ package android.pim.vcard; 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; @@ -41,44 +44,56 @@ public class VCardUtils { // 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); + sKnownPhoneTypeMap_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); + sKnownPhoneTypeMap_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); + sKnownPhoneTypeMap_StoI.put(Constants.ATTR_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.ATTR_TYPE_HOME, Phone.TYPE_HOME); + sKnownPhoneTypeMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK); + sKnownPhoneTypeMap_StoI.put(Constants.ATTR_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.ATTR_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER); + sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_CALLBACK, Phone.TYPE_CALLBACK); + sKnownPhoneTypeMap_StoI.put( + Constants.ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN); + sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO); + sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_TTY_TDD, Phone.TYPE_TTY_TDD); + sKnownPhoneTypeMap_StoI.put(Constants.ATTR_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); + + 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) { 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. @@ -100,7 +115,7 @@ public class VCardUtils { 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; } else if (type < 0) { @@ -133,7 +148,11 @@ public class VCardUtils { return type; } } - + + public static String getPropertyNameForIm(int protocol) { + return sKnownImPropNameMap_ItoS.get(protocol); + } + public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) { // TODO: check the following. // - it may violate vCard spec @@ -187,7 +206,10 @@ public class VCardUtils { } 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 +222,12 @@ public class VCardUtils { 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 +235,20 @@ public class VCardUtils { * 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,7 +311,36 @@ public class VCardUtils { } 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; 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 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis 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 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis } } 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 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis * @see #setWidgetLayoutResource(int) */ public void setLayoutResource(int layoutResId) { - - if (!mHasSpecifiedLayout) { + if (layoutResId != mLayoutResId) { + // Layout changed mHasSpecifiedLayout = true; } - + mLayoutResId = layoutResId; } @@ -360,6 +355,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis * @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 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn * 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 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn } }; + 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 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn if (mIsSyncing) { return; } - + mIsSyncing = true; } @@ -128,7 +153,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn preferences.add(preference); - if (!mHasReturnedViewTypeCount) { + if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) { addPreferenceClassName(preference); } @@ -143,15 +168,28 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn } } + /** + * 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 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn 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 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn 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 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn 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..faa63af 100644 --- a/core/java/android/provider/Calendar.java +++ b/core/java/android/provider/Calendar.java @@ -514,6 +514,12 @@ public final class Calendar { * <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"; } /** 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 @@ public final class MediaStore { 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 @@ public final class MediaStore { * * @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 @@ public final class MediaStore { * @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 @@ public final class MediaStore { 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 @@ public final class MediaStore { } 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 @@ public final class MediaStore { * @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 final class MediaStore { */ 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,26 @@ public final class MediaStore { * @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); + } + + /** + * 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 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); } /** @@ -1607,18 +1665,32 @@ public final class MediaStore { * * @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..456181c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3110,6 +3110,13 @@ public final class Settings { = "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 +3630,6 @@ public final class Settings { */ 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 +3644,32 @@ public final class Settings { 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 @@ public final class Telephony { * <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 @@ public final class Telephony { * @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 @@ public final class Telephony { return false; } - ContentValues values = new ContentValues(2); + ContentValues values = new ContentValues(3); values.put(TYPE, folder); if (markAsUnread) { @@ -274,6 +280,7 @@ public final class Telephony { } 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 67b30a9..0db29a4 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -61,7 +61,7 @@ import java.util.Map; 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ extends Layout else end++; + int firstWidthLineCount = 1; int firstwidth = outerwidth; int restwidth = outerwidth; @@ -171,8 +172,12 @@ extends Layout 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 @@ extends Layout 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 @@ extends ParagraphStyle 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 @@ public final class ViewRoot extends Handler implements ViewParent, 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 @@ public final class ViewRoot extends Handler implements ViewParent, 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 @@ public final class ViewRoot extends Handler implements ViewParent, 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 @@ package android.webkit; 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 @@ class BrowserFrame extends Handler { } /** + * 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 @@ import java.util.ListIterator; 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; - private LinkedList<Chunk> mChunks; - - /** free pool */ - private LinkedList<Chunk> mPool; + // 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 int mMinCapacity; + private LinkedList<Chunk> mChunks; 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 @@ class ByteArrayBuilder { /** * 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 @@ class ByteArrayBuilder { 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 @@ class ByteArrayBuilder { 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; + } + 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); } - mChunks.addLast(c); - return c; } public static class Chunk { @@ -153,5 +136,19 @@ class ByteArrayBuilder { 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..f9dec7f 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -107,6 +107,7 @@ class CallbackProxy extends Handler { 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 @@ class CallbackProxy extends Handler { } /** + * 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. */ @@ -662,6 +673,12 @@ class CallbackProxy extends Handler { mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj); } break; + + case OPEN_FILE_CHOOSER: + if (mWebChromeClient != null) { + mWebChromeClient.openFileChooser((UploadFile) msg.obj); + } + break; } } @@ -1348,4 +1365,40 @@ class CallbackProxy extends Handler { 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 @@ class HTML5VideoViewProxy extends Handler 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 @@ class HTML5VideoViewProxy extends Handler 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 @@ public final class HttpDateTime { * 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 @@ class LoadListener extends Handler implements EventHandler { 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 @@ class LoadListener extends Handler implements EventHandler { * 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 @@ class LoadListener extends Handler implements EventHandler { 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; - - if (c.mLength != 0) { - System.arraycopy(c.mArray, 0, cert, offset, c.mLength); - offset += c.mLength; + // 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; + } + 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 @@ class LoadListener extends Handler implements EventHandler { } nativeAddData(c.mArray, c.mLength); } - mDataBuilder.releaseChunk(c); + c.release(); checker.responseAlert("res nativeAddData"); } } @@ -1210,8 +1217,17 @@ class LoadListener extends Handler implements EventHandler { // 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 @@ public class MimeTypeMap { 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 @@ public class MimeTypeMap { 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 @@ class Network { } 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 @@ public final class URLUtil { /** 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 class WebChromeClient { 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 @@ public class WebSettings { * 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 @@ import java.util.ArrayList; // 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 @@ import java.util.ArrayList; 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 @@ import java.util.ArrayList; } // 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 @@ import java.util.ArrayList; public void onEditorAction(int actionCode) { switch (actionCode) { case EditorInfo.IME_ACTION_NEXT: + // Since the cursor will no longer be in the same place as the + // focus, set the focus controller back to inactive + mWebView.setFocusControllerInactive(); 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(); - // Since the cursor will no longer be in the same place as the - // focus, set the focus controller back to inactive - mWebView.setFocusControllerInactive(); + setDefaultSelection(); mWebView.invalidate(); - mOkayForFocusNotToMatch = true; break; case EditorInfo.IME_ACTION_DONE: super.onEditorAction(actionCode); @@ -331,6 +349,14 @@ import java.util.ArrayList; } @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 @@ import java.util.ArrayList; 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 @@ import java.util.ArrayList; } /** + * 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 @@ import java.util.ArrayList; 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 @@ import java.util.ArrayList; } /** - * 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 fe91229..98a6c2c 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -64,7 +64,9 @@ import android.widget.AbsoluteLayout; 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.ArrayList; 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 @@ public class WebView extends AbsoluteLayout 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 @@ public class WebView extends AbsoluteLayout 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 @@ public class WebView extends AbsoluteLayout 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 @@ public class WebView extends AbsoluteLayout 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 @@ public class WebView extends AbsoluteLayout "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 @@ public class WebView extends AbsoluteLayout "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 @@ public class WebView extends AbsoluteLayout 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 @@ -1713,6 +1725,7 @@ public class WebView extends AbsoluteLayout * as the data member with "url" as key. The result can be null. */ public void requestImageRef(Message msg) { + if (0 == mNativeClass) return; // client isn't initialized int contentX = viewToContentX((int) mLastTouchX + mScrollX); int contentY = viewToContentY((int) mLastTouchY + mScrollY); String ref = nativeImageURI(contentX, contentY); @@ -2331,6 +2344,7 @@ public class WebView extends AbsoluteLayout * @param forward Direction to search. */ public void findNext(boolean forward) { + if (0 == mNativeClass) return; // client isn't initialized nativeFindNext(forward); } @@ -2341,6 +2355,7 @@ public class WebView extends AbsoluteLayout * that were found. */ public int findAll(String find) { + if (0 == mNativeClass) return 0; // client isn't initialized if (mFindIsUp == false) { recordNewContentSize(mContentWidth, mContentHeight + mFindHeight, false); @@ -2348,6 +2363,7 @@ public class WebView extends AbsoluteLayout } int result = nativeFindAll(find.toLowerCase(), find.toUpperCase()); invalidate(); + mLastFind = find; return result; } @@ -2355,6 +2371,9 @@ public class WebView extends AbsoluteLayout // 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 @@ -2605,12 +2624,12 @@ public class WebView extends AbsoluteLayout if (mHeightCanMeasure) { if (getMeasuredHeight() != contentToViewDimension(mContentHeight) - && updateLayout) { + || updateLayout) { requestLayout(); } } else if (mWidthCanMeasure) { if (getMeasuredWidth() != contentToViewDimension(mContentWidth) - && updateLayout) { + || updateLayout) { requestLayout(); } } else { @@ -2630,6 +2649,16 @@ public class WebView extends AbsoluteLayout } /** + * 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. @@ -2810,10 +2839,7 @@ public class WebView extends AbsoluteLayout 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()) { @@ -2836,6 +2862,21 @@ public class WebView extends AbsoluteLayout */ 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) { @@ -2845,8 +2886,22 @@ public class WebView extends AbsoluteLayout } 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); @@ -2863,19 +2918,13 @@ public class WebView extends AbsoluteLayout 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()) { @@ -2927,11 +2976,12 @@ public class WebView extends AbsoluteLayout 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) { @@ -2951,6 +3001,10 @@ public class WebView extends AbsoluteLayout if (mFindIsUp && !animateScroll) { nativeDrawMatches(canvas); } + if (mFocusSizeChanged) { + mFocusSizeChanged = false; + didUpdateTextViewBounds(true); + } } // draw history @@ -3046,6 +3100,16 @@ public class WebView extends AbsoluteLayout 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, @@ -3101,6 +3165,7 @@ public class WebView extends AbsoluteLayout && nativeTextGeneration() == mTextGeneration) { mWebTextView.setTextAndKeepSelection(text); } else { + // FIXME: Determine whether this is necessary. Selection.setSelection(spannable, start, end); } } else { @@ -3133,34 +3198,12 @@ public class WebView extends AbsoluteLayout 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(); } } @@ -3227,23 +3270,22 @@ public class WebView extends AbsoluteLayout 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; @@ -3255,6 +3297,9 @@ public class WebView extends AbsoluteLayout 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); @@ -3304,10 +3349,7 @@ public class WebView extends AbsoluteLayout } } - 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(), @@ -3316,13 +3358,17 @@ public class WebView extends AbsoluteLayout // 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); } } @@ -3387,7 +3433,13 @@ public class WebView extends AbsoluteLayout 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 @@ -3397,21 +3449,23 @@ public class WebView extends AbsoluteLayout 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; } @@ -3427,13 +3481,29 @@ public class WebView extends AbsoluteLayout 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() { - mExtendSelection = false; - mShiftIsPressed = true; - nativeHideCursor(); + if (0 == mNativeClass) return; // client isn't initialized + setUpSelectXY(); } private boolean commitCopy() { @@ -3451,6 +3521,7 @@ public class WebView extends AbsoluteLayout mExtendSelection = false; } mShiftIsPressed = false; + invalidate(); // remove selection region and pointer if (mTouchMode == TOUCH_SELECT_MODE) { mTouchMode = TOUCH_INIT_MODE; } @@ -3594,6 +3665,24 @@ public class WebView extends AbsoluteLayout 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); @@ -3702,8 +3791,10 @@ public class WebView extends AbsoluteLayout 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: { @@ -3725,6 +3816,7 @@ public class WebView extends AbsoluteLayout 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) { @@ -3829,12 +3921,21 @@ public class WebView extends AbsoluteLayout // 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); @@ -3846,56 +3947,47 @@ public class WebView extends AbsoluteLayout 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); + } + if ((deltaX | deltaY) != 0) { + scrollBy(deltaX, deltaY); + if (deltaX != 0) { + mLastTouchX = x; } - mLastTouchY = y; + if (deltaY != 0) { + mLastTouchY = y; + } + mHeldMotionless = MOTIONLESS_FALSE; } else { - scrollBy(deltaX, deltaY); - mLastTouchX = x; - mLastTouchY = y; + // keep the scrollbar on the screen even there is no + // scroll + keepScrollBarsVisible = true; } mLastTouchTime = eventTime; mUserScroll = true; @@ -3914,13 +4006,17 @@ public class WebView extends AbsoluteLayout } } - 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; } @@ -3973,6 +4069,9 @@ public class WebView extends AbsoluteLayout 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 @@ -4012,6 +4111,9 @@ public class WebView extends AbsoluteLayout } 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; @@ -4038,6 +4140,7 @@ public class WebView extends AbsoluteLayout 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; @@ -4096,6 +4199,7 @@ public class WebView extends AbsoluteLayout commitCopy(); } else { mExtendSelection = true; + invalidate(); // draw the i-beam instead of the arrow } return true; // discard press if copy in progress } @@ -4143,8 +4247,8 @@ public class WebView extends AbsoluteLayout 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 @@ -4226,8 +4330,11 @@ public class WebView extends AbsoluteLayout } 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; } @@ -4241,8 +4348,8 @@ public class WebView extends AbsoluteLayout + " 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); @@ -4317,7 +4424,7 @@ public class WebView extends AbsoluteLayout 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; @@ -4597,21 +4704,21 @@ public class WebView extends AbsoluteLayout } 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); } } @@ -4681,15 +4788,6 @@ public class WebView extends AbsoluteLayout 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; @@ -4838,14 +4936,6 @@ public class WebView extends AbsoluteLayout } /* 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; @@ -4876,9 +4966,10 @@ public class WebView extends AbsoluteLayout 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) { @@ -5040,6 +5131,9 @@ public class WebView extends AbsoluteLayout / mZoomOverviewWidth, false); } } + if (draw.mFocusSizeChanged && inEditingMode()) { + mFocusSizeChanged = true; + } break; } case WEBCORE_INITIALIZED_MSG_ID: @@ -5080,9 +5174,7 @@ public class WebView extends AbsoluteLayout } 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 @@ -5166,9 +5258,36 @@ public class WebView extends AbsoluteLayout 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; @@ -5192,8 +5311,16 @@ public class WebView extends AbsoluteLayout // 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() { @@ -5214,6 +5341,54 @@ public class WebView extends AbsoluteLayout } @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 @@ -5248,12 +5423,11 @@ public class WebView extends AbsoluteLayout 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; @@ -5267,8 +5441,7 @@ public class WebView extends AbsoluteLayout } } - private InvokeListBox(String[] array, boolean[] enabled, int - selection) { + private InvokeListBox(String[] array, int[] enabled, int selection) { mSelection = selection; mMultiple = false; @@ -5399,10 +5572,11 @@ public class WebView extends AbsoluteLayout * 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)); @@ -5413,10 +5587,11 @@ public class WebView extends AbsoluteLayout * <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)); } @@ -5500,7 +5675,7 @@ public class WebView extends AbsoluteLayout 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 @@ -5575,6 +5750,17 @@ public class WebView extends AbsoluteLayout } /** + * 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. */ @@ -5592,7 +5778,7 @@ public class WebView extends AbsoluteLayout /* 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(); @@ -5605,13 +5791,15 @@ public class WebView extends AbsoluteLayout 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(); @@ -5621,6 +5809,7 @@ public class WebView extends AbsoluteLayout /* 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(); @@ -5638,7 +5827,6 @@ public class WebView extends AbsoluteLayout 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, @@ -5660,7 +5848,6 @@ public class WebView extends AbsoluteLayout // 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 @@ package android.webkit; 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.Picture; 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 @@ final class WebViewCore { 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 @@ final class WebViewCore { */ 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 @@ final class WebViewCore { */ 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 @@ final class WebViewCore { int mY; } - static class PluginStateData { - int mFrame; - int mNode; - int mState; - } - static class GeolocationPermissionsData { String mOrigin; boolean mAllow; @@ -720,7 +750,7 @@ final class WebViewCore { "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 @@ final class WebViewCore { 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 @@ final class WebViewCore { 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 @@ final class WebViewCore { 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 @@ final class WebViewCore { 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 class WebViewCore { 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 @@ final class WebViewCore { if (animatingZoom) { df = mZoomFilter; } else if (animatingScroll) { - df = null; + df = mScrollFilter; } canvas.setDrawFilter(df); boolean tookTooLong = nativeDrawContent(canvas, color); @@ -2013,9 +2042,16 @@ final class WebViewCore { // 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 @@ final class WebViewCore { 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 @@ final class WebViewCore { 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 @@ final class WebViewCore { } // 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 @@ final class WebViewCore { 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.content.Context; 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 class WebViewDatabase { 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 @@ public class WebViewDatabase { 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 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // 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 @@ public class CheckedTextView extends TextView implements Checkable { * @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 @@ public class CheckedTextView extends TextView implements Checkable { 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.database.Cursor; 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 @@ public class QuickContactBadge extends ImageView implements OnClickListener { 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 class QuickContactBadge extends ImageView implements OnClickListener { 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 @@ public class QuickContactBadge extends ImageView implements OnClickListener { 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 bf3d26e..3ed995b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5198,7 +5198,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public: *matrix = canvas->getTotalMatrix(); } }; - -/////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gCanvasMethods[] = { {"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer}, @@ -965,6 +966,42 @@ static JNINativeMethod gCanvasMethods[] = { {"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 register_android_graphics_Canvas(JNIEnv* env) { 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 @@ bool GraphicsJNI::hasException(JNIEnv *env) { /////////////////////////////////////////////////////////////////////////////// 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 @@ AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array, } 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 @@ AutoJavaIntArray::~AutoJavaIntArray() { } 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 @@ AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array, } 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 @@ private: 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 @@ private: jfloatArray fArray; float* fPtr; int fLen; + int fReleaseMode; }; class AutoJavaIntArray { @@ -112,7 +119,8 @@ private: 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 @@ private: 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 @@ static jstring doStringCommand(JNIEnv *env, const char *cmd) 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 Binary files differindex 42fc0c5..fe220e4 100644 --- a/core/res/assets/images/combobox-disabled.png +++ b/core/res/assets/images/combobox-disabled.png diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png Binary files differindex 838dc65..abcdf72 100644 --- a/core/res/assets/images/combobox-noHighlight.png +++ b/core/res/assets/images/combobox-noHighlight.png 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"> + + <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" /> - <ListView android:id="@+id/packages_list" - android:layout_width="fill_parent" android:layout_height="fill_parent"/> + <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 @@ This is the basic layout for a screen, with all of its features enabled. android:layout_toLeftOf="@id/progress_circular" android:layout_toRightOf="@android:id/left_icon" > - <!-- 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_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_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" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index f93fb01..67fac9f 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"K souboru nelze získat přístup."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Požadovaný soubor nebyl nalezen."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Je zpracováváno příliš mnoho požadavků. Opakujte akci později."</string> - <string name="notification_title" msgid="1259940370369187045">"Chyba přihlášení k účtu <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synchronizace"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synchronizace"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Příliš mnoho smazaných položek služby <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Sleduje vaši fyzickou polohu"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Síťová komunikace"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Umožňuje aplikacím získat přístup k různým funkcím sítě."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Vaše účty"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Přístup k dostupným účtům."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Řízení hardwaru"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Přímý přístup k hardwaru telefonu."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefonní hovory"</string> @@ -316,7 +319,7 @@ <string name="permlab_callPrivileged" msgid="4198349211108497879">"přímé volání na libovolná telefonní čísla"</string> <string name="permdesc_callPrivileged" msgid="244405067160028452">"Umožňuje aplikaci bez vašeho zásahu vytočit jakékoli telefonní číslo, včetně čísel tísňového volání. Škodlivé aplikace mohou provádět zbytečná a nezákonná volání na tísňové linky."</string> <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"přímo spustit nastavení telefonu CDMA"</string> - <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Umožňuje aplikaci zahájit poskytování CDMA. Škodlivé aplikace mohou poskytování CDMA zahájit samovolně."</string> + <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Umožňuje aplikaci zahájit zřizování CDMA. Škodlivé aplikace mohou zřizování CDMA zahájit samovolně."</string> <string name="permlab_locationUpdates" msgid="7785408253364335740">"ovládání oznámení o aktualizaci polohy"</string> <string name="permdesc_locationUpdates" msgid="2300018303720930256">"Umožňuje povolit či zakázat aktualizace polohy prostřednictvím bezdrátového připojení. Aplikace toto nastavení obvykle nepoužívají."</string> <string name="permlab_checkinProperties" msgid="7855259461268734914">"přístup k vlastnostem Checkin"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Vlastní"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Domů"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobil"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Práce"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Pracovní fax"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Domácí fax"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Pager"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Jiné"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Zpětné volání"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Auto"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Firma (hlavní)"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Hlavní"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Jiný fax"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radiotelefon"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"Telefon pro sluchově postižené (TTY/TDD)"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Pracovní mobil"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Pracovní pager"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Asistent"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Narozeniny"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Výročí"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Událost"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Vlastní"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Domů"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Práce"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Jiné"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobil"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Vlastní"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Domů"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Práce"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Jiné"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Vlastní"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Domů"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Práce"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Jiné"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Vlastní"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Práce"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Jiné"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Vlastní"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"pomocí <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> pomocí <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Zadejte kód PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Nesprávný kód PIN"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Chcete-li telefon odemknout, stiskněte Menu a poté 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Zkuste to prosím znovu"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Nabíjení (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Nabito."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Připojte dobíjecí zařízení."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Není vložena SIM karta."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"V telefonu není žádná karta SIM."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Nabíjení..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Prosím připojte dobíjecí zařízení"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Baterie je vybitá:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Zbývá <xliff:g id="NUMBER">%d%%</xliff:g> nebo méně."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Využití baterie"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Test továrního nastavení se nezdařil"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Test FACTORY_TEST lze provést pouze u balíčků nainstalovaných ve složce /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Chcete opustit tuto stránku?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Vyberte OK, chcete-li pokračovat, nebo Zrušit, chcete-li na stránce zůstat."</string> <string name="save_password_label" msgid="6860261758665825069">"Potvrdit"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dvojitým klepnutím můžete zobrazení přiblížit nebo oddálit."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"čtení historie a záložek Prohlížeče"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Umožňuje aplikaci číst všechny navštívené adresy URL a záložky Prohlížeče."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"zápis do historie a záložek Prohlížeče"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Opravdu chcete kartu SD naformátovat? Všechna data na kartě budou ztracena."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formátovat"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Ladění přes rozhraní USB připojeno"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Vyberte, chcete-li zakázat ladění USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Výběr metody zadávání dat"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" AÁBCČDĎEÉĚFGHCHIÍJKLMNŇOÓPQRŘSŠTŤUÚVWXYÝZŽ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789AÁBCČDĎEÉĚFGHCHIÍJKLMNŇOÓPQRŘSŠTŤUÚVWXYÝZŽ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"kandidáti"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Požadováno oprávnění"\n"pro účet <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Metoda zadávání dat"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synchronizace"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Usnadnění"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Tapeta"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Změnit tapetu"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Protokol PPTP (Point-to-Point Tunneling Protocol)"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index fa89fea..7b37709 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Der kunne ikke oprettes adgang til filen."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Den anmodede fil blev ikke fundet."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Der behandles for mange anmodninger. Prøv igen senere."</string> - <string name="notification_title" msgid="1259940370369187045">"Loginfejl for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synkroniser"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synkroniser"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"For mange <xliff:g id="CONTENT_TYPE">%s</xliff:g> sletninger"</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Overvåg din fysiske placering"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Netværkskommunikation"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Tillader programmer at få adgang til forskellige netværksfunktioner."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Dine konti"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Få adgang til de tilgængelige konti."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Hardwarekontroller"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Direkte adgang til hardware på håndsættet."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefonopkald"</string> @@ -212,7 +215,7 @@ <string name="permlab_backup" msgid="470013022865453920">"kontroller sikkerhedskopiering af system, og gendan"</string> <string name="permdesc_backup" msgid="4837493065154256525">"Tillader et program at kontrollere systemets sikkerhedskopierings- og gendannelsesmekanisme. Ikke til brug til normale programmer."</string> <string name="permlab_backup_data" msgid="4057625941707926463">"Sikkerhedskopier og gendan programmets data"</string> - <string name="permdesc_backup_data" msgid="8274426305151227766">"Tillader et program at deltage i systemets sikkerhedskopierings- og gendannelsesmekanisme."</string> + <string name="permdesc_backup_data" msgid="8274426305151227766">"Tillader et program at kontrollere systemets sikkerhedskopierings- og gendannelsesmekanisme."</string> <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"vis uautoriserede vinduer"</string> <string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Tillader oprettelse af vinduer, der er beregnet til at blive brugt af den interne systembrugergrænseflade. Ikke til brug for normale programmer."</string> <string name="permlab_systemAlertWindow" msgid="3372321942941168324">"vis underretninger på systemniveau"</string> @@ -316,7 +319,7 @@ <string name="permlab_callPrivileged" msgid="4198349211108497879">"ring direkte op til alle telefonnumre"</string> <string name="permdesc_callPrivileged" msgid="244405067160028452">"Tillader programmet at ringe til alle telefonnumre inklusive nødnumre uden din indgriben. Ondsindede programmer kan eventuelt foretage unødvendige og ulovlige opkald til nødtjenester."</string> <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"start CDMA-telefonopsætning direkte"</string> - <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Tillader, at programmet starter CDMA-levering. Ondsindede programmer kan starte unødvendig CDMA-levering"</string> + <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Tillader, at programmet starter CDMA-levering. Onsindede programmer kan starte unødvendig CDMA-levering"</string> <string name="permlab_locationUpdates" msgid="7785408253364335740">"kontroller meddelelser om placeringsopdatering"</string> <string name="permdesc_locationUpdates" msgid="2300018303720930256">"Tillader aktivering/deaktivering af placeringsdata fra radioen. Ikke til brug til normale programmer."</string> <string name="permlab_checkinProperties" msgid="7855259461268734914">"egenskaber for adgangskontrol"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Tilpasset"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Start"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobil"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Arbejde"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Arbejdsfax"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Hjemmefax"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Personsøger"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Andre"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Tilbagekald"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Bil"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Virksomhed (hovednummer)"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Hoved"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Andre faxmeddelelser"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Arbejdsmobiltelefon"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Personsøger"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistent"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Fødselsdato"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Årsdag"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Begivenhed"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Tilpasset"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Start"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Arbejde"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Andre"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobil"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Tilpasset"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Start"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Arbejde"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Andre"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Tilpasset"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Start"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Arbejde"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Andre"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Tilpasset"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Arbejde"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Andre"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Tilpasset"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Indtast PIN-kode"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Forkert PIN-kode!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Tryk på Menu og dernæst på 0 for at låse op."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager! Prøv igen"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Oplader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Opladt."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Tilslut din oplader."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Der er ikke noget SIM-kort."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Der er ikke noget SIM-kort i telefonen."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Oplader ..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Forbind oplader"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er ved at blive tomt:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre tilbage."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Batteriforbrug"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fabrikstest mislykkedes"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Handlingen FACTORY_TEST understøttes kun af pakker installeret i /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"Javascript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Naviger væk fra denne side?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n" Vælg OK for at fortsætte eller Annuller for at blive på den aktuelle side."</string> <string name="save_password_label" msgid="6860261758665825069">"Bekræft"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dobbeltklik for at zoome ind eller ud."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"læs browserens oversigt og bogmærker"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillader programmet at læse alle de webadresser, browseren har besøgt, og alle browserens bogmærker."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriv browserens oversigt og bogmærker"</string> @@ -623,7 +681,7 @@ <item quantity="one" msgid="2178576254385739855">"i morgen"</item> <item quantity="other" msgid="2973062968038355991">"om <xliff:g id="COUNT">%d</xliff:g> dage"</item> </plurals> - <string name="preposition_for_date" msgid="9093949757757445117">"den <xliff:g id="DATE">%s</xliff:g>"</string> + <string name="preposition_for_date" msgid="9093949757757445117">"til <xliff:g id="DATE">%s</xliff:g>"</string> <string name="preposition_for_time" msgid="5506831244263083793">"kl. <xliff:g id="TIME">%s</xliff:g>"</string> <string name="preposition_for_year" msgid="5040395640711867177">"i <xliff:g id="YEAR">%s</xliff:g>"</string> <string name="day" msgid="8144195776058119424">"dag"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Er du sikker på, du ønsker at formatere SD-kortet? Alle data på kortet mistes."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formater"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-fejlretning forbundet"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Vælg for at deaktivere USB-fejlretning."</string> - <string name="select_input_method" msgid="6865512749462072765">"Vælg indtastningsmetode"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"kandidater"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Der er anmodet om tilladelse"\n"til kontoen <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Inputmetode"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synkroniser"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Tilgængelighed"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Tapet"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Skift tapet"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Point-to-Point Tunneling Protocol"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index ebf3b66..af68303 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Auf die Datei konnte nicht zugegriffen werden."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Die angeforderte Datei wurde nicht gefunden."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Es werden zurzeit zu viele Anfragen verarbeitet. Versuchen Sie es später erneut."</string> - <string name="notification_title" msgid="1259940370369187045">"Fehler bei Anmeldung für <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synchronisieren"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synchronisieren"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Zu viele <xliff:g id="CONTENT_TYPE">%s</xliff:g> gelöscht."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Ihren physischen Standort überwachen"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Netzwerkkommunikation"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Ermöglicht Anwendungen den Zugriff auf verschiedene Netzwerkfunktionen."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Ihre Konten"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Zugriff auf verfügbare Konten"</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Hardware-Steuerelemente"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Direkter Zugriff auf Hardware über Headset"</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Anrufe"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Benutzerdefiniert"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Privat"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobil"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Arbeit"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax (Beruflich)"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax (privat)"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Pager"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Andere"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Rückruf"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Auto"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Firma (Hauptnummer)"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Hauptnummer"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Weitere Faxnummer"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY/TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Handy (geschäftlich)"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Pager (beruflich)"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Zweite Nummer"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Geburtstag"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Jahrestag"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Termin"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Benutzerdefiniert"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Privat"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Beruflich"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Andere"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobil"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Benutzerdefiniert"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Privat"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Beruflich"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Andere"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Benutzerdefiniert"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Privat"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Beruflich"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Andere"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Benutzerdefiniert"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Beruflich"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Andere"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Benutzerdefiniert"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"über <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> über <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-Code eingeben"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Falscher PIN-Code!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Drücken Sie zum Entsperren die Menütaste und dann auf \"0\"."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tut uns leid. Versuchen Sie es noch einmal."</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Wird geladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Aufgeladen"</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Bitte Ladegerät anschließen"</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Keine SIM-Karte."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Keine SIM-Karte im Telefon."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Wird aufgeladen..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Ladegerät anschließen"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Akku ist fast leer."</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> oder weniger verbleiben."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Akkuverbrauch"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Werkstest fehlgeschlagen"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Die Aktion FACTORY_TEST wird nur für unter \"/system/app\" gespeicherte Pakete unterstützt."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Von dieser Seite navigieren?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Wählen Sie \"OK\", um fortzufahren, oder wählen Sie \"Abbrechen\", um auf der aktuellen Seite zu bleiben."</string> <string name="save_password_label" msgid="6860261758665825069">"Bestätigen"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Tipp: Zum Heranzoomen und Vergrößern zweimal tippen"</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"Browserverlauf und Lesezeichen lesen"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Ermöglicht der Anwendung, alle URLs, die mit dem Browser besucht wurden, sowie alle Lesezeichen des Browsers zu lesen."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"Browserverlauf und Lesezeichen schreiben"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Möchten Sie die SD-Karte wirklich formatieren? Alle Daten auf Ihrer Karte gehen dann verloren."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-Debugging verbunden"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Zum Deaktivieren des USB-Debugging auswählen"</string> - <string name="select_input_method" msgid="6865512749462072765">"Eingabemethode auswählen"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"Kandidaten"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Berechtigung erforderlich"\n"für Konto <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Eingabemethode"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synchronisieren"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Eingabehilfen"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Hintergrund"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Hintergrundbild ändern"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Point-to-Point-Tunneling-Protokoll"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 062658d..a4ec8c1 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Η πρόσβαση στο αρχείο δεν ήταν δυνατή."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Το αρχείο που ζητήθηκε δεν βρέθηκε."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Πραγματοποιείται επεξεργασία πάρα πολλών αιτημάτων. Προσπαθήστε ξανά αργότερα."</string> - <string name="notification_title" msgid="1259940370369187045">"Σφάλμα σύνδεσης για <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Συγχρονισμός"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Συγχρονισμός"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Πάρα πολλές <xliff:g id="CONTENT_TYPE">%s</xliff:g> διαγραφές."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Παρακολούθηση της φυσικής τοποθεσίας σας"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Επικοινωνία δικτύου"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Επιτρέπει σε εφαρμογές να αποκτήσουν πρόσβαση σε διάφορες λειτουργίες δικτύου."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Οι λογαριασμοί σας"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Πρόσβαση στους διαθέσιμους λογαριασμούς."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Στοιχεία ελέγχου υλικού"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Άμεση πρόσβαση στο υλικό της συσκευής τηλεφώνου."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Τηλεφωνικές κλήσεις"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Προσαρμοσμένο"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Οικία"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Κινητό"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Εργασία"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Φαξ εργασίας"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Φαξ οικίας"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Βομβητής"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Άλλο"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Επανάκληση"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Αυτοκίνητο"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Κύρια εταιρική γραμμή"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Κύριος"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Άλλο fax"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Πομπός"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Τέλεξ"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"Τηλέφωνο TTY/TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Κινητό τηλέφωνο εργασίας"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Βομβητής εργασίας"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Βοηθός"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Γενέθλια"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Επέτειος"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Συμβάν"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Προσαρμοσμένο"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Οικία"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Εργασία"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Άλλο"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Κινητό"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Προσαρμοσμένο"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Οικία"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Εργασία"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Άλλο"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Προσαρμοσμένο"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Οικία"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Εργασία"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Άλλο"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Προσαρμοσμένο"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Εργασία"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Άλλο"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Προσαρμοσμένο"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"μέσω <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> μέσω <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Πληκτρολογήστε τον κωδικό αριθμό PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Εσφαλμένος κωδικός αριθμός PIN!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Για ξεκλείδωμα, πατήστε το πλήκτρο Menu και, στη συνέχεια, το πλήκτρο 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Προσπαθήστε αργότερα"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Φόρτιση (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Φορτίστηκε."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Συνδέστε τον φορτιστή."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Δεν υπάρχει κάρτα SIM."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Δεν υπάρχει κάρτα SIM στο τηλέφωνο."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Φόρτιση..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Συνδέστε τον φορτιστή"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Η στάθμη της μπαταρίας είναι χαμηλή:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Απομένει <xliff:g id="NUMBER">%d%%</xliff:g> ή λιγότερο."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Χρήση μπαταρίας"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Η εργοστασιακή δοκιμή απέτυχε"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Η ενέργεια FACTORY_TEST υποστηρίζεται μόνο για πακέτα που είναι εγκατεστημένα στον κατάλογο /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Απομάκρυνση από αυτή τη σελίδα;"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Επιλέξτε OK για συνέχεια, ή Ακύρωση για παραμονή στην τρέχουσα σελίδα."</string> <string name="save_password_label" msgid="6860261758665825069">"Επιβεβαίωση"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Συμβουλή: διπλό άγγιγμα για μεγέθυνση και σμίκρυνση."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ανάγνωση ιστορικού και σελιδοδεικτών προγράμματος περιήγησης"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Επιτρέπει στην εφαρμογή την ανάγνωση όλων των διευθύνσεων URL που το πρόγραμμα περιήγησης έχει επισκεφθεί και όλων των σελιδοδεικτών του προγράμματος περιήγησης."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"εγγραφή ιστορικού και σελιδοδεικτών προγράμματος περιήγησης"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Είστε βέβαιοι ότι θέλετε να διαμορφώσετε την κάρτα SD; Όλα τα δεδομένα στην κάρτα σας θα χαθούν."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Διαμόρφωση"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Συνδέθηκε ο εντοπισμός σφαλμάτων USB"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Επιλογή μεθόδου εισόδου"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"υποψήφιοι"</u></string> @@ -777,19 +837,22 @@ <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> <string name="allow" msgid="7225948811296386551">"Να επιτρέπεται"</string> <string name="deny" msgid="2081879885755434506">"Άρνηση"</string> <string name="permission_request_notification_title" msgid="5390555465778213840">"Απαιτείται άδεια"</string> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Ζητήθηκε άδεια"\n"για τον λογαριασμό <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Μέθοδος εισόδου"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Συγχρονισμός"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Προσβασιμότητα"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Ταπετσαρία"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Αλλαγή ταπετσαρίας"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Πρωτόκολλο Point-to-Point Tunneling Protocol (PPTP)"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 88f16a9..025ae60 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -777,8 +777,6 @@ <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> <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> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 2757978..ebe1bb0 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"No se ha podido acceder al archivo."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"No se ha encontrado el archivo solicitado."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Se están procesando demasiadas solicitudes. Vuelve a intentarlo más tarde."</string> - <string name="notification_title" msgid="1259940370369187045">"Error de acceso a la cuenta <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Sincronización"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sincronización"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Demasiadas eliminaciones de <xliff:g id="CONTENT_TYPE">%s</xliff:g>"</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Controlar su ubicación física"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Comunicación de red"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Permite que las aplicaciones accedan a distintas funciones de red."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Tus cuentas"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Acceder a las cuentas disponibles"</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Controles de hardware"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Acceso directo al hardware del móvil"</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Llamadas de teléfono"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizado"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Casa"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Móvil"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Trabajo"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax del trabajo"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax de casa"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Buscapersonas"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Otro"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Devolución de llamada"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Coche"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Teléfono principal de la empresa"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"RDSI"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Principal"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Otro fax"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Télex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Móvil del trabajo"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Buscapersonas del trabajo"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Asistente"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Cumpleaños"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Aniversario"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizado"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Casa"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Trabajo"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Otro"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Móvil"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizada"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Casa"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Trabajo"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Otro"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Personalizada"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Casa"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Trabajo"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Otro"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizada"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo!"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Trabajo"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Otra"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Personalizada"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduce el código PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"El código PIN es incorrecto."</string> <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Inténtalo de nuevo"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Cargado"</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Conecta el cargador"</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Falta la tarjeta SIM"</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"No se ha insertado ninguna tarjeta SIM en el teléfono."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Se está agotando la batería:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> o menos disponible"</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fallo en la prueba de fábrica"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST sólo es compatible con los paquetes instalados en /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"¿Quieres salir de esta página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Selecciona \"Aceptar\" para continuar o \"Cancelar\" para permanecer en la página actual."</string> <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Sugerencia: toca dos veces para ampliar o reducir."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"leer información de marcadores y del historial del navegador"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que la aplicación lea todas las URL que ha visitado el navegador y todos sus marcadores."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"escribir en marcadores y en el historial del navegador"</string> @@ -624,7 +682,7 @@ <item quantity="other" msgid="2973062968038355991">"dentro de <xliff:g id="COUNT">%d</xliff:g> días"</item> </plurals> <string name="preposition_for_date" msgid="9093949757757445117">"el <xliff:g id="DATE">%s</xliff:g>"</string> - <string name="preposition_for_time" msgid="5506831244263083793">"a la(s) <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="preposition_for_time" msgid="5506831244263083793">"a las <xliff:g id="TIME">%s</xliff:g>"</string> <string name="preposition_for_year" msgid="5040395640711867177">"en <xliff:g id="YEAR">%s</xliff:g>"</string> <string name="day" msgid="8144195776058119424">"día"</string> <string name="days" msgid="4774547661021344602">"días"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de la tarjeta."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formato"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Dispositivo de depuración USB conectado"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleccionar para inhabilitar la depuración USB"</string> - <string name="select_input_method" msgid="6865512749462072765">"Seleccionar método de introducción de texto"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Permiso solicitado"\n"para la cuenta <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Método de introducción de texto"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Sincronización"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Accesibilidad"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Fondo de pantalla"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Cambiar fondo de pantalla"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Protocolo de túnel punto a punto"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 7a07a7c..5647063 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Impossible d\'accéder au fichier."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Le fichier demandé est introuvable."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Trop de requêtes sont en cours de traitement. Veuillez réessayer ultérieurement."</string> - <string name="notification_title" msgid="1259940370369187045">"Erreur de connexion au compte <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synchroniser"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synchronisation"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Trop de contenus supprimés (<xliff:g id="CONTENT_TYPE">%s</xliff:g>)."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Suivre votre position géographique"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Communications réseau"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Permet à des applications d\'accéder à différentes fonctionnalités du réseau."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Vos comptes"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Accéder aux comptes disponibles"</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Commandes du matériel"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Permet d\'accéder directement au matériel de l\'appareil."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Appels"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Personnalisé"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Domicile"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobile"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Bureau"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Télécopie bureau"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Télécopie domicile"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Téléavertisseur"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Autre"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Rappel"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Voiture"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Société (principal)"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"RNIS"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Principal"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Autre télécopie"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Télex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY/TTD (malentendants)"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Mobile (professionnel)"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Téléavertisseur (professionnel)"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistant"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Anniversaire"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Fête"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Événement"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Personnalisé"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Domicile"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Bureau"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Autre"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobile"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Personnalisée"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Domicile"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Bureau"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Autre"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Personnalisée"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Domicile"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Bureau"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Autre"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Personnalisée"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Bureau"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Autre"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Personnalisée"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Saisissez le code PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Le code PIN est incorrect !"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Pour débloquer le clavier, appuyez sur \"Menu\" puis sur 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Désolé. Merci de réessayer."</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Chargement (<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Chargé"</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Branchez votre chargeur."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Aucune carte SIM n\'a été trouvée."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Aucune carte SIM n\'est insérée dans le téléphone."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Chargement..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Branchez le chargeur"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Le niveau de la batterie est bas :"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Maximum <xliff:g id="NUMBER">%d%%</xliff:g> restants."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Utilisation de la batterie"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Échec du test usine"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"L\'action FACTORY_TEST est uniquement prise en charge pour les paquets de données installés dans in/system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Vous souhaitez quitter cette page ?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Sélectionnez OK pour continuer ou Annuler pour rester sur la page actuelle."</string> <string name="save_password_label" msgid="6860261758665825069">"Confirmer"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Conseil : Appuyez deux fois pour effectuer un zoom avant ou arrière."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"lire l\'historique et les favoris du navigateur"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Autorise l\'application à lire toutes les URL auxquelles le navigateur a accédé et tous ses favoris."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"écrire dans l\'historique et les favoris du navigateur"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Voulez-vous vraiment formater la carte SD ? Toutes les données de cette carte seront perdues."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Débogage USB connecté"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Sélectionnez cette option pour désactiver le débogage USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Sélectionner un mode de saisie"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"candidats"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Autorisation demandée"\n"pour le compte <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Mode de saisie"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synchronisation"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Accessibilité"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Fond d\'écran"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Changer de fond d\'écran"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Protocole de tunnelisation point-à-point"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 8d2aefa..f0647e7 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Impossibile accedere al file."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Impossibile trovare il file richiesto."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Troppe richieste in fase di elaborazione. Riprova più tardi."</string> - <string name="notification_title" msgid="1259940370369187045">"Errore di accesso per <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Sinc"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sincronizzazione"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Troppe eliminazioni di <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Monitorare la posizione fisica dell\'utente"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Comunicazione di rete"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Consentono l\'accesso delle applicazioni a varie funzionalità di rete."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"I tuoi account"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Accedere agli account disponibili."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Controlli hardware"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Accedere direttamente all\'hardware del ricevitore."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefonate"</string> @@ -316,7 +319,7 @@ <string name="permlab_callPrivileged" msgid="4198349211108497879">"chiamata diretta di tutti i n. telefono"</string> <string name="permdesc_callPrivileged" msgid="244405067160028452">"Consente all\'applicazione di chiamare qualsiasi numero, compresi quelli di emergenza, automaticamente. Le applicazioni dannose potrebbero effettuare chiamate non necessarie e illegali a servizi di emergenza."</string> <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"avviare direttamente la configurazione del telefono CDMA"</string> - <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Consente all\'applicazione di avviare il servizio di provisioning CDMA. Le applicazioni dannose potrebbero avviare il servizio di provisioning CDMA quando non è necessario"</string> + <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Consente all\'applicazione di avviare il servizio di CDMA provisioning. Le applicazioni dannose potrebbero avviare il servizio di CDMA provisioning quando non è necessario"</string> <string name="permlab_locationUpdates" msgid="7785408253364335740">"controllo notifiche aggiornamento posizione"</string> <string name="permdesc_locationUpdates" msgid="2300018303720930256">"Consente l\'attivazione/disattivazione delle notifiche di aggiornamento della posizione dal segnale cellulare. Da non usare per normali applicazioni."</string> <string name="permlab_checkinProperties" msgid="7855259461268734914">"accesso a proprietà di archiviazione"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizzato"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Casa"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Cellulare"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Ufficio"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax ufficio"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax casa"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Cercapersone"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Altro"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Callback"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Automobile"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Azienda, principale"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Principale"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Altro fax"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Segnale cellulare"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Cellulare ufficio"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Cercapersone ufficio"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistente"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Compleanno"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Anniversario"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizzato"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Casa"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Ufficio"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Altro"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Cellulare"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizzato"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Casa"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Ufficio"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Altro"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Personalizzato"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Casa"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Ufficio"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Altro"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizzato"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Ufficio"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Altro"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Personalizzato"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"tramite <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> tramite <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Inserisci il PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Codice PIN errato."</string> <string name="keyguard_label_text" msgid="861796461028298424">"Per sbloccare, premi Menu, poi 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Riprova"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"In carica (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Carico."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Collegare il caricabatterie."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nessuna SIM presente."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Nessuna SIM presente nel telefono."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"In carica..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Collegare il caricabatterie"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteria quasi scarica:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> rimanente o meno."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Utilizzo batteria"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Test di fabbrica non riuscito"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"L\'azione FACTORY_TEST è supportata soltanto per i pacchetti installati in /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Uscire da questa pagina?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Seleziona OK per continuare o Annulla per rimanere nella pagina corrente."</string> <string name="save_password_label" msgid="6860261758665825069">"Conferma"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Suggerimento. Tocca due volte per aumentare/ridurre lo zoom."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"lettura cronologia e segnalibri del browser"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Consente all\'applicazione di leggere tutti gli URL visitati e tutti i segnalibri del browser."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"creazione cronologia e segnalibri del browser"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Formattare la scheda SD? Tutti i dati sulla scheda verranno persi."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatta"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Debug USB collegato"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleziona per disattivare il debug USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Seleziona metodo di inserimento"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"candidati"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Autorizzazione richiesta"\n"per l\'account <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Metodo inserimento"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Sinc"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Accesso facilitato"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Sfondo"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Cambia sfondo"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Protocollo di tunneling Point-to-Point"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 8c3836c..9517af5 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"ファイルにアクセスできませんでした。"</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"要求されたファイルが見つかりませんでした。"</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"処理中のリクエストが多すぎます。しばらくしてからもう一度試してください。"</string> - <string name="notification_title" msgid="1259940370369187045">"ログインエラー: <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"同期"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"同期"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"<xliff:g id="CONTENT_TYPE">%s</xliff:g>での削除が多すぎます。"</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"現在地を追跡"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"ネットワーク通信"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"ネットワークのさまざまな機能へのアクセスをアプリケーションに許可します。"</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"アカウント"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"利用可能なアカウントにアクセスします。"</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"ハードウェアの制御"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"携帯電話のハードウェアに直接アクセスします。"</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"電話/通話"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"カスタム"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"自宅"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"携帯"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"勤務先"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"FAX(勤務先)"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"FAX(自宅)"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"ポケベル"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"その他"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"コールバック"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"クルマ"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"会社代表番号"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"メイン"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"FAX(その他)"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"無線"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"テレックス"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"携帯電話(勤務先)"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"ポケベル(勤務先)"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"アシスタント"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"誕生日"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"記念日"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"予定"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"カスタム"</string> - <string name="emailTypeHome" msgid="449227236140433919">"自宅"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"勤務先"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"その他"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"携帯"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"カスタム"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"自宅"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"勤務先"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"その他"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"カスタム"</string> - <string name="imTypeHome" msgid="6241181032954263892">"自宅"</string> - <string name="imTypeWork" msgid="1371489290242433090">"勤務先"</string> - <string name="imTypeOther" msgid="5377007495735915478">"その他"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"カスタム"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Googleトーク"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"勤務先"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"その他"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"カスタム"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>経由"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>、更新元: <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PINコードを入力"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PINコードが正しくありません。"</string> <string name="keyguard_label_text" msgid="861796461028298424">"MENU、0キーでロック解除"</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"やり直してください"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"充電中(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"充電完了。"</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"充電してください。"</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIMカードが挿入されていません"</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"SIMカードが挿入されていません"</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"充電中..."</string> <string name="battery_low_title" msgid="7923774589611311406">"充電してください"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"電池が残り少なくなっています:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"残り<xliff:g id="NUMBER">%d%%</xliff:g>未満です。"</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"電池使用量"</string> <string name="factorytest_failed" msgid="5410270329114212041">"出荷時試験が失敗"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST操作は、/system/appにインストールされたパッケージのみが対象です。"</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"このページから移動しますか?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"移動する場合は[OK]、今のページに残る場合は[キャンセル]を選択してください。"</string> <string name="save_password_label" msgid="6860261758665825069">"確認"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"ヒント: ダブルタップで拡大/縮小できます。"</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ブラウザの履歴とブックマークを読み取る"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"ブラウザでアクセスしたすべてのURLおよびブラウザのすべてのブックマークの読み取りをアプリケーションに許可します。"</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"ブラウザの履歴とブックマークを書き込む"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"SDカードをフォーマットしてもよろしいですか?カード内のすべてのデータが失われます。"</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"フォーマット"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USBデバッグが接続されました"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"USBデバッグを無効にする場合に選択します。"</string> - <string name="select_input_method" msgid="6865512749462072765">"入力方法の選択"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"候補"</u></string> @@ -777,19 +837,22 @@ <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> <string name="allow" msgid="7225948811296386551">"許可"</string> <string name="deny" msgid="2081879885755434506">"拒否"</string> <string name="permission_request_notification_title" msgid="5390555465778213840">"リクエスト済み権限"</string> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"次のアカウントにアクセスする権限が"\n"リクエストされました:<xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"入力方法"</string> <string name="sync_binding_label" msgid="3687969138375092423">"同期"</string> - <string name="accessibility_binding_label" msgid="4148120742096474641">"ユーザー補助"</string> + <string name="accessibility_binding_label" msgid="4148120742096474641">"アクセシビリティ"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"壁紙"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"壁紙を変更"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"ポイントツーポイントトンネリングプロトコル"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 840fa99..0ef3824 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"파일에 액세스할 수 없습니다."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"요청한 파일을 찾을 수 없습니다."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"처리 중인 요청이 너무 많습니다. 잠시 후에 다시 시도해 주세요."</string> - <string name="notification_title" msgid="1259940370369187045">"<xliff:g id="ACCOUNT">%1$s</xliff:g>에 로그인 오류 발생"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"동기화"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"동기화"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"<xliff:g id="CONTENT_TYPE">%s</xliff:g> 삭제가 너무 많습니다."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"실제 위치 모니터링"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"네트워크 통신"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"응용프로그램이 다양한 네트워크 기능에 액세스할 수 있도록 합니다."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"계정"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"사용 가능한 계정에 액세스합니다."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"하드웨어 제어"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"휴대전화의 하드웨어에 직접 액세스합니다."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"전화 통화"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"맞춤설정"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"집"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"휴대전화"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"직장"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"직장 팩스"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"집(팩스)"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"호출기"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"기타"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"콜백"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"카폰"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"회사 기본전화"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"기본"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"기타 팩스"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"무선통신"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"텔렉스"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"직장 휴대전화"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"직장 호출기"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"비서"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"생일"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"기념일"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"일정"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"맞춤설정"</string> - <string name="emailTypeHome" msgid="449227236140433919">"집"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"직장"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"기타"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"모바일"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"맞춤설정"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"집"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"직장"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"기타"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"맞춤설정"</string> - <string name="imTypeHome" msgid="6241181032954263892">"집"</string> - <string name="imTypeWork" msgid="1371489290242433090">"직장"</string> - <string name="imTypeOther" msgid="5377007495735915478">"기타"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"맞춤설정"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google 토크"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"직장"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"기타"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"맞춤설정"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>을(를) 통해"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>(<xliff:g id="SOURCE">%2$s</xliff:g> 사용)"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN 코드 입력"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 코드가 잘못되었습니다."</string> <string name="keyguard_label_text" msgid="861796461028298424">"잠금해제하려면 메뉴를 누른 다음 0을 누릅니다."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"죄송합니다. 다시 시도하세요."</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"충전 중(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"충전되었습니다."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"충전기를 연결하세요."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIM 카드가 없습니다."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"휴대전화에 SIM 카드가 없습니다."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"충전 중..."</string> <string name="battery_low_title" msgid="7923774589611311406">"충전기를 연결하세요."</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"배터리 전원이 부족합니다."</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"잔여 배터리가 <xliff:g id="NUMBER">%d%%</xliff:g> 이하입니다."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"배터리 사용"</string> <string name="factorytest_failed" msgid="5410270329114212041">"출고 테스트 불합격"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST 작업은 /system/app 디렉토리에 설치된 패키지에 대해서만 지원됩니다."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"자바스크립트"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"다른 페이지를 탐색하시겠습니까?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"계속하려면 \'확인\'을 선택하고 현재 페이지에 그대로 있으려면 \'취소\'를 선택하세요."</string> <string name="save_password_label" msgid="6860261758665825069">"확인"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"도움말: 축소/확대하려면 두 번 누릅니다."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"브라우저의 기록 및 북마크 읽기"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"응용프로그램이 브라우저로 방문한 모든 URL과 브라우저의 모든 북마크를 읽도록 허용합니다."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"브라우저의 기록 및 북마크 쓰기"</string> @@ -740,13 +798,15 @@ <string name="usb_storage_stop_message" msgid="2390958966725232848">"USB 저장소를 끄기 전에 반드시 USB 호스트에서 마운트 해제하세요. USB 저장소를 끄려면 \'끄기\'를 선택하세요."</string> <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"USB 저장소 끄기"</string> <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"취소"</string> - <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"USB 저장소를 끄는 동안 문제가 발생했습니다. USB 호스트와 연결을 해제했는지 확인한 다음 다시 시도하세요."</string> + <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"USB 저장소를 끄는 동안 문제가 발생했습니다. USB 호스트를 마운트 해제했는지 확인한 다음 다시 시도하세요."</string> <string name="extmedia_format_title" msgid="8663247929551095854">"SD 카드 포맷"</string> <string name="extmedia_format_message" msgid="3621369962433523619">"SD 카드를 포맷하시겠습니까? 포맷하면 카드의 모든 데이터를 잃게 됩니다."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"포맷"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB 디버깅 연결됨"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"USB 디버깅을 사용하지 않으려면 선택합니다."</string> - <string name="select_input_method" msgid="6865512749462072765">"입력 방법 선택"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"가능한 원인"</u></string> @@ -777,19 +837,22 @@ <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> <string name="allow" msgid="7225948811296386551">"허용"</string> <string name="deny" msgid="2081879885755434506">"거부"</string> <string name="permission_request_notification_title" msgid="5390555465778213840">"권한 요청"</string> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"<xliff:g id="ACCOUNT">%s</xliff:g> 계정에 대해"\n"권한 요청"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"입력 방법"</string> <string name="sync_binding_label" msgid="3687969138375092423">"동기화"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"접근성"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"배경화면"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"배경화면 변경"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"PPTP(Point-to-Point Tunneling Protocol)"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index ced4f16..aa15ad1 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Kunne ikke åpne filen."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Fant ikke den forespurte filen."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"For mange forespørsler blir behandlet. Prøv igjen senere."</string> - <string name="notification_title" msgid="1259940370369187045">"Innloggingsfeil for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synkronisering"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synkronisering"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"For mange slettinger av <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Overvåking av telefonens fysiske plassering"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Nettverkstilgang"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Gir applikasjoner tilgang til diverse nettverksfunksjoner."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Google-kontoer"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Tilgang til tilgjengelige Google-kontoer."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Maskinvarekontroll"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Direkte tilgang til maskinvaren på telefonen."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefonsamtaler"</string> @@ -195,7 +198,7 @@ <string name="permdesc_shutdown" msgid="7046500838746291775">"Lar applikasjonen sette aktivitetshåndtereren i avslutningstilstand. Slår ikke systemet helt av."</string> <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"forhindre applikasjonsbytte"</string> <string name="permdesc_stopAppSwitches" msgid="3857886086919033794">"Lar applikasjonen forhindre brukeren fra å bytte til en annen applikasjon."</string> - <string name="permlab_runSetActivityWatcher" msgid="7811586187574696296">"overvåke og kontrollere all applikasjonsoppstart"</string> + <string name="permlab_runSetActivityWatcher" msgid="7811586187574696296">"Blokker popup-vinduer"</string> <string name="permdesc_runSetActivityWatcher" msgid="3228701938345388092">"Lar applikasjonen overvåke og kontrollere hvordan systemet starter applikasjoner. Ondsinnede applikasjoner kan ta over systemet helt. Denne rettigheten behøves bare for utvikling, aldri for vanlig bruk av telefonen."</string> <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"kringkaste melding om fjernet pakke"</string> <string name="permdesc_broadcastPackageRemoved" msgid="3453286591439891260">"Lar applikasjonen kringkaste en melding om at en applikasjonspakke er blitt fjernet. Ondsinnede applikasjoner kan bruke dette til å drepe vilkårlige andre kjørende applikasjoner."</string> @@ -210,9 +213,9 @@ <string name="permlab_batteryStats" msgid="7863923071360031652">"endre batteristatistikk"</string> <string name="permdesc_batteryStats" msgid="5847319823772230560">"Lar applikasjonen endre på innsamlet batteristatistikk. Ikke ment for vanlige applikasjoner."</string> <string name="permlab_backup" msgid="470013022865453920">"kontrollere backup og gjenoppretting"</string> - <string name="permdesc_backup" msgid="4837493065154256525">"Lar applikasjonen kontrollere systemets backup- og gjenopprettingsmekanisme. Ikke ment for vanlige applikasjoner."</string> - <string name="permlab_backup_data" msgid="4057625941707926463">"foreta backup og gjenoppretting av applikasjonens data"</string> - <string name="permdesc_backup_data" msgid="8274426305151227766">"Lar applikasjonen delta i systemets backup- og gjenopprettingsmekanisme."</string> + <string name="permdesc_backup" msgid="4837493065154256525">"Gir programmet tillatelse til å kontrollere systemets mekanismer for sikkerhetskopiering gjenoppretting. Ikke beregnet på vanlige programmer."</string> + <string name="permlab_backup_data" msgid="4057625941707926463">"sikkerhetskopier og gjenopprett programmets data"</string> + <string name="permdesc_backup_data" msgid="8274426305151227766">"Gir programmet tillatelse til å ta del i systemets mekanismer for sikkerhetskopiering og gjenoppretting."</string> <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"vis uautoriserte vinduer"</string> <string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Tillater at det opprettes vinduer ment for bruk av systemets interne brukergrensesnitt. Ikke ment for vanlige applikasjoner."</string> <string name="permlab_systemAlertWindow" msgid="3372321942941168324">"vise advarsler på systemnivå"</string> @@ -258,7 +261,7 @@ <string name="permlab_writeSettings" msgid="1365523497395143704">"endre globale systeminnstillinger"</string> <string name="permdesc_writeSettings" msgid="838789419871034696">"Lar applikasjonen endre systemets innstillingsdata. Ondsinnede applikasjoner kan skade systemets innstillinger."</string> <string name="permlab_writeSecureSettings" msgid="204676251876718288">"endre sikre systeminnstillinger"</string> - <string name="permdesc_writeSecureSettings" msgid="5497873143539034724">"Gir programmet tillatelse til å endre systemets data for sikkerhetsinnstilling. Ikke ment for vanlige programmer."</string> + <string name="permdesc_writeSecureSettings" msgid="5497873143539034724">"Gir programmet tillatelse til å endre systemets data for sikkerhetsinnstilling. Ikke beregnet på vanlige programmer."</string> <string name="permlab_writeGservices" msgid="2149426664226152185">"redigere Google-tjenestekartet"</string> <string name="permdesc_writeGservices" msgid="6602362746516676175">"Lar applikasjonen redigere Google-tjenestekartet. Ikke ment for bruk av vanlige applikasjoner."</string> <string name="permlab_receiveBootCompleted" msgid="7776779842866993377">"starte automatisk sammen med systemet"</string> @@ -315,8 +318,8 @@ <string name="permdesc_callPhone" msgid="3369867353692722456">"Lar applikasjonen ringe telefonnummer uten inngripen fra brukeren. Ondsinnede applikasjoner kan forårsake uventede oppringinger på telefonregningen. Merk at dette ikke gir applikasjonen lov til å ringe nødnummer."</string> <string name="permlab_callPrivileged" msgid="4198349211108497879">"ringe vilkårlige telefonnummer direkte"</string> <string name="permdesc_callPrivileged" msgid="244405067160028452">"Lar applikasjonen ringe hvilket som helst telefonnummer, inkludert nødnummer, uten inngripen fra brukeren. Ondsinnede applikasjoner kan forårsake unødvendige og ulovlige samtaler til nødtjenester."</string> - <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"begynne CDMA-telefonoppsett direkte"</string> - <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Lar applikasjonen begynne CDMA-oppsett. Ondsinnede applikasjoner kan bruke dette til å starte CDMA-oppsett uten grunn."</string> + <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"start CDMA-telefonoppsett direkte"</string> + <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Gir programmet tillatelse til å starte klargjøring av CDMA. Skadelige programmer kan starte klargjøring av CDMA uten grunn"</string> <string name="permlab_locationUpdates" msgid="7785408253364335740">"kontrollere varsling for plasseringsendring"</string> <string name="permdesc_locationUpdates" msgid="2300018303720930256">"Lar applikasjonen slå av/på varsling om plasseringsendringer fra radioen. Ikke ment for vanlige applikasjoner."</string> <string name="permlab_checkinProperties" msgid="7855259461268734914">"få tilgang til egenskaper for innsjekking"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Egendefinert"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Hjemme"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobil"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Arbeid"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Faks arbeid"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Faks hjemme"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Personsøker"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Annen"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Tilbakering"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Bil"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Firma hoved"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Hoved"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Faks annen"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Teleks"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"Teksttelefon"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Mobil arbeid"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Personsøker arbeid"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistent"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Bursdag"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Jubileum"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Akivitet"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Egendefinert"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Hjemme"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Arbeid"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Annen"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobil"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Egendefinert"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Hjemme"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Arbeid"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Annen"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Egendefinert"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Hjemme"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Arbeid"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Annen"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Egendefinert"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"OQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Arbeid"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Annen"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Egendefinert"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Skriv inn PIN-kode:"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Gal PIN-kode!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"For å låse opp, trykk på menyknappen og deretter 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager, prøv igjen:"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Lader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Fullt ladet"</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Koble til en batterilader."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Mangler SIM-kort."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Ikke noe SIM-kort i telefonen."</string> @@ -530,17 +586,19 @@ <string name="battery_status_charging" msgid="756617993998772213">"Lader…"</string> <string name="battery_low_title" msgid="7923774589611311406">"Koble til en lader"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er nesten tomt:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"mindre enn <xliff:g id="NUMBER">%d%%</xliff:g> igjen."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Batteribruk"</string> - <string name="factorytest_failed" msgid="5410270329114212041">"Fabrikktesten feilet"</string> + <string name="factorytest_failed" msgid="5410270329114212041">"Factory test failed"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"The FACTORY_TEST action is only supported for packages installed in /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"No package was found that provides the FACTORY_TEST action."</string> - <string name="factorytest_reboot" msgid="6320168203050791643">"Omstart"</string> + <string name="factorytest_reboot" msgid="6320168203050791643">"Reboot"</string> <string name="js_dialog_title" msgid="8143918455087008109">"Siden \'<xliff:g id="TITLE">%s</xliff:g> sier:"</string> <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Naviger bort fra denne siden?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Velg OK for å fortsette, eller Avbryt for å forbli på denne siden."</string> <string name="save_password_label" msgid="6860261758665825069">"Bekreft"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Dobbelttrykk for å zoome inn og ut."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"lese nettleserens logg og bokmerker"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Lar applikasjonen lese alle adresser nettleseren har besøkt, og alle nettleserens bokmerker."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skrive til nettleserens logg og bokmerker"</string> @@ -623,7 +681,7 @@ <item quantity="one" msgid="2178576254385739855">"i morgen"</item> <item quantity="other" msgid="2973062968038355991">"om <xliff:g id="COUNT">%d</xliff:g> d"</item> </plurals> - <string name="preposition_for_date" msgid="9093949757757445117">"<xliff:g id="DATE">%s</xliff:g>"</string> + <string name="preposition_for_date" msgid="9093949757757445117">"den <xliff:g id="DATE">%s</xliff:g>"</string> <string name="preposition_for_time" msgid="5506831244263083793">"kl. <xliff:g id="TIME">%s</xliff:g>"</string> <string name="preposition_for_year" msgid="5040395640711867177">"i <xliff:g id="YEAR">%s</xliff:g>"</string> <string name="day" msgid="8144195776058119424">"dag"</string> @@ -675,24 +733,24 @@ <string name="dialog_alert_title" msgid="2049658708609043103">"Merk"</string> <string name="capital_on" msgid="1544682755514494298">"På"</string> <string name="capital_off" msgid="6815870386972805832">"Av"</string> - <string name="whichApplication" msgid="4533185947064773386">"Fullfør med"</string> - <string name="alwaysUse" msgid="4583018368000610438">"Bruk som standardvalg."</string> - <string name="clearDefaultHintMsg" msgid="4815455344600932173">"Fjern standardvalg i Innstillinger > Applikasjoner > Installerte applikasjoner."</string> - <string name="chooseActivity" msgid="1009246475582238425">"Velg en aktivitet"</string> - <string name="noApplications" msgid="1691104391758345586">"Ingen applikasjoner kan gjøre dette."</string> - <string name="aerr_title" msgid="653922989522758100">"Beklager!"</string> - <string name="aerr_application" msgid="4683614104336409186">"Applikasjonen <xliff:g id="APPLICATION">%1$s</xliff:g> (prosess <xliff:g id="PROCESS">%2$s</xliff:g>) stoppet uventet. Prøv igjen."</string> - <string name="aerr_process" msgid="1551785535966089511">"Prosessen <xliff:g id="PROCESS">%1$s</xliff:g> stoppet uventet. Prøv igjen."</string> - <string name="anr_title" msgid="3100070910664756057">"Beklager!"</string> - <string name="anr_activity_application" msgid="3538242413112507636">"Aktiviteten <xliff:g id="ACTIVITY">%1$s</xliff:g> (i applikasjonen <xliff:g id="APPLICATION">%2$s</xliff:g>) svarer ikke."</string> - <string name="anr_activity_process" msgid="5420826626009561014">"Aktiviteten <xliff:g id="ACTIVITY">%1$s</xliff:g> (i prosessen <xliff:g id="PROCESS">%2$s</xliff:g>) svarer ikke."</string> - <string name="anr_application_process" msgid="4185842666452210193">"Applikasjonen <xliff:g id="APPLICATION">%1$s</xliff:g> (i prosessen <xliff:g id="PROCESS">%2$s</xliff:g>) svarer ikke."</string> - <string name="anr_process" msgid="1246866008169975783">"Prosessen <xliff:g id="PROCESS">%1$s</xliff:g> svarer ikke."</string> - <string name="force_close" msgid="3653416315450806396">"Tving avslutning"</string> + <string name="whichApplication" msgid="4533185947064773386">"Complete action using"</string> + <string name="alwaysUse" msgid="4583018368000610438">"Use by default for this action."</string> + <string name="clearDefaultHintMsg" msgid="4815455344600932173">"Clear default in Home Settings > Applications > Manage applications."</string> + <string name="chooseActivity" msgid="1009246475582238425">"Select an action"</string> + <string name="noApplications" msgid="1691104391758345586">"No applications can perform this action."</string> + <string name="aerr_title" msgid="653922989522758100">"Sorry!"</string> + <string name="aerr_application" msgid="4683614104336409186">"The application <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has stopped unexpectedly. Please try again."</string> + <string name="aerr_process" msgid="1551785535966089511">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has stopped unexpectedly. Please try again."</string> + <string name="anr_title" msgid="3100070910664756057">"Sorry!"</string> + <string name="anr_activity_application" msgid="3538242413112507636">"Activity <xliff:g id="ACTIVITY">%1$s</xliff:g> (in application <xliff:g id="APPLICATION">%2$s</xliff:g>) is not responding."</string> + <string name="anr_activity_process" msgid="5420826626009561014">"Activity <xliff:g id="ACTIVITY">%1$s</xliff:g> (in process <xliff:g id="PROCESS">%2$s</xliff:g>) is not responding."</string> + <string name="anr_application_process" msgid="4185842666452210193">"Application <xliff:g id="APPLICATION">%1$s</xliff:g> (in process <xliff:g id="PROCESS">%2$s</xliff:g>) is not responding."</string> + <string name="anr_process" msgid="1246866008169975783">"Process <xliff:g id="PROCESS">%1$s</xliff:g> is not responding."</string> + <string name="force_close" msgid="3653416315450806396">"Force close"</string> <string name="report" msgid="4060218260984795706">"Rapportér"</string> - <string name="wait" msgid="7147118217226317732">"Vent"</string> + <string name="wait" msgid="7147118217226317732">"Wait"</string> <string name="debug" msgid="9103374629678531849">"Debug"</string> - <string name="sendText" msgid="5132506121645618310">"Velg mål for tekst"</string> + <string name="sendText" msgid="5132506121645618310">"Select an action for text"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Ringetonevolum"</string> <string name="volume_music" msgid="5421651157138628171">"Medievolum"</string> <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"Spiller over Bluetooth"</string> @@ -743,10 +801,12 @@ <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Det har oppstått et problem ved deaktiveringen av USB-lagring. Kontroller at du har demontert USB-verten, og prøv igjen."</string> <string name="extmedia_format_title" msgid="8663247929551095854">"Formatere minnekort"</string> <string name="extmedia_format_message" msgid="3621369962433523619">"Er du sikker på at du ønsker å formatere minnekortet? Alle data på kortet vil gå tapt."</string> - <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatér"</string> + <string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-debugging tilkoblet"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Velg for å deaktivere USB-debugging."</string> - <string name="select_input_method" msgid="6865512749462072765">"Velg inndatametode"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ"</string> <string name="candidates_style" msgid="4333913089637062257">"TAG_FONT"<u>"kandidater"</u>"CLOSE_FONT"</string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Trenger tillatelse"\n"for konto <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Inndatametode"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synkronisering"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Tilgjengelighet"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Bakgrunnsbilde"</string> - <string name="chooser_wallpaper" msgid="7873476199295190279">"Velg bakgrunnsbilde"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Punkt-til-punkt-tunneleringsprotokoll"</string> - <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> + <string name="chooser_wallpaper" msgid="7873476199295190279">"Endre bakgrunnsbilde"</string> + <!-- no translation found for pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index c5f5a4b..c299ef8 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Het bestand kan niet worden geopend."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Het opgevraagde bestand is niet gevonden."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Er worden te veel aanvragen verwerkt. Probeer het later opnieuw."</string> - <string name="notification_title" msgid="1259940370369187045">"Fout bij aanmelding voor \'<xliff:g id="ACCOUNT">%1$s</xliff:g>\'"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synchroniseren"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synchroniseren"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Te veel verwijderen voor <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -434,61 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Aangepast"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Thuis"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobiel"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Werk"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax werk"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax thuis"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Pager"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Overig"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Terugbelnummer"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Auto"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Bedrijf, algemeen"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Algemeen"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Andere fax"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"Teksttelefoon"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Mobiel werk"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Pager werk"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistent"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> <!-- no translation found for eventTypeBirthday (2813379844211390740) --> <skip /> <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> <skip /> <!-- no translation found for eventTypeOther (5834288791948564594) --> <skip /> - <string name="emailTypeCustom" msgid="8525960257804213846">"Aangepast"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Thuis"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Werk"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Overig"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobiel"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Aangepast"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Thuis"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Werk"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Overig"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Aangepast"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Thuis"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Werk"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Overig"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Aangepast"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Werk"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Overig"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Aangepast"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-code invoeren"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Onjuiste PIN-code!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Druk op \'Menu\' en vervolgens op 0 om te ontgrendelen."</string> @@ -503,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Probeer het opnieuw"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Opladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Opgeladen."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Sluit de oplader aan."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Geen SIM-kaart."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Geen SIM-kaart in telefoon."</string> @@ -535,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Opladen..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Sluit de oplader aan"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"De accu raakt op:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> of minder resterend."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Accugebruik"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fabriekstest mislukt"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"De actie FACTORY_TEST wordt alleen ondersteund voor pakketten die zijn geïnstalleerd in /system/app."</string> @@ -545,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Wilt u deze pagina verlaten?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Kies OK om door te gaan of Annuleren om op de huidige pagina te blijven."</string> <string name="save_password_label" msgid="6860261758665825069">"Bevestigen"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Tip: tik tweemaal om in of uit te zoomen."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"browsergeschiedenis en bladwijzers lezen"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Hiermee kan een toepassing de URL\'s lezen die u via de browser heeft bezocht, evenals alle bladwijzers van de browser."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"browsergeschiedenis en bladwijzers schrijven"</string> @@ -669,7 +722,7 @@ <string name="paste" msgid="5629880836805036433">"Plakken"</string> <string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string> <string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string> - <string name="addToDictionary" msgid="8793624991686948709">"\'<xliff:g id="WORD">%s</xliff:g>\' toevoegen aan woordenboek"</string> + <string name="addToDictionary" msgid="8793624991686948709">"\' <xliff:g id="WORD">%s</xliff:g>\' toevoegen aan woordenboek"</string> <string name="editTextMenuTitle" msgid="1672989176958581452">"Tekst bewerken"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"Weinig ruimte"</string> <string name="low_internal_storage_view_text" msgid="635106544616378836">"Opslagruimte van telefoon raakt op."</string> @@ -750,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Weet u zeker dat u de SD-kaart wilt formatteren? Alle gegevens op uw kaart gaan dan verloren."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatteren"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-foutopsporing verbonden"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Selecteer deze optie om USB-foutopsporing uit te schakelen."</string> - <string name="select_input_method" msgid="6865512749462072765">"Invoermethode selecteren"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"kandidaten"</u></string> @@ -782,12 +837,11 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Toestemming gevraagd"\n"voor account \'<xliff:g id="ACCOUNT">%s</xliff:g>\'"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Invoermethode"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synchroniseren"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Toegankelijkheid"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 388a9e7..f53bd4c 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Nie można uzyskać dostępu do pliku."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Nie znaleziono żądanego pliku."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Zbyt wiele żądań jest przetwarzanych. Spróbuj ponownie później."</string> - <string name="notification_title" msgid="1259940370369187045">"Błąd logowania na konto <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synchronizacja"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synchronizuj"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Zbyt wiele usuwanych <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Monitorowanie fizycznej lokalizacji"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Połączenia sieciowe"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Pozwól aplikacjom na dostęp do różnych funkcji sieci."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Twoje konta"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Dostęp do udostępnionych kont."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Sterowanie sprzętowe"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Bezpośredni dostęp do elementów sprzętowych telefonu."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Połączenia telefoniczne"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Niestandardowy"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Domowy"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Komórkowy"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Służbowy"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Faks służbowy"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Faks domowy"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Pager"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Inny"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Połączenie zwrotne"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Samochód"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Firmowy główny"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Główny"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Inny faks"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Teleks"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Służbowy komórkowy"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Pager służbowy"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Asystent"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"Wiadomość MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Urodziny"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Rocznica"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Wydarzenie"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Niestandardowy"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Domowy"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Służbowy"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Inny"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Komórkowy"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Niestandardowy"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Domowy"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Służbowy"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Inny"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Niestandardowy"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Domowy"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Służbowy"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Inny"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Niestandardowy"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Służbowy"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Inny"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Niestandardowy"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"przez <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> za pośrednictwem: <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Wprowadź kod PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Błędny kod PIN!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Aby odblokować, naciśnij Menu, a następnie 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Niestety, spróbuj ponownie"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Ładowanie (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Naładowany."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Podłącz ładowarkę."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Brak karty SIM."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Brak karty SIM w telefonie."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Ładowanie..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Podłącz ładowarkę"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Bateria się rozładowuje:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Pozostało: <xliff:g id="NUMBER">%d%%</xliff:g> lub mniej."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Użycie baterii"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Nieudany test fabryczny"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Czynność FACTORY_TEST jest obsługiwana tylko dla pakietów zainstalowanych w katalogu /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Czy opuścić tę stronę?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Wybierz opcję OK, aby kontynuować, lub opcję Anuluj, aby pozostać na tej stronie."</string> <string name="save_password_label" msgid="6860261758665825069">"Potwierdź"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Wskazówka: dotknij dwukrotnie, aby powiększyć lub pomniejszyć."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"odczyt historii i zakładek przeglądarki"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Umożliwia aplikacji odczyt wszystkich adresów URL odwiedzonych przez przeglądarkę, a także wszystkich zakładek przeglądarki."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"zapis historii i zakładek przeglądarki"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Czy na pewno sformatować kartę SD? Wszystkie dane na karcie zostaną utracone."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatuj"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Podłączono moduł debugowania USB"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Wybierz, aby wyłączyć debugowanie USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Wybierz metodę wprowadzania"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" AĄBCĆDEĘFGHIJKLŁMNŃOÓPQRSŚTUVWXYZŹŻ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"kandydaci"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Prośba o pozwolenie"\n"dotyczące konta <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Metoda wprowadzania"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synchronizacja"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Ułatwienia dostępu"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Tapeta"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Zmień tapetę"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Protokół PPTP"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index b2edaae..891e7ce 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Não foi possível aceder ao ficheiro."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Não foi possível localizar o ficheiro pedido."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Existem demasiados pedidos em processamento. Tente novamente mais tarde."</string> - <string name="notification_title" msgid="1259940370369187045">"Erro de início de sessão para <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Sincronização"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sincronização"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Demasiadas eliminações de <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Monitorizar a sua localização física"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Comunicação de rede"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Permite o acesso de aplicações a várias funcionalidades de rede."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"As suas contas"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Aceda às contas disponíveis."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Controlos de hardware"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Aceda directamente ao hardware no telefone."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Chamadas"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizado"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Residência"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Telemóvel"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Emprego"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax do emprego"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax da residência"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Pager"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Outro"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Rechamada"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Automóvel"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Telefone principal da empresa"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"RDIS"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Principal"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Outro fax"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Rádio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Telemóvel do emprego"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Pager do trabalho"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistente"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Data de nascimento"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Aniversário"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizado"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Residência"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Emprego"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Outro"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Telemóvel"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizado"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Residência"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Emprego"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Outro"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Personalizado"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Residência"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Emprego"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Outro"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizado"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Emprego"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Outro"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"através do <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> através de <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduzir código PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorrecto!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, prima Menu e, em seguida, 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lamentamos, tente novamente"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"A carregar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Ligue o carregador."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Nenhum cartão SIM."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Nenhum cartão SIM no telefone."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"A carregar..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Ligue o carregador"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está a ficar fraca:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Restam <xliff:g id="NUMBER">%d%%</xliff:g> ou menos."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Utilização da bateria"</string> <string name="factorytest_failed" msgid="5410270329114212041">"O teste de fábrica falhou"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"A acção FACTORY_TEST apenas é suportada para pacotes instalados em /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Navegar para outra página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Seleccione OK para continuar ou Cancelar para permanecer na página actual."</string> <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Sugestão: toque duas vezes para aumentar ou diminuir o zoom."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ler histórico e marcadores do browser"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que a aplicação leia todos os URLs visitados pelo browser e todos os marcadores do browser."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"gravar histórico e marcadores do browser"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Tem a certeza de que pretende formatar o cartão SD? Perder-se-ão todos os dados no cartão."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatar"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB ligada"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleccione para desactivar depuração USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Seleccionar método de entrada"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Autorização solicitada"\n"para a conta <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Método de entrada"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Sincronização"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Acessibilidade"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Imagem de fundo"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Alterar imagem de fundo"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Protocolo de túnel ponto a ponto (PPTP)"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 725053c..265fb62 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Não foi possível acessar o arquivo."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"O arquivo solicitado não foi encontrado."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Há muitas solicitações sendo processadas. Tente novamente mais tarde."</string> - <string name="notification_title" msgid="1259940370369187045">"Erro de login para <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Sincronizar"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sincronizar"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Muitas exclusões de <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Monitora o seu local físico."</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Comunicação da rede"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Permite que os aplicativos acessem diversos recursos de rede."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Suas contas"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Acessar as contas disponíveis."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Controles de hardware"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Acessa o hardware diretamente no aparelho."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Chamadas telefônicas"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizado"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Página inicial"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Celular"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Comercial"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax comercial"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax residencial"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Pager"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Outros"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Retorno de chamada"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Carro"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Empresa (principal)"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Principal"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Outro fax"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Rádio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Celular comercial"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Pager comercial"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistente"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Aniversário"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Data comemorativa"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizado"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Página inicial"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Comercial"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Outros"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Celular"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizado"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Página inicial"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Comercial"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Outros"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Personalizado"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Página inicial"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Comercial"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Outros"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizado"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Comercial"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Outros"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"por meio de <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Digite o código PIN"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorreto!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, pressione Menu e, em seguida, 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tente novamente"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Carregando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Conecte o seu carregador."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Sem cartão SIM."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Não há um cartão SIM no telefone."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Carregando..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Conecte o carregador"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está ficando baixa:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> ou menos restante(s)."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Uso da bateria"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Falha no teste de fábrica"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"A ação FACTORY_TEST é suportada apenas para pacotes instalados em /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Deseja sair desta página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Selecione OK para continuar ou Cancelar para permanecer na página atual."</string> <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Dica: toque duas vezes para aumentar e diminuir o zoom."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"ler histórico e favoritos do Navegador"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que o aplicativo leia todos os URLs visitados pelo Navegador e todos os favoritos do Navegador."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"gravar histórico e favoritos do Navegador"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Tem certeza de que deseja formatar o cartão SD? Todos os dados no seu cartão serão perdidos."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatar"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB conectada"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Selecione para desativar a depuração USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Selecionar método de entrada"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Solicitada a permissão"\n"para a conta <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Método de entrada"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Sincronizar"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Acessibilidade"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Papel de parede"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Alterar papel de parede"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Protocolo de encapsulamento ponto a ponto"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 1e1500b..373c45e 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Не удается получить доступ к файлу."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Не удалось найти указанные файлы."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Обрабатывается слишком много запросов. Повторите попытку позднее."</string> - <string name="notification_title" msgid="1259940370369187045">"Ошибка входа (<xliff:g id="ACCOUNT">%1$s</xliff:g>)"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Синхр."</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Синхр."</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Слишком много удалений <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Отслеживание физического местоположения"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Сетевой обмен данными"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Позволяет приложениям получать доступ к различным сетевым функциям."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Ваши аккаунты"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Доступ к имеющимся аккаунтам."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Элементы управления аппаратным обеспечением"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Прямой доступ к аппаратному обеспечению телефона."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Телефонные вызовы"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Особый"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Дом"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Моб."</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Раб."</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Раб. факс"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Дом. факс"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Пейджер"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Другой"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Обр. вызов"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"В авто"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Раб., осн."</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Основной"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Доп. факс"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Радио"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Телекс"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"Телетайп"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Раб. моб."</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Рабочий пейджер"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Секретарь"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"День рождения"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Юбилей"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Мероприятие"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Особый"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Дом"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Раб."</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Другой"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Моб."</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Особый"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Дом"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Раб."</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Другой"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Особый"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Дом"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Раб."</string> - <string name="imTypeOther" msgid="5377007495735915478">"Другой"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Особый"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Раб."</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Другой"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Особый"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"с помощью <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> с помощью <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Введите PIN-код"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Неверный PIN-код!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Для разблокировки нажмите \"Меню\", а затем 0."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Повторите попытку"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Идет зарядка (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Заряжена."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Подключите зарядное устройство."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Нет SIM-карты."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"SIM-карта не установлена."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Идет зарядка..."</string> <string name="battery_low_title" msgid="7923774589611311406">"Подключите зарядное устройство"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Батарея разряжена:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Осталось <xliff:g id="NUMBER">%d%%</xliff:g> или меньше."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Расход заряда батареи"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Не удалось провести стандартный тест"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Действие FACTORY_TEST поддерживается только для пакетов, установленных в /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Перейти с этой страницы?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Нажмите \"ОК\", чтобы продолжить, или \"Отмена\", чтобы остаться на текущей странице."</string> <string name="save_password_label" msgid="6860261758665825069">"Подтвердите"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Совет: нажмите дважды, чтобы увеличить и уменьшить масштаб."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"считывать историю и закладки браузера"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Разрешает приложению считывать все URL, посещенные браузером, и все его закладки."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"записывать историю и закладки браузера"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Отформатировать карту SD? Все данные, находящиеся на карте, будут уничтожены."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Формат"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Отладка USB подключена"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Нажмите, чтобы отключить отладку USB."</string> - <string name="select_input_method" msgid="6865512749462072765">"Выберите способ ввода"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"варианты"</u></string> @@ -777,19 +837,22 @@ <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> <string name="allow" msgid="7225948811296386551">"Разрешить"</string> <string name="deny" msgid="2081879885755434506">"Отклонить"</string> <string name="permission_request_notification_title" msgid="5390555465778213840">"Разрешение запрошено"</string> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Требуется разрешение"\n"для аккаунта <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Способ ввода"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Синхр."</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Специальные возможности"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Фоновый рисунок"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Изменить фоновый рисунок"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Протокол PPTP"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 823ba22..db77ee1 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Det gick inte att komma åt filen."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"Den begärda filen hittades inte."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"För många begäranden bearbetas. Försök igen senare."</string> - <string name="notification_title" msgid="1259940370369187045">"Inloggningsfel för <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Synkronisera"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Synkronisera"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"För många <xliff:g id="CONTENT_TYPE">%s</xliff:g>-borttagningar."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Övervaka din fysiska plats"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Nätverkskommunikation"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Tillåt att program kommer åt olika nätverksfunktioner."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Dina konton"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Få åtkomst till tillgängliga konton."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Kontroller för maskinvara"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Direkt åtkomst till maskinvara på handenheten."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefonsamtal"</string> @@ -316,7 +319,7 @@ <string name="permlab_callPrivileged" msgid="4198349211108497879">"ringa telefonnummer direkt"</string> <string name="permdesc_callPrivileged" msgid="244405067160028452">"Tillåter att programmet ringer ett telefonnummer, inklusive nödnummer, utan att du behöver göra något. Skadliga program kan ringa onödiga och olagliga samtal till räddningtjänsten."</string> <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"starta CDMA-telefoninställningar direkt"</string> - <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Tillåter att programmet startar CDMA-anslutning. Skadliga program kan starta CDMA-anslutningar i onödan."</string> + <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Tillåter att programmet startar CDMA-anslutning. Skadliga program kan skada CDMA-anslutningar i onödan."</string> <string name="permlab_locationUpdates" msgid="7785408253364335740">"styra meddelanden för platsuppdatering"</string> <string name="permdesc_locationUpdates" msgid="2300018303720930256">"Tillåter aktivering och inaktivering av avisering om platsuppdatering i radion. Används inte av vanliga program."</string> <string name="permlab_checkinProperties" msgid="7855259461268734914">"få åtkomst till incheckningsegenskaper"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Anpassad"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Hem"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobil"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Arbete"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Arbetsfax"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Hemfax"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Personsökare"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Övrigt"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Återuppringning"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Bil"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Nummer till företag"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Telefonnummer"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Annat faxnummer"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Mobiltelefon, arbetet"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Personsökare, arbetet"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistent"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Födelsedag"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Högtidsdag"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Händelse"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Anpassad"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Hem"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Arbete"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Övrigt"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobil"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Anpassad"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Hem"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Arbete"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Övrigt"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Anpassad"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Hem"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Arbete"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Övrigt"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Anpassad"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Arbete"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Övrigt"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Anpassad"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ange PIN-kod"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Fel PIN-kod!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Meny och sedan på 0 om du vill låsa upp."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Försök igen"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Laddar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Laddad."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Anslut din laddare."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Inget SIM-kort."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Inget SIM-kort i telefonen."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Laddar…"</string> <string name="battery_low_title" msgid="7923774589611311406">"Anslut laddaren"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet håller på att ta slut:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre kvar."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Batteriförbrukning"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Det gick fel vid fabrikstestet"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Åtgärden FACTORY_TEST stöds endast för paket som har installerats i /system/app."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Vill du lämna den här den här sidan?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Tryck på OK om du vill fortsätta eller på Avbryt om du vill vara kvar på den aktuella sidan."</string> <string name="save_password_label" msgid="6860261758665825069">"Bekräfta"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"Tips! Dubbelklicka om du vill zooma in eller ut."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"läsa webbläsarhistorik och bokmärken"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillåter att program läser alla webbadresser som webbläsaren har öppnat och alla webbläsarens bokmärken."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriva webbläsarhistorik och bokmärken"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"Vill du formatera SD-kortet? Alla data på ditt kort kommer att gå förlorade."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-felsökning ansluten"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"Välj att inaktivera USB-felsökning."</string> - <string name="select_input_method" msgid="6865512749462072765">"Välj indatametod"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"kandidater"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Behörighet krävs"\n"för kontot <xliff:g id="ACCOUNT">%s</xliff:g>"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Indatametod"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Synkronisera"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Tillgänglighet"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Bakgrund"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Ändra bakgrund"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Point-to-Point Tunneling Protocol"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index f95fc0a..503ad7d 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"Dosyaya erişilemedi."</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"İstenen dosya bulunamadı."</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Çok fazla sayıda istek işleniyor. Daha sonra yeniden deneyin."</string> - <string name="notification_title" msgid="1259940370369187045">"<xliff:g id="ACCOUNT">%1$s</xliff:g> hesabı için oturum açma hatası"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"Senk."</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Senk."</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Çok fazla <xliff:g id="CONTENT_TYPE">%s</xliff:g> silme var."</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Fiziksel konumunuzu izleyin"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Ağ iletişimi"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Uygulamaların çeşitli ağ özelliklerine erişmesine izin verir."</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"Hesaplarınız"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Kullanılabilir hesaplara erişin."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Donanım denetimleri"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Telefon donanımına doğrudan erişim."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefon çağrıları"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"Özel"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"Ev"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobil"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"İş"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"İş Faksı"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Ev Faksı"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"Çağrı cihazı"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"Diğer"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"Geri Arama"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"Araç"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Şirket Merkezi"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"Ana"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Diğer Faks"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"Telsiz"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Teleks"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"İş Yeri Cep Telefonu"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"İş Yeri Çağrı Cihazı"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"Yardımcı"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"Doğum günü"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"Yıldönümü"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"Etkinlik"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"Özel"</string> - <string name="emailTypeHome" msgid="449227236140433919">"Ev"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"İş"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"Diğer"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"Mobil"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"Özel"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"Ev"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"İş"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"Diğer"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"Özel"</string> - <string name="imTypeHome" msgid="6241181032954263892">"Ev"</string> - <string name="imTypeWork" msgid="1371489290242433090">"İş"</string> - <string name="imTypeOther" msgid="5377007495735915478">"Diğer"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"Özel"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"İş"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"Diğer"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"Özel"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g> aracılığıyla"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="SOURCE">%2$s</xliff:g> ile <xliff:g id="DATE">%1$s</xliff:g> tarihinde"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN kodunu gir"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Yanlış PIN kodu!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"Kilidi açmak için önce Menü\'ye, sonra 0\'a basın."</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Üzgünüz, lütfen yeniden deneyin"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"Şarj oluyor (<xliff:g id="PERCENT">%%</xliff:g><xliff:g id="NUMBER">%d</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"Şarj oldu."</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"Şarj cihazınızı bağlayın."</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIM kart yok."</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Telefonda SIM kart yok."</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"Şarj oluyor…"</string> <string name="battery_low_title" msgid="7923774589611311406">"Lütfen şarj cihazını takın"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"Pil tükeniyor:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> veya daha az kaldı."</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"Pil kullanımı"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fabrika testi yapılamadı"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST işlemi yalnızca /system/app dizinine yüklenmiş paketler için desteklenir."</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"Bu sayfadan ayrılıyor musunuz?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Devam etmek için Tamam\'ı, sayfada kalmak için İptal\'i tıklatın."</string> <string name="save_password_label" msgid="6860261758665825069">"Onayla"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"İpucu: Yakınlaştırmak ve uzaklaştırmak için iki kez hafifçe vurun."</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"Tarayıcı geçmişini ve favorileri oku"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Uygulamaya Tarayıcının ziyaret etmiş olduğu tüm URL\'leri ve Tarayıcının tüm favorilerini okuma izni verir."</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"Tarayıcı geçmişini ve favorileri yaz"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"SD kartı biçimlendirmek istediğinizden emin misiniz? Kartınızdaki tüm veriler yok olacak."</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"Biçimlendir"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB hata ayıklaması bağlandı"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"USB hata ayıklamasını devre dışı bırakmak için tıklayın."</string> - <string name="select_input_method" msgid="6865512749462072765">"Giriş yöntemini seç"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"adaylar"</u></string> @@ -777,19 +837,22 @@ <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> <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> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"<xliff:g id="ACCOUNT">%s</xliff:g> hesabı için"\n"İzin İstendi"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"Giriş yöntemi"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Senkronizasyon"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Erişebilirlik"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Duvar Kağıdı"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Duvar kağıdını değiştir"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"Noktadan Noktaya Tünel Protokolü Kuralları"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index ab5554b..474fffc 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"无法访问该文件。"</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"找不到请求的文件。"</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"正在处理的请求太多,请稍后重试。"</string> - <string name="notification_title" msgid="1259940370369187045">"<xliff:g id="ACCOUNT">%1$s</xliff:g> 发生登录错误"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"同步"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"同步"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"太多<xliff:g id="CONTENT_TYPE">%s</xliff:g>删除项。"</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"监视您的物理位置"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"网络通信"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"允许应用程序访问各种网络功能。"</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"您的帐户"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"访问可用的帐户。"</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"硬件控制"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"直接访问手机上的硬件。"</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"手机通话"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"自定义"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"住宅"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"手机"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"单位"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"单位传真"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"住宅传真"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"寻呼机"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"其他"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"回拨号码"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"车载电话"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"公司总机"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"总机"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"其他传真"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"无线装置"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"电报"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"单位手机"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"单位寻呼机"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"助理"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"彩信"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"生日"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"周年纪念"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"活动"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"自定义"</string> - <string name="emailTypeHome" msgid="449227236140433919">"住宅"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"单位"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"其他"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"手机"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"自定义"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"住宅"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"单位"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"其他"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"自定义"</string> - <string name="imTypeHome" msgid="6241181032954263892">"住宅"</string> - <string name="imTypeWork" msgid="1371489290242433090">"单位"</string> - <string name="imTypeOther" msgid="5377007495735915478">"其他"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"自定义"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"雅虎"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"公司"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"其他"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"自定义"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"通过 <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"时间:<xliff:g id="DATE">%1$s</xliff:g>,方式:<xliff:g id="SOURCE">%2$s</xliff:g>"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"输入 PIN 码"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 码不正确!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"要解锁,请先按 MENU 再按 0。"</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,请重试"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充电 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"已充满。"</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"连接您的充电器。"</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"没有 SIM 卡"</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"手机中无 SIM 卡"</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"正在充电..."</string> <string name="battery_low_title" msgid="7923774589611311406">"请连接充电器"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"电量在减少:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"电量剩余 <xliff:g id="NUMBER">%d%%</xliff:g> 或更少。"</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"电量使用情况"</string> <string name="factorytest_failed" msgid="5410270329114212041">"出厂测试失败"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"只有 /system/app 中安装的包支持 FACTORY_TEST 操作。"</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"是否从该页面导航至它处?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"选择“确定”继续,或选择“取消”留在当前页面。"</string> <string name="save_password_label" msgid="6860261758665825069">"确认"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"提示:点按两次可放大和缩小。"</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"读取浏览器的历史记录和书签"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"允许应用程序读取用浏览器访问过的所有网址,以及浏览器的所有书签。"</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"写入浏览器的历史记录和书签"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"您确定要格式化 SD 卡?卡上的所有数据都会丢失。"</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"格式化"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"已连接 USB 调试"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"选择停用 USB 调试。"</string> - <string name="select_input_method" msgid="6865512749462072765">"选择输入法"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"候选"</u></string> @@ -777,19 +837,22 @@ <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> <string name="allow" msgid="7225948811296386551">"允许"</string> <string name="deny" msgid="2081879885755434506">"拒绝"</string> <string name="permission_request_notification_title" msgid="5390555465778213840">"已请求权限"</string> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"已为帐户<xliff:g id="ACCOUNT">%s</xliff:g>"\n"请求了权限"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"输入法"</string> <string name="sync_binding_label" msgid="3687969138375092423">"同步"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"辅助功能"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"壁纸"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"更改壁纸"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"点对点隧道协议"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index da02fe7..3542536 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -111,7 +111,8 @@ <string name="httpErrorFile" msgid="8250549644091165175">"無法存取此檔案。"</string> <string name="httpErrorFileNotFound" msgid="5588380756326017105">"找不到要求的檔案。"</string> <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"太多執行要求。請稍後再試一次。"</string> - <string name="notification_title" msgid="1259940370369187045">"<xliff:g id="ACCOUNT">%1$s</xliff:g>登入錯誤"</string> + <!-- no translation found for notification_title (1259940370369187045) --> + <skip /> <string name="contentServiceSync" msgid="8353523060269335667">"同步處理"</string> <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"同步處理"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"同時刪除太多 <xliff:g id="CONTENT_TYPE">%s</xliff:g>。"</string> @@ -147,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"監視實際位置"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"網路通訊"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"允許應用程式存取多項網路功能。"</string> - <string name="permgrouplab_accounts" msgid="3359646291125325519">"您的帳戶"</string> - <string name="permgroupdesc_accounts" msgid="4948732641827091312">"存取可用帳戶。"</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"硬體控制"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"在免持設備上直接存取硬體。"</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"撥打電話"</string> @@ -432,58 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="1644738059053355820">"自訂"</string> - <string name="phoneTypeHome" msgid="2570923463033985887">"住家"</string> - <string name="phoneTypeMobile" msgid="6501463557754751037">"行動裝置"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"公司"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"公司傳真"</string> - <string name="phoneTypeFaxHome" msgid="2067265972322971467">"住家傳真"</string> - <string name="phoneTypePager" msgid="7582359955394921732">"呼叫器"</string> - <string name="phoneTypeOther" msgid="1544425847868765990">"其他"</string> - <string name="phoneTypeCallback" msgid="2712175203065678206">"回撥電話"</string> - <string name="phoneTypeCar" msgid="8738360689616716982">"汽車電話"</string> - <string name="phoneTypeCompanyMain" msgid="540434356461478916">"公司代表號"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> - <string name="phoneTypeMain" msgid="6766137010628326916">"代表號"</string> - <string name="phoneTypeOtherFax" msgid="8587657145072446565">"其他傳真"</string> - <string name="phoneTypeRadio" msgid="4093738079908667513">"無線電"</string> - <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> - <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY/TDD"</string> - <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"公司行動電話"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"公司呼叫器"</string> - <string name="phoneTypeAssistant" msgid="5596772636128562884">"助理"</string> - <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> - <string name="eventTypeBirthday" msgid="2813379844211390740">"生日"</string> - <string name="eventTypeAnniversary" msgid="3876779744518284000">"週年紀念日"</string> - <string name="eventTypeOther" msgid="5834288791948564594">"活動"</string> - <string name="emailTypeCustom" msgid="8525960257804213846">"自訂"</string> - <string name="emailTypeHome" msgid="449227236140433919">"住家"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"公司"</string> - <string name="emailTypeOther" msgid="2923008695272639549">"其他"</string> - <string name="emailTypeMobile" msgid="119919005321166205">"行動裝置"</string> - <string name="postalTypeCustom" msgid="8903206903060479902">"自訂"</string> - <string name="postalTypeHome" msgid="8165756977184483097">"住家"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"公司"</string> - <string name="postalTypeOther" msgid="2726111966623584341">"其他"</string> - <string name="imTypeCustom" msgid="2074028755527826046">"自訂"</string> - <string name="imTypeHome" msgid="6241181032954263892">"住家"</string> - <string name="imTypeWork" msgid="1371489290242433090">"公司"</string> - <string name="imTypeOther" msgid="5377007495735915478">"其他"</string> - <string name="imProtocolCustom" msgid="6919453836618749992">"自訂"</string> - <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> - <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string> - <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string> - <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string> - <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string> - <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> - <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> - <string name="imProtocolNetMeeting" msgid="8287625655986827971">"網路會議"</string> - <string name="orgTypeWork" msgid="29268870505363872">"公司"</string> - <string name="orgTypeOther" msgid="3951781131570124082">"其他"</string> - <string name="orgTypeCustom" msgid="225523415372088322">"自訂"</string> - <string name="contact_status_update_attribution" msgid="5112589886094402795">"透過 <xliff:g id="SOURCE">%1$s</xliff:g>"</string> - <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>透過「<xliff:g id="SOURCE">%2$s</xliff:g>」"</string> + <!-- no translation found for phoneTypeCustom (1644738059053355820) --> + <skip /> + <!-- no translation found for phoneTypeHome (2570923463033985887) --> + <skip /> + <!-- no translation found for phoneTypeMobile (6501463557754751037) --> + <skip /> + <!-- no translation found for phoneTypeWork (8863939667059911633) --> + <skip /> + <!-- no translation found for phoneTypeFaxWork (3517792160008890912) --> + <skip /> + <!-- no translation found for phoneTypeFaxHome (2067265972322971467) --> + <skip /> + <!-- no translation found for phoneTypePager (7582359955394921732) --> + <skip /> + <!-- no translation found for phoneTypeOther (1544425847868765990) --> + <skip /> + <!-- no translation found for phoneTypeCallback (2712175203065678206) --> + <skip /> + <!-- no translation found for phoneTypeCar (8738360689616716982) --> + <skip /> + <!-- no translation found for phoneTypeCompanyMain (540434356461478916) --> + <skip /> + <!-- no translation found for phoneTypeIsdn (8022453193171370337) --> + <skip /> + <!-- no translation found for phoneTypeMain (6766137010628326916) --> + <skip /> + <!-- no translation found for phoneTypeOtherFax (8587657145072446565) --> + <skip /> + <!-- no translation found for phoneTypeRadio (4093738079908667513) --> + <skip /> + <!-- no translation found for phoneTypeTelex (3367879952476250512) --> + <skip /> + <!-- no translation found for phoneTypeTtyTdd (8606514378585000044) --> + <skip /> + <!-- no translation found for phoneTypeWorkMobile (1311426989184065709) --> + <skip /> + <!-- no translation found for phoneTypeWorkPager (649938731231157056) --> + <skip /> + <!-- no translation found for phoneTypeAssistant (5596772636128562884) --> + <skip /> + <!-- no translation found for phoneTypeMms (7254492275502768992) --> + <skip /> + <!-- no translation found for eventTypeBirthday (2813379844211390740) --> + <skip /> + <!-- no translation found for eventTypeAnniversary (3876779744518284000) --> + <skip /> + <!-- no translation found for eventTypeOther (5834288791948564594) --> + <skip /> + <!-- no translation found for emailTypeCustom (8525960257804213846) --> + <skip /> + <!-- no translation found for emailTypeHome (449227236140433919) --> + <skip /> + <!-- no translation found for emailTypeWork (3548058059601149973) --> + <skip /> + <!-- no translation found for emailTypeOther (2923008695272639549) --> + <skip /> + <!-- no translation found for emailTypeMobile (119919005321166205) --> + <skip /> + <!-- no translation found for postalTypeCustom (8903206903060479902) --> + <skip /> + <!-- no translation found for postalTypeHome (8165756977184483097) --> + <skip /> + <!-- no translation found for postalTypeWork (5268172772387694495) --> + <skip /> + <!-- no translation found for postalTypeOther (2726111966623584341) --> + <skip /> + <!-- no translation found for imTypeCustom (2074028755527826046) --> + <skip /> + <!-- no translation found for imTypeHome (6241181032954263892) --> + <skip /> + <!-- no translation found for imTypeWork (1371489290242433090) --> + <skip /> + <!-- no translation found for imTypeOther (5377007495735915478) --> + <skip /> + <!-- no translation found for imProtocolCustom (6919453836618749992) --> + <skip /> + <!-- no translation found for imProtocolAim (7050360612368383417) --> + <skip /> + <!-- no translation found for imProtocolMsn (144556545420769442) --> + <skip /> + <!-- no translation found for imProtocolYahoo (8271439408469021273) --> + <skip /> + <!-- no translation found for imProtocolSkype (9019296744622832951) --> + <skip /> + <!-- no translation found for imProtocolQq (8887484379494111884) --> + <skip /> + <!-- no translation found for imProtocolGoogleTalk (3808393979157698766) --> + <skip /> + <!-- no translation found for imProtocolIcq (1574870433606517315) --> + <skip /> + <!-- no translation found for imProtocolJabber (2279917630875771722) --> + <skip /> + <!-- no translation found for imProtocolNetMeeting (8287625655986827971) --> + <skip /> + <!-- no translation found for orgTypeWork (29268870505363872) --> + <skip /> + <!-- no translation found for orgTypeOther (3951781131570124082) --> + <skip /> + <!-- no translation found for orgTypeCustom (225523415372088322) --> + <skip /> + <!-- no translation found for contact_status_update_attribution (5112589886094402795) --> + <skip /> + <!-- no translation found for contact_status_update_attribution_with_date (5945386376369979909) --> + <skip /> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"輸入 PIN 碼"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 碼錯誤!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"如要解鎖,請按 Menu 鍵,然後按 0。"</string> @@ -498,7 +553,8 @@ <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,請再試一次"</string> <string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充電 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> <string name="lockscreen_charged" msgid="4938930459620989972">"充電完成。"</string> - <string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> + <!-- no translation found for lockscreen_battery_short (3617549178603354656) --> + <skip /> <string name="lockscreen_low_battery" msgid="1482873981919249740">"請連接充電器。"</string> <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"沒有 SIM 卡。"</string> <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"手機未插入 SIM 卡。"</string> @@ -530,7 +586,8 @@ <string name="battery_status_charging" msgid="756617993998772213">"充電中"</string> <string name="battery_low_title" msgid="7923774589611311406">"請連接充電器"</string> <string name="battery_low_subtitle" msgid="7388781709819722764">"電池電量即將不足:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"還剩 <xliff:g id="NUMBER">%d%%</xliff:g> 以下。"</string> + <!-- no translation found for battery_low_percent_format (696154104579022959) --> + <skip /> <string name="battery_low_why" msgid="7279169609518386372">"電池使用狀況"</string> <string name="factorytest_failed" msgid="5410270329114212041">"出廠測試失敗"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"只有安裝在 /system/app 裡的程式才能支援 FACTORY_TEST 操作。"</string> @@ -540,7 +597,8 @@ <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string> <string name="js_dialog_before_unload" msgid="1901675448179653089">"離開此頁?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n" 選取 [確定] 離開此頁;或 [取消] 留在此頁。"</string> <string name="save_password_label" msgid="6860261758665825069">"確認"</string> - <string name="double_tap_toast" msgid="1068216937244567247">"提示:輕按兩下可放大縮小。"</string> + <!-- no translation found for double_tap_toast (1068216937244567247) --> + <skip /> <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"讀取瀏覽器的記錄與書籤"</string> <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"允許應用程式讀取瀏覽器曾經造訪過的所有網址,以及瀏覽器的所有書籤。"</string> <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"寫入瀏覽器的記錄與書籤"</string> @@ -745,8 +803,10 @@ <string name="extmedia_format_message" msgid="3621369962433523619">"確定要將 SD 卡格式化嗎?該 SD 卡中的所有資料將會遺失。"</string> <string name="extmedia_format_button_format" msgid="4131064560127478695">"格式化"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"USB 偵錯模式已啟用"</string> - <string name="adb_active_notification_message" msgid="8470296818270110396">"選取以停用 USB 偵錯。"</string> - <string name="select_input_method" msgid="6865512749462072765">"選取輸入方式"</string> + <!-- no translation found for adb_active_notification_message (8470296818270110396) --> + <skip /> + <!-- no translation found for select_input_method (6865512749462072765) --> + <skip /> <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> <string name="candidates_style" msgid="4333913089637062257"><u>"待選項目"</u></string> @@ -777,19 +837,22 @@ <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> <string name="allow" msgid="7225948811296386551">"允許"</string> <string name="deny" msgid="2081879885755434506">"拒絕"</string> <string name="permission_request_notification_title" msgid="5390555465778213840">"已要求權限"</string> - <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"帳戶 <xliff:g id="ACCOUNT">%s</xliff:g> 的"\n"權限要求"</string> + <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) --> + <skip /> <string name="input_method_binding_label" msgid="1283557179944992649">"輸入方式"</string> <string name="sync_binding_label" msgid="3687969138375092423">"同步處理"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"協助工具"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"桌布"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"變更桌布"</string> - <string name="pptp_vpn_description" msgid="2688045385181439401">"點對點通道通訊協定"</string> - <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 pptp_vpn_description (2688045385181439401) --> + <skip /> + <!-- no translation found for l2tp_vpn_description (3750692169378923304) --> + <skip /> + <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) --> + <skip /> + <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) --> + <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/IMediaPlayerService.h b/include/media/IMediaPlayerService.h index 303444c..d5c1594 100644 --- a/include/media/IMediaPlayerService.h +++ b/include/media/IMediaPlayerService.h @@ -42,7 +42,7 @@ public: virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) = 0; virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; - virtual sp<IOMX> createOMX() = 0; + virtual sp<IOMX> getOMX() = 0; // Take a peek at currently playing audio, for visualization purposes. // This returns a buffer of 16 bit mono PCM data, or NULL if no visualization buffer is currently available. diff --git a/include/media/IOMX.h b/include/media/IOMX.h index 10e0197..6f3ba1c 100644 --- a/include/media/IOMX.h +++ b/include/media/IOMX.h @@ -42,57 +42,57 @@ public: typedef void *buffer_id; typedef void *node_id; - virtual status_t list_nodes(List<String8> *list) = 0; + virtual status_t listNodes(List<String8> *list) = 0; - virtual status_t allocate_node(const char *name, node_id *node) = 0; - virtual status_t free_node(node_id node) = 0; + virtual status_t allocateNode( + const char *name, const sp<IOMXObserver> &observer, + node_id *node) = 0; - virtual status_t send_command( + virtual status_t freeNode(node_id node) = 0; + + virtual status_t sendCommand( node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) = 0; - virtual status_t get_parameter( + virtual status_t getParameter( node_id node, OMX_INDEXTYPE index, void *params, size_t size) = 0; - virtual status_t set_parameter( + virtual status_t setParameter( node_id node, OMX_INDEXTYPE index, const void *params, size_t size) = 0; - virtual status_t get_config( + virtual status_t getConfig( node_id node, OMX_INDEXTYPE index, void *params, size_t size) = 0; - virtual status_t set_config( + virtual status_t setConfig( node_id node, OMX_INDEXTYPE index, const void *params, size_t size) = 0; - virtual status_t use_buffer( + virtual status_t useBuffer( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) = 0; - virtual status_t allocate_buffer( + virtual status_t allocateBuffer( node_id node, OMX_U32 port_index, size_t size, buffer_id *buffer) = 0; - virtual status_t allocate_buffer_with_backup( + virtual status_t allocateBufferWithBackup( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) = 0; - virtual status_t free_buffer( + virtual status_t freeBuffer( node_id node, OMX_U32 port_index, buffer_id buffer) = 0; - virtual status_t observe_node( - node_id node, const sp<IOMXObserver> &observer) = 0; - - virtual void fill_buffer(node_id node, buffer_id buffer) = 0; + virtual status_t fillBuffer(node_id node, buffer_id buffer) = 0; - virtual void empty_buffer( + virtual status_t emptyBuffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, OMX_U32 flags, OMX_TICKS timestamp) = 0; - virtual status_t get_extension_index( + virtual status_t getExtensionIndex( node_id node, const char *parameter_name, OMX_INDEXTYPE *index) = 0; @@ -162,7 +162,7 @@ class IOMXObserver : public IInterface { public: DECLARE_META_INTERFACE(OMXObserver); - virtual void on_message(const omx_message &msg) = 0; + virtual void onMessage(const omx_message &msg) = 0; }; class IOMXRenderer : public IInterface { 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 @@ public: 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 @@ public: 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 @@ class DataSource : public RefBase { 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,11 +29,13 @@ namespace android { class FileSource : public DataSource { public: FileSource(const char *filename); - virtual ~FileSource(); - status_t InitCheck() const; + virtual status_t initCheck() const; + + virtual ssize_t readAt(off_t offset, void *data, size_t size); - virtual ssize_t read_at(off_t offset, void *data, size_t size); +protected: + virtual ~FileSource(); private: FILE *mFile; 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 ~HTTPDataSource(); + virtual status_t initCheck() const; - // XXXandih - status_t InitCheck() const { return OK; } + virtual ssize_t readAt(off_t offset, void *data, size_t size); - virtual ssize_t read_at(off_t offset, void *data, size_t size); +protected: + virtual ~HTTPDataSource(); private: enum { kBufferSize = 64 * 1024 }; - HTTPStream mHttp; + HTTPStream *mHttp; char *mHost; int mPort; char *mPath; @@ -49,6 +50,8 @@ private: size_t mBufferLength; off_t mBufferOffset; + status_t mInitCheck; + HTTPDataSource(const HTTPDataSource &); HTTPDataSource &operator=(const HTTPDataSource &); }; 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 @@ enum { 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 @@ public: 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 @@ struct MediaSource : public RefBase { // 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 @@ public: TYPE_NONE = 'none', TYPE_C_STRING = 'cstr', TYPE_INT32 = 'in32', + TYPE_INT64 = 'in64', TYPE_FLOAT = 'floa', TYPE_POINTER = 'ptr ', }; @@ -71,11 +73,13 @@ public: 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 @@ public: // Assumes ownership of "fd". MmapSource(int fd, int64_t offset, int64_t length); - virtual ~MmapSource(); - - 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); 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 OMXCodecObserver; 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 @@ private: kRequiresFlushCompleteEmulation = 16, kRequiresAllocateBufferOnOutputPorts = 32, kRequiresFlushBeforeShutdown = 64, - kOutputDimensionsAre16Aligned = 128, }; struct BufferInfo { @@ -107,7 +110,6 @@ private: sp<IOMX> mOMX; IOMX::node_id mNode; - sp<OMXCodecObserver> mObserver; uint32_t mQuirks; bool mIsEncoder; char *mMIME; @@ -125,6 +127,7 @@ private: bool mInitialBufferSubmit; bool mSignalledEOS; bool mNoMoreOutputData; + bool mOutputPortSettingsHaveChanged; int64_t mSeekTimeUs; Mutex mLock; @@ -155,6 +158,8 @@ private: 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 @@ private: 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 @@ class ShoutcastSource : public MediaSource { 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 @@ public: 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 @@ public class Ringtone { 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..7c6bca3 100644 --- a/media/java/android/media/ThumbnailUtil.java +++ b/media/java/android/media/ThumbnailUtil.java @@ -33,6 +33,7 @@ import android.graphics.Canvas; 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; @@ -305,8 +306,12 @@ public class ThumbnailUtil { 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); + byte[] thumbData = null; Bitmap bitmap = null; + MediaFileType fileType = MediaFile.getFileType(filePath); + if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) { + thumbData = createThumbnailFromEXIF(filePath, targetSize); + } if (thumbData != null) { BitmapFactory.Options options = new BitmapFactory.Options(); @@ -454,6 +459,7 @@ public class ThumbnailUtil { 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 +488,7 @@ public class ThumbnailUtil { 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(); diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp index 98f7ef1..cca3e9b 100644 --- a/media/libmedia/IMediaPlayerService.cpp +++ b/media/libmedia/IMediaPlayerService.cpp @@ -35,7 +35,7 @@ enum { DECODE_FD, CREATE_MEDIA_RECORDER, CREATE_METADATA_RETRIEVER, - CREATE_OMX, + GET_OMX, SNOOP }; @@ -123,10 +123,10 @@ public: return interface_cast<IMemory>(reply.readStrongBinder()); } - virtual sp<IOMX> createOMX() { + virtual sp<IOMX> getOMX() { Parcel data, reply; data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); - remote()->transact(CREATE_OMX, data, &reply); + remote()->transact(GET_OMX, data, &reply); return interface_cast<IOMX>(reply.readStrongBinder()); } }; @@ -207,9 +207,9 @@ status_t BnMediaPlayerService::onTransact( reply->writeStrongBinder(retriever->asBinder()); return NO_ERROR; } break; - case CREATE_OMX: { + case GET_OMX: { CHECK_INTERFACE(IMediaPlayerService, data, reply); - sp<IOMX> omx = createOMX(); + sp<IOMX> omx = getOMX(); reply->writeStrongBinder(omx->asBinder()); return NO_ERROR; } break; diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp index 0cec7bb..76a9e7d 100644 --- a/media/libmedia/IOMX.cpp +++ b/media/libmedia/IOMX.cpp @@ -24,7 +24,6 @@ enum { ALLOC_BUFFER, ALLOC_BUFFER_WITH_BACKUP, FREE_BUFFER, - OBSERVE_NODE, FILL_BUFFER, EMPTY_BUFFER, GET_EXTENSION_INDEX, @@ -76,7 +75,7 @@ public: : BpInterface<IOMX>(impl) { } - virtual status_t list_nodes(List<String8> *list) { + virtual status_t listNodes(List<String8> *list) { list->clear(); Parcel data, reply; @@ -93,10 +92,12 @@ public: return OK; } - virtual status_t allocate_node(const char *name, node_id *node) { + virtual status_t allocateNode( + const char *name, const sp<IOMXObserver> &observer, node_id *node) { Parcel data, reply; data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); data.writeCString(name); + data.writeStrongBinder(observer->asBinder()); remote()->transact(ALLOCATE_NODE, data, &reply); status_t err = reply.readInt32(); @@ -109,7 +110,7 @@ public: return err; } - virtual status_t free_node(node_id node) { + virtual status_t freeNode(node_id node) { Parcel data, reply; data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); data.writeIntPtr((intptr_t)node); @@ -118,7 +119,7 @@ public: return reply.readInt32(); } - virtual status_t send_command( + virtual status_t sendCommand( node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { Parcel data, reply; data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); @@ -130,7 +131,7 @@ public: return reply.readInt32(); } - virtual status_t get_parameter( + virtual status_t getParameter( node_id node, OMX_INDEXTYPE index, void *params, size_t size) { Parcel data, reply; @@ -151,7 +152,7 @@ public: return OK; } - virtual status_t set_parameter( + virtual status_t setParameter( node_id node, OMX_INDEXTYPE index, const void *params, size_t size) { Parcel data, reply; @@ -165,7 +166,7 @@ public: return reply.readInt32(); } - virtual status_t get_config( + virtual status_t getConfig( node_id node, OMX_INDEXTYPE index, void *params, size_t size) { Parcel data, reply; @@ -186,7 +187,7 @@ public: return OK; } - virtual status_t set_config( + virtual status_t setConfig( node_id node, OMX_INDEXTYPE index, const void *params, size_t size) { Parcel data, reply; @@ -200,7 +201,7 @@ public: return reply.readInt32(); } - virtual status_t use_buffer( + virtual status_t useBuffer( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) { Parcel data, reply; @@ -222,7 +223,7 @@ public: return err; } - virtual status_t allocate_buffer( + virtual status_t allocateBuffer( node_id node, OMX_U32 port_index, size_t size, buffer_id *buffer) { Parcel data, reply; @@ -244,7 +245,7 @@ public: return err; } - virtual status_t allocate_buffer_with_backup( + virtual status_t allocateBufferWithBackup( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) { Parcel data, reply; @@ -266,7 +267,7 @@ public: return err; } - virtual status_t free_buffer( + virtual status_t freeBuffer( node_id node, OMX_U32 port_index, buffer_id buffer) { Parcel data, reply; data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); @@ -278,26 +279,17 @@ public: return reply.readInt32(); } - virtual status_t observe_node( - node_id node, const sp<IOMXObserver> &observer) { + virtual status_t fillBuffer(node_id node, buffer_id buffer) { Parcel data, reply; data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); data.writeIntPtr((intptr_t)node); - data.writeStrongBinder(observer->asBinder()); - remote()->transact(OBSERVE_NODE, data, &reply); + data.writeIntPtr((intptr_t)buffer); + remote()->transact(FILL_BUFFER, data, &reply); return reply.readInt32(); } - virtual void fill_buffer(node_id node, buffer_id buffer) { - Parcel data, reply; - data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); - data.writeIntPtr((intptr_t)node); - data.writeIntPtr((intptr_t)buffer); - remote()->transact(FILL_BUFFER, data, &reply, IBinder::FLAG_ONEWAY); - } - - virtual void empty_buffer( + virtual status_t emptyBuffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, @@ -310,10 +302,12 @@ public: 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(); } - virtual status_t get_extension_index( + virtual status_t getExtensionIndex( node_id node, const char *parameter_name, OMX_INDEXTYPE *index) { @@ -375,7 +369,7 @@ status_t BnOMX::onTransact( CHECK_INTERFACE(IOMX, data, reply); List<String8> list; - list_nodes(&list); + listNodes(&list); reply->writeInt32(list.size()); for (List<String8>::iterator it = list.begin(); @@ -390,8 +384,14 @@ status_t BnOMX::onTransact( { CHECK_INTERFACE(IOMX, data, reply); + const char *name = data.readCString(); + + sp<IOMXObserver> observer = + interface_cast<IOMXObserver>(data.readStrongBinder()); + node_id node; - status_t err = allocate_node(data.readCString(), &node); + + status_t err = allocateNode(name, observer, &node); reply->writeInt32(err); if (err == OK) { reply->writeIntPtr((intptr_t)node); @@ -406,7 +406,7 @@ status_t BnOMX::onTransact( node_id node = (void*)data.readIntPtr(); - reply->writeInt32(free_node(node)); + reply->writeInt32(freeNode(node)); return NO_ERROR; } @@ -421,7 +421,7 @@ status_t BnOMX::onTransact( static_cast<OMX_COMMANDTYPE>(data.readInt32()); OMX_S32 param = data.readInt32(); - reply->writeInt32(send_command(node, cmd, param)); + reply->writeInt32(sendCommand(node, cmd, param)); return NO_ERROR; } @@ -439,7 +439,7 @@ status_t BnOMX::onTransact( void *params = malloc(size); data.read(params, size); - status_t err = get_parameter(node, index, params, size); + status_t err = getParameter(node, index, params, size); reply->writeInt32(err); @@ -463,7 +463,7 @@ status_t BnOMX::onTransact( size_t size = data.readInt32(); void *params = const_cast<void *>(data.readInplace(size)); - reply->writeInt32(set_parameter(node, index, params, size)); + reply->writeInt32(setParameter(node, index, params, size)); return NO_ERROR; } @@ -481,7 +481,7 @@ status_t BnOMX::onTransact( void *params = malloc(size); data.read(params, size); - status_t err = get_config(node, index, params, size); + status_t err = getConfig(node, index, params, size); reply->writeInt32(err); @@ -505,7 +505,7 @@ status_t BnOMX::onTransact( size_t size = data.readInt32(); void *params = const_cast<void *>(data.readInplace(size)); - reply->writeInt32(set_config(node, index, params, size)); + reply->writeInt32(setConfig(node, index, params, size)); return NO_ERROR; } @@ -520,7 +520,7 @@ status_t BnOMX::onTransact( interface_cast<IMemory>(data.readStrongBinder()); buffer_id buffer; - status_t err = use_buffer(node, port_index, params, &buffer); + status_t err = useBuffer(node, port_index, params, &buffer); reply->writeInt32(err); if (err == OK) { @@ -539,7 +539,7 @@ status_t BnOMX::onTransact( size_t size = data.readInt32(); buffer_id buffer; - status_t err = allocate_buffer(node, port_index, size, &buffer); + status_t err = allocateBuffer(node, port_index, size, &buffer); reply->writeInt32(err); if (err == OK) { @@ -559,7 +559,7 @@ status_t BnOMX::onTransact( interface_cast<IMemory>(data.readStrongBinder()); buffer_id buffer; - status_t err = allocate_buffer_with_backup( + status_t err = allocateBufferWithBackup( node, port_index, params, &buffer); reply->writeInt32(err); @@ -578,19 +578,7 @@ status_t BnOMX::onTransact( node_id node = (void*)data.readIntPtr(); OMX_U32 port_index = data.readInt32(); buffer_id buffer = (void*)data.readIntPtr(); - reply->writeInt32(free_buffer(node, port_index, buffer)); - - return NO_ERROR; - } - - case OBSERVE_NODE: - { - CHECK_INTERFACE(IOMX, data, reply); - - node_id node = (void*)data.readIntPtr(); - sp<IOMXObserver> observer = - interface_cast<IOMXObserver>(data.readStrongBinder()); - reply->writeInt32(observe_node(node, observer)); + reply->writeInt32(freeBuffer(node, port_index, buffer)); return NO_ERROR; } @@ -601,7 +589,7 @@ status_t BnOMX::onTransact( node_id node = (void*)data.readIntPtr(); buffer_id buffer = (void*)data.readIntPtr(); - fill_buffer(node, buffer); + reply->writeInt32(fillBuffer(node, buffer)); return NO_ERROR; } @@ -617,9 +605,10 @@ status_t BnOMX::onTransact( OMX_U32 flags = data.readInt32(); OMX_TICKS timestamp = data.readInt64(); - empty_buffer( - node, buffer, range_offset, range_length, - flags, timestamp); + reply->writeInt32( + emptyBuffer( + node, buffer, range_offset, range_length, + flags, timestamp)); return NO_ERROR; } @@ -632,7 +621,7 @@ status_t BnOMX::onTransact( const char *parameter_name = data.readCString(); OMX_INDEXTYPE index; - status_t err = get_extension_index(node, parameter_name, &index); + status_t err = getExtensionIndex(node, parameter_name, &index); reply->writeInt32(err); @@ -683,7 +672,7 @@ public: : BpInterface<IOMXObserver>(impl) { } - virtual void on_message(const omx_message &msg) { + virtual void onMessage(const omx_message &msg) { Parcel data, reply; data.writeInterfaceToken(IOMXObserver::getInterfaceDescriptor()); data.write(&msg, sizeof(msg)); @@ -705,7 +694,7 @@ status_t BnOMXObserver::onTransact( data.read(&msg, sizeof(msg)); // XXX Could use readInplace maybe? - on_message(msg); + onMessage(msg); return NO_ERROR; } diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index f21eb73..6fc9fa2 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -18,8 +18,9 @@ LOCAL_SRC_FILES:= \ 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 @@ -50,7 +51,7 @@ LOCAL_C_INCLUDES := external/tremor/Tremor \ $(JNI_H_INCLUDE) \ $(call include-path-for, graphics corecg) \ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ - $(TOP)/frameworks/base/media/libstagefright/omx + $(TOP)/frameworks/base/media/libstagefright/include LOCAL_MODULE:= libmediaplayerservice diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 9d2c779..de5b542 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -284,8 +284,14 @@ sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClie return c; } -sp<IOMX> MediaPlayerService::createOMX() { - return new OMX; +sp<IOMX> MediaPlayerService::getOMX() { + Mutex::Autolock autoLock(mLock); + + if (mOMX.get() == NULL) { + mOMX = new OMX; + } + + return mOMX; } status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const @@ -360,11 +366,44 @@ extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, 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/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index 43c4915..b00f5b7 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -185,7 +185,7 @@ public: virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); virtual sp<IMemory> snoop(); - virtual sp<IOMX> createOMX(); + virtual sp<IOMX> getOMX(); virtual status_t dump(int fd, const Vector<String16>& args); @@ -284,6 +284,7 @@ private: SortedVector< wp<Client> > mClients; SortedVector< wp<MediaRecorderClient> > mMediaRecorderClients; int32_t mNextConnId; + sp<IOMX> mOMX; }; // ---------------------------------------------------------------------------- diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp index 8eabe5d..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 @@ static sp<MediaMetadataRetrieverBase> createRetriever(player_type playerType) 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; } 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 @@ status_t TestPlayerStub::resetInternal() 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 @@ sp<MediaSource> AMRExtractor::getTrack(size_t index) { 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 @@ status_t AMRSource::read( *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 @@ status_t AMRSource::read( // 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 @@ status_t AMRSource::read( } 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 @@ bool SniffAMR( 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 3c343a3..b774609 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -17,6 +17,7 @@ ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true) LOCAL_SRC_FILES += \ AMRExtractor.cpp \ CachingDataSource.cpp \ + CameraSource.cpp \ DataSource.cpp \ FileSource.cpp \ HTTPDataSource.cpp \ diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 319488e..7b4d178 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -209,15 +209,9 @@ void AudioPlayer::fillBuffer(void *data, size_t size) { 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 @@ CachingDataSource::~CachingDataSource() { 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 @@ ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) { 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; -}; +struct DummySurface : public BnSurface { + DummySurface() {} -class CameraSourceClient : public BnCameraClient { -public: - CameraSourceClient() - : mSource(NULL) { + virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage) { + return NULL; } - virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { - CHECK(mSource != NULL); - mSource->notifyCallback(msgType, ext1, ext2); + virtual status_t registerBuffers(const BufferHeap &buffers) { + return OK; } - virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) { - CHECK(mSource != NULL); - mSource->dataCallback(msgType, data); - } + virtual void postBuffer(ssize_t offset) {} + virtual void unregisterBuffers() {} - void setCameraSource(CameraSource *source) { - mSource = source; + 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: - CameraSource *mSource; + wp<CameraSource> mSource; + + CameraSourceListener(const CameraSourceListener &); + CameraSourceListener &operator=(const CameraSourceListener &); }; -class DummySurface : public BnSurface { -public: - DummySurface() {} +CameraSourceListener::CameraSourceListener(const sp<CameraSource> &source) + : mSource(source) { +} - virtual status_t registerBuffers(const BufferHeap &buffers) { - return OK; - } +CameraSourceListener::~CameraSourceListener() { +} - virtual void postBuffer(ssize_t offset) { - } +void CameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) { + LOGV("notify(%d, %d, %d)", msgType, ext1, ext2); +} - virtual void unregisterBuffers() { - } - - virtual sp<OverlayRef> createOverlay( - uint32_t w, uint32_t h, int32_t format) { - return NULL; +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<ICameraService> service = - interface_cast<ICameraService>( - sm->getService(String16("media.camera"))); + sp<Camera> camera = Camera::connect(); - sp<CameraSourceClient> client = new CameraSourceClient; - sp<ICamera> camera = service->connect(client); - - CameraSource *source = new CameraSource(camera, client); - client->setCameraSource(source); + if (camera.get() == NULL) { + return NULL; + } - 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(); - CHECK_EQ(err, OK); + mCamera->setListener(new CameraSourceListener(this)); - err = mCamera->setPreviewDisplay(new DummySurface); + sp<ISurface> dummy = new DummySurface; + status_t err = mCamera->setPreviewDisplay(dummy); 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 @@ status_t CameraSource::stop() { CHECK(mStarted); mCamera->stopPreview(); - mCamera->unlock(); mStarted = false; @@ -157,8 +178,8 @@ sp<MetaData> CameraSource::getFormat() { 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 @@ status_t CameraSource::read( } sp<IMemory> frame; + int64_t frameTime; { Mutex::Autolock autoLock(mLock); @@ -184,41 +206,33 @@ status_t CameraSource::read( frame = *mFrames.begin(); mFrames.erase(mFrames.begin()); - } - int count = mNumFrames++; + frameTime = *mFrameTimes.begin(); + mFrameTimes.erase(mFrameTimes.begin()); + } - *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..210b2f6 100644 --- a/media/libstagefright/DataSource.cpp +++ b/media/libstagefright/DataSource.cpp @@ -14,11 +14,12 @@ * limitations under the License. */ -#include <media/stagefright/AMRExtractor.h> +#include "include/AMRExtractor.h" +#include "include/MP3Extractor.h" +#include "include/MPEG4Extractor.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 +28,7 @@ bool DataSource::getUInt16(off_t offset, uint16_t *x) { *x = 0; uint8_t byte[2]; - if (read_at(offset, byte, 2) != 2) { + if (readAt(offset, byte, 2) != 2) { return false; } 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 @@ FileSource::~FileSource() { } } -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 698223b..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/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 @@ HTTPDataSource::HTTPDataSource(const char *uri) 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 @@ ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) { 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 @@ ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) { } 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 @@ status_t JPEGSource::read( 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 @@ status_t JPEGSource::parseJPEG() { 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..736f1a9d6 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 @@ static bool get_mp3_frame_size( *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 get_mp3_frame_size( 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 @@ static bool Resync( 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 @@ static bool Resync( 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 @@ MP3Extractor::MP3Extractor(const sp<DataSource> &source) off_t fileSize; if (mDataSource->getSize(&fileSize) == OK) { - mMeta->setInt32( + mMeta->setInt64( kKeyDuration, - 8 * (fileSize - mFirstFramePos) / bitrate); - mMeta->setInt32(kKeyTimeScale, 1000); + 8000 * (fileSize - mFirstFramePos) / bitrate); } } } @@ -362,7 +394,7 @@ sp<MediaSource> MP3Extractor::getTrack(size_t index) { 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 @@ status_t MP3Source::read( 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 @@ status_t MP3Source::read( 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 @@ status_t MP3Source::read( 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..5c3720e 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 @@ public: // 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 @@ size_t MPEG4Extractor::countTracks() { 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 @@ sp<MetaData> MPEG4Extractor::getTrackMetaData(size_t index) { 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 @@ static void MakeFourCCString(uint32_t x, char *s) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ sp<MediaSource> MPEG4Extractor::getTrack(size_t index) { } return new MPEG4Source( - track->meta, mDataSource, track->sampleTable); + track->meta, mDataSource, track->timescale, track->sampleTable); } //////////////////////////////////////////////////////////////////////////////// @@ -730,10 +753,11 @@ sp<MediaSource> MPEG4Extractor::getTrack(size_t index) { 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 @@ MPEG4Source::MPEG4Source( 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 @@ status_t MPEG4Source::read( 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 @@ status_t MPEG4Source::read( 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 @@ status_t MPEG4Source::read( // 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 @@ status_t MPEG4Source::read( 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,7 +995,7 @@ bool SniffMPEG4( 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; } 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 @@ void MPEG4Writer::Track::threadEntry() { 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); - - info.timestamp = (int64_t)units * 1000 / scale; + int64_t timestampUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); + + // 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 @@ size_t MediaBuffer::range_length() const { } 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/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp index 8535f52..d51802c 100644 --- a/media/libstagefright/MediaExtractor.cpp +++ b/media/libstagefright/MediaExtractor.cpp @@ -18,12 +18,16 @@ #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 <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 +45,7 @@ sp<MediaExtractor> MediaExtractor::Create( } mime = tmp.string(); - LOGI("Autodetected media content as '%s' with confidence %.2f", + LOGV("Autodetected media content as '%s' with confidence %.2f", mime, confidence); } @@ -58,4 +62,25 @@ sp<MediaExtractor> MediaExtractor::Create( 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 2e609e3..76f4182 100644 --- a/media/libstagefright/MediaPlayerImpl.cpp +++ b/media/libstagefright/MediaPlayerImpl.cpp @@ -18,15 +18,15 @@ #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/MediaExtractor.h> #include <media/stagefright/MediaPlayerImpl.h> @@ -69,18 +69,7 @@ MediaPlayerImpl::MediaPlayerImpl(const char *uri) 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; @@ -250,6 +239,13 @@ void MediaPlayerImpl::videoEntry() { 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; @@ -261,15 +257,9 @@ void MediaPlayerImpl::videoEntry() { 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; @@ -379,12 +369,10 @@ void MediaPlayerImpl::init() { 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; } } @@ -609,6 +597,9 @@ void MediaPlayerImpl::populateISurface() { 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 @@ bool MetaData::setInt32(uint32_t key, int32_t value) { 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 @@ bool MetaData::findInt32(uint32_t key, int32_t *value) { 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 @@ MmapSource::MmapSource(const char *filename) 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 @@ MmapSource::~MmapSource() { } } -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/OMXClient.cpp b/media/libstagefright/OMXClient.cpp index dba7a2a..9de873e 100644 --- a/media/libstagefright/OMXClient.cpp +++ b/media/libstagefright/OMXClient.cpp @@ -35,7 +35,7 @@ status_t OMXClient::connect() { CHECK(service.get() != NULL); - mOMX = service->createOMX(); + mOMX = service->getOMX(); CHECK(mOMX.get() != NULL); return OK; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 1a23fb2..41a9fe9 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; @@ -85,12 +88,15 @@ static const CodecInfo kEncoderInfo[] = { #define CODEC_LOGV(x, ...) LOGV("[%s] "x, mComponentName, ##__VA_ARGS__) struct OMXCodecObserver : public BnOMXObserver { - OMXCodecObserver(const wp<OMXCodec> &target) - : mTarget(target) { + OMXCodecObserver() { + } + + void setCodec(const sp<OMXCodec> &target) { + mTarget = target; } // from IOMXObserver - virtual void on_message(const omx_message &msg) { + virtual void onMessage(const omx_message &msg) { sp<OMXCodec> codec = mTarget.promote(); if (codec.get() != NULL) { @@ -166,51 +172,37 @@ static void InitOMXParams(T *params) { 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; - 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); - } +static bool IsSoftwareCodec(const char *componentName) { + if (!strncmp("OMX.PV.", componentName, 7)) { + return true; + } - if (!componentName) { - return NULL; - } + return false; +} - // If a specific codec is requested, skip the non-matching ones. - if (matchComponentName && strcmp(componentName, matchComponentName)) { - continue; - } +static int CompareSoftwareCodecsFirst( + const String8 *elem1, const String8 *elem2) { + bool isSoftwareCodec1 = IsSoftwareCodec(elem1->string()); + bool isSoftwareCodec2 = IsSoftwareCodec(elem2->string()); - LOGV("Attempting to allocate OMX node '%s'", componentName); + if (isSoftwareCodec1) { + if (isSoftwareCodec2) { return 0; } + return -1; + } - status_t err = omx->allocate_node(componentName, &node); - if (err == OK) { - LOGI("Successfully allocated OMX node '%s'", componentName); - break; - } + 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; @@ -218,21 +210,16 @@ sp<OMXCodec> OMXCodec::Create( 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 |= kRequiresAllocateBufferOnInputPorts; quirks |= kRequiresAllocateBufferOnOutputPorts; - quirks |= kOutputDimensionsAre16Aligned; } if (!strncmp(componentName, "OMX.TI.", 7)) { @@ -245,10 +232,98 @@ sp<OMXCodec> OMXCodec::Create( 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); + uint32_t type; const void *data; size_t size; @@ -326,12 +401,14 @@ sp<OMXCodec> OMXCodec::Create( 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") && (profile != kAVCProfileBaseline || level > 39)) { - // This stream exceeds the decoder's capabilities. + // This stream exceeds the decoder's capabilities. The decoder + // does not handle this gracefully and would clobber the heap + // and wreak havoc instead... LOGE("Profile and/or level exceed the decoder's capabilities."); return NULL; @@ -406,7 +483,7 @@ void OMXCodec::setMinBufferSize(OMX_U32 portIndex, OMX_U32 size) { InitOMXParams(&def); def.nPortIndex = portIndex; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -415,7 +492,7 @@ void OMXCodec::setMinBufferSize(OMX_U32 portIndex, OMX_U32 size) { } - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); } @@ -433,7 +510,7 @@ status_t OMXCodec::setVideoPortFormatType( OMX_U32 index = 0; for (;;) { format.nIndex = index; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoPortFormat, &format, sizeof(format)); @@ -445,7 +522,7 @@ status_t OMXCodec::setVideoPortFormatType( // 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 @@ -478,17 +555,33 @@ status_t OMXCodec::setVideoPortFormatType( return UNKNOWN_ERROR; } - CODEC_LOGI("found a match."); - status_t err = mOMX->set_parameter( + CODEC_LOGV("found a match."); + status_t err = mOMX->setParameter( mNode, OMX_IndexParamVideoPortFormat, &format, sizeof(format)); 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)) { @@ -509,12 +602,13 @@ void OMXCodec::setVideoInputFormat( 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); @@ -522,7 +616,7 @@ void OMXCodec::setVideoInputFormat( OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -534,7 +628,7 @@ void OMXCodec::setVideoInputFormat( video_def->eCompressionFormat = compressionFormat; video_def->eColorFormat = OMX_COLOR_FormatUnused; - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -543,12 +637,12 @@ void OMXCodec::setVideoInputFormat( InitOMXParams(&def); def.nPortIndex = kPortIndexInput; - err = mOMX->get_parameter( + err = mOMX->getParameter( 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); @@ -557,14 +651,108 @@ void OMXCodec::setVideoInputFormat( video_def->eCompressionFormat = OMX_VIDEO_CodingUnused; video_def->eColorFormat = colorFormat; - err = mOMX->set_parameter( + 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)) { @@ -588,7 +776,7 @@ void OMXCodec::setVideoOutputFormat( format.nPortIndex = kPortIndexOutput; format.nIndex = 0; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoPortFormat, &format, sizeof(format)); CHECK_EQ(err, OK); @@ -601,7 +789,7 @@ void OMXCodec::setVideoOutputFormat( || format.eColorFormat == OMX_COLOR_FormatCbYCrY || format.eColorFormat == OMX_QCOM_COLOR_FormatYVU420SemiPlanar); - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamVideoPortFormat, &format, sizeof(format)); CHECK_EQ(err, OK); @@ -614,7 +802,7 @@ void OMXCodec::setVideoOutputFormat( OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -632,9 +820,10 @@ void OMXCodec::setVideoOutputFormat( video_def->nFrameWidth = width; video_def->nFrameHeight = height; + video_def->eCompressionFormat = compressionFormat; video_def->eColorFormat = OMX_COLOR_FormatUnused; - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -643,7 +832,7 @@ void OMXCodec::setVideoOutputFormat( InitOMXParams(&def); def.nPortIndex = kPortIndexOutput; - err = mOMX->get_parameter( + err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); CHECK_EQ(def.eDomain, OMX_PortDomainVideo); @@ -656,7 +845,7 @@ void OMXCodec::setVideoOutputFormat( video_def->nFrameWidth = width; video_def->nFrameHeight = height; - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); } @@ -680,13 +869,11 @@ OMXCodec::OMXCodec( mInitialBufferSubmit(true), mSignalledEOS(false), mNoMoreOutputData(false), + mOutputPortSettingsHaveChanged(false), mSeekTimeUs(-1) { mPortStatus[kPortIndexInput] = ENABLED; mPortStatus[kPortIndexOutput] = ENABLED; - mObserver = new OMXCodecObserver(this); - mOMX->observe_node(mNode, mObserver); - setComponentRole(); } @@ -744,7 +931,7 @@ void OMXCodec::setComponentRole( roleParams.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0'; - status_t err = omx->set_parameter( + status_t err = omx->setParameter( node, OMX_IndexParamStandardComponentRole, &roleParams, sizeof(roleParams)); @@ -761,10 +948,7 @@ void OMXCodec::setComponentRole() { OMXCodec::~OMXCodec() { CHECK(mState == LOADED || mState == ERROR); - status_t err = mOMX->observe_node(mNode, NULL); - CHECK_EQ(err, OK); - - err = mOMX->free_node(mNode); + status_t err = mOMX->freeNode(mNode); CHECK_EQ(err, OK); mNode = NULL; @@ -786,7 +970,7 @@ status_t OMXCodec::init() { status_t err; if (!(mQuirks & kRequiresLoadedToIdleAfterAllocation)) { - err = mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); CHECK_EQ(err, OK); setState(LOADED_TO_IDLE); } @@ -795,7 +979,7 @@ status_t OMXCodec::init() { CHECK_EQ(err, OK); if (mQuirks & kRequiresLoadedToIdleAfterAllocation) { - err = mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); CHECK_EQ(err, OK); setState(LOADED_TO_IDLE); @@ -832,7 +1016,7 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { InitOMXParams(&def); def.nPortIndex = portIndex; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); if (err != OK) { @@ -849,14 +1033,14 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { IOMX::buffer_id buffer; if (portIndex == kPortIndexInput && (mQuirks & kRequiresAllocateBufferOnInputPorts)) { - err = mOMX->allocate_buffer_with_backup( + err = mOMX->allocateBufferWithBackup( mNode, portIndex, mem, &buffer); } else if (portIndex == kPortIndexOutput && (mQuirks & kRequiresAllocateBufferOnOutputPorts)) { - err = mOMX->allocate_buffer_with_backup( + err = mOMX->allocateBufferWithBackup( mNode, portIndex, mem, &buffer); } else { - err = mOMX->use_buffer(mNode, portIndex, mem, &buffer); + err = mOMX->useBuffer(mNode, portIndex, mem, &buffer); } if (err != OK) { @@ -923,7 +1107,7 @@ void OMXCodec::on_message(const omx_message &msg) { CODEC_LOGV("Port is disabled, freeing buffer %p", buffer); status_t err = - mOMX->free_buffer(mNode, kPortIndexInput, buffer); + mOMX->freeBuffer(mNode, kPortIndexInput, buffer); CHECK_EQ(err, OK); buffers->removeAt(i); @@ -969,7 +1153,7 @@ void OMXCodec::on_message(const omx_message &msg) { CODEC_LOGV("Port is disabled, freeing buffer %p", buffer); status_t err = - mOMX->free_buffer(mNode, kPortIndexOutput, buffer); + mOMX->freeBuffer(mNode, kPortIndexOutput, buffer); CHECK_EQ(err, OK); buffers->removeAt(i); @@ -989,12 +1173,8 @@ void OMXCodec::on_message(const omx_message &msg) { 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); @@ -1063,6 +1243,71 @@ void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { } } +// 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: @@ -1085,6 +1330,15 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { 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); @@ -1139,7 +1393,7 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { mPortStatus[kPortIndexOutput] = SHUTTING_DOWN; status_t err = - mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); CHECK_EQ(err, OK); } } else { @@ -1179,7 +1433,7 @@ void OMXCodec::onStateChange(OMX_STATETYPE newState) { { CODEC_LOGV("Now Idle."); if (mState == LOADED_TO_IDLE) { - status_t err = mOMX->send_command( + status_t err = mOMX->sendCommand( mNode, OMX_CommandStateSet, OMX_StateExecuting); CHECK_EQ(err, OK); @@ -1196,7 +1450,7 @@ void OMXCodec::onStateChange(OMX_STATETYPE newState) { countBuffersWeOwn(mPortBuffers[kPortIndexOutput]), mPortBuffers[kPortIndexOutput].size()); - status_t err = mOMX->send_command( + status_t err = mOMX->sendCommand( mNode, OMX_CommandStateSet, OMX_StateLoaded); CHECK_EQ(err, OK); @@ -1279,7 +1533,7 @@ status_t OMXCodec::freeBuffersOnPort( CODEC_LOGV("freeing buffer %p on port %ld", info->mBuffer, portIndex); status_t err = - mOMX->free_buffer(mNode, portIndex, info->mBuffer); + mOMX->freeBuffer(mNode, portIndex, info->mBuffer); if (err != OK) { stickyErr = err; @@ -1339,7 +1593,7 @@ bool OMXCodec::flushPortAsync(OMX_U32 portIndex) { } status_t err = - mOMX->send_command(mNode, OMX_CommandFlush, portIndex); + mOMX->sendCommand(mNode, OMX_CommandFlush, portIndex); CHECK_EQ(err, OK); return true; @@ -1352,7 +1606,7 @@ void OMXCodec::disablePortAsync(OMX_U32 portIndex) { mPortStatus[portIndex] = DISABLING; status_t err = - mOMX->send_command(mNode, OMX_CommandPortDisable, portIndex); + mOMX->sendCommand(mNode, OMX_CommandPortDisable, portIndex); CHECK_EQ(err, OK); freeBuffersOnPort(portIndex, true); @@ -1365,7 +1619,7 @@ void OMXCodec::enablePortAsync(OMX_U32 portIndex) { mPortStatus[portIndex] = ENABLING; status_t err = - mOMX->send_command(mNode, OMX_CommandPortEnable, portIndex); + mOMX->sendCommand(mNode, OMX_CommandPortEnable, portIndex); CHECK_EQ(err, OK); } @@ -1417,10 +1671,11 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { memcpy(info->mMem->pointer(), specific->mData, specific->mSize); } - mOMX->empty_buffer( + status_t err = mOMX->emptyBuffer( mNode, info->mBuffer, 0, size, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG, 0); + CHECK_EQ(err, OK); info->mOwnedByComponent = true; @@ -1441,7 +1696,7 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { } OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME; - OMX_TICKS timestamp = 0; + OMX_TICKS timestampUs = 0; size_t srcLength = 0; if (err != OK) { @@ -1461,28 +1716,29 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { (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); } } - mOMX->empty_buffer( - mNode, info->mBuffer, 0, srcLength, - flags, timestamp); - - info->mOwnedByComponent = true; - if (srcBuffer != NULL) { srcBuffer->release(); srcBuffer = NULL; } + + err = mOMX->emptyBuffer( + mNode, info->mBuffer, 0, srcLength, + flags, timestampUs); + + if (err != OK) { + setState(ERROR); + return; + } + + info->mOwnedByComponent = true; } void OMXCodec::fillOutputBuffer(BufferInfo *info) { @@ -1495,7 +1751,8 @@ void OMXCodec::fillOutputBuffer(BufferInfo *info) { } CODEC_LOGV("Calling fill_buffer on buffer %p", info->mBuffer); - mOMX->fill_buffer(mNode, info->mBuffer); + status_t err = mOMX->fillBuffer(mNode, info->mBuffer); + CHECK_EQ(err, OK); info->mOwnedByComponent = true; } @@ -1539,7 +1796,7 @@ void OMXCodec::setRawAudioFormat( InitOMXParams(&pcmParams); pcmParams.nPortIndex = portIndex; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams)); CHECK_EQ(err, OK); @@ -1560,7 +1817,7 @@ void OMXCodec::setRawAudioFormat( pcmParams.eChannelMapping[1] = OMX_AUDIO_ChannelRF; } - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams)); CHECK_EQ(err, OK); @@ -1573,14 +1830,14 @@ void OMXCodec::setAMRFormat() { def.nPortIndex = kPortIndexInput; status_t err = - mOMX->get_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + mOMX->getParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); CHECK_EQ(err, OK); def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; def.eAMRBandMode = OMX_AUDIO_AMRBandModeNB0; - err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + err = mOMX->setParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); CHECK_EQ(err, OK); } @@ -1604,14 +1861,14 @@ void OMXCodec::setAMRWBFormat() { def.nPortIndex = kPortIndexInput; status_t err = - mOMX->get_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + mOMX->getParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); CHECK_EQ(err, OK); def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; def.eAMRBandMode = OMX_AUDIO_AMRBandModeWB0; - err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + err = mOMX->setParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); CHECK_EQ(err, OK); } @@ -1636,7 +1893,7 @@ void OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate) { InitOMXParams(&profile); profile.nPortIndex = kPortIndexInput; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); CHECK_EQ(err, OK); @@ -1644,7 +1901,7 @@ void OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate) { profile.nSampleRate = sampleRate; profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS; - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); CHECK_EQ(err, OK); } @@ -1668,7 +1925,7 @@ void OMXCodec::setImageOutputFormat( InitOMXParams(&def); def.nPortIndex = kPortIndexOutput; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -1717,7 +1974,7 @@ void OMXCodec::setImageOutputFormat( def.nBufferCountActual = def.nBufferCountMin; - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); } @@ -1728,7 +1985,7 @@ void OMXCodec::setJPEGInputFormat( InitOMXParams(&def); def.nPortIndex = kPortIndexInput; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -1742,7 +1999,7 @@ void OMXCodec::setJPEGInputFormat( def.nBufferSize = compressedSize; def.nBufferCountActual = def.nBufferCountMin; - err = mOMX->set_parameter( + err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); } @@ -1786,6 +2043,7 @@ status_t OMXCodec::start(MetaData *) { mInitialBufferSubmit = true; mSignalledEOS = false; mNoMoreOutputData = false; + mOutputPortSettingsHaveChanged = false; mSeekTimeUs = -1; mFilledBuffers.clear(); @@ -1832,7 +2090,7 @@ status_t OMXCodec::stop() { mPortStatus[kPortIndexOutput] = SHUTTING_DOWN; status_t err = - mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); CHECK_EQ(err, OK); } @@ -1856,6 +2114,8 @@ status_t OMXCodec::stop() { } sp<MetaData> OMXCodec::getFormat() { + Mutex::Autolock autoLock(mLock); + return mOutputFormat; } @@ -1869,9 +2129,24 @@ status_t OMXCodec::read( return UNKNOWN_ERROR; } + bool seeking = false; + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + seeking = true; + } + if (mInitialBufferSubmit) { mInitialBufferSubmit = false; + if (seeking) { + CHECK(seekTimeUs >= 0); + mSeekTimeUs = seekTimeUs; + + // There's no reason to trigger the code below, there's + // nothing to flush yet. + seeking = false; + } + drainInputBuffers(); if (mState == EXECUTING) { @@ -1881,8 +2156,7 @@ status_t OMXCodec::read( } } - int64_t seekTimeUs; - if (options && options->getSeekTo(&seekTimeUs)) { + if (seeking) { CODEC_LOGV("seeking to %lld us (%.2f secs)", seekTimeUs, seekTimeUs / 1E6); mSignalledEOS = false; @@ -1919,6 +2193,12 @@ status_t OMXCodec::read( return ERROR_END_OF_STREAM; } + if (mOutputPortSettingsHaveChanged) { + mOutputPortSettingsHaveChanged = false; + + return INFO_FORMAT_CHANGED; + } + size_t index = *mFilledBuffers.begin(); mFilledBuffers.erase(mFilledBuffers.begin()); @@ -2019,8 +2299,6 @@ static const char *colorFormatString(OMX_COLOR_FORMATTYPE type) { 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) { @@ -2164,7 +2442,7 @@ void OMXCodec::dumpPortStatus(OMX_U32 portIndex) { InitOMXParams(&def); def.nPortIndex = portIndex; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -2230,7 +2508,7 @@ void OMXCodec::dumpPortStatus(OMX_U32 portIndex) { InitOMXParams(¶ms); params.nPortIndex = portIndex; - err = mOMX->get_parameter( + err = mOMX->getParameter( mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); CHECK_EQ(err, OK); @@ -2249,7 +2527,7 @@ void OMXCodec::dumpPortStatus(OMX_U32 portIndex) { InitOMXParams(&amr); amr.nPortIndex = portIndex; - err = mOMX->get_parameter( + err = mOMX->getParameter( mNode, OMX_IndexParamAudioAmr, &amr, sizeof(amr)); CHECK_EQ(err, OK); @@ -2281,7 +2559,7 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { InitOMXParams(&def); def.nPortIndex = kPortIndexOutput; - status_t err = mOMX->get_parameter( + status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); @@ -2307,7 +2585,7 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { InitOMXParams(¶ms); params.nPortIndex = kPortIndexOutput; - err = mOMX->get_parameter( + err = mOMX->getParameter( mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); CHECK_EQ(err, OK); @@ -2339,7 +2617,7 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { InitOMXParams(&amr); amr.nPortIndex = kPortIndexOutput; - err = mOMX->get_parameter( + err = mOMX->getParameter( mNode, OMX_IndexParamAudioAmr, &amr, sizeof(amr)); CHECK_EQ(err, OK); @@ -2388,7 +2666,7 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { 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); @@ -2436,8 +2714,9 @@ status_t QueryCodecs( return OK; } + sp<OMXCodecObserver> observer = new OMXCodecObserver; IOMX::node_id node; - status_t err = omx->allocate_node(componentName, &node); + status_t err = omx->allocateNode(componentName, observer, &node); if (err != OK) { continue; @@ -2455,7 +2734,7 @@ status_t QueryCodecs( param.nPortIndex = queryDecoders ? 0 : 1; for (param.nProfileIndex = 0;; ++param.nProfileIndex) { - err = omx->get_parameter( + err = omx->getParameter( node, OMX_IndexParamVideoProfileLevelQuerySupported, ¶m, sizeof(param)); @@ -2470,7 +2749,7 @@ status_t QueryCodecs( caps->mProfileLevels.push(profileLevel); } - CHECK_EQ(omx->free_node(node), OK); + CHECK_EQ(omx->freeNode(node), OK); } } 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 @@ SampleTable::~SampleTable() { } 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 @@ status_t SampleTable::setChunkOffsetParams( } 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::setChunkOffsetParams( } 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 @@ status_t SampleTable::setSampleToChunkParams( } 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::setSampleToChunkParams( } 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 @@ status_t SampleTable::setSampleSizeParams( } 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::setSampleSizeParams( } 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 @@ status_t SampleTable::setTimeToSampleParams( 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 @@ status_t SampleTable::setTimeToSampleParams( 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 @@ status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) { 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 @@ status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) { 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 @@ status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) { 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 @@ status_t SampleTable::getChunkForSample( 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 @@ status_t SampleTable::getSampleSize( 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 @@ status_t SampleTable::getSampleSize( 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 @@ status_t SampleTable::getSampleSize( 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 @@ status_t SampleTable::getSampleSize( 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 @@ status_t SampleTable::findClosestSyncSample( 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 @@ status_t SampleTable::findClosestSyncSample( 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 8e8f4fa..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/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/include/media/stagefright/AMRExtractor.h b/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 @@ public: 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 index 01bcd18..01bcd18 100644 --- a/include/media/stagefright/ESDS.h +++ b/media/libstagefright/include/ESDS.h diff --git a/include/media/stagefright/HTTPStream.h b/media/libstagefright/include/HTTPStream.h index 3d0d67a..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/string.h> #include <utils/KeyedVector.h> namespace android { diff --git a/include/media/stagefright/MP3Extractor.h b/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 @@ public: 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 index 932e30f..ce4736d 100644 --- a/include/media/stagefright/MPEG4Extractor.h +++ b/media/libstagefright/include/MPEG4Extractor.h @@ -33,7 +33,7 @@ public: 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 @@ private: sp<MetaData> meta; uint32_t timescale; sp<SampleTable> sampleTable; + bool includes_expensive_metadata; }; sp<DataSource> mDataSource; diff --git a/media/libstagefright/omx/OMX.h b/media/libstagefright/include/OMX.h index 6325f79..a4b62b2 100644 --- a/media/libstagefright/omx/OMX.h +++ b/media/libstagefright/include/OMX.h @@ -19,66 +19,67 @@ #include <media/IOMX.h> #include <utils/threads.h> +#include <utils/KeyedVector.h> namespace android { -class NodeMeta; +class OMXNodeInstance; -class OMX : public BnOMX { +class OMX : public BnOMX, + public IBinder::DeathRecipient { public: OMX(); - virtual status_t list_nodes(List<String8> *list); + virtual status_t listNodes(List<String8> *list); - virtual status_t allocate_node(const char *name, node_id *node); - virtual status_t free_node(node_id node); + virtual status_t allocateNode( + const char *name, const sp<IOMXObserver> &observer, node_id *node); - virtual status_t send_command( + virtual status_t freeNode(node_id node); + + virtual status_t sendCommand( node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param); - virtual status_t get_parameter( + virtual status_t getParameter( node_id node, OMX_INDEXTYPE index, void *params, size_t size); - virtual status_t set_parameter( + virtual status_t setParameter( node_id node, OMX_INDEXTYPE index, const void *params, size_t size); - virtual status_t get_config( + virtual status_t getConfig( node_id node, OMX_INDEXTYPE index, void *params, size_t size); - virtual status_t set_config( + virtual status_t setConfig( node_id node, OMX_INDEXTYPE index, const void *params, size_t size); - virtual status_t use_buffer( + virtual status_t useBuffer( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer); - virtual status_t allocate_buffer( + virtual status_t allocateBuffer( node_id node, OMX_U32 port_index, size_t size, buffer_id *buffer); - virtual status_t allocate_buffer_with_backup( + virtual status_t allocateBufferWithBackup( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer); - virtual status_t free_buffer( + virtual status_t freeBuffer( node_id node, OMX_U32 port_index, buffer_id buffer); - virtual status_t observe_node( - node_id node, const sp<IOMXObserver> &observer); - - virtual void fill_buffer(node_id node, buffer_id buffer); + virtual status_t fillBuffer(node_id node, buffer_id buffer); - virtual void empty_buffer( + virtual status_t emptyBuffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, OMX_U32 flags, OMX_TICKS timestamp); - virtual status_t get_extension_index( + virtual status_t getExtensionIndex( node_id node, const char *parameter_name, OMX_INDEXTYPE *index); @@ -90,44 +91,38 @@ public: size_t encodedWidth, size_t encodedHeight, size_t displayWidth, size_t displayHeight); -private: - static OMX_CALLBACKTYPE kCallbacks; - - Mutex mLock; - - struct CallbackDispatcher; - sp<CallbackDispatcher> mDispatcher; - - static OMX_ERRORTYPE OnEvent( - OMX_IN OMX_HANDLETYPE hComponent, - OMX_IN OMX_PTR pAppData, - OMX_IN OMX_EVENTTYPE eEvent, - OMX_IN OMX_U32 nData1, - OMX_IN OMX_U32 nData2, - OMX_IN OMX_PTR pEventData); - - static OMX_ERRORTYPE OnEmptyBufferDone( - OMX_IN OMX_HANDLETYPE hComponent, - OMX_IN OMX_PTR pAppData, - OMX_IN OMX_BUFFERHEADERTYPE* pBuffer); - - static OMX_ERRORTYPE OnFillBufferDone( - OMX_IN OMX_HANDLETYPE hComponent, - OMX_IN OMX_PTR pAppData, - OMX_IN OMX_BUFFERHEADERTYPE* pBuffer); + virtual void binderDied(const wp<IBinder> &the_late_who); OMX_ERRORTYPE OnEvent( - NodeMeta *meta, + node_id node, OMX_IN OMX_EVENTTYPE eEvent, OMX_IN OMX_U32 nData1, OMX_IN OMX_U32 nData2, OMX_IN OMX_PTR pEventData); OMX_ERRORTYPE OnEmptyBufferDone( - NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); OMX_ERRORTYPE OnFillBufferDone( - NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + + void invalidateNodeID(node_id node); + +private: + Mutex mLock; + + struct CallbackDispatcher; + sp<CallbackDispatcher> mDispatcher; + + int32_t mNodeCounter; + + KeyedVector<wp<IBinder>, OMXNodeInstance *> mLiveNodes; + KeyedVector<node_id, OMXNodeInstance *> mNodeIDToInstance; + + node_id makeNodeID(OMXNodeInstance *instance); + OMXNodeInstance *findInstance(node_id node); + + void invalidateNodeID_l(node_id node); OMX(const OMX &); OMX &operator=(const OMX &); diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h new file mode 100644 index 0000000..09a8816 --- /dev/null +++ b/media/libstagefright/include/OMXNodeInstance.h @@ -0,0 +1,125 @@ +/* + * 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 OMX_NODE_INSTANCE_H_ + +#define OMX_NODE_INSTANCE_H_ + +#include "OMX.h" + +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class IOMXObserver; + +struct OMXNodeInstance { + OMXNodeInstance( + OMX *owner, const sp<IOMXObserver> &observer); + + void setHandle(OMX::node_id node_id, OMX_HANDLETYPE handle); + + OMX *owner(); + sp<IOMXObserver> observer(); + OMX::node_id nodeID(); + + status_t freeNode(); + + status_t sendCommand(OMX_COMMANDTYPE cmd, OMX_S32 param); + status_t getParameter(OMX_INDEXTYPE index, void *params, size_t size); + + status_t setParameter( + OMX_INDEXTYPE index, const void *params, size_t size); + + status_t getConfig(OMX_INDEXTYPE index, void *params, size_t size); + status_t setConfig(OMX_INDEXTYPE index, const void *params, size_t size); + + status_t useBuffer( + OMX_U32 portIndex, const sp<IMemory> ¶ms, + OMX::buffer_id *buffer); + + status_t allocateBuffer( + OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer); + + status_t allocateBufferWithBackup( + OMX_U32 portIndex, const sp<IMemory> ¶ms, + OMX::buffer_id *buffer); + + status_t freeBuffer(OMX_U32 portIndex, OMX::buffer_id buffer); + + status_t fillBuffer(OMX::buffer_id buffer); + + status_t emptyBuffer( + OMX::buffer_id buffer, + OMX_U32 rangeOffset, OMX_U32 rangeLength, + OMX_U32 flags, OMX_TICKS timestamp); + + status_t getExtensionIndex( + const char *parameterName, OMX_INDEXTYPE *index); + + void onMessage(const omx_message &msg); + void onObserverDied(); + void onGetHandleFailed(); + + static OMX_CALLBACKTYPE kCallbacks; + +private: + Mutex mLock; + + OMX *mOwner; + OMX::node_id mNodeID; + OMX_HANDLETYPE mHandle; + sp<IOMXObserver> mObserver; + + struct ActiveBuffer { + OMX_U32 mPortIndex; + OMX::buffer_id mID; + }; + Vector<ActiveBuffer> mActiveBuffers; + + ~OMXNodeInstance(); + + void addActiveBuffer(OMX_U32 portIndex, OMX::buffer_id id); + void removeActiveBuffer(OMX_U32 portIndex, OMX::buffer_id id); + void freeActiveBuffers(); + + static OMX_ERRORTYPE OnEvent( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData); + + static OMX_ERRORTYPE OnEmptyBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + + static OMX_ERRORTYPE OnFillBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + + OMXNodeInstance(const OMXNodeInstance &); + OMXNodeInstance &operator=(const OMXNodeInstance &); +}; + +} // namespace android + +#endif // OMX_NODE_INSTANCE_H_ + diff --git a/include/media/stagefright/QComHardwareRenderer.h b/media/libstagefright/include/QComHardwareRenderer.h index 8292dd5..8292dd5 100644 --- a/include/media/stagefright/QComHardwareRenderer.h +++ b/media/libstagefright/include/QComHardwareRenderer.h diff --git a/include/media/stagefright/SampleTable.h b/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 @@ public: // 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 @@ public: 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 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 @@ public: 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 @@ private: 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 index ef42648..ef42648 100644 --- a/include/media/stagefright/TIHardwareRenderer.h +++ b/media/libstagefright/include/TIHardwareRenderer.h diff --git a/include/media/stagefright/TimedEventQueue.h b/media/libstagefright/include/TimedEventQueue.h index a264421..a264421 100644 --- a/include/media/stagefright/TimedEventQueue.h +++ b/media/libstagefright/include/TimedEventQueue.h diff --git a/include/media/stagefright/string.h b/media/libstagefright/include/stagefright_string.h index 5dc7116..5dc7116 100644 --- a/include/media/stagefright/string.h +++ b/media/libstagefright/include/stagefright_string.h diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk index 4cadccd..edbc04e 100644 --- a/media/libstagefright/omx/Android.mk +++ b/media/libstagefright/omx/Android.mk @@ -10,7 +10,9 @@ LOCAL_C_INCLUDES += $(TOP)/hardware/ti/omap3/liboverlay LOCAL_C_INCLUDES += $(JNI_H_INCLUDE) LOCAL_SRC_FILES:= \ + ColorConverter.cpp \ OMX.cpp \ + OMXNodeInstance.cpp \ QComHardwareRenderer.cpp \ SoftwareRenderer.cpp \ TIHardwareRenderer.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 8b83dd6..e361018 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -18,65 +18,28 @@ #define LOG_TAG "OMX" #include <utils/Log.h> -#include <sys/socket.h> - -#include "OMX.h" +#include "../include/OMX.h" #include "OMXRenderer.h" #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> namespace android { -class NodeMeta { -public: - NodeMeta(OMX *owner) - : mOwner(owner), - mHandle(NULL) { - } - - OMX *owner() const { - return mOwner; - } - - void setHandle(OMX_HANDLETYPE handle) { - CHECK_EQ(mHandle, NULL); - mHandle = handle; - } - - OMX_HANDLETYPE handle() const { - return mHandle; - } - - void setObserver(const sp<IOMXObserver> &observer) { - mObserver = observer; - } - - sp<IOMXObserver> observer() { - return mObserver; - } - -private: - OMX *mOwner; - OMX_HANDLETYPE mHandle; - sp<IOMXObserver> mObserver; - - NodeMeta(const NodeMeta &); - NodeMeta &operator=(const NodeMeta &); -}; - //////////////////////////////////////////////////////////////////////////////// struct OMX::CallbackDispatcher : public RefBase { - CallbackDispatcher(); + CallbackDispatcher(OMX *owner); void post(const omx_message &msg); @@ -85,6 +48,8 @@ protected: private: Mutex mLock; + + OMX *mOwner; bool mDone; Condition mQueueChanged; List<omx_message> mQueue; @@ -100,8 +65,9 @@ private: CallbackDispatcher &operator=(const CallbackDispatcher &); }; -OMX::CallbackDispatcher::CallbackDispatcher() - : mDone(false) { +OMX::CallbackDispatcher::CallbackDispatcher(OMX *owner) + : mOwner(owner), + mDone(false) { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); @@ -130,12 +96,12 @@ void OMX::CallbackDispatcher::post(const omx_message &msg) { } void OMX::CallbackDispatcher::dispatch(const omx_message &msg) { - NodeMeta *meta = static_cast<NodeMeta *>(msg.node); - - sp<IOMXObserver> observer = meta->observer(); - if (observer.get() != NULL) { - observer->on_message(msg); + OMXNodeInstance *instance = mOwner->findInstance(msg.node); + if (instance == NULL) { + LOGV("Would have dispatched a message to a node that's already gone."); + return; } + instance->onMessage(msg); } // static @@ -213,46 +179,30 @@ private: BufferMeta &operator=(const BufferMeta &); }; -// static -OMX_CALLBACKTYPE OMX::kCallbacks = { - &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone -}; - -// static -OMX_ERRORTYPE OMX::OnEvent( - OMX_IN OMX_HANDLETYPE hComponent, - OMX_IN OMX_PTR pAppData, - OMX_IN OMX_EVENTTYPE eEvent, - OMX_IN OMX_U32 nData1, - OMX_IN OMX_U32 nData2, - OMX_IN OMX_PTR pEventData) { - NodeMeta *meta = static_cast<NodeMeta *>(pAppData); - return meta->owner()->OnEvent(meta, eEvent, nData1, nData2, pEventData); +OMX::OMX() + : mDispatcher(new CallbackDispatcher(this)), + mNodeCounter(0) { } -// static -OMX_ERRORTYPE OMX::OnEmptyBufferDone( - OMX_IN OMX_HANDLETYPE hComponent, - OMX_IN OMX_PTR pAppData, - OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { - NodeMeta *meta = static_cast<NodeMeta *>(pAppData); - return meta->owner()->OnEmptyBufferDone(meta, pBuffer); -} +void OMX::binderDied(const wp<IBinder> &the_late_who) { + OMXNodeInstance *instance; -// static -OMX_ERRORTYPE OMX::OnFillBufferDone( - OMX_IN OMX_HANDLETYPE hComponent, - OMX_IN OMX_PTR pAppData, - OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { - NodeMeta *meta = static_cast<NodeMeta *>(pAppData); - return meta->owner()->OnFillBufferDone(meta, pBuffer); -} + { + Mutex::Autolock autoLock(mLock); -OMX::OMX() - : mDispatcher(new CallbackDispatcher) { + ssize_t index = mLiveNodes.indexOfKey(the_late_who); + CHECK(index >= 0); + + instance = mLiveNodes.editValueAt(index); + mLiveNodes.removeItemsAt(index); + + invalidateNodeID_l(instance->nodeID()); + } + + instance->onObserverDied(); } -status_t OMX::list_nodes(List<String8> *list) { +status_t OMX::listNodes(List<String8> *list) { OMX_MasterInit(); // XXX Put this somewhere else. list->clear(); @@ -269,204 +219,132 @@ status_t OMX::list_nodes(List<String8> *list) { return OK; } -status_t OMX::allocate_node(const char *name, node_id *node) { +status_t OMX::allocateNode( + const char *name, const sp<IOMXObserver> &observer, node_id *node) { Mutex::Autolock autoLock(mLock); *node = 0; OMX_MasterInit(); // XXX Put this somewhere else. - NodeMeta *meta = new NodeMeta(this); + OMXNodeInstance *instance = new OMXNodeInstance(this, observer); OMX_HANDLETYPE handle; OMX_ERRORTYPE err = OMX_MasterGetHandle( - &handle, const_cast<char *>(name), meta, &kCallbacks); + &handle, const_cast<char *>(name), instance, + &OMXNodeInstance::kCallbacks); if (err != OMX_ErrorNone) { - LOGE("FAILED to allocate omx component '%s'", name); + LOGV("FAILED to allocate omx component '%s'", name); - delete meta; - meta = NULL; + instance->onGetHandleFailed(); return UNKNOWN_ERROR; } - meta->setHandle(handle); + *node = makeNodeID(instance); - *node = meta; + instance->setHandle(*node, handle); + + mLiveNodes.add(observer->asBinder(), instance); + observer->asBinder()->linkToDeath(this); return OK; } -status_t OMX::free_node(node_id node) { - Mutex::Autolock autoLock(mLock); - - NodeMeta *meta = static_cast<NodeMeta *>(node); - - OMX_ERRORTYPE err = OMX_MasterFreeHandle(meta->handle()); +status_t OMX::freeNode(node_id node) { + OMXNodeInstance *instance = findInstance(node); - delete meta; - meta = NULL; + ssize_t index = mLiveNodes.indexOfKey(instance->observer()->asBinder()); + CHECK(index >= 0); + mLiveNodes.removeItemsAt(index); + instance->observer()->asBinder()->unlinkToDeath(this); - return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; + return instance->freeNode(); } -status_t OMX::send_command( +status_t OMX::sendCommand( node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { - Mutex::Autolock autoLock(mLock); - - NodeMeta *meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = OMX_SendCommand(meta->handle(), cmd, param, NULL); - - return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; + return findInstance(node)->sendCommand(cmd, param); } -status_t OMX::get_parameter( +status_t OMX::getParameter( node_id node, OMX_INDEXTYPE index, void *params, size_t size) { - Mutex::Autolock autoLock(mLock); - - NodeMeta *meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = OMX_GetParameter(meta->handle(), index, params); - - return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; + return findInstance(node)->getParameter( + index, params, size); } -status_t OMX::set_parameter( +status_t OMX::setParameter( node_id node, OMX_INDEXTYPE index, const void *params, size_t size) { - Mutex::Autolock autoLock(mLock); - - NodeMeta *meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = - OMX_SetParameter(meta->handle(), index, const_cast<void *>(params)); - - return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; + return findInstance(node)->setParameter( + index, params, size); } -status_t OMX::get_config( +status_t OMX::getConfig( node_id node, OMX_INDEXTYPE index, void *params, size_t size) { - Mutex::Autolock autoLock(mLock); - - NodeMeta *meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = OMX_GetConfig(meta->handle(), index, params); - - return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; + return findInstance(node)->getConfig( + index, params, size); } -status_t OMX::set_config( +status_t OMX::setConfig( node_id node, OMX_INDEXTYPE index, const void *params, size_t size) { - Mutex::Autolock autoLock(mLock); - - NodeMeta *meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = - OMX_SetConfig(meta->handle(), index, const_cast<void *>(params)); - - return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; + return findInstance(node)->setConfig( + index, params, size); } -status_t OMX::use_buffer( +status_t OMX::useBuffer( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) { - Mutex::Autolock autoLock(mLock); - - BufferMeta *buffer_meta = new BufferMeta(this, params); - - OMX_BUFFERHEADERTYPE *header; - - NodeMeta *node_meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = - OMX_UseBuffer(node_meta->handle(), &header, port_index, buffer_meta, - params->size(), static_cast<OMX_U8 *>(params->pointer())); - - if (err != OMX_ErrorNone) { - LOGE("OMX_UseBuffer failed with error %d (0x%08x)", err, err); - - delete buffer_meta; - buffer_meta = NULL; - - *buffer = 0; - return UNKNOWN_ERROR; - } - - *buffer = header; - - return OK; + return findInstance(node)->useBuffer( + port_index, params, buffer); } -status_t OMX::allocate_buffer( +status_t OMX::allocateBuffer( node_id node, OMX_U32 port_index, size_t size, buffer_id *buffer) { - Mutex::Autolock autoLock(mLock); - - BufferMeta *buffer_meta = new BufferMeta(this, size); - - OMX_BUFFERHEADERTYPE *header; - - NodeMeta *node_meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = - OMX_AllocateBuffer(node_meta->handle(), &header, port_index, - buffer_meta, size); - - if (err != OMX_ErrorNone) { - delete buffer_meta; - buffer_meta = NULL; - - *buffer = 0; - return UNKNOWN_ERROR; - } - - *buffer = header; - - return OK; + return findInstance(node)->allocateBuffer( + port_index, size, buffer); } -status_t OMX::allocate_buffer_with_backup( +status_t OMX::allocateBufferWithBackup( node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, buffer_id *buffer) { - Mutex::Autolock autoLock(mLock); - - BufferMeta *buffer_meta = new BufferMeta(this, params, true); - - OMX_BUFFERHEADERTYPE *header; - - NodeMeta *node_meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = - OMX_AllocateBuffer( - node_meta->handle(), &header, port_index, buffer_meta, - params->size()); - - if (err != OMX_ErrorNone) { - delete buffer_meta; - buffer_meta = NULL; - - *buffer = 0; - return UNKNOWN_ERROR; - } - - *buffer = header; - - return OK; + return findInstance(node)->allocateBufferWithBackup( + port_index, params, buffer); } -status_t OMX::free_buffer(node_id node, OMX_U32 port_index, buffer_id buffer) { - OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; - BufferMeta *buffer_meta = static_cast<BufferMeta *>(header->pAppPrivate); +status_t OMX::freeBuffer(node_id node, OMX_U32 port_index, buffer_id buffer) { + return findInstance(node)->freeBuffer( + port_index, buffer); +} - NodeMeta *node_meta = static_cast<NodeMeta *>(node); - OMX_ERRORTYPE err = - OMX_FreeBuffer(node_meta->handle(), port_index, header); +status_t OMX::fillBuffer(node_id node, buffer_id buffer) { + return findInstance(node)->fillBuffer(buffer); +} - delete buffer_meta; - buffer_meta = NULL; +status_t OMX::emptyBuffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { + return findInstance(node)->emptyBuffer( + buffer, range_offset, range_length, flags, timestamp); +} - return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +status_t OMX::getExtensionIndex( + node_id node, + const char *parameter_name, + OMX_INDEXTYPE *index) { + return findInstance(node)->getExtensionIndex( + parameter_name, index); } OMX_ERRORTYPE OMX::OnEvent( - NodeMeta *meta, + node_id node, OMX_IN OMX_EVENTTYPE eEvent, OMX_IN OMX_U32 nData1, OMX_IN OMX_U32 nData2, @@ -475,7 +353,7 @@ OMX_ERRORTYPE OMX::OnEvent( omx_message msg; msg.type = omx_message::EVENT; - msg.node = meta; + msg.node = node; msg.u.event_data.event = eEvent; msg.u.event_data.data1 = nData1; msg.u.event_data.data2 = nData2; @@ -484,14 +362,14 @@ OMX_ERRORTYPE OMX::OnEvent( return OMX_ErrorNone; } - + OMX_ERRORTYPE OMX::OnEmptyBufferDone( - NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { + node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { LOGV("OnEmptyBufferDone buffer=%p", pBuffer); omx_message msg; msg.type = omx_message::EMPTY_BUFFER_DONE; - msg.node = meta; + msg.node = node; msg.u.buffer_data.buffer = pBuffer; mDispatcher->post(msg); @@ -500,14 +378,12 @@ OMX_ERRORTYPE OMX::OnEmptyBufferDone( } OMX_ERRORTYPE OMX::OnFillBufferDone( - NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { + node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { LOGV("OnFillBufferDone buffer=%p", pBuffer); - BufferMeta *buffer_meta = static_cast<BufferMeta *>(pBuffer->pAppPrivate); - buffer_meta->CopyFromOMX(pBuffer); omx_message msg; msg.type = omx_message::FILL_BUFFER_DONE; - msg.node = meta; + msg.node = node; msg.u.extended_buffer_data.buffer = pBuffer; msg.u.extended_buffer_data.range_offset = pBuffer->nOffset; msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen; @@ -520,62 +396,31 @@ OMX_ERRORTYPE OMX::OnFillBufferDone( return OMX_ErrorNone; } -status_t OMX::observe_node( - node_id node, const sp<IOMXObserver> &observer) { - NodeMeta *node_meta = static_cast<NodeMeta *>(node); +OMX::node_id OMX::makeNodeID(OMXNodeInstance *instance) { + // mLock is already held. - node_meta->setObserver(observer); + node_id node = (node_id)++mNodeCounter; + mNodeIDToInstance.add(node, instance); - return OK; + return node; } -void OMX::fill_buffer(node_id node, buffer_id buffer) { - OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; - header->nFilledLen = 0; - header->nOffset = 0; - header->nFlags = 0; +OMXNodeInstance *OMX::findInstance(node_id node) { + Mutex::Autolock autoLock(mLock); - NodeMeta *node_meta = static_cast<NodeMeta *>(node); + ssize_t index = mNodeIDToInstance.indexOfKey(node); - OMX_ERRORTYPE err = - OMX_FillThisBuffer(node_meta->handle(), header); - CHECK_EQ(err, OMX_ErrorNone); + return index < 0 ? NULL : mNodeIDToInstance.valueAt(index); } -void OMX::empty_buffer( - node_id node, - buffer_id buffer, - OMX_U32 range_offset, OMX_U32 range_length, - OMX_U32 flags, OMX_TICKS timestamp) { - OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; - header->nFilledLen = range_length; - header->nOffset = range_offset; - header->nFlags = flags; - header->nTimeStamp = timestamp; - - BufferMeta *buffer_meta = - static_cast<BufferMeta *>(header->pAppPrivate); - buffer_meta->CopyToOMX(header); - - NodeMeta *node_meta = static_cast<NodeMeta *>(node); - - OMX_ERRORTYPE err = - OMX_EmptyThisBuffer(node_meta->handle(), header); - CHECK_EQ(err, OMX_ErrorNone); +void OMX::invalidateNodeID(node_id node) { + Mutex::Autolock autoLock(mLock); + invalidateNodeID_l(node); } -status_t OMX::get_extension_index( - node_id node, - const char *parameter_name, - OMX_INDEXTYPE *index) { - NodeMeta *node_meta = static_cast<NodeMeta *>(node); - - OMX_ERRORTYPE err = - OMX_GetExtensionIndex( - node_meta->handle(), - const_cast<char *>(parameter_name), index); - - return err == OMX_ErrorNone ? OK : UNKNOWN_ERROR; +void OMX::invalidateNodeID_l(node_id node) { + // mLock is held. + mNodeIDToInstance.removeItem(node); } //////////////////////////////////////////////////////////////////////////////// diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp new file mode 100644 index 0000000..ab8be53 --- /dev/null +++ b/media/libstagefright/omx/OMXNodeInstance.cpp @@ -0,0 +1,460 @@ +/* + * 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 "OMXNodeInstance" +#include <utils/Log.h> + +#include "../include/OMXNodeInstance.h" + +#include "pv_omxcore.h" + +#include <binder/IMemory.h> +#include <media/stagefright/MediaDebug.h> + +namespace android { + +struct BufferMeta { + BufferMeta(const sp<IMemory> &mem, bool is_backup = false) + : mMem(mem), + mIsBackup(is_backup) { + } + + BufferMeta(size_t size) + : mSize(size), + mIsBackup(false) { + } + + void CopyFromOMX(const OMX_BUFFERHEADERTYPE *header) { + if (!mIsBackup) { + return; + } + + memcpy((OMX_U8 *)mMem->pointer() + header->nOffset, + header->pBuffer + header->nOffset, + header->nFilledLen); + } + + void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) { + if (!mIsBackup) { + return; + } + + memcpy(header->pBuffer + header->nOffset, + (const OMX_U8 *)mMem->pointer() + header->nOffset, + header->nFilledLen); + } + +private: + sp<IMemory> mMem; + size_t mSize; + bool mIsBackup; + + BufferMeta(const BufferMeta &); + BufferMeta &operator=(const BufferMeta &); +}; + +// static +OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = { + &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone +}; + +OMXNodeInstance::OMXNodeInstance( + OMX *owner, const sp<IOMXObserver> &observer) + : mOwner(owner), + mNodeID(NULL), + mHandle(NULL), + mObserver(observer) { +} + +OMXNodeInstance::~OMXNodeInstance() { + CHECK_EQ(mHandle, NULL); +} + +void OMXNodeInstance::setHandle(OMX::node_id node_id, OMX_HANDLETYPE handle) { + CHECK_EQ(mHandle, NULL); + mNodeID = node_id; + mHandle = handle; +} + +OMX *OMXNodeInstance::owner() { + return mOwner; +} + +sp<IOMXObserver> OMXNodeInstance::observer() { + return mObserver; +} + +OMX::node_id OMXNodeInstance::nodeID() { + return mNodeID; +} + +static status_t StatusFromOMXError(OMX_ERRORTYPE err) { + return (err == OMX_ErrorNone) ? OK : UNKNOWN_ERROR; +} + +status_t OMXNodeInstance::freeNode() { + // Transition the node from its current state all the way down + // to "Loaded". + // This ensures that all active buffers are properly freed even + // for components that don't do this themselves on a call to + // "FreeHandle". + + OMX_STATETYPE state; + CHECK_EQ(OMX_GetState(mHandle, &state), OMX_ErrorNone); + switch (state) { + case OMX_StateExecuting: + { + LOGV("forcing Executing->Idle"); + sendCommand(OMX_CommandStateSet, OMX_StateIdle); + OMX_ERRORTYPE err; + while ((err = OMX_GetState(mHandle, &state)) == OMX_ErrorNone + && state != OMX_StateIdle) { + usleep(100000); + } + CHECK_EQ(err, OMX_ErrorNone); + + // fall through + } + + case OMX_StateIdle: + { + LOGV("forcing Idle->Loaded"); + sendCommand(OMX_CommandStateSet, OMX_StateLoaded); + + freeActiveBuffers(); + + OMX_ERRORTYPE err; + while ((err = OMX_GetState(mHandle, &state)) == OMX_ErrorNone + && state != OMX_StateLoaded) { + LOGV("waiting for Loaded state..."); + usleep(100000); + } + CHECK_EQ(err, OMX_ErrorNone); + + // fall through + } + + case OMX_StateLoaded: + break; + + default: + CHECK(!"should not be here, unknown state."); + break; + } + + OMX_ERRORTYPE err = OMX_MasterFreeHandle(mHandle); + mHandle = NULL; + + if (err != OMX_ErrorNone) { + LOGE("FreeHandle FAILED with error 0x%08x.", err); + } + + mOwner->invalidateNodeID(mNodeID); + mNodeID = NULL; + + LOGV("OMXNodeInstance going away."); + delete this; + + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::sendCommand( + OMX_COMMANDTYPE cmd, OMX_S32 param) { + Mutex::Autolock autoLock(mLock); + + OMX_ERRORTYPE err = OMX_SendCommand(mHandle, cmd, param, NULL); + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::getParameter( + OMX_INDEXTYPE index, void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + OMX_ERRORTYPE err = OMX_GetParameter(mHandle, index, params); + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::setParameter( + OMX_INDEXTYPE index, const void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + OMX_ERRORTYPE err = OMX_SetParameter( + mHandle, index, const_cast<void *>(params)); + + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::getConfig( + OMX_INDEXTYPE index, void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + OMX_ERRORTYPE err = OMX_GetConfig(mHandle, index, params); + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::setConfig( + OMX_INDEXTYPE index, const void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + OMX_ERRORTYPE err = OMX_SetConfig( + mHandle, index, const_cast<void *>(params)); + + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::useBuffer( + OMX_U32 portIndex, const sp<IMemory> ¶ms, + OMX::buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(params); + + OMX_BUFFERHEADERTYPE *header; + + OMX_ERRORTYPE err = OMX_UseBuffer( + mHandle, &header, portIndex, buffer_meta, + params->size(), static_cast<OMX_U8 *>(params->pointer())); + + if (err != OMX_ErrorNone) { + LOGE("OMX_UseBuffer failed with error %d (0x%08x)", err, err); + + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + + return UNKNOWN_ERROR; + } + + *buffer = header; + + addActiveBuffer(portIndex, *buffer); + + return OK; +} + +status_t OMXNodeInstance::allocateBuffer( + OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(size); + + OMX_BUFFERHEADERTYPE *header; + + OMX_ERRORTYPE err = OMX_AllocateBuffer( + mHandle, &header, portIndex, buffer_meta, size); + + if (err != OMX_ErrorNone) { + LOGE("OMX_AllocateBuffer failed with error %d (0x%08x)", err, err); + + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + + return UNKNOWN_ERROR; + } + + *buffer = header; + + addActiveBuffer(portIndex, *buffer); + + return OK; +} + +status_t OMXNodeInstance::allocateBufferWithBackup( + OMX_U32 portIndex, const sp<IMemory> ¶ms, + OMX::buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(params, true); + + OMX_BUFFERHEADERTYPE *header; + + OMX_ERRORTYPE err = OMX_AllocateBuffer( + mHandle, &header, portIndex, buffer_meta, params->size()); + + if (err != OMX_ErrorNone) { + LOGE("OMX_AllocateBuffer failed with error %d (0x%08x)", err, err); + + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + + return UNKNOWN_ERROR; + } + + *buffer = header; + + addActiveBuffer(portIndex, *buffer); + + return OK; +} + +status_t OMXNodeInstance::freeBuffer( + OMX_U32 portIndex, OMX::buffer_id buffer) { + Mutex::Autolock autoLock(mLock); + + removeActiveBuffer(portIndex, buffer); + + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + BufferMeta *buffer_meta = static_cast<BufferMeta *>(header->pAppPrivate); + + OMX_ERRORTYPE err = OMX_FreeBuffer(mHandle, portIndex, header); + + delete buffer_meta; + buffer_meta = NULL; + + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::fillBuffer(OMX::buffer_id buffer) { + Mutex::Autolock autoLock(mLock); + + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + header->nFilledLen = 0; + header->nOffset = 0; + header->nFlags = 0; + + OMX_ERRORTYPE err = OMX_FillThisBuffer(mHandle, header); + + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::emptyBuffer( + OMX::buffer_id buffer, + OMX_U32 rangeOffset, OMX_U32 rangeLength, + OMX_U32 flags, OMX_TICKS timestamp) { + Mutex::Autolock autoLock(mLock); + + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + header->nFilledLen = rangeLength; + header->nOffset = rangeOffset; + header->nFlags = flags; + header->nTimeStamp = timestamp; + + BufferMeta *buffer_meta = + static_cast<BufferMeta *>(header->pAppPrivate); + buffer_meta->CopyToOMX(header); + + OMX_ERRORTYPE err = OMX_EmptyThisBuffer(mHandle, header); + + return StatusFromOMXError(err); +} + +status_t OMXNodeInstance::getExtensionIndex( + const char *parameterName, OMX_INDEXTYPE *index) { + Mutex::Autolock autoLock(mLock); + + OMX_ERRORTYPE err = OMX_GetExtensionIndex( + mHandle, const_cast<char *>(parameterName), index); + + return StatusFromOMXError(err); +} + +void OMXNodeInstance::onMessage(const omx_message &msg) { + if (msg.type == omx_message::FILL_BUFFER_DONE) { + OMX_BUFFERHEADERTYPE *buffer = + static_cast<OMX_BUFFERHEADERTYPE *>( + msg.u.extended_buffer_data.buffer); + + BufferMeta *buffer_meta = + static_cast<BufferMeta *>(buffer->pAppPrivate); + + buffer_meta->CopyFromOMX(buffer); + } + + mObserver->onMessage(msg); +} + +void OMXNodeInstance::onObserverDied() { + LOGE("!!! Observer died. Quickly, do something, ... anything..."); + + // Try to force shutdown of the node and hope for the best. + freeNode(); +} + +void OMXNodeInstance::onGetHandleFailed() { + delete this; +} + +// static +OMX_ERRORTYPE OMXNodeInstance::OnEvent( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData) { + OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); + return instance->owner()->OnEvent( + instance->nodeID(), eEvent, nData1, nData2, pEventData); +} + +// static +OMX_ERRORTYPE OMXNodeInstance::OnEmptyBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { + OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); + return instance->owner()->OnEmptyBufferDone(instance->nodeID(), pBuffer); +} + +// static +OMX_ERRORTYPE OMXNodeInstance::OnFillBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { + OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); + return instance->owner()->OnFillBufferDone(instance->nodeID(), pBuffer); +} + +void OMXNodeInstance::addActiveBuffer(OMX_U32 portIndex, OMX::buffer_id id) { + ActiveBuffer active; + active.mPortIndex = portIndex; + active.mID = id; + mActiveBuffers.push(active); +} + +void OMXNodeInstance::removeActiveBuffer( + OMX_U32 portIndex, OMX::buffer_id id) { + bool found = false; + for (size_t i = 0; i < mActiveBuffers.size(); ++i) { + if (mActiveBuffers[i].mPortIndex == portIndex + && mActiveBuffers[i].mID == id) { + found = true; + mActiveBuffers.removeItemsAt(i); + break; + } + } + + if (!found) { + LOGW("Attempt to remove an active buffer we know nothing about..."); + } +} + +void OMXNodeInstance::freeActiveBuffers() { + // Make sure to count down here, as freeBuffer will in turn remove + // the active buffer from the vector... + for (size_t i = mActiveBuffers.size(); i--;) { + freeBuffer(mActiveBuffers[i].mPortIndex, mActiveBuffers[i].mID); + } +} + +} // namespace android + diff --git a/media/libstagefright/omx/QComHardwareRenderer.cpp b/media/libstagefright/omx/QComHardwareRenderer.cpp index 7dc368f..8e78c77 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 @@ SoftwareRenderer::SoftwareRenderer( 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( 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; - } - - 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; - } + mConverter.convert( + mDecodedWidth, mDecodedHeight, + data, 0, dst, 2 * mDecodedWidth); 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/string.cpp b/media/libstagefright/string.cpp index 5b16784..bd6204b 100644 --- a/media/libstagefright/string.cpp +++ b/media/libstagefright/string.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include <media/stagefright/string.h> +#include "include/stagefright_string.h" namespace android { diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index 695d061..f485d26 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -18,7 +18,6 @@ package android.opengl; 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.GL; 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; @@ -681,7 +682,10 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } 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]; @@ -691,8 +695,10 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } 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"); @@ -817,11 +823,17 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback */ 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); /* @@ -966,16 +978,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * accesses EGL. */ try { - try { - sEglSemaphore.acquire(); - } catch (InterruptedException e) { - return; - } + sGLAccessLock.acquire(); guardedRun(); } catch (InterruptedException e) { // fall thru and exit normally } finally { - sEglSemaphore.release(); + sGLAccessLock.release(); } } @@ -1040,6 +1048,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } if (changed) { gl = (GL10) mEglHelper.createSurface(getHolder()); + sGLAccessLock.checkGLDriver(gl); tellRendererSurfaceChanged = true; } if (tellRendererSurfaceCreated) { @@ -1241,7 +1250,56 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } - private static final Semaphore sEglSemaphore = new Semaphore(1); + private static class GLAccessLock { + public synchronized void acquire() throws InterruptedException { + if (! mGLESVersionCheckComplete) { + mGLESVersion = SystemProperties.getInt( + "ro.opengles.version", + ConfigurationInfo.GL_ES_VERSION_UNDEFINED); + if (mGLESVersion >= kGLES_20) { + mMultipleGLESContextsAllowed = true; + } + mGLESVersionCheckComplete = true; + } + + while ((! mMultipleGLESContextsAllowed) + && mGLContextCount > 0) { + wait(); + } + + mGLContextCount++; + + } + + public synchronized void release() { + mGLContextCount--; + notifyAll(); + } + + 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; + } + } + + private boolean mGLESVersionCheckComplete; + private int mGLESVersion; + 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 GLAccessLock sGLAccessLock = new GLAccessLock(); + private boolean mSizeChanged = true; private GLThread mGLThread; 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 @@ import javax.microedition.khronos.opengles.GL10; */ 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 @@ class GL2JNIView extends GLSurfaceView { 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 @@ class GL2JNIView extends GLSurfaceView { 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/packages/SubscribedFeedsProvider/res/values-pt-rPT/strings.xml b/packages/SubscribedFeedsProvider/res/values-pt-rPT/strings.xml index 29f69ac..a52fd20 100644 --- a/packages/SubscribedFeedsProvider/res/values-pt-rPT/strings.xml +++ b/packages/SubscribedFeedsProvider/res/values-pt-rPT/strings.xml @@ -16,5 +16,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="5400580392303600842">"Sincronizar feeds"</string> - <string name="provider_label" msgid="3669714991966737047">"Transferir Subscrições"</string> + <string name="provider_label" msgid="3669714991966737047">"Subscrições de Emissão"</string> </resources> diff --git a/packages/VpnServices/res/values-nb/strings.xml b/packages/VpnServices/res/values-nb/strings.xml index 9aac828..506f999 100644 --- a/packages/VpnServices/res/values-nb/strings.xml +++ b/packages/VpnServices/res/values-nb/strings.xml @@ -16,7 +16,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4589592829302498102">"VPN-tjenester"</string> - <string name="vpn_notification_title_connected" msgid="8598654486956133580">"Koblet til VPNet <xliff:g id="PROFILENAME">%s</xliff:g>"</string> - <string name="vpn_notification_title_disconnected" msgid="6216572264382192027">"Koblet fra VPNet <xliff:g id="PROFILENAME">%s</xliff:g>"</string> + <string name="vpn_notification_title_connected" msgid="8598654486956133580">"<xliff:g id="PROFILENAME">%s</xliff:g> er VPN-tilkoblet"</string> + <string name="vpn_notification_title_disconnected" msgid="6216572264382192027">"<xliff:g id="PROFILENAME">%s</xliff:g> er VPN-frakoblet"</string> <string name="vpn_notification_hint_disconnected" msgid="1952209867082269429">"Trykk for å koble til et VPN på nytt"</string> </resources> diff --git a/packages/VpnServices/res/values-zh-rCN/strings.xml b/packages/VpnServices/res/values-zh-rCN/strings.xml index ee8878f..940e210 100644 --- a/packages/VpnServices/res/values-zh-rCN/strings.xml +++ b/packages/VpnServices/res/values-zh-rCN/strings.xml @@ -16,7 +16,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4589592829302498102">"虚拟专用网服务"</string> - <string name="vpn_notification_title_connected" msgid="8598654486956133580">"VPN“<xliff:g id="PROFILENAME">%s</xliff:g>”已连接"</string> - <string name="vpn_notification_title_disconnected" msgid="6216572264382192027">"VPN“<xliff:g id="PROFILENAME">%s</xliff:g>”连接已断开"</string> + <string name="vpn_notification_title_connected" msgid="8598654486956133580">"<xliff:g id="PROFILENAME">%s</xliff:g> VPN 已连接"</string> + <string name="vpn_notification_title_disconnected" msgid="6216572264382192027">"<xliff:g id="PROFILENAME">%s</xliff:g> VPN 连接已断开"</string> <string name="vpn_notification_hint_disconnected" msgid="1952209867082269429">"轻触可重新连接到虚拟专用网。"</string> </resources> 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { // javadoc from interface public int stopUsingNetworkFeature(int networkType, String feature) { + enforceChangePermission(); + int pid = getCallingPid(); int uid = getCallingUid(); @@ -611,7 +613,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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.EventLog; import android.util.Log; import android.accounts.AccountManagerService; +import java.io.File; import java.util.Timer; import java.util.TimerTask; @@ -294,6 +295,14 @@ class ServerThread extends Thread { } 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 @@ public class WifiService extends IWifiManager.Stub { 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 @@ public class WifiService extends IWifiManager.Stub { 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 @@ public class WifiService extends IWifiManager.Stub { 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 @@ public class WifiService extends IWifiManager.Stub { !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 @@ public class WifiService extends IWifiManager.Stub { : 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); + } + if (!WifiNative.setNetworkVariableCommand( + netId, + varName, + value)) { + if (DBG) { + Log.d(TAG, config.SSID + ": failed to set " + varName + + ": " + value); + } + break setVariables; } - break setVariables; } } - return netId; } @@ -1432,6 +1447,11 @@ public class WifiService extends IWifiManager.Stub { } 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 @@ public class WifiService extends IWifiManager.Stub { 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 class WifiService extends IWifiManager.Stub { } } + 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 94667eb..0f2367d 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -9303,15 +9303,6 @@ public class WindowManagerService extends IWindowManager.Stub & 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 @@ public class PhoneNumberUtils } /** + * 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 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils if (!isDialable(ca)) { ia--; skipCmp = true; + numNonDialableCharsInA++; } cb = b.charAt(ib); @@ -369,6 +408,7 @@ public class PhoneNumberUtils if (!isDialable(cb)) { ib--; skipCmp = true; + numNonDialableCharsInB++; } if (!skipCmp) { @@ -380,13 +420,16 @@ public class PhoneNumberUtils } 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 class PhoneNumberUtils */ public static String toCallerIDMinMatch(String phoneNumber) { - String np = extractNetworkPortion(phoneNumber); + String np = extractNetworkPortionAlt(phoneNumber); return internalGetStrippedReversed(np, MIN_MATCH); } @@ -603,7 +646,7 @@ public class PhoneNumberUtils */ public static String getStrippedReversed(String phoneNumber) { - String np = extractNetworkPortion(phoneNumber); + String np = extractNetworkPortionAlt(phoneNumber); if (np == null) return null; @@ -1017,8 +1060,8 @@ public class PhoneNumberUtils * 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 @@ public class PhoneNumberUtils } /** + * 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 @@ public class PhoneNumberUtils } /** - * 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 @@ public class PhoneNumberUtils // 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 @@ public class PhoneNumberUtils // 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 @@ public class DriverCall implements Comparable { 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 22fbe8b..a5d3b22 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -257,12 +257,6 @@ public class CDMAPhone extends PhoneBase { } 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; } @@ -804,10 +798,6 @@ public class CDMAPhone extends PhoneBase { } 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); } @@ -1317,7 +1307,7 @@ public class CDMAPhone extends PhoneBase { @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) { @@ -1330,9 +1320,7 @@ public class CDMAPhone extends PhoneBase { @Override public int getCdmaEriIconIndex() { - int roamInd = getServiceState().getCdmaRoamingIndicator(); - int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator(); - return mEriManager.getCdmaEriIconIndex(roamInd, defRoamInd); + return getServiceState().getCdmaEriIconIndex(); } /** @@ -1342,9 +1330,7 @@ public class CDMAPhone extends PhoneBase { */ @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 @@ public class CdmaConnection extends Connection { 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 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { } } + 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 @@ public class GSMPhone extends PhoneBase { } // 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 @@ public class GsmConnection extends Connection { 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 @@ include $(CLEAR_VARS) 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.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="android.permission.USE_CREDENTIALS" /> + <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/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/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 @@ public class PropertyNode { } @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 @@ public class PropertyNode { 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..a0d6d16 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java @@ -0,0 +1,265 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import junit.framework.TestCase; + +/** + * Utility class which verifies input VNode. + * + * This class first checks whether each propertyNode in the VNode is in the + * "ordered expected property list". + * If the node does not exist in the "ordered list", the class refers to + * "unorderd expected property set" and checks the node is expected somewhere. + */ +public class PropertyNodesVerifier { + 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 PropertyNodesVerifier(TestCase testCase) { + mOrderedNodeMap = new HashMap<String, List<PropertyNode>>(); + mUnorderedNodeList = new ArrayList<PropertyNode>(); + mTestCase = testCase; + } + + // WithOrder + + public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue) { + return addNodeWithOrder(propName, propValue, null, null, null, null, null); + } + + public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue, + List<String> propValueList) { + return addNodeWithOrder(propName, propValue, propValueList, null, null, null, null); + } + + public PropertyNodesVerifier addNodeWithOrder(String propName, List<String> propValueList) { + StringBuffer buffer = new StringBuffer(); + boolean first = true; + for (String propValueElem : propValueList) { + if (first) { + first = false; + } else { + buffer.append(';'); + } + buffer.append(propValueElem); + } + return addNodeWithOrder(propName, buffer.toString(), propValueList, + null, null, null, null); + } + + public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue, + TypeSet paramMap_TYPE) { + return addNodeWithOrder(propName, propValue, null, null, null, paramMap_TYPE, null); + } + + public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue, + List<String> propValueList, TypeSet paramMap_TYPE) { + return addNodeWithOrder(propName, propValue, propValueList, null, null, + paramMap_TYPE, null); + } + + public PropertyNodesVerifier 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 PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue) { + return addNodeWithoutOrder(propName, propValue, null, null, null, null, null); + } + + public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue, + List<String> propValueList) { + return addNodeWithoutOrder(propName, propValue, propValueList, null, null, null, null); + } + + public PropertyNodesVerifier addNodeWithoutOrder(String propName, List<String> propValueList) { + StringBuffer buffer = new StringBuffer(); + boolean first = true; + for (String propValueElem : propValueList) { + if (first) { + first = false; + } else { + buffer.append(';'); + } + buffer.append(propValueElem); + } + return addNodeWithoutOrder(propName, buffer.toString(), propValueList, + null, null, null, null); + } + + public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue, + TypeSet paramMap_TYPE) { + return addNodeWithoutOrder(propName, propValue, null, null, null, paramMap_TYPE, null); + } + + public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue, + List<String> propValueList, TypeSet paramMap_TYPE) { + return addNodeWithoutOrder(propName, propValue, propValueList, null, null, + paramMap_TYPE, null); + } + + public PropertyNodesVerifier 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 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..cfa9ab3 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java @@ -0,0 +1,1288 @@ +/* + * 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.VCardComposer; +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.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.Im; +import android.provider.ContactsContract.CommonDataKinds.Nickname; +import android.provider.ContactsContract.CommonDataKinds.Note; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.Photo; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; +import android.provider.ContactsContract.CommonDataKinds.Website; +import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; +import android.test.mock.MockCursor; + +import com.android.unit_tests.vcard.PropertyNodesVerifier.TypeSet; + +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.Iterator; +import java.util.List; + +/** + * 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"); + } +} + +/** + * 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 AndroidTestCase { + private static final int V21 = 0; + private static final int V30 = 1; + + private static final byte[] sPhotoByteArray = + VCardImporterTests.sPhotoByteArrayForComplicatedCase; + + public class ExportTestResolver extends MockContentResolver { + ExportTestProvider mProvider = new ExportTestProvider(); + public ExportTestResolver() { + addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider); + addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider); + } + + public ContentValues buildData(String mimeType) { + return mProvider.buildData(mimeType); + } + } + + public static class MockEntityIterator implements EntityIterator { + Collection<Entity> mEntityCollection; + Iterator<Entity> mIterator; + + // TODO: Support multiple vCard entries. + public MockEntityIterator(Collection<ContentValues> contentValuesCollection) { + mEntityCollection = new ArrayList<Entity>(); + Entity entity = new Entity(new ContentValues()); + for (ContentValues contentValues : contentValuesCollection) { + entity.addSubValue(Data.CONTENT_URI, contentValues); + } + mEntityCollection.add(entity); + mIterator = mEntityCollection.iterator(); + } + + public boolean hasNext() { + return mIterator.hasNext(); + } + + public Entity next() { + return mIterator.next(); + } + + public void reset() { + mIterator = mEntityCollection.iterator(); + } + + public void close() { + } + } + + public class ExportTestProvider extends MockContentProvider { + List<ContentValues> mContentValuesList = new ArrayList<ContentValues>(); + public ContentValues buildData(String mimeType) { + ContentValues contentValues = new ContentValues(); + contentValues.put(Data.MIMETYPE, mimeType); + mContentValuesList.add(contentValues); + return contentValues; + } + + @Override + public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs, + String sortOrder) { + assert(uri != null); + assert(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())); + final String authority = uri.getAuthority(); + assert(RawContacts.CONTENT_URI.getAuthority().equals(authority)); + + return new MockEntityIterator(mContentValuesList); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + assert(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri)); + // Support multiple rows. + return new MockCursor() { + int mCurrentPosition = -1; + + @Override + public int getCount() { + return 1; + } + + @Override + public boolean moveToFirst() { + mCurrentPosition = 0; + return true; + } + + @Override + public boolean moveToNext() { + if (mCurrentPosition == 0 || mCurrentPosition == -1) { + mCurrentPosition++; + return true; + } else { + return false; + } + } + + @Override + public boolean isBeforeFirst() { + return mCurrentPosition < 0; + } + + @Override + public boolean isAfterLast() { + return mCurrentPosition > 0; + } + + @Override + public int getColumnIndex(String columnName) { + assertEquals(Contacts._ID, columnName); + return 0; + } + + @Override + public int getInt(int columnIndex) { + assertEquals(0, columnIndex); + return 0; + } + + @Override + public String getString(int columnIndex) { + return String.valueOf(getInt(columnIndex)); + } + + @Override + public void close() { + } + }; + } + } + + public static class VCardVerificationHandler implements VCardComposer.OneEntryHandler { + final private TestCase mTestCase; + final private List<PropertyNodesVerifier> mPropertyNodesVerifierList; + final private boolean mIsV30; + // To allow duplication, use list instead of set. + // TODO: support multiple vCard entries. + final private List<String> mExpectedLineList; + int mCount; + + public VCardVerificationHandler(final TestCase testCase, final int version) { + mTestCase = testCase; + mPropertyNodesVerifierList = new ArrayList<PropertyNodesVerifier>(); + mIsV30 = (version == V30); + mExpectedLineList = new ArrayList<String>(); + mCount = 1; + } + + public PropertyNodesVerifier addNewVerifier() { + PropertyNodesVerifier verifier = new PropertyNodesVerifier(mTestCase); + mPropertyNodesVerifierList.add(verifier); + verifier.addNodeWithOrder("VERSION", mIsV30 ? "3.0" : "2.1"); + return verifier; + } + + public PropertyNodesVerifier addNewVerifierWithEmptyName() { + PropertyNodesVerifier verifier = addNewVerifier(); + if (mIsV30) { + verifier.addNodeWithOrder("N", "").addNodeWithOrder("FN", ""); + } + return verifier; + } + + public VCardVerificationHandler addExpectedLine(String line) { + mExpectedLineList.add(line); + return this; + } + + public boolean onInit(final Context context) { + return true; + } + + public boolean onEntryCreated(final String vcard) { + if (!mExpectedLineList.isEmpty()) { + verifyLines(vcard); + } + verifyNodes(vcard); + return true; + } + + private void verifyLines(final String vcard) { + final String[] lineArray = vcard.split("\\r?\\n"); + final int length = lineArray.length; + for (int i = 0; i < length; i++) { + final String line = lineArray[i]; + // TODO: support multiple vcard entries. + if ("BEGIN:VCARD".equals(line) || "END:VCARD".equals(line) || + (mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equals(line)) { + continue; + } + final int index = mExpectedLineList.indexOf(line); + if (index >= 0) { + mExpectedLineList.remove(index); + } else { + mTestCase.fail("Unexpected line: " + line); + } + } + if (!mExpectedLineList.isEmpty()) { + StringBuffer buffer = new StringBuffer(); + for (String expectedLine : mExpectedLineList) { + buffer.append(expectedLine); + buffer.append("\n"); + } + mTestCase.fail("Expected line(s) not found:" + buffer.toString()); + } + } + + private void verifyNodes(final String vcard) { + if (mPropertyNodesVerifierList.size() == 0) { + mTestCase.fail("Too many vCard entries seems to be inserted(No." + + mCount + " of the entries (No.1 is the first entry))"); + } + PropertyNodesVerifier propertyNodesVerifier = + mPropertyNodesVerifierList.get(0); + mPropertyNodesVerifierList.remove(0); + VCardParser parser = (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21()); + VNodeBuilder builder = new VNodeBuilder(); + InputStream is; + try { + is = new ByteArrayInputStream(vcard.getBytes("UTF-8")); + mTestCase.assertEquals(true, parser.parse(is, null, builder)); + is.close(); + mTestCase.assertEquals(1, builder.vNodeList.size()); + propertyNodesVerifier.verify(builder.vNodeList.get(0)); + } catch (IOException e) { + mTestCase.fail("Unexpected IOException: " + e.getMessage()); + } catch (VCardException e) { + mTestCase.fail("Unexpected VCardException: " + e.getMessage()); + } finally { + mCount++; + } + } + + public void onTerminate() { + } + } + + private class CustomMockContext extends MockContext { + final ContentResolver mResolver; + public CustomMockContext(ContentResolver resolver) { + mResolver = resolver; + } + + @Override + public ContentResolver getContentResolver() { + return mResolver; + } + } + + //// Followings are actual tests //// + + private void verifyOneComposition(ExportTestResolver resolver, + VCardVerificationHandler handler, int version) { + final boolean isV30 = (version == V30); + + int vcardType = (isV30 ? VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8 + : VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + VCardComposer composer = new VCardComposer(new CustomMockContext(resolver), vcardType); + composer.addHandler(handler); + if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) { + fail("init() failed. Reason: " + composer.getErrorReason()); + } + assertFalse(composer.isAfterLast()); + assertTrue(composer.createOneEntry()); + assertTrue(composer.isAfterLast()); + composer.terminate(); + } + + public void testSimpleV21() { + ExportTestResolver resolver = new ExportTestResolver(); + ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "Ando"); + contentValues.put(StructuredName.GIVEN_NAME, "Roid"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, V21); + handler.addNewVerifier() + .addNodeWithoutOrder("FN", "Roid Ando") + .addNodeWithoutOrder("N", "Ando;Roid;;;", Arrays.asList("Ando", "Roid", "", "", "")); + + verifyOneComposition(resolver, handler, V21); + } + + private void testStructuredNameBasic(int version) { + final boolean isV30 = (version == V30); + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName"); + contentValues.put(StructuredName.GIVEN_NAME, "AppropriateGivenName"); + contentValues.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName"); + contentValues.put(StructuredName.PREFIX, "AppropriatePrefix"); + contentValues.put(StructuredName.SUFFIX, "AppropriateSuffix"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + PropertyNodesVerifier verifier = handler.addNewVerifier() + .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) { + verifier.addNodeWithoutOrder("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle " + + "AppropriatePhoneticFamily"); + } + + verifyOneComposition(resolver, handler, version); + } + + 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(); + + ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1"); + contentValues.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1"); + contentValues.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1"); + contentValues.put(StructuredName.PREFIX, "DoNotEmitPrefix1"); + contentValues.put(StructuredName.SUFFIX, "DoNotEmitSuffix1"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1"); + + // With "IS_PRIMARY=1". This is what we should use. + contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName"); + contentValues.put(StructuredName.GIVEN_NAME, "AppropriateGivenName"); + contentValues.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName"); + contentValues.put(StructuredName.PREFIX, "AppropriatePrefix"); + contentValues.put(StructuredName.SUFFIX, "AppropriateSuffix"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle"); + contentValues.put(StructuredName.IS_PRIMARY, 1); + + // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first. + contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2"); + contentValues.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2"); + contentValues.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2"); + contentValues.put(StructuredName.PREFIX, "DoNotEmitPrefix2"); + contentValues.put(StructuredName.SUFFIX, "DoNotEmitSuffix2"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2"); + contentValues.put(StructuredName.IS_PRIMARY, 1); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + PropertyNodesVerifier verifier = handler.addNewVerifier() + .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) { + verifier.addNodeWithoutOrder("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle " + + "AppropriatePhoneticFamily"); + } + + verifyOneComposition(resolver, handler, version); + } + + 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(); + + ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1"); + contentValues.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1"); + contentValues.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1"); + contentValues.put(StructuredName.PREFIX, "DoNotEmitPrefix1"); + contentValues.put(StructuredName.SUFFIX, "DoNotEmitSuffix1"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1"); + + // With "IS_PRIMARY=1", but we should ignore this time. + contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2"); + contentValues.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2"); + contentValues.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2"); + contentValues.put(StructuredName.PREFIX, "DoNotEmitPrefix2"); + contentValues.put(StructuredName.SUFFIX, "DoNotEmitSuffix2"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2"); + contentValues.put(StructuredName.IS_PRIMARY, 1); + + // With "IS_SUPER_PRIMARY=1". This is what we should use. + contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName"); + contentValues.put(StructuredName.GIVEN_NAME, "AppropriateGivenName"); + contentValues.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName"); + contentValues.put(StructuredName.PREFIX, "AppropriatePrefix"); + contentValues.put(StructuredName.SUFFIX, "AppropriateSuffix"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle"); + contentValues.put(StructuredName.IS_SUPER_PRIMARY, 1); + + contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3"); + contentValues.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3"); + contentValues.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3"); + contentValues.put(StructuredName.PREFIX, "DoNotEmitPrefix3"); + contentValues.put(StructuredName.SUFFIX, "DoNotEmitSuffix3"); + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily3"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven3"); + contentValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3"); + contentValues.put(StructuredName.IS_PRIMARY, 1); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + PropertyNodesVerifier verifier = handler.addNewVerifier() + .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) { + verifier.addNodeWithoutOrder("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle" + + " AppropriatePhoneticFamily"); + } + + verifyOneComposition(resolver, handler, version); + } + + public void testStructuredNameUseSuperPrimaryV21() { + testStructuredNameUseSuperPrimaryCommon(V21); + } + + public void testStructuredNameUseSuperPrimaryV30() { + testStructuredNameUseSuperPrimaryCommon(V30); + } + + public void testNickNameV30() { + ExportTestResolver resolver = new ExportTestResolver(); + ContentValues contentValues = resolver.buildData(Nickname.CONTENT_ITEM_TYPE); + contentValues.put(Nickname.NAME, "Nicky"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, V30); + handler.addNewVerifierWithEmptyName() + .addNodeWithOrder("NICKNAME", "Nicky"); + + verifyOneComposition(resolver, handler, V30); + } + + private void testPhoneBasicCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "1"); + contentValues.put(Phone.TYPE, Phone.TYPE_HOME); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("TEL", "1", new TypeSet("HOME", "VOICE")); + + verifyOneComposition(resolver, handler, version); + } + + 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(); + + ContentValues contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "10"); + contentValues.put(Phone.TYPE, Phone.TYPE_HOME); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "20"); + contentValues.put(Phone.TYPE, Phone.TYPE_WORK); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "30"); + contentValues.put(Phone.TYPE, Phone.TYPE_FAX_HOME); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "40"); + contentValues.put(Phone.TYPE, Phone.TYPE_FAX_WORK); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "50"); + contentValues.put(Phone.TYPE, Phone.TYPE_MOBILE); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "60"); + contentValues.put(Phone.TYPE, Phone.TYPE_PAGER); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "70"); + contentValues.put(Phone.TYPE, Phone.TYPE_OTHER); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "80"); + contentValues.put(Phone.TYPE, Phone.TYPE_CAR); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "90"); + contentValues.put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "100"); + contentValues.put(Phone.TYPE, Phone.TYPE_ISDN); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "110"); + contentValues.put(Phone.TYPE, Phone.TYPE_MAIN); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "120"); + contentValues.put(Phone.TYPE, Phone.TYPE_OTHER_FAX); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "130"); + contentValues.put(Phone.TYPE, Phone.TYPE_TELEX); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "140"); + contentValues.put(Phone.TYPE, Phone.TYPE_WORK_MOBILE); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "150"); + contentValues.put(Phone.TYPE, Phone.TYPE_WORK_PAGER); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "160"); + contentValues.put(Phone.TYPE, Phone.TYPE_MMS); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .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", "MOBILE")) + .addNodeWithoutOrder("TEL", "150", new TypeSet("WORK", "PAGER")) + .addNodeWithoutOrder("TEL", "160", new TypeSet("MSG")); + } + + public void testPhoneVariousTypeSupportV21() { + testPhoneVariousTypeSupport(V21); + } + + public void testPhoneVariousTypeSupportV30() { + testPhoneVariousTypeSupport(V30); + } + + /** + * Tests that "PREF"s are emitted appropriately. + */ + private void testPhonePrefHandlingCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "1"); + contentValues.put(Phone.TYPE, Phone.TYPE_HOME); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "2"); + contentValues.put(Phone.TYPE, Phone.TYPE_WORK); + contentValues.put(Phone.IS_PRIMARY, 1); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "3"); + contentValues.put(Phone.TYPE, Phone.TYPE_FAX_HOME); + contentValues.put(Phone.IS_PRIMARY, 1); + + contentValues = resolver.buildData(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "4"); + contentValues.put(Phone.TYPE, Phone.TYPE_FAX_WORK); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("TEL", "4", new TypeSet("WORK", "FAX")) + .addNodeWithoutOrder("TEL", "3", new TypeSet("HOME", "FAX", "PREF")) + .addNodeWithoutOrder("TEL", "2", new TypeSet("WORK", "VOICE", "PREF")) + .addNodeWithoutOrder("TEL", "1", new TypeSet("HOME", "VOICE")); + + verifyOneComposition(resolver, handler, version); + } + + public void testPhonePrefHandlingV21() { + testPhonePrefHandlingCommon(V21); + } + + public void testPhonePrefHandlingV30() { + testPhonePrefHandlingCommon(V30); + } + + private void testEmailBasicCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + ContentValues contentValues = resolver.buildData(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "sample@example.com"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("EMAIL", "sample@example.com"); + + verifyOneComposition(resolver, handler, version); + } + + public void testEmailBasicV21() { + testEmailBasicCommon(V21); + } + + public void testEmailBasicV30() { + testEmailBasicCommon(V30); + } + + private void testEmailVariousTypeSupportCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "type_home@example.com"); + contentValues.put(Email.TYPE, Email.TYPE_HOME); + + contentValues = resolver.buildData(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "type_work@example.com"); + contentValues.put(Email.TYPE, Email.TYPE_WORK); + + contentValues = resolver.buildData(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "type_mobile@example.com"); + contentValues.put(Email.TYPE, Email.TYPE_MOBILE); + + contentValues = resolver.buildData(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "type_other@example.com"); + contentValues.put(Email.TYPE, Email.TYPE_OTHER); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + + handler.addNewVerifierWithEmptyName() + .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"); + + verifyOneComposition(resolver, handler, version); + } + + public void testEmailVariousTypeSupportV21() { + testEmailVariousTypeSupportCommon(V21); + } + + public void testEmailVariousTypeSupportV30() { + testEmailVariousTypeSupportCommon(V30); + } + + private void testEmailPrefHandlingCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "type_home@example.com"); + contentValues.put(Email.TYPE, Email.TYPE_HOME); + contentValues.put(Email.IS_PRIMARY, 1); + + contentValues = resolver.buildData(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "type_notype@example.com"); + contentValues.put(Email.IS_PRIMARY, 1); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("EMAIL", "type_notype@example.com", new TypeSet("PREF")) + .addNodeWithoutOrder("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF")); + + verifyOneComposition(resolver, handler, version); + } + + 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 + ContentValues contentValues = resolver.buildData(StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(StructuredPostal.POBOX, "Pobox"); + contentValues.put(StructuredPostal.NEIGHBORHOOD, "Neighborhood"); + contentValues.put(StructuredPostal.STREET, "Street"); + contentValues.put(StructuredPostal.CITY, "City"); + contentValues.put(StructuredPostal.REGION, "Region"); + contentValues.put(StructuredPostal.POSTCODE, "100"); + contentValues.put(StructuredPostal.COUNTRY, "Country"); + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("ADR", "Pobox;Neighborhood;Street;City;Region;100;Country", + Arrays.asList("Pobox", "Neighborhood", "Street", "City", + "Region", "100", "Country"), new TypeSet("HOME")); + + verifyOneComposition(resolver, handler, version); + } + + public void testPostalOnlyWithStructuredDataV21() { + testPostalOnlyWithStructuredDataCommon(V21); + } + + public void testPostalOnlyWithStructuredDataV30() { + testPostalOnlyWithStructuredDataCommon(V30); + } + + private void testPostalOnlyWithFormattedAddressCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(StructuredPostal.FORMATTED_ADDRESS, + "Formatted address CA 123-334 United Statue"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;", + Arrays.asList("", "Formatted address CA 123-334 United Statue", + "", "", "", "", ""), new TypeSet("HOME")); + + verifyOneComposition(resolver, handler, version); + } + + 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(); + + ContentValues contentValues = resolver.buildData(StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(StructuredPostal.POBOX, "Pobox"); + contentValues.put(StructuredPostal.COUNTRY, "Country"); + contentValues.put(StructuredPostal.FORMATTED_ADDRESS, + "Formatted address CA 123-334 United Statue"); // Should be ignored + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("ADR", "Pobox;;;;;;Country", + Arrays.asList("Pobox", "", "", "", "", "", "Country"), new TypeSet("HOME")); + + verifyOneComposition(resolver, handler, version); + } + + public void testPostalWithBothStructuredAndFormattedV21() { + testPostalWithBothStructuredAndFormattedCommon(V21); + } + + public void testPostalWithBothStructuredAndFormattedV30() { + testPostalWithBothStructuredAndFormattedCommon(V30); + } + + private void testOrganizationCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + ContentValues contentValues = resolver.buildData(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "CompanyX"); + contentValues.put(Organization.DEPARTMENT, "DepartmentY"); + contentValues.put(Organization.TITLE, "TitleZ"); + contentValues.put(Organization.JOB_DESCRIPTION, "Description Rambda"); // Ignored. + contentValues.put(Organization.OFFICE_LOCATION, "Mountain View"); // Ignored. + contentValues.put(Organization.PHONETIC_NAME, "PhoneticName!"); // Ignored + contentValues.put(Organization.SYMBOL, "(^o^)/~~"); // Ignore him (her). + + contentValues = resolver.buildData(Organization.CONTENT_ITEM_TYPE); + contentValues.putNull(Organization.COMPANY); + contentValues.put(Organization.DEPARTMENT, "DepartmentXX"); + contentValues.putNull(Organization.TITLE); + + contentValues = resolver.buildData(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "CompanyXYZ"); + contentValues.putNull(Organization.DEPARTMENT); + contentValues.put(Organization.TITLE, "TitleXYZYX"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + + // Currently we do not use group but depend on the order. + handler.addNewVerifierWithEmptyName() + .addNodeWithOrder("ORG", "CompanyX;DepartmentY", + Arrays.asList("CompanyX", "DepartmentY")) + .addNodeWithOrder("TITLE", "TitleZ") + .addNodeWithOrder("ORG", "DepartmentXX") + .addNodeWithOrder("ORG", "CompanyXYZ") + .addNodeWithOrder("TITLE", "TitleXYZYX"); + + verifyOneComposition(resolver, handler, version); + } + + public void testOrganizationV21() { + testOrganizationCommon(V21); + } + + public void testOrganizationV30() { + testOrganizationCommon(V30); + } + + private void testImVariousTypeSupportCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_AIM); + contentValues.put(Im.DATA, "aim"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_MSN); + contentValues.put(Im.DATA, "msn"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_YAHOO); + contentValues.put(Im.DATA, "yahoo"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_SKYPE); + contentValues.put(Im.DATA, "skype"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_QQ); + contentValues.put(Im.DATA, "qq"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK); + contentValues.put(Im.DATA, "google talk"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_ICQ); + contentValues.put(Im.DATA, "icq"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_JABBER); + contentValues.put(Im.DATA, "jabber"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING); + contentValues.put(Im.DATA, "netmeeting"); + + // No determined way to express unknown type... + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .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"); + + verifyOneComposition(resolver, handler, version); + } + + public void testImBasiV21() { + testImVariousTypeSupportCommon(V21); + } + + public void testImBasicV30() { + testImVariousTypeSupportCommon(V30); + } + + private void testImPrefHandlingCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_AIM); + contentValues.put(Im.DATA, "aim1"); + + contentValues = resolver.buildData(Im.CONTENT_ITEM_TYPE); + contentValues.put(Im.PROTOCOL, Im.PROTOCOL_AIM); + contentValues.put(Im.DATA, "aim2"); + contentValues.put(Im.TYPE, Im.TYPE_HOME); + contentValues.put(Im.IS_PRIMARY, 1); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("X-AIM", "aim1") + .addNodeWithoutOrder("X-AIM", "aim2", new TypeSet("HOME", "PREF")); + + verifyOneComposition(resolver, handler, version); + } + + public void testImPrefHandlingV21() { + testImPrefHandlingCommon(V21); + } + + public void testImPrefHandlingV30() { + testImPrefHandlingCommon(V30); + } + + private void testWebsiteCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Website.CONTENT_ITEM_TYPE); + contentValues.put(Website.URL, "http://website.example.android.com/index.html"); + contentValues.put(Website.TYPE, Website.TYPE_BLOG); + + contentValues = resolver.buildData(Website.CONTENT_ITEM_TYPE); + contentValues.put(Website.URL, "ftp://ftp.example.android.com/index.html"); + contentValues.put(Website.TYPE, Website.TYPE_FTP); + + // We drop TYPE information since vCard (especially 3.0) does not allow us to emit it. + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("URL", "ftp://ftp.example.android.com/index.html") + .addNodeWithoutOrder("URL", "http://website.example.android.com/index.html"); + + verifyOneComposition(resolver, handler, version); + } + + public void testWebsiteV21() { + testWebsiteCommon(V21); + } + + public void testWebsiteV30() { + testWebsiteCommon(V30); + } + + private void testEventCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Event.CONTENT_ITEM_TYPE); + contentValues.put(Event.TYPE, Event.TYPE_ANNIVERSARY); + contentValues.put(Event.START_DATE, "1982-06-16"); + + contentValues = resolver.buildData(Event.CONTENT_ITEM_TYPE); + contentValues.put(Event.TYPE, Event.TYPE_BIRTHDAY); + contentValues.put(Event.START_DATE, "2008-10-22"); + + contentValues = resolver.buildData(Event.CONTENT_ITEM_TYPE); + contentValues.put(Event.TYPE, Event.TYPE_OTHER); + contentValues.put(Event.START_DATE, "2018-03-12"); + + contentValues = resolver.buildData(Event.CONTENT_ITEM_TYPE); + contentValues.put(Event.TYPE, Event.TYPE_CUSTOM); + contentValues.put(Event.LABEL, "The last day"); + contentValues.put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed."); + + contentValues = resolver.buildData(Event.CONTENT_ITEM_TYPE); + contentValues.put(Event.TYPE, Event.TYPE_BIRTHDAY); + contentValues.put(Event.START_DATE, "2009-05-19"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithoutOrder("BDAY", "2008-10-22"); + + verifyOneComposition(resolver, handler, version); + } + + public void testEventV21() { + testEventCommon(V21); + } + + public void testEventV30() { + testEventCommon(V30); + } + + private void testNoteCommon(int version) { + ExportTestResolver resolver = new ExportTestResolver(); + + ContentValues contentValues = resolver.buildData(Note.CONTENT_ITEM_TYPE); + contentValues.put(Note.NOTE, "note1"); + + contentValues = resolver.buildData(Note.CONTENT_ITEM_TYPE); + contentValues.put(Note.NOTE, "note2"); + contentValues.put(Note.IS_PRIMARY, 1); // Just ignored. + + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifierWithEmptyName() + .addNodeWithOrder("NOTE", "note1") + .addNodeWithOrder("NOTE", "note2"); + + verifyOneComposition(resolver, handler, version); + } + + public void testNoteV21() { + testNoteCommon(V21); + } + + public void testNoteV30() { + testNoteCommon(V30); + } + + private void testPhotoCommon(int version) { + final boolean isV30 = version == V30; + ExportTestResolver resolver = new ExportTestResolver(); + ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "PhotoTest"); + + contentValues = resolver.buildData(Photo.CONTENT_ITEM_TYPE); + contentValues.put(Photo.PHOTO, sPhotoByteArray); + + ContentValues contentValuesForPhoto = new ContentValues(); + contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64")); + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + handler.addNewVerifier() + .addNodeWithoutOrder("FN", "PhotoTest") + .addNodeWithoutOrder("N", "PhotoTest;;;;", Arrays.asList("PhotoTest", "", "", "", "")) + .addNodeWithOrder("PHOTO", null, null, sPhotoByteArray, + contentValuesForPhoto, new TypeSet("JPEG"), null); + + verifyOneComposition(resolver, handler, version); + } + + public void testPhotoV21() { + testPhotoCommon(V21); + } + + public void testPhotoV30() { + testPhotoCommon(V30); + } + + public void testV30HandleEscape() { + final int version = V30; + ExportTestResolver resolver = new ExportTestResolver(); + ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "\\"); + contentValues.put(StructuredName.GIVEN_NAME, ";"); + contentValues.put(StructuredName.MIDDLE_NAME, ","); + contentValues.put(StructuredName.PREFIX, "\n"); + contentValues.put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]"); + VCardVerificationHandler handler = new VCardVerificationHandler(this, version); + // Verifies the vCard String correctly escapes each character which must be escaped. + handler.addExpectedLine("N:\\\\;\\;;\\,;\\n;") + .addExpectedLine("FN:[<{Unescaped:Asciis}>]"); + handler.addNewVerifier() + .addNodeWithoutOrder("FN", "[<{Unescaped:Asciis}>]") + .addNodeWithoutOrder("N", Arrays.asList("\\", ";", ",", "\n", "")); + + verifyOneComposition(resolver, handler, version); + } + + /** + * 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(); + ContentValues contentValues = resolver.buildData(Nickname.CONTENT_ITEM_TYPE); + contentValues.put(Nickname.NAME, "Nicky"); + + VCardVerificationHandler handler = new VCardVerificationHandler(this, V21); + handler.addNewVerifierWithEmptyName() + .addNodeWithOrder("X-ANDROID-CUSTOM", Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;"); + + // TODO: also test import part. + + verifyOneComposition(resolver, handler, V21); + } +} 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..90552e5 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java @@ -0,0 +1,1001 @@ +/* + * 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_V21; +import android.pim.vcard.VCardParser_V30; +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.PropertyNodesVerifier.TypeSet; + +import java.io.IOException; +import java.io.InputStream; +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 { + VCardParser_V21 parser = new VCardParser_V21(); + VNodeBuilder builder = new VNodeBuilder(); + InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1); + assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); + is.close(); + assertEquals(1, builder.vNodeList.size()); + PropertyNodesVerifier verifier = new PropertyNodesVerifier(this) + .addNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", "")); + verifier.verify(builder.vNodeList.get(0)); + } + + public void testV21SimpleCase1_Type_Generic() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "Ando"); + contentValues.put(StructuredName.GIVEN_NAME, "Roid"); + contentValues.put(StructuredName.DISPLAY_NAME, "Roid Ando"); + verifier.verify(); + } + + public void testV21SimpleCase1_Type_Japanese() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "Ando"); + contentValues.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" + contentValues.put(StructuredName.DISPLAY_NAME, "Roid Ando"); + verifier.verify(); + } + + public void testV21SimpleCase2() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_simple_2, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.DISPLAY_NAME, "Ando Roid"); + verifier.verify(); + } + + public void testV21SimpleCase3() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_simple_3, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "Ando"); + contentValues.put(StructuredName.GIVEN_NAME, "Roid"); + // "FN" field should be prefered since it should contain the original order intended by + // the author of the file. + contentValues.put(StructuredName.DISPLAY_NAME, "Ando Roid"); + verifier.verify(); + } + + /** + * Tests ';' is properly handled by VCardParser implementation. + */ + public void testV21BackslashCase_Parsing() 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(this) + .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(builder.vNodeList.get(0)); + } + + /** + * Tests ContactStruct correctly ignores redundant fields in "N" property values and + * inserts name related data. + */ + public void testV21BackslashCase() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_backslash, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + // FAMILY_NAME is empty and removed in this test... + contentValues.put(StructuredName.GIVEN_NAME, "A;B\\"); + contentValues.put(StructuredName.MIDDLE_NAME, "C\\;"); + contentValues.put(StructuredName.PREFIX, "D"); + contentValues.put(StructuredName.SUFFIX, ":E"); + contentValues.put(StructuredName.DISPLAY_NAME, "A;B\\C\\;D:E\\\\"); + verifier.verify(); + } + + public void testOrgBeforTitle() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_org_before_title, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.DISPLAY_NAME, "Normal Guy"); + + contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "Company"); + contentValues.put(Organization.DEPARTMENT, "Organization Devision Room Sheet No."); + contentValues.put(Organization.TITLE, "Excellent Janitor"); + contentValues.put(Organization.TYPE, Organization.TYPE_WORK); + verifier.verify(); + } + + public void testTitleBeforOrg() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_title_before_org, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.DISPLAY_NAME, "Nice Guy"); + + contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "Marverous"); + contentValues.put(Organization.DEPARTMENT, "Perfect Great Good Bad Poor"); + contentValues.put(Organization.TITLE, "Cool Title"); + contentValues.put(Organization.TYPE, Organization.TYPE_WORK); + verifier.verify(); + } + + /** + * 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 { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_pref_handling, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.DISPLAY_NAME, "Smith"); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "1"); + contentValues.put(Phone.TYPE, Phone.TYPE_HOME); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "2"); + contentValues.put(Phone.TYPE, Phone.TYPE_WORK); + contentValues.put(Phone.IS_PRIMARY, 1); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.NUMBER, "3"); + contentValues.put(Phone.TYPE, Phone.TYPE_ISDN); + + contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "test@example.com"); + contentValues.put(Email.TYPE, Email.TYPE_HOME); + contentValues.put(Email.IS_PRIMARY, 1); + + contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.DATA, "test2@examination.com"); + contentValues.put(Email.TYPE, Email.TYPE_MOBILE); + contentValues.put(Email.IS_PRIMARY, 1); + + contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "Company"); + contentValues.put(Organization.TITLE, "Engineer"); + contentValues.put(Organization.TYPE, Organization.TYPE_WORK); + + contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "Mystery"); + contentValues.put(Organization.TITLE, "Blogger"); + contentValues.put(Organization.TYPE, Organization.TYPE_WORK); + + contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "Poetry"); + contentValues.put(Organization.TITLE, "Poet"); + contentValues.put(Organization.TYPE, Organization.TYPE_WORK); + verifier.verify(); + } + + /** + * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser. + */ + public void testV21ComplicatedCase_Parsing() 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"); + PropertyNodesVerifier verifier = new PropertyNodesVerifier(this) + .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, contentValuesForQP, 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, contentValuesForQP, + 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, contentValuesForQP, null, null) + .addNodeWithOrder("PHOTO", null, + null, sPhotoByteArrayForComplicatedCase, contentValuesForPhoto, + 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(builder.vNodeList.get(0)); + } + + /** + * Checks ContactStruct correctly inserts values in a complicated vCard + * into ContentResolver. + */ + public void testV21ComplicatedCase() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_complicated, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "Gump"); + contentValues.put(StructuredName.GIVEN_NAME, "Forrest"); + contentValues.put(StructuredName.MIDDLE_NAME, "Hoge"); + contentValues.put(StructuredName.PREFIX, "Pos"); + contentValues.put(StructuredName.SUFFIX, "Tao"); + contentValues.put(StructuredName.DISPLAY_NAME, "Joe Due"); + + contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.TYPE, Organization.TYPE_WORK); + contentValues.put(Organization.COMPANY, "Gump Shrimp Co."); + contentValues.put(Organization.DEPARTMENT, "Sales Dept.;Manager Fish keeper"); + contentValues.put(Organization.TITLE, "Shrimp Man"); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.TYPE, Phone.TYPE_WORK); + // Phone number is expected to be formated with NAMP format in default. + contentValues.put(Phone.NUMBER, "111-555-1212"); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.TYPE, Phone.TYPE_HOME); + contentValues.put(Phone.NUMBER, "404-555-1212"); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.TYPE, Phone.TYPE_MOBILE); + contentValues.put(Phone.NUMBER, "031-111-1111"); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM); + contentValues.put(Phone.LABEL, "VIDEO"); + contentValues.put(Phone.NUMBER, "032-222-2222"); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM); + contentValues.put(Phone.LABEL, "VOICE"); + contentValues.put(Phone.NUMBER, "033-333-3333"); + + contentValues = verifier.createExpected(StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK); + contentValues.put(StructuredPostal.COUNTRY, "United States of America"); + contentValues.put(StructuredPostal.POSTCODE, "30314"); + contentValues.put(StructuredPostal.REGION, "LA"); + contentValues.put(StructuredPostal.CITY, "Baytown"); + contentValues.put(StructuredPostal.STREET, "100 Waters Edge"); + contentValues.put(StructuredPostal.FORMATTED_ADDRESS, + "100 Waters Edge Baytown LA 30314 United States of America"); + + contentValues = verifier.createExpected(StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME); + contentValues.put(StructuredPostal.COUNTRY, "United States of America"); + contentValues.put(StructuredPostal.POSTCODE, "30314"); + contentValues.put(StructuredPostal.REGION, "LA"); + contentValues.put(StructuredPostal.CITY, "Baytown"); + contentValues.put(StructuredPostal.STREET, "42 Plantation St."); + contentValues.put(StructuredPostal.FORMATTED_ADDRESS, + "42 Plantation St. Baytown LA 30314 United States of America"); + + contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE); + // "TYPE=INTERNET" -> TYPE_CUSTOM + the label "INTERNET" + contentValues.put(Email.TYPE, Email.TYPE_CUSTOM); + contentValues.put(Email.LABEL, "INTERNET"); + contentValues.put(Email.DATA, "forrestgump@walladalla.com"); + contentValues.put(Email.IS_PRIMARY, 1); + + contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE); + contentValues.put(Email.TYPE, Email.TYPE_MOBILE); + contentValues.put(Email.DATA, "cell@example.com"); + + contentValues = verifier.createExpected(Note.CONTENT_ITEM_TYPE); + contentValues.put(Note.NOTE, "The following note is the example from RFC 2045."); + + contentValues = verifier.createExpected(Note.CONTENT_ITEM_TYPE); + contentValues.put(Note.NOTE, + "Now's the time for all folk to come to the aid of their country."); + + contentValues = verifier.createExpected(Photo.CONTENT_ITEM_TYPE); + // No information about its image format can be inserted. + contentValues.put(Photo.PHOTO, sPhotoByteArrayForComplicatedCase); + + contentValues = verifier.createExpected(Event.CONTENT_ITEM_TYPE); + contentValues.put(Event.START_DATE, "19800101"); + contentValues.put(Event.TYPE, Event.TYPE_BIRTHDAY); + + contentValues = verifier.createExpected(Website.CONTENT_ITEM_TYPE); + contentValues.put(Website.URL, "http://www.example.com/"); + contentValues.put(Website.TYPE, Website.TYPE_HOMEPAGE); + verifier.verify(); + } + + public void testV30Simple_Parsing() 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(this) + .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(builder.vNodeList.get(0)); + } + + public void testV30Simple() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v30_simple, VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "And"); + contentValues.put(StructuredName.GIVEN_NAME, "Roid"); + contentValues.put(StructuredName.DISPLAY_NAME, "And Roid"); + + contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE); + contentValues.put(Organization.COMPANY, "Open"); + contentValues.put(Organization.DEPARTMENT, "Handset Alliance"); + contentValues.put(Organization.TYPE, Organization.TYPE_WORK); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM); + contentValues.put(Phone.LABEL, "VOICE"); + contentValues.put(Phone.NUMBER, "030-000-0000"); + contentValues.put(Phone.IS_PRIMARY, 1); + verifier.verify(); + } + + public void testV21Japanese1_Parsing() 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(this) + .addNodeWithOrder("VERSION", "2.1", null, null, null, null, null) + .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""), + null, contentValuesForShiftJis, null, null) + .addNodeWithOrder("SOUND", "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;", + null, null, contentValuesForShiftJis, + new TypeSet("X-IRMC-N"), null) + .addNodeWithOrder("TEL", "0300000000", null, null, null, + new TypeSet("VOICE", "PREF"), null); + verifier.verify(builder.vNodeList.get(0)); + } + + private void testV21Japanese1Common(ContactStructVerifier verifier, boolean japanese) + throws IOException, VCardException { + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9"); + contentValues.put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9"); + // While vCard parser does not split "SOUND" property values, ContactStruct care it. + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E"); + + contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE); + // Phone number formatting is different. + if (japanese) { + contentValues.put(Phone.NUMBER, "03-0000-0000"); + } else { + contentValues.put(Phone.NUMBER, "030-000-0000"); + } + contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM); + contentValues.put(Phone.LABEL, "VOICE"); + contentValues.put(Phone.IS_PRIMARY, 1); + verifier.verify(); + } + /** + * 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 { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + testV21Japanese1Common(verifier, 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 { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); + testV21Japanese1Common(verifier, 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 { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); + testV21Japanese1Common(verifier, true); + } + + public void testV21Japanese2_Parsing() 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(this) + .addNodeWithOrder("VERSION", "2.1") + .addNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;", + Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031", + "", "", ""), + null, contentValuesForShiftJis, null, null) + .addNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031", + null, null, contentValuesForShiftJis, null, null) + .addNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;", + null, null, contentValuesForShiftJis, + 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, contentValuesForQP, new TypeSet("HOME"), null) + .addNodeWithOrder("NOTE", "\u30E1\u30E2", null, null, contentValuesForQP, null, null); + verifier.verify(builder.vNodeList.get(0)); + } + + public void testV21Japanese2_Type_Generic_Utf8() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_japanese_2, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + ContentValues contentValues = + verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE); + contentValues.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4"); + contentValues.put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9\u0031"); + contentValues.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. + contentValues.put(StructuredName.PHONETIC_FAMILY_NAME, + "\uFF71\uFF9D\uFF84\uFF9E\uFF73"); + contentValues.put(StructuredName.PHONETIC_GIVEN_NAME, + "\uFF9B\uFF72\uFF84\uFF9E\u0031"); + + contentValues = verifier.createExpected(StructuredPostal.CONTENT_ITEM_TYPE); + contentValues.put(StructuredPostal.POSTCODE, "150-8512"); + contentValues.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"); + contentValues.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"); + contentValues.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME); + contentValues = verifier.createExpected(Note.CONTENT_ITEM_TYPE); + contentValues.put(Note.NOTE, "\u30E1\u30E2"); + verifier.verify(); + } + + // Following tests are old ones, though they still work fine. + + 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(this) + .addNodeWithOrder("VERSION", "2.1") + .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""), + null, contentValuesForShiftJis, null, null) + .addNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;", + null, null, contentValuesForShiftJis, + 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.verify(builder.vNodeList.get(0)); + + verifier = new PropertyNodesVerifier(this) + .addNodeWithOrder("VERSION", "2.1") + .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""), + null, contentValuesForShiftJis, null, null) + .addNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;", + null, null, contentValuesForShiftJis, + 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.verify(builder.vNodeList.get(1)); + verifier = new PropertyNodesVerifier(this) + .addNodeWithOrder("VERSION", "2.1") + .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;", + Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""), + null, contentValuesForShiftJis, null, null) + .addNodeWithOrder("SOUND", + "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;", + null, null, contentValuesForShiftJis, + 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(builder.vNodeList.get(2)); + } +} 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..e71149b --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java @@ -0,0 +1,341 @@ +/* + * 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.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.net.Uri; +import android.pim.vcard.EntryCommitter; +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.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.MockContentProvider; +import android.test.mock.MockContentResolver; +import android.text.TextUtils; + +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.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.Map.Entry; + +/** + * BaseClass for vCard unit tests with utility classes. + * Please do not add each unit test here. + */ +/* package */ class VCardTestsBase extends AndroidTestCase { + + public class ImportVerificationResolver extends MockContentResolver { + ImportVerificationProvider mVerificationProvider = new ImportVerificationProvider(); + @Override + public ContentProviderResult[] applyBatch(String authority, + ArrayList<ContentProviderOperation> operations) { + equalsString(authority, RawContacts.CONTENT_URI.toString()); + return mVerificationProvider.applyBatch(operations); + } + + public void addExpectedContentValues(ContentValues expectedContentValues) { + mVerificationProvider.addExpectedContentValues(expectedContentValues); + } + + public void verify() { + mVerificationProvider.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 ImportVerificationProvider extends MockContentProvider { + final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues; + + public ImportVerificationProvider() { + 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 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 == null) { + 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); + } + } + } + + public class ContactStructVerifier { + private final int mResourceId; + private final int mVCardType; + private final ImportVerificationResolver mResolver; + // private final String mCharset; + public ContactStructVerifier(int resId, int vCardType) { + mResourceId = resId; + mVCardType = vCardType; + mResolver = new ImportVerificationResolver(); + } + + public ContentValues createExpected(String mimeType) { + ContentValues contentValues = new ContentValues(); + contentValues.put(Data.MIMETYPE, mimeType); + mResolver.addExpectedContentValues(contentValues); + return contentValues; + } + + public void verify() throws IOException, VCardException { + InputStream is = getContext().getResources().openRawResource(mResourceId); + final VCardParser vCardParser; + if (VCardConfig.isV30(mVCardType)) { + vCardParser = new VCardParser_V30(true); // use StrictParsing + } else { + vCardParser = new VCardParser_V21(); + } + VCardDataBuilder builder = + new VCardDataBuilder(null, null, false, mVCardType, null); + builder.addEntryHandler(new EntryCommitter(mResolver)); + try { + vCardParser.parse(is, builder); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + } + } + } + mResolver.verify(); + } + } + + /** + * 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 String value = entry.getValue().toString(); + if (Data.MIMETYPE.equals(key)) { + mimeTypeValue = value; + } else { + assertNotNull(key); + sortedMap.put(key, (value != null ? value.toString() : "")); + } + } + 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 @@ package com.android.unit_tests.vcard; 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 @@ import java.util.List; * 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 @@ public class VNodeBuilder implements VCardBuilder { 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 @@ public class MathTest extends TestCase { /** * @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 @@ public class StrictMathTest extends TestCase { /** * @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 @@ import junit.framework.TestCase; 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 @@ public class PhoneNumberUtilsTest extends TestCase { } @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 @@ public class PhoneNumberUtilsTest extends TestCase { // 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 @@ interface IWifiManager 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 @@ public class WifiManager { 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 @@ public class WifiMonitor { 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 @@ public class WifiStateTracker extends NetworkStateTracker { /** * 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 @@ public class WifiStateTracker extends NetworkStateTracker { 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 @@ public class WifiStateTracker extends NetworkStateTracker { */ 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 @@ public class WifiStateTracker extends NetworkStateTracker { * 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 @@ public class WifiStateTracker extends NetworkStateTracker { 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 @@ public class WifiStateTracker extends NetworkStateTracker { } } } + 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; |