diff options
146 files changed, 9735 insertions, 2547 deletions
@@ -108,6 +108,7 @@ LOCAL_SRC_FILES += \ core/java/android/hardware/ISensorService.aidl \ core/java/android/net/IConnectivityManager.aidl \ core/java/android/os/ICheckinService.aidl \ + core/java/android/os/IDropBox.aidl \ core/java/android/os/IHardwareService.aidl \ core/java/android/os/IMessenger.aidl \ core/java/android/os/IMountService.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/DropBoxEntry.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 6148047..2a7c516 100644 --- a/api/current.xml +++ b/api/current.xml @@ -118824,6 +118824,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" @@ -118856,6 +118873,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" @@ -119281,6 +119319,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" @@ -119313,6 +119368,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" 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/stagefright.cpp b/cmds/stagefright/stagefright.cpp index 5397a69..4d4d013 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -71,19 +71,22 @@ 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 units, scale; - CHECK(buffer->meta_data()->findInt32(kKeyTimeUnits, &units)); - CHECK(buffer->meta_data()->findInt32(kKeyTimeScale, &scale)); - int64_t timestamp = ((OMX_TICKS)units * 1000000) / scale; + int64_t timestampUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); bool failed = false; if (seekTimeUs >= 0) { - int64_t diff = timestamp - seekTimeUs; + int64_t diff = timestampUs - seekTimeUs; if (diff > 500000) { printf("ERROR: "); @@ -92,7 +95,7 @@ static void playSource(OMXClient *client, const sp<MediaSource> &source) { } printf("buffer has timestamp %lld us (%.2f secs)\n", - timestamp, timestamp / 1E6); + timestampUs, timestampUs / 1E6); buffer->release(); buffer = NULL; @@ -138,6 +141,12 @@ 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; } diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 3a11cb3..da19d67 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -223,7 +223,11 @@ public class AccountManagerService mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler, MESSAGE_CONNECTED, MESSAGE_DISCONNECTED); - mSimWatcher = new SimWatcher(mContext); + if (SystemProperties.getBoolean("ro.config.sim_password_clear", false)) { + mSimWatcher = new SimWatcher(mContext); + } else { + mSimWatcher = null; + } sThis.set(this); onRegisteredServicesCacheChanged(); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index ff48583..5b34ef9 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/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/DropBoxEntry.aidl b/core/java/android/os/DropBoxEntry.aidl new file mode 100644 index 0000000..225eee1 --- /dev/null +++ b/core/java/android/os/DropBoxEntry.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 DropBoxEntry; diff --git a/core/java/android/os/DropBoxEntry.java b/core/java/android/os/DropBoxEntry.java new file mode 100644 index 0000000..e3816a8 --- /dev/null +++ b/core/java/android/os/DropBoxEntry.java @@ -0,0 +1,163 @@ +/* + * 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 java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.util.zip.GZIPInputStream; + +/** + * A single entry retrieved from an {@link IDropBox} implementation. + * This may include a reference to a stream, so you must call + * {@link #close()} when you are done using it. + * + * {@pending} + */ +public class DropBoxEntry implements Parcelable { + private final String mTag; + private final long mTimeMillis; + + private final String mText; + private final ParcelFileDescriptor mFileDescriptor; + private final int mFlags; + + /** 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 (possibly compressed). */ + public static final int IS_TEXT = 2; + + /** Flag value: Content can been decompressed with {@link GZIPOutputStream}. */ + public static final int IS_GZIPPED = 4; + + /** Create a new DropBoxEntry with the specified contents. */ + public DropBoxEntry(String tag, long timeMillis, String text) { + if (tag == null || text == null) throw new NullPointerException(); + mTag = tag; + mTimeMillis = timeMillis; + mText = text; + mFileDescriptor = null; + mFlags = IS_TEXT; + } + + /** Create a new DropBoxEntry with the specified contents. */ + public DropBoxEntry(String tag, long millis, File data, int flags) throws IOException { + if (tag == null) throw new NullPointerException(); + if (((flags & IS_EMPTY) != 0) != (data == null)) throw new IllegalArgumentException(); + + mTag = tag; + mTimeMillis = millis; + mText = null; + mFlags = flags; + mFileDescriptor = data == null ? null : + ParcelFileDescriptor.open(data, ParcelFileDescriptor.MODE_READ_ONLY); + } + + /** Internal constructor for CREATOR.createFromParcel(). */ + private DropBoxEntry(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) { + mText = null; + mFileDescriptor = null; + } else if (value instanceof String) { + if ((flags & IS_TEXT) == 0) throw new IllegalArgumentException(); + mText = (String) value; + mFileDescriptor = null; + } else if (value instanceof ParcelFileDescriptor) { + mText = null; + mFileDescriptor = (ParcelFileDescriptor) value; + } else { + throw new IllegalArgumentException(); + } + } + + /** Close the input stream associated with this entry. */ + public synchronized 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 maxLength 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 maxLength) { + if (mText != null) return mText.substring(0, Math.min(maxLength, mText.length())); + if ((mFlags & IS_TEXT) == 0) return null; + + try { + InputStream stream = getInputStream(); + if (stream == null) return null; + char[] buf = new char[maxLength]; + InputStreamReader reader = new InputStreamReader(stream); + return new String(buf, 0, Math.max(0, reader.read(buf))); + } catch (IOException e) { + return null; + } + } + + /** @return the uncompressed contents of the entry, or null if the contents were lost */ + public InputStream getInputStream() throws IOException { + if (mText != null) return new ByteArrayInputStream(mText.getBytes("UTF8")); + if (mFileDescriptor == null) return null; + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor); + return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is; + } + + public static final Parcelable.Creator<DropBoxEntry> CREATOR = new Parcelable.Creator() { + public DropBoxEntry[] newArray(int size) { return new DropBoxEntry[size]; } + public DropBoxEntry createFromParcel(Parcel in) { + return new DropBoxEntry( + 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(mText); + } + out.writeInt(mFlags); + } +} diff --git a/core/java/android/os/IDropBox.aidl b/core/java/android/os/IDropBox.aidl new file mode 100644 index 0000000..f951e52 --- /dev/null +++ b/core/java/android/os/IDropBox.aidl @@ -0,0 +1,90 @@ +/* + * 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.os.DropBoxEntry; +import android.os.ParcelFileDescriptor; + +/** + * 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. + * + * <p>This interface is implemented by a system service you can access: + * + * <pre>IDropBox.Stub.asInterface(ServiceManager.getService("dropbox"));</pre> + * + * <p>Other system services and debugging tools may scan the drop box to upload + * entries for processing. + * + * {@pending} + */ +interface IDropBox { + /** + * 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 + */ + void addText(String tag, String data); + + /** + * Stores binary data. The data 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, defined in {@link DropBoxEntry} + */ + void addData(String tag, in byte[] data, int flags); + + /** + * 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 data file descriptor to read from + * @param flags describing the data, defined in {@link DropBoxEntry} + */ + void addFile(String tag, in ParcelFileDescriptor data, int flags); + + /** + * 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 + */ + boolean isTagEnabled(String tag); + + /** + * Gets the next entry from the drop box *after* the specified time. + * Requires android.permission.READ_LOGS. + * + * @param millis time of the last entry seen + * @return the next entry, or null if there are no more entries + */ + DropBoxEntry getNextEntry(long millis); + + // 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..aaa7215 100644 --- a/core/java/android/pim/vcard/Constants.java +++ b/core/java/android/pim/vcard/Constants.java @@ -16,15 +16,46 @@ 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"; + // 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 the current (as of 2009-08-17) ContactsStruct and de-fact vCard extensions // shown in http://en.wikipedia.org/wiki/VCard support are defined here. public static final String PROPERTY_X_AIM = "X-AIM"; @@ -39,7 +70,19 @@ package android.pim.vcard; // 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"; - + + // Android specific properties + // Use only in vCard paser code. + public static final String PROPERTY_X_NICKNAME = "X-NICKNAME"; + + // Properties for DoCoMo vCard. + public static final String PROPERTY_X_CLASS = "X-CLASS"; + public static final String PROPERTY_X_REDUCTION = "X-REDUCTION"; + public static final String PROPERTY_X_NO = "X-NO"; + public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE"; + + public static final String ATTR_TYPE = "TYPE"; + // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0 // // e.g. @@ -59,6 +102,7 @@ package android.pim.vcard; public static final String ATTR_TYPE_VOICE = "VOICE"; public static final String ATTR_TYPE_INTERNET = "INTERNET"; + // Abbreviation of "preferable"? We interpret this value as "primary" property. public static final String ATTR_TYPE_PREF = "PREF"; // Phone types valid in vCard and known to ContactsContract, but not so common. @@ -73,17 +117,26 @@ 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) + // Attribute for Phones, which are not formally 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"; + public static final String ATTR_PHONE_EXTRA_TYPE_OTHER = "OTHER"; + public static final String ATTR_PHONE_EXTRA_TYPE_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"; + public static final String ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN"; + public static final String ATTR_PHONE_EXTRA_TYPE_RADIO = "RADIO"; + public static final String ATTR_PHONE_EXTRA_TYPE_TELEX = "TELEX"; + public static final String ATTR_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD"; + public static final String ATTR_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT"; + + // 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. diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java index 36e5e23..046fb02 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); @@ -71,9 +71,6 @@ public class ContactStruct { sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK); } - /** - * @hide only for testing - */ static public class PhoneData { public final int type; public final String data; @@ -90,7 +87,7 @@ 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 +122,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 +199,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,40 +248,46 @@ 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); } } @@ -304,7 +307,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof ImData) { + if (!(obj instanceof ImData)) { return false; } ImData imData = (ImData)obj; @@ -319,19 +322,37 @@ public class ContactStruct { } } - /** - * @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 +363,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 +402,7 @@ public class ContactStruct { mPropertyName = null; mParameterMap.clear(); mPropertyValueList.clear(); + mPropertyBytes = null; } } @@ -421,15 +439,6 @@ public class ContactStruct { 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); } @@ -444,186 +453,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 +472,24 @@ public class ContactStruct { } } - PhoneData phoneData = new PhoneData(type, - PhoneNumberUtils.formatNumber(builder.toString()), - label, isPrimary); - + final String formattedPhoneNumber; + { + final String rawPhoneNumber = builder.toString(); + if (VCardConfig.isJapaneseDevice(mVCardType)) { + // As of 2009-10-07, there's no formatNumber() which accepts + // the second argument and returns String directly. + final SpannableStringBuilder tmpBuilder = + new SpannableStringBuilder(rawPhoneNumber); + PhoneNumberUtils.formatNumber(tmpBuilder, PhoneNumberUtils.FORMAT_JAPAN); + formattedPhoneNumber = tmpBuilder.toString(); + } else { + // There's no information available on vCard side. Depend on the default + // behavior, which may cause problem in the future when the additional format + // rule is supported (e.g. PhoneNumberUtils.FORMAT_KLINGON) + formattedPhoneNumber = PhoneNumberUtils.formatNumber(rawPhoneNumber); + } + } + PhoneData phoneData = new PhoneData(type, formattedPhoneNumber, label, isPrimary); mPhoneList.add(phoneData); } @@ -666,19 +509,116 @@ 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); + /** + * 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 type, String data, String label, boolean isPrimary) { if (mImList == null) { mImList = new ArrayList<ImData>(); @@ -693,43 +633,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 +666,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 +707,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.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 +755,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 +788,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 +796,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 +820,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 +872,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,9 +885,7 @@ 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; @@ -1002,20 +910,20 @@ public class ContactStruct { type = Phone.TYPE_HOME; } addIm(type, propValue, null, isPrimary); - } else if (propName.equals("NOTE")) { + } 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_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")) { + } else if (propName.equals(Constants.PROPERTY_BDAY)) { mBirthday = propValue; /*} else if (propName.equals("REV")) { // Revision of this VCard entry. I think we can ignore this. @@ -1048,7 +956,10 @@ public class ContactStruct { * 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 +990,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, @@ -1103,25 +1012,10 @@ public class ContactStruct { */ 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. @@ -1181,22 +1075,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 +1097,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,7 +1157,6 @@ 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); @@ -1282,22 +1173,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 +1198,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); @@ -1364,4 +1252,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..9cb9c90 100644 --- a/core/java/android/pim/vcard/VCardComposer.java +++ b/core/java/android/pim/vcard/VCardComposer.java @@ -72,14 +72,30 @@ 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"; @@ -95,28 +111,72 @@ 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; 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. } 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 +271,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; @@ -287,6 +288,7 @@ public class VCardComposer { private final boolean mUsesUtf8; private final boolean mUsesShiftJis; private final boolean mUsesQPToPrimaryProperties; + private final boolean mAppendTypeParamName; private Cursor mCursor; private int mIdColumn; @@ -298,20 +300,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 +321,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); @@ -371,6 +354,7 @@ public class VCardComposer { mUsesUtf8 = VCardConfig.usesUtf8(vcardType); mUsesShiftJis = VCardConfig.usesShiftJis(vcardType); mUsesQPToPrimaryProperties = VCardConfig.usesQPToPrimaryProperties(vcardType); + mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType); mHandlerList = new ArrayList<OneEntryHandler>(); if (mIsDoCoMo) { @@ -389,50 +373,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 +417,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 +506,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 +522,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 +551,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); @@ -684,13 +572,13 @@ public class VCardComposer { // TODO: GroupMembership 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 +636,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, ""); } } @@ -822,7 +710,7 @@ public class VCardComposer { } // 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))) { builder.append(VCARD_ATTR_SEPARATOR); @@ -858,7 +746,7 @@ public class VCardComposer { escapeCharacters(fullname); // FN property - builder.append(VCARD_PROPERTY_FULL_NAME); + builder.append(Constants.PROPERTY_FN); if (shouldAppendCharsetAttribute(encodedFullname)) { builder.append(VCARD_ATTR_SEPARATOR); builder.append(mVCardAttributeCharset); @@ -879,7 +767,7 @@ public class VCardComposer { 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); @@ -896,10 +784,10 @@ public class VCardComposer { builder.append(VCARD_ITEM_SEPARATOR); builder.append(VCARD_COL_SEPARATOR); } else if (mIsDoCoMo) { - appendVCardLine(builder, VCARD_PROPERTY_NAME, ""); + appendVCardLine(builder, Constants.PROPERTY_N, ""); } else if (mIsV30) { - appendVCardLine(builder, VCARD_PROPERTY_NAME, ""); - appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, ""); + appendVCardLine(builder, Constants.PROPERTY_N, ""); + appendVCardLine(builder, Constants.PROPERTY_FN, ""); } String phoneticFamilyName = primaryContentValues @@ -926,7 +814,7 @@ public class VCardComposer { phoneticFamilyName, phoneticMiddleName, phoneticGivenName); - builder.append(VCARD_PROPERTY_SORT_STRING); + 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); @@ -944,7 +832,7 @@ public class VCardComposer { // We chose to use DoCoMo's way 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); @@ -987,7 +875,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); @@ -1009,7 +897,7 @@ public class VCardComposer { } 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); @@ -1032,7 +920,7 @@ public class VCardComposer { } 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); @@ -1055,7 +943,7 @@ public class VCardComposer { } 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); @@ -1078,9 +966,9 @@ public class VCardComposer { if (contentValuesList != null) { final String propertyNickname; if (mIsV30) { - propertyNickname = VCARD_PROPERTY_NICKNAME; - } else if (mUsesAndroidProperty) { - propertyNickname = VCARD_PROPERTY_X_NICKNAME; + propertyNickname = Constants.PROPERTY_NICKNAME; + /*} else if (mUsesAndroidProperty) { + propertyNickname = VCARD_PROPERTY_X_NICKNAME;*/ } else { // There's no way to add this field. return; @@ -1194,7 +1082,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); @@ -1290,7 +1178,7 @@ public class VCardComposer { website = website.trim(); } if (!TextUtils.isEmpty(website)) { - appendVCardLine(builder, VCARD_PROPERTY_URL, website); + appendVCardLine(builder, Constants.PROPERTY_URL, website); } } } @@ -1313,7 +1201,7 @@ public class VCardComposer { birthday = birthday.trim(); } if (!TextUtils.isEmpty(birthday)) { - appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday); + appendVCardLine(builder, Constants.PROPERTY_BDAY, birthday); } } } @@ -1336,13 +1224,13 @@ public class VCardComposer { } if (!TextUtils.isEmpty(company)) { - appendVCardLine(builder, VCARD_PROPERTY_ORG, company, + appendVCardLine(builder, Constants.PROPERTY_ORG, company, !VCardUtils.containsOnlyPrintableAscii(company), (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(company))); } if (!TextUtils.isEmpty(title)) { - appendVCardLine(builder, VCARD_PROPERTY_TITLE, title, + appendVCardLine(builder, Constants.PROPERTY_TITLE, title, !VCardUtils.containsOnlyPrintableAscii(title), (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(title))); @@ -1420,7 +1308,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 +1319,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); } } @@ -1517,7 +1405,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); @@ -1550,7 +1438,7 @@ public class VCardComposer { private void appendVCardPostalLine(final StringBuilder builder, final Integer typeAsObject, final String label, final ContentValues contentValues) { - builder.append(VCARD_PROPERTY_ADR); + builder.append(Constants.PROPERTY_ADR); builder.append(VCARD_ATTR_SEPARATOR); // Note: Not sure why we need to emit "empty" line even when actual data does not exist. @@ -1684,7 +1572,7 @@ public class VCardComposer { private void appendVCardEmailLine(final StringBuilder builder, final Integer typeAsObject, final String label, final String data) { - builder.append(VCARD_PROPERTY_EMAIL); + builder.append(Constants.PROPERTY_EMAIL); final int typeAsPrimitive; if (typeAsObject == null) { @@ -1743,7 +1631,7 @@ public class VCardComposer { private void appendVCardTelephoneLine(final StringBuilder builder, final Integer typeAsObject, final String label, String encodedData) { - builder.append(VCARD_PROPERTY_TEL); + builder.append(Constants.PROPERTY_TEL); builder.append(VCARD_ATTR_SEPARATOR); final int typeAsPrimitive; @@ -1870,7 +1758,7 @@ public class VCardComposer { private void appendTypeAttribute(final StringBuilder builder, final String type) { // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF" - if (mIsV30) { + if (mIsV30 || mAppendTypeParamName) { builder.append(Constants.ATTR_TYPE).append(VCARD_ATTR_EQUAL); } builder.append(type); @@ -1961,4 +1849,118 @@ 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; + } + // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help. + 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); + + 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)); + // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help. + 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); + 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..03ed329 100644 --- a/core/java/android/pim/vcard/VCardConfig.java +++ b/core/java/android/pim/vcard/VCardConfig.java @@ -41,8 +41,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 ... @@ -105,8 +105,23 @@ public class VCardConfig { * behavior around this flag in the future. Do not use this flag without any reason. */ public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000; - - // VCard types + + /** + * 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. + * + * XXX: Really rare? + * + * e.g. int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM); + */ + public static final int FLAG_APPEND_TYPE_PARAM = 0x08000000; + + //// The followings are VCard types available from importer/exporter. //// /** * General vCard format with the version 2.1. Uses UTF-8 for the charset. @@ -300,6 +315,10 @@ public class VCardConfig { ((vcardType & FLAG_USE_QP_TO_PRIMARY_PROPERTIES) != 0)); } + public static boolean appendTypeParamName(int vcardType) { + return (vcardType & FLAG_APPEND_TYPE_PARAM) != 0; + } + private VCardConfig() { } }
\ No newline at end of file diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardDataBuilder.java index d2026d0..d00f616 100644 --- a/core/java/android/pim/vcard/VCardDataBuilder.java +++ b/core/java/android/pim/vcard/VCardDataBuilder.java @@ -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..b3bf426 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -22,9 +22,11 @@ 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,9 +43,9 @@ 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; - + static { sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>(); sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>(); @@ -59,14 +61,15 @@ public class VCardUtils { sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK); sKnownPhoneTypesMap_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_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER); + sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_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); + Constants.ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN); + sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO); + sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_TELEX, Phone.TYPE_TELEX); + sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_TTY_TDD, Phone.TYPE_TTY_TDD); + sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_ASSISTANT, + Phone.TYPE_ASSISTANT); sPhoneTypesSetUnknownToContacts = new HashSet<String>(); sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM); @@ -74,11 +77,11 @@ public class VCardUtils { sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS); sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO); } - + 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. @@ -187,7 +190,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); @@ -282,7 +288,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 8c9581d..9bacde6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3611,7 +3611,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 @@ -3626,6 +3625,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/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/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index a6d644b..5c2b3c9 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*/ @@ -898,6 +899,7 @@ public final class ViewRoot extends Handler implements ViewParent, // all at once. newSurface = true; fullRedrawNeeded = true; + mPreviousTransparentRegion.setEmpty(); if (mGlWanted && !mUseGL) { initializeGL(); @@ -1243,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); @@ -1355,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/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/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..17345bc 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -1210,8 +1210,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/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/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/WebView.java b/core/java/android/webkit/WebView.java index 4bc1a0e..6631163 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2630,6 +2630,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. @@ -2836,6 +2846,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) { @@ -2863,19 +2888,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()) { @@ -2951,6 +2970,10 @@ public class WebView extends AbsoluteLayout if (mFindIsUp && !animateScroll) { nativeDrawMatches(canvas); } + if (mFocusSizeChanged) { + mFocusSizeChanged = false; + didUpdateTextViewBounds(true); + } } // draw history @@ -3304,7 +3327,7 @@ public class WebView extends AbsoluteLayout } } - if (nativeCursorIsPlugin()) { + if (nativeFocusCandidateIsPlugin()) { nativeUpdatePluginReceivesEvents(); invalidate(); } else if (nativeCursorIsTextInput()) { @@ -3316,7 +3339,9 @@ 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()) { + return mWebTextView.onKeyDown(keyCode, 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. @@ -4037,6 +4062,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; @@ -4679,8 +4705,12 @@ public class WebView extends AbsoluteLayout // called by JNI private void sendPluginState(int state) { WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData(); - psd.mFrame = nativeCursorFramePointer(); - psd.mNode = nativeCursorNodePointer(); + psd.mFrame = nativeFocusCandidateFramePointer(); + psd.mNode = nativeFocusCandidatePointer(); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "sendPluginState frame=" + psd.mFrame + + " node=" + psd.mNode); + } psd.mState = state; mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd); } @@ -4871,9 +4901,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) { @@ -5035,6 +5066,9 @@ public class WebView extends AbsoluteLayout / mZoomOverviewWidth, false); } } + if (draw.mFocusSizeChanged && inEditingMode()) { + mFocusSizeChanged = true; + } break; } case WEBCORE_INITIALIZED_MSG_ID: @@ -5161,6 +5195,11 @@ public class WebView extends AbsoluteLayout hideSoftKeyboard(); } else { displaySoftKeyboard(false); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "REQUEST_KEYBOARD" + + " focusCandidateIsPlugin=" + + nativeFocusCandidateIsPlugin()); + } } break; @@ -5570,6 +5609,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. */ @@ -5587,7 +5637,6 @@ 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 nativeCursorIsTextInput(); private native Point nativeCursorPosition(); private native String nativeCursorText(); @@ -5606,7 +5655,9 @@ public class WebView extends AbsoluteLayout 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(); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 86685fb..ad19b0f 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. @@ -1593,6 +1631,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 +1646,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( @@ -2175,6 +2215,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/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/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/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/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index fba6c08..7251f36 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -148,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="7140261692496314430">"Vaše účty Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Přístup k dostupným účtům Google."</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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"zbývá méně než <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">"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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 99f8ec9..41f1a96 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -148,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="7140261692496314430">"Dine Google-konti"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Få adgang til tilgængelige Google-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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"mindre end <xliff:g id="NUMBER">%d%%</xliff:g> 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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 02f685a..6f6aa9f 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -148,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="7140261692496314430">"Ihre Google-Konten"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Greift auf verfügbare Google-Konten zu."</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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"Nur noch weniger als <xliff:g id="NUMBER">%d%%</xliff:g> vorhanden."</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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 23c6565..c2f421c 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -148,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="7140261692496314430">"Οι λογαριασμοί σας Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Πρόσβαση στους διαθέσιμους λογαριασμούς Google."</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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"απομένουν λιγότερο από <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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"Προσβασιμότητα"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Ταπετσαρία"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Αλλαγή ταπετσαρίας"</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/strings.xml b/core/res/res/values-es/strings.xml index 74f7e11..b5e9814 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -148,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="7140261692496314430">"Tus cuentas de Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Acceder a las cuentas de Google 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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"menos del <xliff:g id="NUMBER">%d%%</xliff:g> 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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 c0f59f2..9f842c3 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -148,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="7140261692496314430">"Vos comptes Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Accédez aux comptes Google 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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"Batterie restante inférieure à <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">"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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 d62625f..7edb480 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -133,7 +133,7 @@ <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Modalità silenziosa"</string> <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Audio non attivo"</string> <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Audio attivo"</string> - <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Modalità aereo attiva"</string> + <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Modalità aereo"</string> <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modalità aereo attiva"</string> <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modalità aereo non attiva"</string> <string name="safeMode" msgid="2788228061547930246">"Modalità provvisoria"</string> @@ -148,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="7140261692496314430">"I tuoi account Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Accedere agli account Google 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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"energia residua inferiore a <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">"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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 55e6b50..e0fc9f5 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -148,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="7140261692496314430">"Googleアカウント"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"利用可能なGoogleアカウントへのアクセス"</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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"残量<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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"アクセシビリティ"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"壁紙"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"壁紙を変更"</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 3db36fc..2de1e08 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -148,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="7140261692496314430">"Google 계정"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"사용 가능한 Google 계정에 액세스합니다."</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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"<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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"접근성"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"배경화면"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"배경화면 변경"</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 19524d8..eead1cd 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -148,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="7140261692496314430">"Google-kontoer"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"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> @@ -196,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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,7 +586,8 @@ <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="6564958083485073855">"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">"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> @@ -488,6 +597,8 @@ <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> + <!-- 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> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"Tilgjengelighet"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Bakgrunnsbilde"</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 085a99c..eb4ebad 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -148,8 +148,10 @@ <string name="permgroupdesc_location" msgid="2430258821648348660">"Uw fysieke locatie bijhouden"</string> <string name="permgrouplab_network" msgid="5808983377727109831">"Netwerkcommunicatie"</string> <string name="permgroupdesc_network" msgid="5035763698958415998">"Toepassingen toestaan verschillende netwerkfuncties te openen."</string> - <string name="permgrouplab_accounts" msgid="7140261692496314430">"Uw Google-accounts"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Toegang tot de beschikbare Google-accounts."</string> + <!-- no translation found for permgrouplab_accounts (3359646291125325519) --> + <skip /> + <!-- no translation found for permgroupdesc_accounts (4948732641827091312) --> + <skip /> <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Bedieningselementen hardware"</string> <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Rechtstreekse toegang tot hardware op de handset."</string> <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefoonoproepen"</string> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"minder dan <xliff:g id="NUMBER">%d%%</xliff:g> 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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"Toegankelijkheid"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Achtergrond"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Achtergrond wijzigen"</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-pl/strings.xml b/core/res/res/values-pl/strings.xml index 145deda..abf60a1 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -148,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="7140261692496314430">"Twoje konta Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Uzyskaj dostęp do dostępnych kont Google."</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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"pozostało mniej niż <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">"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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 4e9a0a8..732d455 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -148,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="7140261692496314430">"As suas Contas Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Aceda às Contas Google 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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"resta menos de <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">"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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 72990a1..973c7ec 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -148,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="7140261692496314430">"Suas Contas do Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Acessar as Contas do Google 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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"menos de <xliff:g id="NUMBER">%d%%</xliff:g> restantes."</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> @@ -488,6 +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> + <!-- 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> @@ -585,7 +696,7 @@ <string name="weeks" msgid="6509623834583944518">"semanas"</string> <string name="year" msgid="4001118221013892076">"ano"</string> <string name="years" msgid="6881577717993213522">"anos"</string> - <string name="every_weekday" msgid="8777593878457748503">"Todos os dias da semana (de segunda a sexta)"</string> + <string name="every_weekday" msgid="8777593878457748503">"De segunda a sexta-feira"</string> <string name="daily" msgid="5738949095624133403">"Diariamente"</string> <string name="weekly" msgid="983428358394268344">"Semanalmente na <xliff:g id="DAY">%s</xliff:g>"</string> <string name="monthly" msgid="2667202947170988834">"Mensalmente"</string> @@ -719,7 +830,7 @@ <string name="ime_action_go" msgid="8320845651737369027">"Ir"</string> <string name="ime_action_search" msgid="658110271822807811">"Pesquisar"</string> <string name="ime_action_send" msgid="2316166556349314424">"Enviar"</string> - <string name="ime_action_next" msgid="3138843904009813834">"Próximo"</string> + <string name="ime_action_next" msgid="3138843904009813834">"Avançar"</string> <string name="ime_action_done" msgid="8971516117910934605">"Concluído"</string> <string name="ime_action_default" msgid="2840921885558045721">"Executar"</string> <string name="dial_number_using" msgid="5789176425167573586">"Discar número"\n"usando <xliff:g id="NUMBER">%s</xliff:g>"</string> @@ -738,4 +849,12 @@ <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> + <!-- 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 a286312..4c54df3 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -148,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="7140261692496314430">"Ваши аккаунты Google"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Входить в доступные аккаунты Google."</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> @@ -388,7 +390,7 @@ <string name="permdesc_readDictionary" msgid="1082972603576360690">"Позволяет приложению считывать любые слова, имена и фразы личного пользования, которые могут храниться в пользовательском словаре."</string> <string name="permlab_writeDictionary" msgid="6703109511836343341">"записывать в пользовательский словарь"</string> <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Позволяет приложению записывать новые слова в пользовательский словарь."</string> - <string name="permlab_sdcardWrite" msgid="8079403759001777291">"изменить/удалить содержание SD-карты"</string> + <string name="permlab_sdcardWrite" msgid="8079403759001777291">"изменять/удалять содержание SD-карты"</string> <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Разрешает приложению запись на SD-карту"</string> <string-array name="phoneTypes"> <item msgid="8901098336658710359">"Домашний"</item> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"осталось менее <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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"Специальные возможности"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Фоновый рисунок"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"Изменить фоновый рисунок"</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 0f0d966..e82dafc 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -148,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="7140261692496314430">"Dina Google-konton"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Få åtkomst till tillgängliga Google-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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"Mindre än <xliff:g id="NUMBER">%d%%</xliff:g> återstår."</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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 a63a387..7b983be 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -148,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="7140261692496314430">"Google hesaplarınız"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Kullanılabilir Google hesaplarına 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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"<xliff:g id="NUMBER">%d%%</xliff:g> adetten 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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <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> + <!-- 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 2974203..d2a34aa 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -29,9 +29,9 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"语音信箱"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"出现连接问题或 MMI 码无效。"</string> - <string name="serviceEnabled" msgid="8147278346414714315">"服务已启用。"</string> + <string name="serviceEnabled" msgid="8147278346414714315">"已启用服务。"</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"已针对以下内容启用了服务:"</string> - <string name="serviceDisabled" msgid="1937553226592516411">"服务已被停用。"</string> + <string name="serviceDisabled" msgid="1937553226592516411">"已停用服务。"</string> <string name="serviceRegistered" msgid="6275019082598102493">"注册成功。"</string> <string name="serviceErased" msgid="1288584695297200972">"清除成功。"</string> <string name="passwordIncorrect" msgid="7612208839450128715">"密码不正确。"</string> @@ -42,25 +42,25 @@ <string name="invalidPin" msgid="3850018445187475377">"输入一个 4 至 8 位数的 PIN。"</string> <string name="needPuk" msgid="919668385956251611">"您的 SIM 卡被 PUK 锁定。请输入 PUK 码进行解锁。"</string> <string name="needPuk2" msgid="4526033371987193070">"输入 PUK2 以解锁 SIM 卡。"</string> - <string name="ClipMmi" msgid="6952821216480289285">"来电显示"</string> - <string name="ClirMmi" msgid="7784673673446833091">"外拨电话显示"</string> + <string name="ClipMmi" msgid="6952821216480289285">"来电者信息"</string> + <string name="ClirMmi" msgid="7784673673446833091">"外拨者信息"</string> <string name="CfMmi" msgid="5123218989141573515">"呼叫转接"</string> <string name="CwMmi" msgid="9129678056795016867">"呼叫等待"</string> <string name="BaMmi" msgid="455193067926770581">"呼叫限制"</string> <string name="PwdMmi" msgid="7043715687905254199">"密码更改"</string> <string name="PinMmi" msgid="3113117780361190304">"PIN 码更改"</string> <string name="CnipMmi" msgid="3110534680557857162">"来电显示"</string> - <string name="CnirMmi" msgid="3062102121430548731">"来电显示已禁用"</string> + <string name="CnirMmi" msgid="3062102121430548731">"来电显示受限制"</string> <string name="ThreeWCMmi" msgid="9051047170321190368">"三方通话"</string> <string name="RuacMmi" msgid="7827887459138308886">"拒绝不想接听的骚扰电话"</string> <string name="CndMmi" msgid="3116446237081575808">"主叫号码传送"</string> <string name="DndMmi" msgid="1265478932418334331">"请勿打扰"</string> - <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"来电显示默认设置为受限制。下一个呼叫:受限制"</string> - <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"来电显示默认设置为受限制。下一个呼叫:不受限制"</string> - <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"来电显示默认设置为不受限制。下一个呼叫:受限制"</string> - <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"来电显示默认设置为不受限制。下一个呼叫:不受限制"</string> + <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"来电者信息在默认情况下受限制。在下一次通话中:受限制"</string> + <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"来电者信息在默认情况受限制。在下一次通话中:不受限制"</string> + <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"来电者信息在默认情况下不受限制。在下一次通话中:受限制"</string> + <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"来电者信息在默认情况下不受限制。在下一次通话中:不受限制"</string> <string name="serviceNotProvisioned" msgid="8614830180508686666">"未提供服务。"</string> - <string name="CLIRPermanent" msgid="5460892159398802465">"无法更改来电显示设置。"</string> + <string name="CLIRPermanent" msgid="5460892159398802465">"无法更改来电者信息设置。"</string> <string name="RestrictedChangedTitle" msgid="5592189398956187498">"访问受限情况已发生变化"</string> <string name="RestrictedOnData" msgid="8653794784690065540">"数据服务已禁用。"</string> <string name="RestrictedOnEmergency" msgid="6581163779072833665">"紧急服务已禁用。"</string> @@ -116,7 +116,7 @@ <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> - <string name="low_memory" msgid="6632412458436461203">"手机存储空间已满!请删除一些文件来腾出空间。"</string> + <string name="low_memory" msgid="6632412458436461203">"手机内存已用完!请删除一些文件来腾出空间。"</string> <string name="me" msgid="6545696007631404292">"我"</string> <string name="power_dialog" msgid="1319919075463988638">"手机选项"</string> <string name="silent_mode" msgid="7167703389802618663">"静音模式"</string> @@ -131,8 +131,8 @@ <string name="global_action_lock" msgid="2844945191792119712">"屏幕锁定"</string> <string name="global_action_power_off" msgid="4471879440839879722">"关机"</string> <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"静音模式"</string> - <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"声音已“关闭”"</string> - <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"声音已“开启”"</string> + <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"已关闭声音"</string> + <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"已打开声音"</string> <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"飞行模式"</string> <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"飞行模式已开启"</string> <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"飞行模式已关闭"</string> @@ -148,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="7140261692496314430">"您的 Google 帐户"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"访问可用的 Google 帐户。"</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> @@ -164,8 +166,8 @@ <string name="permdesc_statusBar" msgid="1365473595331989732">"允许应用程序停用状态栏,或者添加和删除系统图标。"</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展开/折叠状态栏"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允许应用程序展开或折叠状态栏。"</string> - <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截对外呼叫"</string> - <string name="permdesc_processOutgoingCalls" msgid="2228988201852654461">"允许应用程序处理对外呼叫和更改要拨打的号码。恶意应用程序可借此监视、重定向或阻止对外呼叫。"</string> + <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截外拨电话"</string> + <string name="permdesc_processOutgoingCalls" msgid="2228988201852654461">"允许应用程序处理外拨电话和更改要拨打的号码。恶意应用程序可借此监视、重定向或阻止外拨电话。"</string> <string name="permlab_receiveSms" msgid="2697628268086208535">"接收短信"</string> <string name="permdesc_receiveSms" msgid="6298292335965966117">"允许应用程序接收和处理短信。恶意应用程序可借此监视您的信息,或者将信息删除而不向您显示。"</string> <string name="permlab_receiveMms" msgid="8894700916188083287">"接收彩信"</string> @@ -178,7 +180,7 @@ <string name="permdesc_writeSms" msgid="6299398896177548095">"允许应用程序写入手机或 SIM 卡中存储的短信。恶意应用程序可借此删除您的信息。"</string> <string name="permlab_receiveWapPush" msgid="8258226427716551388">"接收 WAP"</string> <string name="permdesc_receiveWapPush" msgid="5979623826128082171">"允许应用程序接收和处理 WAP 消息。恶意应用程序可借此监视您的消息,或者将消息删除而不向您显示。"</string> - <string name="permlab_getTasks" msgid="5005277531132573353">"检索所运行的应用程序"</string> + <string name="permlab_getTasks" msgid="5005277531132573353">"检索当前运行的应用程序"</string> <string name="permdesc_getTasks" msgid="7048711358713443341">"允许应用程序检索有关当前和最近运行的任务的信息。恶意应用程序可借此发现有关其他应用程序的私有信息。"</string> <string name="permlab_reorderTasks" msgid="5669588525059921549">"对正在运行的应用程序重新排序"</string> <string name="permdesc_reorderTasks" msgid="126252774270522835">"允许应用程序将任务移至前台和后台。恶意应用程序可借此强行进到前台,而不受您的控制。"</string> @@ -204,11 +206,11 @@ <string name="permdesc_broadcastSmsReceived" msgid="9122419277306740155">"允许应用程序广播已收到短信的通知。恶意应用程序可借此伪造收到的短信。"</string> <string name="permlab_broadcastWapPush" msgid="3145347413028582371">"发送 WAP 一键接收广播"</string> <string name="permdesc_broadcastWapPush" msgid="3955303669461378091">"允许应用程序广播收到 WAP 一键信息的通知。恶意应用程序可借此乱发彩信或暗中用恶意内容替换任意网页中的内容。"</string> - <string name="permlab_setProcessLimit" msgid="2451873664363662666">"限制所运行进程的数量"</string> + <string name="permlab_setProcessLimit" msgid="2451873664363662666">"限制运行的进程个数"</string> <string name="permdesc_setProcessLimit" msgid="7824786028557379539">"允许应用程序控制将运行的最大进程数。普通应用程序从不需要使用此权限。"</string> <string name="permlab_setAlwaysFinish" msgid="5342837862439543783">"关闭所有后台应用程序"</string> <string name="permdesc_setAlwaysFinish" msgid="8773936403987091620">"允许应用程序控制活动是否始终是一转至后台就完成。普通应用程序从不需要使用此权限。"</string> - <string name="permlab_batteryStats" msgid="7863923071360031652">"修改电池使用情况统计信息"</string> + <string name="permlab_batteryStats" msgid="7863923071360031652">"修改电池统计信息"</string> <string name="permdesc_batteryStats" msgid="5847319823772230560">"允许修改收集的电池使用情况统计信息。普通应用程序不能使用此权限。"</string> <string name="permlab_backup" msgid="470013022865453920">"控制系统备份和还原"</string> <string name="permdesc_backup" msgid="4837493065154256525">"允许应用程序控制系统的备份和还原机制。不适用于普通应用程序。"</string> @@ -221,7 +223,7 @@ <string name="permlab_setAnimationScale" msgid="2805103241153907174">"修改全局动画速度"</string> <string name="permdesc_setAnimationScale" msgid="7181522138912391988">"允许应用程序随时更改全局动画速度(加快或放慢动画)。"</string> <string name="permlab_manageAppTokens" msgid="17124341698093865">"管理应用程序令牌"</string> - <string name="permdesc_manageAppTokens" msgid="977127907524195988">"允许应用程序创建和管理自己的令牌,从而绕开正常的 Z 排序方式。普通应用程序从不需要使用此权限。"</string> + <string name="permdesc_manageAppTokens" msgid="977127907524195988">"允许应用程序创建和管理自己的令牌,从而绕开正常的 Z 方向排序。普通应用程序从不需要使用此权限。"</string> <string name="permlab_injectEvents" msgid="1378746584023586600">"按键和控制按钮"</string> <string name="permdesc_injectEvents" msgid="3946098050410874715">"允许应用程序将其自己的输入活动(按键等)提供给其他应用程序。恶意应用程序可借此掌控手机。"</string> <string name="permlab_readInputState" msgid="469428900041249234">"记录您输入的内容和采取的操作"</string> @@ -230,7 +232,7 @@ <string name="permdesc_bindInputMethod" msgid="3734838321027317228">"允许手机用户绑定至输入法的顶级界面。普通应用程序从不需要使用此权限。"</string> <string name="permlab_bindWallpaper" msgid="8716400279937856462">"绑定到壁纸"</string> <string name="permdesc_bindWallpaper" msgid="5287754520361915347">"允许手机用户绑定到壁纸的顶级界面。应该从不需要将此权限授予普通应用程序。"</string> - <string name="permlab_setOrientation" msgid="3365947717163866844">"更改屏幕浏览模式"</string> + <string name="permlab_setOrientation" msgid="3365947717163866844">"更改屏幕显示方向"</string> <string name="permdesc_setOrientation" msgid="6335814461615851863">"允许应用程序随时更改屏幕的旋转方向。普通应用程序从不需要使用此权限。"</string> <string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"向应用程序发送 Linux 信号"</string> <string name="permdesc_signalPersistentProcesses" msgid="3565530463215015289">"允许应用程序请求将提供的信号发送给所有持续的进程。"</string> @@ -247,7 +249,7 @@ <string name="permlab_installPackages" msgid="335800214119051089">"直接安装应用程序"</string> <string name="permdesc_installPackages" msgid="526669220850066132">"允许应用程序安装新的或更新的 Android 包。恶意应用程序可借此添加具有极大权限的新应用程序。"</string> <string name="permlab_clearAppCache" msgid="4747698311163766540">"删除所有应用程序缓存数据"</string> - <string name="permdesc_clearAppCache" msgid="7740465694193671402">"允许应用程序通过删除应用程序缓存目录中的文件释放手机存储空间。对系统进程的访问通常受到严格限制。"</string> + <string name="permdesc_clearAppCache" msgid="7740465694193671402">"允许应用程序通过删除应用程序缓存目录中的文件释放手机内存。通常严格限制访问系统进程。"</string> <string name="permlab_readLogs" msgid="4811921703882532070">"读取系统日志文件"</string> <string name="permdesc_readLogs" msgid="2257937955580475902">"允许应用程序读取系统的各日志文件。这样应用程序可以发现有关您操作手机的一般信息,但这些信息不应包含任何个人信息或私有信息。"</string> <string name="permlab_diagnostic" msgid="8076743953908000342">"读取/写入诊断所拥有的资源"</string> @@ -264,7 +266,7 @@ <string name="permdesc_writeGservices" msgid="6602362746516676175">"允许应用程序修改 Google 服务地图。普通应用程序不能使用此权限。"</string> <string name="permlab_receiveBootCompleted" msgid="7776779842866993377">"开机时自动启动"</string> <string name="permdesc_receiveBootCompleted" msgid="698336728415008796">"允许应用程序在系统完成启动后即自行启动。这样会延长手机的启动时间,而且如果应用程序一直运行,会降低手机的整体速度。"</string> - <string name="permlab_broadcastSticky" msgid="7919126372606881614">"发送顽固广播"</string> + <string name="permlab_broadcastSticky" msgid="7919126372606881614">"发送置顶广播"</string> <string name="permdesc_broadcastSticky" msgid="1920045289234052219">"允许应用程序发送顽固广播,这些广播在结束后仍会保留。恶意应用程序可能会借此使手机耗用太多内存,从而降低其速度或稳定性。"</string> <string name="permlab_readContacts" msgid="6219652189510218240">"读取联系人数据"</string> <string name="permdesc_readContacts" msgid="3371591512896545975">"允许应用程序读取您手机上存储的所有联系人(地址)数据。恶意应用程序可借此将您的数据发送给其他人。"</string> @@ -278,11 +280,11 @@ <string name="permdesc_readCalendar" msgid="5533029139652095734">"允许应用程序读取您手机上存储的所有日历活动。恶意应用程序可借此将您的日历活动发送给其他人。"</string> <string name="permlab_writeCalendar" msgid="377926474603567214">"写入日历数据"</string> <string name="permdesc_writeCalendar" msgid="8674240662630003173">"允许应用程序修改您手机上存储的日历活动。恶意应用程序可借此清除或修改您的日历数据。"</string> - <string name="permlab_accessMockLocation" msgid="8688334974036823330">"用于测试的模拟位置源"</string> + <string name="permlab_accessMockLocation" msgid="8688334974036823330">"禁止使用位置来源进行测试"</string> <string name="permdesc_accessMockLocation" msgid="7648286063459727252">"创建用于测试的模拟位置源。恶意应用程序可借此替代真正的位置源(如 GPS 或网络提供商)返回的位置和/或状态。"</string> - <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"接收额外的位置提供者命令"</string> + <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"访问更多位置提供者的命令"</string> <string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"访问额外的位置提供程序命令。恶意应用程序可借此干扰 GPS 或其他位置源的运作。"</string> - <string name="permlab_installLocationProvider" msgid="6578101199825193873">"安装地点信息提供程序的权限"</string> + <string name="permlab_installLocationProvider" msgid="6578101199825193873">"安装位置信息提供程序"</string> <string name="permdesc_installLocationProvider" msgid="5449175116732002106">"创建用于测试的模拟地点信息源。恶意应用程序可借此覆盖真正的地点信息源(如 GPS 或网络提供商)返回的地点和/或状态,或者监视您的地点并向外部信息源报告。"</string> <string name="permlab_accessFineLocation" msgid="8116127007541369477">"精准位置 (GPS)"</string> <string name="permdesc_accessFineLocation" msgid="7411213317434337331">"访问精准的位置源,例如手机上的全球定位系统(如果适用)。恶意应用程序可能借此确定您所处的位置,并消耗额外的电池电量。"</string> @@ -313,9 +315,9 @@ <string name="permlab_hardware_test" msgid="4148290860400659146">"测试硬件"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"允许应用程序控制各种用于硬件测试的外围设备。"</string> <string name="permlab_callPhone" msgid="3925836347681847954">"直接拨打电话号码"</string> - <string name="permdesc_callPhone" msgid="3369867353692722456">"允许应用程序在没有您干预的情况下呼叫电话号码。恶意应用程序可借此在您的话费单上产生意外通话费。请注意,此权限不允许应用程序呼叫紧急电话号码。"</string> + <string name="permdesc_callPhone" msgid="3369867353692722456">"允许应用程序在无人操作的情况下拨打电话。恶意应用程序可借此在您的话费单上产生意外通话费。请注意,此权限不允许应用程序拨打紧急电话。"</string> <string name="permlab_callPrivileged" msgid="4198349211108497879">"直接呼叫任何电话号码"</string> - <string name="permdesc_callPrivileged" msgid="244405067160028452">"允许应用程序在没有您干预的情况下呼叫任何电话号码(包括紧急电话号码)。恶意应用程序可借此对紧急服务拨打骚扰电话和非法电话。"</string> + <string name="permdesc_callPrivileged" msgid="244405067160028452">"允许应用程序在无人操作的情况下拨打任何电话(包括紧急电话)。恶意应用程序可借此向紧急服务拨打骚扰电话和非法电话。"</string> <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"直接启动 CDMA 电话设置"</string> <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"允许应用程序启动 CDMA 服务。恶意应用程序可能会无端启动 CDMA 服务"</string> <string name="permlab_locationUpdates" msgid="7785408253364335740">"控制位置更新通知"</string> @@ -354,7 +356,7 @@ <string name="permdesc_useCredentials" msgid="7416570544619546974">"允许应用程序请求身份验证标记。"</string> <string name="permlab_accessNetworkState" msgid="6865575199464405769">"查看网络状态"</string> <string name="permdesc_accessNetworkState" msgid="558721128707712766">"允许应用程序查看所有网络的状态。"</string> - <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"互联网完全访问"</string> + <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"完全访问互联网"</string> <string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"允许应用程序创建网络套接字。"</string> <string name="permlab_writeApnSettings" msgid="7823599210086622545">"写入接入点名称设置"</string> <string name="permdesc_writeApnSettings" msgid="7443433457842966680">"允许应用程序修改 APN 设置,例如任何 APN 的代理和端口。"</string> @@ -407,10 +409,10 @@ <item msgid="2374913952870110618">"自定义邮箱"</item> </string-array> <string-array name="postalAddressTypes"> - <item msgid="6880257626740047286">"住宅地址"</item> - <item msgid="5629153956045109251">"单位地址"</item> - <item msgid="4966604264500343469">"其他地址"</item> - <item msgid="4932682847595299369">"自定义地址"</item> + <item msgid="6880257626740047286">"住宅"</item> + <item msgid="5629153956045109251">"单位"</item> + <item msgid="4966604264500343469">"其他"</item> + <item msgid="4932682847595299369">"自定义"</item> </string-array> <string-array name="imAddressTypes"> <item msgid="1738585194601476694">"住宅聊天工具"</item> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -446,15 +552,17 @@ <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"正确!"</string> <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_charged" msgid="4938930459620989972">"已充满。"</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> - <string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"请插入 SIM 卡。"</string> + <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"没有 SIM 卡"</string> + <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"手机中无 SIM 卡"</string> + <string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"请插入 SIM 卡"</string> <string name="lockscreen_network_locked_message" msgid="143389224986028501">"网络已锁定"</string> - <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM 卡被 PUK 锁定。"</string> + <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM 卡被 PUK 锁定"</string> <string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"请参阅《用户指南》或联系客服人员。"</string> - <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡被锁定。"</string> + <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡被锁定"</string> <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"正在解锁 SIM 卡..."</string> <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地绘制了解锁图案。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string> <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地绘制了解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%d</xliff:g> 次后仍不成功,系统会要求您使用自己的 Google 登录信息解锁手机。"\n\n"请在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒后重试。"</string> @@ -478,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="6564958083485073855">"剩余电量不足 <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> @@ -488,6 +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> + <!-- 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> @@ -614,16 +725,16 @@ <string name="addToDictionary" msgid="8793624991686948709">"将“<xliff:g id="WORD">%s</xliff:g>”添加到词典"</string> <string name="editTextMenuTitle" msgid="1672989176958581452">"编辑文字"</string> <string name="low_internal_storage_view_title" msgid="1399732408701697546">"空间不足"</string> - <string name="low_internal_storage_view_text" msgid="635106544616378836">"手机存储空间正在减少。"</string> + <string name="low_internal_storage_view_text" msgid="635106544616378836">"手机内存空间正在减少。"</string> <string name="ok" msgid="5970060430562524910">"确定"</string> <string name="cancel" msgid="6442560571259935130">"取消"</string> <string name="yes" msgid="5362982303337969312">"确定"</string> <string name="no" msgid="5141531044935541497">"取消"</string> <string name="dialog_alert_title" msgid="2049658708609043103">"注意事项"</string> - <string name="capital_on" msgid="1544682755514494298">"开启"</string> + <string name="capital_on" msgid="1544682755514494298">"打开"</string> <string name="capital_off" msgid="6815870386972805832">"关闭"</string> - <string name="whichApplication" msgid="4533185947064773386">"使用以下内容完成操作"</string> - <string name="alwaysUse" msgid="4583018368000610438">"默认用于执行此操作。"</string> + <string name="whichApplication" msgid="4533185947064773386">"使用以下方式发送"</string> + <string name="alwaysUse" msgid="4583018368000610438">"默认使用此方式发送。"</string> <string name="clearDefaultHintMsg" msgid="4815455344600932173">"清除“主屏幕设置”>“应用程序”>“管理应用程序”中的默认设置。"</string> <string name="chooseActivity" msgid="1009246475582238425">"选择一项操作"</string> <string name="noApplications" msgid="1691104391758345586">"没有应用程序可执行此操作。"</string> @@ -680,7 +791,7 @@ <string name="usb_storage_button_unmount" msgid="6092146330053864766">"不安装"</string> <string name="usb_storage_error_message" msgid="2534784751603345363">"使用 SD 卡进行 USB 存储时出现问题。"</string> <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB 已连接"</string> - <string name="usb_storage_notification_message" msgid="7380082404288219341">"选择以将文件复制到计算机/从计算机复制文件。"</string> + <string name="usb_storage_notification_message" msgid="7380082404288219341">"选择将文件复制到计算机/从计算机复制到存储设备。"</string> <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"关闭 USB 存储设备"</string> <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"选中以关闭 USB 存储设备。"</string> <string name="usb_storage_stop_title" msgid="6014127947456185321">"关闭 USB 存储设备"</string> @@ -691,7 +802,7 @@ <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_title" msgid="6729044778949189918">"已连接 USB 调试"</string> <!-- no translation found for adb_active_notification_message (8470296818270110396) --> <skip /> <!-- no translation found for select_input_method (6865512749462072765) --> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"辅助功能"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"壁纸"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"更改壁纸"</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 8f9f310..a2234b4 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -148,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="7140261692496314430">"您的 Google 帳戶"</string> - <string name="permgroupdesc_accounts" msgid="6735915929704895193">"存取可用 Google 帳戶。"</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> @@ -433,6 +435,110 @@ <item msgid="2506857312718630823">"ICQ"</item> <item msgid="1648797903785279353">"Jabber"</item> </string-array> + <!-- 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> @@ -447,6 +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> + <!-- 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> @@ -478,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="6564958083485073855">"電池電量不到 <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> @@ -488,6 +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> + <!-- 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> @@ -738,4 +849,12 @@ <string name="accessibility_binding_label" msgid="4148120742096474641">"協助工具"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"桌布"</string> <string name="chooser_wallpaper" msgid="7873476199295190279">"變更桌布"</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/include/media/IOMX.h b/include/media/IOMX.h index 10e0197..e551d17 100644 --- a/include/media/IOMX.h +++ b/include/media/IOMX.h @@ -84,9 +84,9 @@ public: 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 fill_buffer(node_id node, buffer_id buffer) = 0; - virtual void empty_buffer( + virtual status_t empty_buffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, 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/HTTPDataSource.h b/include/media/stagefright/HTTPDataSource.h index 0587c7c..02d95fd 100644 --- a/include/media/stagefright/HTTPDataSource.h +++ b/include/media/stagefright/HTTPDataSource.h @@ -19,10 +19,11 @@ #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); @@ -40,7 +41,7 @@ private: kBufferSize = 64 * 1024 }; - HTTPStream mHttp; + HTTPStream *mHttp; char *mHost; int mPort; char *mPath; 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/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..d48ea41 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -27,23 +27,23 @@ 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', }; @@ -62,6 +62,7 @@ public: TYPE_NONE = 'none', TYPE_C_STRING = 'cstr', TYPE_INT32 = 'in32', + TYPE_INT64 = 'in64', TYPE_FLOAT = 'floa', TYPE_POINTER = 'ptr ', }; @@ -71,11 +72,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/OMXCodec.h b/include/media/stagefright/OMXCodec.h index ff7e34a..ec043a9 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -124,6 +124,7 @@ private: bool mInitialBufferSubmit; bool mSignalledEOS; bool mNoMoreOutputData; + bool mOutputPortSettingsHaveChanged; int64_t mSeekTimeUs; Mutex mLock; 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/IOMX.cpp b/media/libmedia/IOMX.cpp index 0cec7bb..e74f1a9 100644 --- a/media/libmedia/IOMX.cpp +++ b/media/libmedia/IOMX.cpp @@ -289,15 +289,17 @@ public: return reply.readInt32(); } - virtual void fill_buffer(node_id node, buffer_id buffer) { + virtual status_t 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); + remote()->transact(FILL_BUFFER, data, &reply); + + return reply.readInt32(); } - virtual void empty_buffer( + virtual status_t empty_buffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, @@ -310,7 +312,9 @@ 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( @@ -601,7 +605,7 @@ status_t BnOMX::onTransact( node_id node = (void*)data.readIntPtr(); buffer_id buffer = (void*)data.readIntPtr(); - fill_buffer(node, buffer); + reply->writeInt32(fill_buffer(node, buffer)); return NO_ERROR; } @@ -617,9 +621,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( + empty_buffer( + node, buffer, range_offset, range_length, + flags, timestamp)); return NO_ERROR; } diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index f21eb73..ef41225 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 diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp index ddd4e24..9a0d692 100644 --- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp +++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp @@ -37,6 +37,7 @@ #include "VorbisMetadataRetriever.h" #include "MidiMetadataRetriever.h" #include "MetadataRetrieverClient.h" +#include "StagefrightMetadataRetriever.h" namespace android { @@ -105,9 +106,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..0e92162 --- /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); + + sp<DataSource> source; + if (!strncasecmp("file://", uri, 7)) { + sp<MmapSource> mmapSource = new MmapSource(uri + 7); + if (mmapSource->InitCheck() != OK) { + return ERROR_IO; + } + source = mmapSource; + } else if (!strncasecmp("http://", uri, 7)) { + source = new HTTPDataSource(uri); + source = new CachingDataSource(source, 64 * 1024, 10); + } else { + // Assume it's a filename. + sp<MmapSource> mmapSource = new MmapSource(uri); + if (mmapSource->InitCheck() != OK) { + return ERROR_IO; + } + source = mmapSource; + } + + mExtractor = MediaExtractor::Create(source); + + 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) { + LOGE("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) { + LOGE("no video track found."); + return NULL; + } + + sp<MediaSource> source = mExtractor->getTrack(i); + + if (source.get() == NULL) { + LOGE("unable to instantiate video track."); + return NULL; + } + + sp<MetaData> meta = source->getFormat(); + + sp<MediaSource> decoder = + OMXCodec::Create( + mClient.interface(), meta, false, source); + + if (decoder.get() == NULL) { + LOGE("unable to instantiate video decoder."); + + return NULL; + } + + decoder->start(); + + MediaBuffer *buffer; + status_t err; + do { + err = decoder->read(&buffer); + } while (err == INFO_FORMAT_CHANGED); + + if (err != OK) { + CHECK_EQ(buffer, NULL); + + LOGE("decoding frame failed."); + decoder->stop(); + + return NULL; + } + + LOGI("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/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp index 8d85ce2..1660351 100644 --- a/media/libstagefright/AMRExtractor.cpp +++ b/media/libstagefright/AMRExtractor.cpp @@ -201,10 +201,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 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/CameraSource.cpp b/media/libstagefright/CameraSource.cpp index 596ab67..40028a5 100644 --- a/media/libstagefright/CameraSource.cpp +++ b/media/libstagefright/CameraSource.cpp @@ -191,8 +191,7 @@ status_t CameraSource::read( *buffer = new CameraBuffer(frame); (*buffer)->meta_data()->clear(); - (*buffer)->meta_data()->setInt32(kKeyTimeScale, 15); - (*buffer)->meta_data()->setInt32(kKeyTimeUnits, count); + (*buffer)->meta_data()->setInt64(kKeyTime, (count * 1000000) / 15); (*buffer)->add_ref(); (*buffer)->setObserver(this); 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/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp index 698223b..fa92024 100644 --- a/media/libstagefright/HTTPDataSource.cpp +++ b/media/libstagefright/HTTPDataSource.cpp @@ -14,17 +14,19 @@ * limitations under the License. */ +#include "include/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,38 @@ HTTPDataSource::HTTPDataSource(const char *uri) mPort = port; mPath = strdup(path.c_str()); - status_t err = mHttp.connect(mHost, mPort); + status_t err = mHttp->connect(mHost, mPort); CHECK_EQ(err, OK); } 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); + status_t err = mHttp->connect(mHost, mPort); CHECK_EQ(err, OK); } 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) { + if (offset >= mBufferOffset + && offset < (off_t)(mBufferOffset + mBufferLength)) { size_t num_bytes_available = mBufferLength - (offset - mBufferOffset); size_t copy = num_bytes_available; @@ -119,19 +126,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 +150,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/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp index 7fd699f..b7dd9ba 100644 --- a/media/libstagefright/MP3Extractor.cpp +++ b/media/libstagefright/MP3Extractor.cpp @@ -147,7 +147,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 +171,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->read_at(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; @@ -338,10 +370,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); } } } @@ -492,8 +523,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..da714f8 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "MPEG4Extractor" #include <utils/Log.h> +#include "include/SampleTable.h" + #include <arpa/inet.h> #include <ctype.h> @@ -32,7 +34,6 @@ #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); @@ -390,7 +392,6 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } mLastTrack->timescale = ntohl(timescale); - mLastTrack->meta->setInt32(kKeyTimeScale, mLastTrack->timescale); int64_t duration; if (version == 1) { @@ -409,7 +410,8 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } duration = ntohl(duration32); } - mLastTrack->meta->setInt32(kKeyDuration, duration); + mLastTrack->meta->setInt64( + kKeyDuration, (duration * 1000000) / mLastTrack->timescale); *offset += chunk_size; break; @@ -722,7 +724,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 +732,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 +749,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); } @@ -879,8 +879,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; } @@ -959,8 +959,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; 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/MediaPlayerImpl.cpp b/media/libstagefright/MediaPlayerImpl.cpp index 2e609e3..3747a8d 100644 --- a/media/libstagefright/MediaPlayerImpl.cpp +++ b/media/libstagefright/MediaPlayerImpl.cpp @@ -18,6 +18,9 @@ #define LOG_TAG "MediaPlayerImpl" #include "utils/Log.h" +#include "include/string.h" +#include "include/HTTPStream.h" + #include <OMX_Component.h> #include <unistd.h> @@ -26,7 +29,6 @@ #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> @@ -250,6 +252,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 +270,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 +382,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 +610,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..8277c15 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; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index c4c6149..5201c5a 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; @@ -202,7 +205,7 @@ sp<OMXCodec> OMXCodec::Create( status_t err = omx->allocate_node(componentName, &node); if (err == OK) { - LOGI("Successfully allocated OMX node '%s'", componentName); + LOGV("Successfully allocated OMX node '%s'", componentName); break; } } @@ -217,11 +220,6 @@ 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; @@ -324,9 +322,10 @@ 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 0 if (!strcmp(componentName, "OMX.TI.Video.Decoder") && (profile != kAVCProfileBaseline || level > 39)) { // This stream exceeds the decoder's capabilities. @@ -334,6 +333,7 @@ sp<OMXCodec> OMXCodec::Create( LOGE("Profile and/or level exceed the decoder's capabilities."); return NULL; } +#endif } if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime)) { @@ -443,7 +443,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 @@ -476,7 +476,7 @@ status_t OMXCodec::setVideoPortFormatType( return UNKNOWN_ERROR; } - CODEC_LOGI("found a match."); + CODEC_LOGV("found a match."); status_t err = mOMX->set_parameter( mNode, OMX_IndexParamVideoPortFormat, &format, sizeof(format)); @@ -486,7 +486,7 @@ status_t OMXCodec::setVideoPortFormatType( 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)) { @@ -546,7 +546,7 @@ void OMXCodec::setVideoInputFormat( CHECK_EQ(err, OK); def.nBufferSize = (width * height * 2); // (width * height * 3) / 2; - CODEC_LOGI("Setting nBufferSize = %ld", def.nBufferSize); + CODEC_LOGV("Setting nBufferSize = %ld", def.nBufferSize); CHECK_EQ(def.eDomain, OMX_PortDomainVideo); @@ -562,7 +562,7 @@ void OMXCodec::setVideoInputFormat( 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)) { @@ -678,6 +678,7 @@ OMXCodec::OMXCodec( mInitialBufferSubmit(true), mSignalledEOS(false), mNoMoreOutputData(false), + mOutputPortSettingsHaveChanged(false), mSeekTimeUs(-1) { mPortStatus[kPortIndexInput] = ENABLED; mPortStatus[kPortIndexOutput] = ENABLED; @@ -987,12 +988,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); @@ -1083,6 +1080,9 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { if (mState == RECONFIGURING) { CHECK_EQ(portIndex, kPortIndexOutput); + initOutputFormat(mSource->getFormat()); + mOutputPortSettingsHaveChanged = true; + enablePortAsync(portIndex); status_t err = allocateBuffersOnPort(portIndex); @@ -1415,10 +1415,11 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { memcpy(info->mMem->pointer(), specific->mData, specific->mSize); } - mOMX->empty_buffer( + status_t err = mOMX->empty_buffer( mNode, info->mBuffer, 0, size, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG, 0); + CHECK_EQ(err, OK); info->mOwnedByComponent = true; @@ -1439,7 +1440,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) { @@ -1459,28 +1460,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; - + if (srcBuffer->meta_data()->findInt64(kKeyTime, ×tampUs)) { CODEC_LOGV("Calling empty_buffer on buffer %p (length %d)", info->mBuffer, srcLength); CODEC_LOGV("Calling empty_buffer with timestamp %lld us (%.2f secs)", - timestamp, timestamp / 1E6); + 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->empty_buffer( + mNode, info->mBuffer, 0, srcLength, + flags, timestampUs); + + if (err != OK) { + setState(ERROR); + return; + } + + info->mOwnedByComponent = true; } void OMXCodec::fillOutputBuffer(BufferInfo *info) { @@ -1493,7 +1495,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->fill_buffer(mNode, info->mBuffer); + CHECK_EQ(err, OK); info->mOwnedByComponent = true; } @@ -1784,6 +1787,7 @@ status_t OMXCodec::start(MetaData *) { mInitialBufferSubmit = true; mSignalledEOS = false; mNoMoreOutputData = false; + mOutputPortSettingsHaveChanged = false; mSeekTimeUs = -1; mFilledBuffers.clear(); @@ -1854,6 +1858,8 @@ status_t OMXCodec::stop() { } sp<MetaData> OMXCodec::getFormat() { + Mutex::Autolock autoLock(mLock); + return mOutputFormat; } @@ -1917,6 +1923,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()); @@ -2017,8 +2029,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) { diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp index 8efa7c7..5c5bb4d 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; } @@ -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; } @@ -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; } @@ -187,7 +188,7 @@ 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; } @@ -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; } diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp index 8e8f4fa..d420e6b 100644 --- a/media/libstagefright/ShoutcastSource.cpp +++ b/media/libstagefright/ShoutcastSource.cpp @@ -14,16 +14,17 @@ * limitations under the License. */ +#include "include/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/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..e05d911 100644 --- a/include/media/stagefright/HTTPStream.h +++ b/media/libstagefright/include/HTTPStream.h @@ -18,10 +18,11 @@ #define HTTP_STREAM_H_ +#include "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/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..34a0649 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); //////////////////////////////////////////////////////////////////////////// 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/string.h index 5dc7116..5dc7116 100644 --- a/include/media/stagefright/string.h +++ b/media/libstagefright/include/string.h diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk index 4cadccd..468221e 100644 --- a/media/libstagefright/omx/Android.mk +++ b/media/libstagefright/omx/Android.mk @@ -10,6 +10,7 @@ LOCAL_C_INCLUDES += $(TOP)/hardware/ti/omap3/liboverlay LOCAL_C_INCLUDES += $(JNI_H_INCLUDE) LOCAL_SRC_FILES:= \ + ColorConverter.cpp \ OMX.cpp \ QComHardwareRenderer.cpp \ SoftwareRenderer.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..8d100d9 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -25,11 +25,12 @@ #include "pv_omxcore.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> @@ -283,7 +284,7 @@ status_t OMX::allocate_node(const char *name, node_id *node) { &handle, const_cast<char *>(name), meta, &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; @@ -529,7 +530,7 @@ status_t OMX::observe_node( return OK; } -void OMX::fill_buffer(node_id node, buffer_id buffer) { +status_t OMX::fill_buffer(node_id node, buffer_id buffer) { OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; header->nFilledLen = 0; header->nOffset = 0; @@ -539,10 +540,11 @@ void OMX::fill_buffer(node_id node, buffer_id buffer) { OMX_ERRORTYPE err = OMX_FillThisBuffer(node_meta->handle(), header); - CHECK_EQ(err, OMX_ErrorNone); + + return (err == OMX_ErrorNone) ? OK : UNKNOWN_ERROR; } -void OMX::empty_buffer( +status_t OMX::empty_buffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, @@ -561,7 +563,8 @@ void OMX::empty_buffer( OMX_ERRORTYPE err = OMX_EmptyThisBuffer(node_meta->handle(), header); - CHECK_EQ(err, OMX_ErrorNone); + + return (err == OMX_ErrorNone) ? OK : UNKNOWN_ERROR; } status_t OMX::get_extension_index( diff --git a/media/libstagefright/omx/OMX.h b/media/libstagefright/omx/OMX.h index 6325f79..4c14dd9 100644 --- a/media/libstagefright/omx/OMX.h +++ b/media/libstagefright/omx/OMX.h @@ -70,9 +70,9 @@ public: 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 fill_buffer(node_id node, buffer_id buffer); - virtual void empty_buffer( + virtual status_t empty_buffer( node_id node, buffer_id buffer, OMX_U32 range_offset, OMX_U32 range_length, diff --git a/media/libstagefright/omx/QComHardwareRenderer.cpp b/media/libstagefright/omx/QComHardwareRenderer.cpp index e9930be..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 { @@ -83,6 +84,11 @@ void QComHardwareRenderer::render( } mISurface->postBuffer(offset); + + // Since we cannot tell how long it'll take until surface flinger + // has displayed the data onscreen, we'll just have to guess... + // We must not return the buffer to the decoder before it's been displayed. + usleep(25000); } bool QComHardwareRenderer::getOffset(void *platformPrivate, size_t *offset) { 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..a4a1937 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/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/VpnServices/res/values-zh-rCN/strings.xml b/packages/VpnServices/res/values-zh-rCN/strings.xml index 4de0f38..940e210 100644 --- a/packages/VpnServices/res/values-zh-rCN/strings.xml +++ b/packages/VpnServices/res/values-zh-rCN/strings.xml @@ -15,8 +15,8 @@ --> <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 服务"</string> + <string name="app_label" msgid="4589592829302498102">"虚拟专用网服务"</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">"触摸可重新连接 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..3a4c3ac --- /dev/null +++ b/services/java/com/android/server/DropBoxService.java @@ -0,0 +1,725 @@ +/* + * 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.DropBoxEntry; +import android.os.IDropBox; +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 java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +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 IDropBox} using the filesystem. + * + * {@hide} + */ +public final class DropBoxService extends IDropBox.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 addText(String tag, String data) { + addData(tag, data.getBytes(), DropBoxEntry.IS_TEXT); + } + + public void addData(String tag, byte[] data, int flags) { + File temp = null; + OutputStream out = null; + try { + if ((flags & DropBoxEntry.IS_EMPTY) != 0) throw new IllegalArgumentException(); + + init(); + if (!isTagEnabled(tag)) return; + + long max = trimToFit(); + if (data.length > max) { + Log.w(TAG, "Dropping: " + tag + " (" + data.length + " > " + max + " bytes)"); + // Pass temp = null to createEntry() to leave a tombstone + } else { + temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp"); + out = new FileOutputStream(temp); + if (data.length > mBlockSize && ((flags & DropBoxEntry.IS_GZIPPED) == 0)) { + flags = flags | DropBoxEntry.IS_GZIPPED; + out = new GZIPOutputStream(out); + } + out.write(data); + out.close(); + out = null; + } + + createEntry(temp, tag, flags); + temp = null; + } catch (IOException e) { + Log.e(TAG, "Can't write: " + tag, e); + } finally { + try { if (out != null) out.close(); } catch (IOException e) {} + if (temp != null) temp.delete(); + } + } + + public void addFile(String tag, ParcelFileDescriptor data, int flags) { + File temp = null; + OutputStream output = null; + try { + if ((flags & DropBoxEntry.IS_EMPTY) != 0) throw new IllegalArgumentException(); + + init(); + if (!isTagEnabled(tag)) return; + long max = trimToFit(); + long lastTrim = System.currentTimeMillis(); + + byte[] buffer = new byte[mBlockSize]; + FileInputStream input = new FileInputStream(data.getFileDescriptor()); + + // 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 & DropBoxEntry.IS_GZIPPED) == 0)) { + output = new GZIPOutputStream(output); + flags = flags | DropBoxEntry.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) {} + try { data.close(); } catch (IOException e) {} + 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 DropBoxEntry getNextEntry(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; + } + + for (EntryFile entry : mAllFiles.contents.tailSet(new EntryFile(millis + 1))) { + if (entry.tag == null) continue; + try { + File file = (entry.flags & DropBoxEntry.IS_EMPTY) != 0 ? null : entry.file; + return new DropBoxEntry(entry.tag, entry.timestampMillis, 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 & DropBoxEntry.IS_EMPTY) != 0) { + pw.println(" (contents lost)"); + continue; + } else { + pw.print((entry.flags & DropBoxEntry.IS_GZIPPED) != 0 ? " (comopressed " : " ("); + pw.print((entry.flags & DropBoxEntry.IS_TEXT) != 0 ? "text" : "data"); + pw.format(", %d bytes)", entry.file.length()); + pw.println(); + } + + if (doFile || (doPrint && (entry.flags & DropBoxEntry.IS_TEXT) == 0)) { + if (!doPrint) pw.print(" "); + pw.println(entry.file.getPath()); + } + + if ((entry.flags & DropBoxEntry.IS_TEXT) != 0 && (doPrint || !doFile)) { + DropBoxEntry dbe = null; + try { + dbe = new DropBoxEntry( + 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 (from {@link DropBoxEntry}) + * @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 & DropBoxEntry.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 & DropBoxEntry.IS_TEXT) != 0 ? ".txt" : ".dat") + + ((flags & DropBoxEntry.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 = DropBoxEntry.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 = DropBoxEntry.IS_EMPTY; + return; + } + + int flags = 0; + this.tag = Uri.decode(name.substring(0, at)); + if (name.endsWith(".gz")) { + flags |= DropBoxEntry.IS_GZIPPED; + name = name.substring(0, name.length() - 3); + } + if (name.endsWith(".lost")) { + flags |= DropBoxEntry.IS_EMPTY; + name = name.substring(at + 1, name.length() - 5); + } else if (name.endsWith(".txt")) { + flags |= DropBoxEntry.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 = DropBoxEntry.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 = DropBoxEntry.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 & DropBoxEntry.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..1a7416a 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("dropbox", + 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..89d969d 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -542,7 +542,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 +675,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 +741,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 +904,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; } 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..77ebf8d --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java @@ -0,0 +1,479 @@ +/* + * 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.DropBoxEntry; +import android.os.IDropBox; +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 IDropBox} 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 { + IDropBox dropbox = IDropBox.Stub.asInterface(ServiceManager.getService("dropbox")); + 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(); + + DropBoxEntry e0 = getNextTestEntry(dropbox, before); + DropBoxEntry e1 = getNextTestEntry(dropbox, e0.getTimeMillis()); + DropBoxEntry e2 = getNextTestEntry(dropbox, e1.getTimeMillis()); + assertTrue(null == getNextTestEntry(dropbox, 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 { + IDropBox dropbox = IDropBox.Stub.asInterface(ServiceManager.getService("dropbox")); + long before = System.currentTimeMillis(); + dropbox.addData("DropBoxTest", "TEST".getBytes(), 0); + long after = System.currentTimeMillis(); + + DropBoxEntry e = getNextTestEntry(dropbox, before); + assertTrue(null == getNextTestEntry(dropbox, 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(); + + IDropBox dropbox = IDropBox.Stub.asInterface(ServiceManager.getService("dropbox")); + 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, DropBoxEntry.IS_TEXT); + dropbox.addFile("DropBoxTest", pfd1, DropBoxEntry.IS_TEXT | DropBoxEntry.IS_GZIPPED); + dropbox.addFile("DropBoxTest", pfd2, 0); + dropbox.addFile("DropBoxTest", pfd3, DropBoxEntry.IS_GZIPPED); + + pfd0.close(); + pfd1.close(); + pfd2.close(); + pfd3.close(); + + DropBoxEntry e0 = getNextTestEntry(dropbox, before); + DropBoxEntry e1 = getNextTestEntry(dropbox, e0.getTimeMillis()); + DropBoxEntry e2 = getNextTestEntry(dropbox, e1.getTimeMillis()); + DropBoxEntry e3 = getNextTestEntry(dropbox, e2.getTimeMillis()); + assertTrue(null == getNextTestEntry(dropbox, e3.getTimeMillis())); + + assertTrue(e0.getTimeMillis() > before); + assertTrue(e1.getTimeMillis() > e0.getTimeMillis()); + assertTrue(e2.getTimeMillis() > e1.getTimeMillis()); + assertTrue(e3.getTimeMillis() > e2.getTimeMillis()); + + assertEquals(DropBoxEntry.IS_TEXT, e0.getFlags()); + assertEquals(DropBoxEntry.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 dropbox = new DropBoxService(getContext(), dir); + + // Until a write, the timestamps are taken at face value + DropBoxEntry e0 = getNextTestEntry(dropbox, before); + DropBoxEntry e1 = getNextTestEntry(dropbox, e0.getTimeMillis()); + DropBoxEntry e2 = getNextTestEntry(dropbox, e1.getTimeMillis()); + DropBoxEntry e3 = getNextTestEntry(dropbox, e2.getTimeMillis()); + assertTrue(null == getNextTestEntry(dropbox, 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 = getNextTestEntry(dropbox, before); + e1 = getNextTestEntry(dropbox, e0.getTimeMillis()); + e2 = getNextTestEntry(dropbox, e1.getTimeMillis()); + e3 = getNextTestEntry(dropbox, e2.getTimeMillis()); + assertTrue(null == getNextTestEntry(dropbox, 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(); + dropbox.stop(); + } + + public void testIsTagEnabled() throws Exception { + IDropBox dropbox = IDropBox.Stub.asInterface(ServiceManager.getService("dropbox")); + 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")); + + DropBoxEntry e0 = getNextTestEntry(dropbox, before); + DropBoxEntry e1 = getNextTestEntry(dropbox, e0.getTimeMillis()); + assertTrue(null == getNextTestEntry(dropbox, e1.getTimeMillis())); + + assertEquals("TEST-ENABLED", e0.getText(80)); + assertEquals("TEST-ENABLED-AGAIN", e1.getText(80)); + + e0.close(); + e1.close(); + } + + 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 dropbox = new DropBoxService(getContext(), dir); + 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); + + DropBoxEntry e0 = getNextTestEntry(dropbox, before); + DropBoxEntry e1 = getNextTestEntry(dropbox, e0.getTimeMillis()); + DropBoxEntry e2 = getNextTestEntry(dropbox, e1.getTimeMillis()); + DropBoxEntry e3 = getNextTestEntry(dropbox, e2.getTimeMillis()); + DropBoxEntry e4 = getNextTestEntry(dropbox, e3.getTimeMillis()); + DropBoxEntry e5 = getNextTestEntry(dropbox, e4.getTimeMillis()); + DropBoxEntry e6 = getNextTestEntry(dropbox, e5.getTimeMillis()); + DropBoxEntry e7 = getNextTestEntry(dropbox, e6.getTimeMillis()); + DropBoxEntry e8 = getNextTestEntry(dropbox, e7.getTimeMillis()); + DropBoxEntry e9 = getNextTestEntry(dropbox, e8.getTimeMillis()); + assertTrue(null == getNextTestEntry(dropbox, 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(); + dropbox.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 dropbox = new DropBoxService(getContext(), dir); + dropbox.addText("DropBoxTest", "TEST"); + addRandomEntry(dropbox, "DropBoxTest", blockSize * 20); + + // Verify that things are as expected + DropBoxEntry e0 = getNextTestEntry(dropbox, before); + DropBoxEntry e1 = getNextTestEntry(dropbox, e0.getTimeMillis()); + assertTrue(null == getNextTestEntry(dropbox, 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 = getNextTestEntry(dropbox, before); + assertTrue(null == getNextTestEntry(dropbox, 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 dropbox = new DropBoxService(getContext(), dir); + + dropbox.addText("DropBoxTest", "should be ignored"); + dropbox.addData("DropBoxTest", "should be ignored".getBytes(), 0); + assertTrue(null == getNextTestEntry(dropbox, 0)); + + dir.delete(); // Remove the file so a directory can be created + dropbox.addText("DropBoxTest", "TEST"); + DropBoxEntry e = getNextTestEntry(dropbox, 0); + assertTrue(null == getNextTestEntry(dropbox, e.getTimeMillis())); + assertEquals("DropBoxTest", e.getTag()); + assertEquals("TEST", e.getText(80)); + e.close(); + dropbox.stop(); + } + + private void addRandomEntry(IDropBox 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(DropBoxEntry 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; + } + + private DropBoxEntry getNextTestEntry(IDropBox dropbox, long millis) throws Exception { + for (;;) { + DropBoxEntry entry = dropbox.getNextEntry(millis); + if (entry == null || entry.getTag().startsWith("DropBoxTest")) return entry; + entry.close(); + millis = entry.getTimeMillis(); + } + } +} diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/MockCursor.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/MockCursor.java new file mode 100644 index 0000000..2523b0d --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/MockCursor.java @@ -0,0 +1,254 @@ +/* + * 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.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; + +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/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..3acd6c1 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.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, 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, 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..3f10605 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java @@ -0,0 +1,409 @@ +/* + * 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.vcard.PropertyNodesVerifier.TypeSet; + +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.Photo; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; +import android.util.Log; + +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; + +import junit.framework.TestCase; + +/** + * 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 { + /* package */ 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; + int mCount; + + public VCardVerificationHandler(TestCase testCase, boolean isV30) { + mTestCase = testCase; + mPropertyNodesVerifierList = new ArrayList<PropertyNodesVerifier>(); + mIsV30 = isV30; + mCount = 1; + } + + public PropertyNodesVerifier addNewPropertyNodesVerifier() { + PropertyNodesVerifier propertyNodesVerifier = new PropertyNodesVerifier(mTestCase); + mPropertyNodesVerifierList.add(propertyNodesVerifier); + return propertyNodesVerifier; + } + + public boolean onInit(Context context) { + return true; + } + + public boolean onEntryCreated(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++; + } + return true; + } + + 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 //// + + public void testSimple() { + 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, false); + handler.addNewPropertyNodesVerifier() + .addNodeWithOrder("VERSION", "2.1") + .addNodeWithoutOrder("FN", "Roid Ando") + .addNodeWithoutOrder("N", "Ando;Roid;;;", Arrays.asList("Ando", "Roid", "", "", "")); + + VCardComposer composer = new VCardComposer(new CustomMockContext(resolver), + VCardConfig.VCARD_TYPE_V21_GENERIC); + 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(); + } + + private void testPhotoCommon(boolean isV30) { + 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, isV30); + handler.addNewPropertyNodesVerifier() + .addNodeWithOrder("VERSION", (isV30 ? "3.0" : "2.1")) + .addNodeWithoutOrder("FN", "PhotoTest") + .addNodeWithoutOrder("N", "PhotoTest;;;;", Arrays.asList("PhotoTest", "", "", "", "")) + .addNodeWithOrder("PHOTO", null, null, sPhotoByteArray, + contentValuesForPhoto, new TypeSet("JPEG"), null); + + int vcardType = (isV30 ? VCardConfig.VCARD_TYPE_V30_GENERIC + : VCardConfig.VCARD_TYPE_V21_GENERIC); + 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 testPhotoV21() { + testPhotoCommon(false); + } + + public void testPhotoV30() { + testPhotoCommon(true); + } +} 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..c6f827d --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java @@ -0,0 +1,1297 @@ +/* + * 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.R; +import com.android.unit_tests.vcard.PropertyNodesVerifier.TypeSet; + +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; + +public class VCardImporterTests extends AndroidTestCase { + // 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 class VerificationResolver extends MockContentResolver { + VerificationProvider mVerificationProvider = new VerificationProvider(); + @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)); + + 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; + } + + class VerificationProvider extends MockContentProvider { + final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues; + + public VerificationProvider() { + 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); + } + } + } + + /** + * 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 equalsString(String a, String b) { + if (a == null || a.length() == 0) { + return b == null || b.length() == 0; + } else { + return a.equals(b); + } + } + + private class ContactStructVerifier { + private final int mResourceId; + private final int mVCardType; + private final VerificationResolver mResolver; + // private final String mCharset; + public ContactStructVerifier(int resId, int vCardType) { + mResourceId = resId; + mVCardType = vCardType; + mResolver = new VerificationResolver(); + } + + 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(); + } + } + + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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 VCARD_TYPE_V21_GENERIC. + */ + public void testV21Japanese1_Type_Generic() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC); + testV21Japanese1Common(verifier, false); + } + + /** + * Verifies vCard with Japanese can be parsed correctly with VCARD_TYPE_V21_JAPANESE. + */ + public void testV21Japanese1_Type_Japanese() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE); + testV21Japanese1Common(verifier, true); + } + + /** + * Verifies vCard with Japanese can be parsed correctly with 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() throws IOException, VCardException { + ContactStructVerifier verifier = new ContactStructVerifier( + R.raw.v21_japanese_2, VCardConfig.VCARD_TYPE_V21_GENERIC); + 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/VCardTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java index 7589ba8..b4bb14b 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java @@ -1,923 +1,15 @@ -/* - * 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; +import com.android.unit_tests.AndroidTests; -public class VCardTests extends AndroidTestCase { +import android.test.suitebuilder.TestSuiteBuilder; - // 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)); - } - } - } - }*/ +import junit.framework.TestSuite; - 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)); +public class VCardTests 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/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/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index 234e5b2..1ac13f2 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -80,11 +80,13 @@ public: void setValues(bool val) { mValues = val; } int getCompressionMethod(void) const { return mCompressionMethod; } void setCompressionMethod(int val) { mCompressionMethod = val; } + bool getJunkPath(void) const { return mJunkPath; } + void setJunkPath(bool val) { mJunkPath = val; } const char* getOutputAPKFile() const { return mOutputAPKFile; } void setOutputAPKFile(const char* val) { mOutputAPKFile = val; } - /* - * Input options. + /* + * Input options. */ const char* getAssetSourceDir() const { return mAssetSourceDir; } void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; } @@ -119,7 +121,7 @@ public: void setVersionCode(const char* val) { mVersionCode = val; } const char* getVersionName() const { return mVersionName; } void setVersionName(const char* val) { mVersionName = val; } - + /* * Set and get the file specification. * @@ -161,6 +163,7 @@ private: bool mPseudolocalize; bool mValues; int mCompressionMethod; + bool mJunkPath; const char* mOutputAPKFile; const char* mAssetSourceDir; const char* mProguardFile; @@ -173,13 +176,13 @@ private: android::Vector<const char*> mJarFiles; android::Vector<const char*> mNoCompressExtensions; android::Vector<const char*> mResourceSourceDirs; - + const char* mMinSdkVersion; const char* mTargetSdkVersion; const char* mMaxSdkVersion; const char* mVersionCode; const char* mVersionName; - + /* file specification */ int mArgc; char* const* mArgv; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 4742341..1a536d6 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -848,7 +848,7 @@ int doDump(Bundle* bundle) printf("uses-feature:'android.hardware.camera'\n"); printf("uses-feature:'android.hardware.camera.autofocus'\n"); } - + if (hasMainActivity) { printf("main\n"); } @@ -997,8 +997,15 @@ int doAdd(Bundle* bundle) printf(" '%s'... (from gzip)\n", fileName); result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL); } else { - printf(" '%s'...\n", fileName); - result = zip->add(fileName, bundle->getCompressionMethod(), NULL); + if (bundle->getJunkPath()) { + String8 storageName = String8(fileName).getPathLeaf(); + printf(" '%s' as '%s'...\n", fileName, storageName.string()); + result = zip->add(fileName, storageName.string(), + bundle->getCompressionMethod(), NULL); + } else { + printf(" '%s'...\n", fileName); + result = zip->add(fileName, bundle->getCompressionMethod(), NULL); + } } if (result != NO_ERROR) { fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName); diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index e61010c..98286c0 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -99,6 +99,7 @@ void usage(void) " -f force overwrite of existing files\n" " -g specify a pixel tolerance to force images to grayscale, default 0\n" " -j specify a jar or zip file containing classes to include\n" + " -k junk path of file(s) added\n" " -m make package directories under location specified by -J\n" #if 0 " -p pseudolocalize the default configuration\n" @@ -236,6 +237,9 @@ int main(int argc, char* const argv[]) bundle.setGrayscaleTolerance(tolerance); printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance); break; + case 'k': + bundle.setJunkPath(true); + break; case 'm': bundle.setMakePackageDirs(true); break; diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index 2c0d0f1..b7d3a6e 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -90,6 +90,7 @@ public class WifiStateTracker extends NetworkStateTracker { */ 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; /** * Interval in milliseconds between polling for connection @@ -126,6 +127,14 @@ public class WifiStateTracker extends NetworkStateTracker { private static final int RECONNECT_DELAY_MSECS = 2000; /** + * When the supplicant disconnects from an AP it sometimes forgets + * to restart scanning. Wait this delay before asking it to start + * scanning (in case it forgot). 15 sec is the standard delay between + * scans. + */ + private static final int KICKSTART_SCANNING_DELAY_MSECS = 15000; + + /** * The maximum number of times we will retry a connection to an access point * for which we have failed in acquiring an IP address from DHCP. A value of * N means that we will make N+1 connection attempts in all. @@ -149,6 +158,14 @@ public class WifiStateTracker extends NetworkStateTracker { private int mNumSupplicantLoopIterations = 0; /** + * The current number of supplicant state changes. This is used to determine + * if we've received any new info since we found out it was DISCONNECTED or + * INACTIVE. If we haven't for X ms, we then request a scan - it should have + * done that automatically, but sometimes some firmware does not. + */ + private int mNumSupplicantStateChanges = 0; + + /** * True if we received an event that that a password-key may be incorrect. * If the next incoming supplicant state change event is DISCONNECT, * broadcast a message that we have a possible password error and disable @@ -831,7 +848,16 @@ public class WifiStateTracker extends NetworkStateTracker { } break; + case EVENT_MAYBE_START_SCAN_POST_DISCONNECT: + // Only do this if we haven't gotten a new supplicant status since the timer + // started + if (mNumSupplicantStateChanges == msg.arg1) { + WifiNative.scanCommand(false); // do a passive scan + } + break; + case EVENT_SUPPLICANT_STATE_CHANGED: + mNumSupplicantStateChanges++; SupplicantStateChangeResult supplicantStateResult = (SupplicantStateChangeResult) msg.obj; SupplicantState newState = supplicantStateResult.state; @@ -850,6 +876,17 @@ public class WifiStateTracker extends NetworkStateTracker { int networkId = supplicantStateResult.networkId; /* + * If we get disconnect or inactive we need to start our + * watchdog timer to start a scan + */ + if (newState == SupplicantState.DISCONNECTED || + newState == SupplicantState.INACTIVE) { + sendMessageDelayed(obtainMessage(EVENT_MAYBE_START_SCAN_POST_DISCONNECT, + mNumSupplicantStateChanges, 0), KICKSTART_SCANNING_DELAY_MSECS); + } + + + /* * Did we get to DISCONNECTED state due to an * authentication (password) failure? */ |