am ace8addc: am 2edd6826: Create a new ImeOption that disables fullscreen in landscape, and use it.

Merge commit 'ace8addce47efc03be5038eef48d7fb066b14aae'

* commit 'ace8addce47efc03be5038eef48d7fb066b14aae':
  Create a new ImeOption that disables fullscreen in landscape, and use it.
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index 6419a5c..fae1f26 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -16,7 +16,6 @@
 ** limitations under the License.
 */
 
-//#define LOG_NDEBUG 0
 #define LOG_TAG "CameraService"
 #include <utils/Log.h>
 
@@ -95,7 +94,7 @@
 sp<ICamera> CameraService::connect(const sp<ICameraClient>& cameraClient)
 {
     int callingPid = getCallingPid();
-    LOGD("CameraService::connect E (pid %d, client %p)", callingPid,
+    LOGV("CameraService::connect E (pid %d, client %p)", callingPid,
             cameraClient->asBinder().get());
 
     Mutex::Autolock lock(mServiceLock);
@@ -106,30 +105,30 @@
             sp<ICameraClient> currentCameraClient(currentClient->getCameraClient());
             if (cameraClient->asBinder() == currentCameraClient->asBinder()) {
                 // This is the same client reconnecting...
-                LOGD("CameraService::connect X (pid %d, same client %p) is reconnecting...",
+                LOGV("CameraService::connect X (pid %d, same client %p) is reconnecting...",
                     callingPid, cameraClient->asBinder().get());
                 return currentClient;
             } else {
                 // It's another client... reject it
-                LOGD("CameraService::connect X (pid %d, new client %p) rejected. "
+                LOGV("CameraService::connect X (pid %d, new client %p) rejected. "
                     "(old pid %d, old client %p)",
                     callingPid, cameraClient->asBinder().get(),
                     currentClient->mClientPid, currentCameraClient->asBinder().get());
                 if (kill(currentClient->mClientPid, 0) == -1 && errno == ESRCH) {
-                    LOGD("The old client is dead!");
+                    LOGV("The old client is dead!");
                 }
                 return client;
             }
         } else {
             // can't promote, the previous client has died...
-            LOGD("New client (pid %d) connecting, old reference was dangling...",
+            LOGV("New client (pid %d) connecting, old reference was dangling...",
                     callingPid);
             mClient.clear();
         }
     }
 
     if (mUsers > 0) {
-        LOGD("Still have client, rejected");
+        LOGV("Still have client, rejected");
         return client;
     }
 
@@ -141,7 +140,7 @@
     // the refcount.
     client->trackMe(true, true);
 #endif
-    LOGD("CameraService::connect X");
+    LOGV("CameraService::connect X");
     return client;
 }
 
@@ -157,7 +156,7 @@
 
     if (mClient == 0) {
         // This happens when we have already disconnected.
-        LOGD("removeClient (pid %d): already disconnected", callingPid);
+        LOGV("removeClient (pid %d): already disconnected", callingPid);
         return;
     }
 
@@ -165,7 +164,7 @@
     // Client::~Client() -> disconnect() -> removeClient().
     client = mClient.promote();
     if (client == 0) {
-        LOGD("removeClient (pid %d): no more strong reference", callingPid);
+        LOGV("removeClient (pid %d): no more strong reference", callingPid);
         mClient.clear();
         return;
     }
@@ -178,7 +177,7 @@
         mClient.clear();
     }
 
-    LOGD("removeClient (pid %d) done", callingPid);
+    LOGV("removeClient (pid %d) done", callingPid);
 }
 
 // The reason we need this count is a new CameraService::connect() request may
@@ -212,7 +211,7 @@
         const sp<ICameraClient>& cameraClient, pid_t clientPid)
 {
     int callingPid = getCallingPid();
-    LOGD("Client::Client E (pid %d)", callingPid);
+    LOGV("Client::Client E (pid %d)", callingPid);
     mCameraService = cameraService;
     mCameraClient = cameraClient;
     mClientPid = clientPid;
@@ -237,7 +236,7 @@
     // Callback is disabled by default
     mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
     cameraService->incUsers();
-    LOGD("Client::Client X (pid %d)", callingPid);
+    LOGV("Client::Client X (pid %d)", callingPid);
 }
 
 status_t CameraService::Client::checkPid()
@@ -253,7 +252,7 @@
 status_t CameraService::Client::lock()
 {
     int callingPid = getCallingPid();
-    LOGD("lock from pid %d (mClientPid %d)", callingPid, mClientPid);
+    LOGV("lock from pid %d (mClientPid %d)", callingPid, mClientPid);
     Mutex::Autolock _l(mLock);
     // lock camera to this client if the the camera is unlocked
     if (mClientPid == 0) {
@@ -267,13 +266,13 @@
 status_t CameraService::Client::unlock()
 {
     int callingPid = getCallingPid();
-    LOGD("unlock from pid %d (mClientPid %d)", callingPid, mClientPid);
+    LOGV("unlock from pid %d (mClientPid %d)", callingPid, mClientPid);
     Mutex::Autolock _l(mLock);
     // allow anyone to use camera
     status_t result = checkPid();
     if (result == NO_ERROR) {
         mClientPid = 0;
-        LOGD("clear mCameraClient (pid %d)", callingPid);
+        LOGV("clear mCameraClient (pid %d)", callingPid);
         // we need to remove the reference so that when app goes
         // away, the reference count goes to 0.
         mCameraClient.clear();
@@ -286,7 +285,7 @@
     int callingPid = getCallingPid();
 
     // connect a new process to the camera
-    LOGD("Client::connect E (pid %d, client %p)", callingPid, client->asBinder().get());
+    LOGV("Client::connect E (pid %d, client %p)", callingPid, client->asBinder().get());
 
     // I hate this hack, but things get really ugly when the media recorder
     // service is handing back the camera to the app. The ICameraClient
@@ -310,14 +309,14 @@
 
             // did the client actually change?
             if ((mCameraClient != NULL) && (client->asBinder() == mCameraClient->asBinder())) {
-                LOGD("Connect to the same client");
+                LOGV("Connect to the same client");
                 return NO_ERROR;
             }
 
             mCameraClient = client;
             mClientPid = -1;
             mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
-            LOGD("Connect to the new client (pid %d, client %p)",
+            LOGV("Connect to the new client (pid %d, client %p)",
                 callingPid, mCameraClient->asBinder().get());
         }
 
@@ -344,7 +343,7 @@
     int callingPid = getCallingPid();
 
     // tear down client
-    LOGD("Client::~Client E (pid %d, client %p)",
+    LOGV("Client::~Client E (pid %d, client %p)",
             callingPid, getCameraClient()->asBinder().get());
     if (mSurface != 0 && !mUseOverlay) {
 #if HAVE_ANDROID_OS
@@ -373,23 +372,23 @@
     // make sure we tear down the hardware
     mClientPid = callingPid;
     disconnect();
-    LOGD("Client::~Client X (pid %d)", mClientPid);
+    LOGV("Client::~Client X (pid %d)", mClientPid);
 }
 
 void CameraService::Client::disconnect()
 {
     int callingPid = getCallingPid();
 
-    LOGD("Client::disconnect() E (pid %d client %p)",
+    LOGV("Client::disconnect() E (pid %d client %p)",
             callingPid, getCameraClient()->asBinder().get());
 
     Mutex::Autolock lock(mLock);
     if (mClientPid <= 0) {
-        LOGD("camera is unlocked (mClientPid = %d), don't tear down hardware", mClientPid);
+        LOGV("camera is unlocked (mClientPid = %d), don't tear down hardware", mClientPid);
         return;
     }
     if (checkPid() != NO_ERROR) {
-        LOGD("Different client - don't disconnect");
+        LOGV("Different client - don't disconnect");
         return;
     }
 
@@ -397,7 +396,7 @@
     // from the user directly, or called by the destructor.
     if (mHardware == 0) return;
 
-    LOGD("hardware teardown");
+    LOGV("hardware teardown");
     // Before destroying mHardware, we must make sure it's in the
     // idle state.
     mHardware->stopPreview();
@@ -421,13 +420,13 @@
     mCameraService->removeClient(mCameraClient);
     mCameraService->decUsers();
 
-    LOGD("Client::disconnect() X (pid %d)", callingPid);
+    LOGV("Client::disconnect() X (pid %d)", callingPid);
 }
 
 // pass the buffered ISurface to the camera service
 status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface)
 {
-    LOGD("setPreviewDisplay(%p) (pid %d)",
+    LOGV("setPreviewDisplay(%p) (pid %d)",
          ((surface == NULL) ? NULL : surface.get()), getCallingPid());
     Mutex::Autolock lock(mLock);
     status_t result = checkPid();
@@ -438,7 +437,7 @@
     // asBinder() is safe on NULL (returns NULL)
     if (surface->asBinder() != mSurface->asBinder()) {
         if (mSurface != 0) {
-            LOGD("clearing old preview surface %p", mSurface.get());
+            LOGV("clearing old preview surface %p", mSurface.get());
             if ( !mUseOverlay)
             {
                 mSurface->unregisterBuffers();
@@ -487,7 +486,7 @@
 {
     int callingPid = getCallingPid();
 
-    LOGD("startCameraMode(%d) (pid %d)", mode, callingPid);
+    LOGV("startCameraMode(%d) (pid %d)", mode, callingPid);
 
     /* we cannot call into mHardware with mLock held because
      * mHardware has callbacks onto us which acquire this lock
@@ -512,7 +511,7 @@
 
     default: // CAMERA_PREVIEW_MODE
         if (mSurface == 0) {
-            LOGD("mSurface is not set yet.");
+            LOGV("mSurface is not set yet.");
         }
         return startPreviewMode();
     }
@@ -520,7 +519,7 @@
 
 status_t CameraService::Client::startRecordingMode()
 {
-    LOGD("startRecordingMode (pid %d)", getCallingPid());
+    LOGV("startRecordingMode (pid %d)", getCallingPid());
 
     status_t ret = UNKNOWN_ERROR;
 
@@ -547,7 +546,7 @@
 
 status_t CameraService::Client::setOverlay()
 {
-    LOGD("setOverlay");
+    LOGV("setOverlay");
     int w, h;
     CameraParameters params(mHardware->getParameters());
     params.getPreviewSize(&w, &h);
@@ -573,7 +572,7 @@
             for (int retry = 0; retry < 50; ++retry) {
                 mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT);
                 if (mOverlayRef != NULL) break;
-                LOGD("Overlay create failed - retrying");
+                LOGW("Overlay create failed - retrying");
                 usleep(20000);
             }
             if ( mOverlayRef.get() == NULL )
@@ -623,7 +622,7 @@
 
 status_t CameraService::Client::startPreviewMode()
 {
-    LOGD("startPreviewMode (pid %d)", getCallingPid());
+    LOGV("startPreviewMode (pid %d)", getCallingPid());
 
     // if preview has been enabled, nothing needs to be done
     if (mHardware->previewEnabled()) {
@@ -659,14 +658,14 @@
 
 status_t CameraService::Client::startPreview()
 {
-    LOGD("startPreview (pid %d)", getCallingPid());
+    LOGV("startPreview (pid %d)", getCallingPid());
 
     return startCameraMode(CAMERA_PREVIEW_MODE);
 }
 
 status_t CameraService::Client::startRecording()
 {
-    LOGD("startRecording (pid %d)", getCallingPid());
+    LOGV("startRecording (pid %d)", getCallingPid());
 
     if (mMediaPlayerBeep.get() != NULL) {
         // do not play record jingle if stream volume is 0
@@ -687,7 +686,7 @@
 // stop preview mode
 void CameraService::Client::stopPreview()
 {
-    LOGD("stopPreview (pid %d)", getCallingPid());
+    LOGV("stopPreview (pid %d)", getCallingPid());
 
     // hold main lock during state transition
     {
@@ -701,7 +700,7 @@
 
         mHardware->stopPreview();
         mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-        LOGD("stopPreview(), hardware stopped OK");
+        LOGV("stopPreview(), hardware stopped OK");
 
         if (mSurface != 0 && !mUseOverlay) {
             mSurface->unregisterBuffers();
@@ -718,7 +717,7 @@
 // stop recording mode
 void CameraService::Client::stopRecording()
 {
-    LOGD("stopRecording (pid %d)", getCallingPid());
+    LOGV("stopRecording (pid %d)", getCallingPid());
 
     // hold main lock during state transition
     {
@@ -737,7 +736,7 @@
 
         mHardware->stopRecording();
         mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME);
-        LOGD("stopRecording(), hardware stopped OK");
+        LOGV("stopRecording(), hardware stopped OK");
     }
 
     // hold preview buffer lock
@@ -805,14 +804,14 @@
     int nw, cnt = 0;
     uint32_t written = 0;
 
-    LOGD("opening file [%s]\n", fname);
+    LOGV("opening file [%s]\n", fname);
     int fd = open(fname, O_RDWR | O_CREAT);
     if (fd < 0) {
         LOGE("failed to create file [%s]: %s", fname, strerror(errno));
         return;
     }
 
-    LOGD("writing %d bytes to file [%s]\n", size, fname);
+    LOGV("writing %d bytes to file [%s]\n", size, fname);
     while (written < size) {
         nw = ::write(fd,
                      buf + written,
@@ -825,7 +824,7 @@
         written += nw;
         cnt++;
     }
-    LOGD("done writing %d bytes to file [%s] in %d passes\n",
+    LOGV("done writing %d bytes to file [%s] in %d passes\n",
          size, fname, cnt);
     ::close(fd);
 }
@@ -833,7 +832,7 @@
 
 status_t CameraService::Client::autoFocus()
 {
-    LOGD("autoFocus (pid %d)", getCallingPid());
+    LOGV("autoFocus (pid %d)", getCallingPid());
 
     Mutex::Autolock lock(mLock);
     status_t result = checkPid();
@@ -849,7 +848,7 @@
 
 status_t CameraService::Client::cancelAutoFocus()
 {
-    LOGD("cancelAutoFocus (pid %d)", getCallingPid());
+    LOGV("cancelAutoFocus (pid %d)", getCallingPid());
 
     Mutex::Autolock lock(mLock);
     status_t result = checkPid();
@@ -866,7 +865,7 @@
 // take a picture - image is returned in callback
 status_t CameraService::Client::takePicture()
 {
-    LOGD("takePicture (pid %d)", getCallingPid());
+    LOGV("takePicture (pid %d)", getCallingPid());
 
     Mutex::Autolock lock(mLock);
     status_t result = checkPid();
@@ -933,7 +932,7 @@
             h = size->height;
             w &= ~1;
             h &= ~1;
-            LOGD("Snapshot image width=%d, height=%d", w, h);
+            LOGV("Snapshot image width=%d, height=%d", w, h);
         }
         ISurface::BufferHeap buffers(w, h, w, h,
             PIXEL_FORMAT_YCbCr_420_SP, transform, 0, mHardware->getRawHeap());
@@ -952,7 +951,7 @@
 #if DEBUG_HEAP_LEAKS && 0 // debugging
     if (gWeakHeap == NULL) {
         if (gWeakHeap != heap) {
-            LOGD("SETTING PREVIEW HEAP");
+            LOGV("SETTING PREVIEW HEAP");
             heap->trackMe(true, true);
             gWeakHeap = heap;
         }
@@ -1189,7 +1188,7 @@
 // set preview/capture parameters - key/value pairs
 status_t CameraService::Client::setParameters(const String8& params)
 {
-    LOGD("setParameters(%s)", params.string());
+    LOGV("setParameters(%s)", params.string());
 
     Mutex::Autolock lock(mLock);
     status_t result = checkPid();
@@ -1215,13 +1214,13 @@
     }
 
     String8 params(mHardware->getParameters().flatten());
-    LOGD("getParameters(%s)", params.string());
+    LOGV("getParameters(%s)", params.string());
     return params;
 }
 
 status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2)
 {
-    LOGD("sendCommand (pid %d)", getCallingPid());
+    LOGV("sendCommand (pid %d)", getCallingPid());
     Mutex::Autolock lock(mLock);
     status_t result = checkPid();
     if (result != NO_ERROR) return result;
@@ -1327,7 +1326,7 @@
     status_t err = BnCameraService::onTransact(code, data, reply, flags);
 
 #if DEBUG_HEAP_LEAKS
-    LOGD("+++ onTransact err %d code %d", err, code);
+    LOGV("+++ onTransact err %d code %d", err, code);
 
     if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) {
         // the 'service' command interrogates this binder for its name, and then supplies it
@@ -1335,7 +1334,7 @@
         // ISurfaceComposer (since we delegated the INTERFACE_TRANSACTION handling to
         // BnSurfaceComposer before falling through to this code).
 
-        LOGD("+++ onTransact code %d", code);
+        LOGV("+++ onTransact code %d", code);
 
         CHECK_INTERFACE(ICameraService, data, reply);
 
@@ -1345,13 +1344,13 @@
             if (gWeakHeap != 0) {
                 sp<IMemoryHeap> h = gWeakHeap.promote();
                 IMemoryHeap *p = gWeakHeap.unsafe_get();
-                LOGD("CHECKING WEAK REFERENCE %p (%p)", h.get(), p);
+                LOGV("CHECKING WEAK REFERENCE %p (%p)", h.get(), p);
                 if (h != 0)
                     h->printRefs();
                 bool attempt_to_delete = data.readInt32() == 1;
                 if (attempt_to_delete) {
                     // NOT SAFE!
-                    LOGD("DELETING WEAK REFERENCE %p (%p)", h.get(), p);
+                    LOGV("DELETING WEAK REFERENCE %p (%p)", h.get(), p);
                     if (p) delete p;
                 }
                 return NO_ERROR;
diff --git a/camera/tests/CameraServiceTest/Android.mk b/camera/tests/CameraServiceTest/Android.mk
index c2a02bb..8da7c1f 100644
--- a/camera/tests/CameraServiceTest/Android.mk
+++ b/camera/tests/CameraServiceTest/Android.mk
@@ -14,6 +14,7 @@
 LOCAL_CFLAGS :=
 
 LOCAL_SHARED_LIBRARIES += \
+		libbinder \
                 libcutils \
                 libutils \
                 libui
diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c
index ba74c78..4426874 100644
--- a/cmds/keystore/keystore.c
+++ b/cmds/keystore/keystore.c
@@ -163,19 +163,19 @@
 static int8_t encrypt_blob(char *name, AES_KEY *aes_key)
 {
     uint8_t vector[AES_BLOCK_SIZE];
-    int length = blob.length;
+    int length;
     int fd;
 
     if (read(the_entropy, vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
         return SYSTEM_ERROR;
     }
 
-    length += blob.value - blob.digested;
-    blob.length = htonl(blob.length);
-    MD5(blob.digested, length, blob.digest);
-
-    length += blob.digested - blob.encrypted;
+    length = blob.length + blob.value - blob.encrypted;
     length = (length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
+
+    blob.length = htonl(blob.length);
+    MD5(blob.digested, length - (blob.digested - blob.encrypted), blob.digest);
+
     memcpy(vector, blob.vector, AES_BLOCK_SIZE);
     AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key, vector,
                     AES_ENCRYPT);
@@ -184,11 +184,9 @@
     length += blob.encrypted - (uint8_t *)&blob;
 
     fd = open(".tmp", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
-    if (fd == -1 || write(fd, &blob, length) != length) {
-        return SYSTEM_ERROR;
-    }
+    length -= write(fd, &blob, length);
     close(fd);
-    return rename(".tmp", name) ? SYSTEM_ERROR : NO_ERROR;
+    return (length || rename(".tmp", name)) ? SYSTEM_ERROR : NO_ERROR;
 }
 
 static int8_t decrypt_blob(char *name, AES_KEY *aes_key)
@@ -210,14 +208,15 @@
     AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key,
                     blob.vector, AES_DECRYPT);
     length -= blob.digested - blob.encrypted;
-    if (!memcmp(blob.digest, MD5(blob.digested, length, NULL),
-                MD5_DIGEST_LENGTH)) {
+    if (memcmp(blob.digest, MD5(blob.digested, length, NULL),
+               MD5_DIGEST_LENGTH)) {
         return VALUE_CORRUPTED;
     }
 
     length -= blob.value - blob.digested;
     blob.length = ntohl(blob.length);
-    return (length < blob.length) ? VALUE_CORRUPTED : NO_ERROR;
+    return (blob.length < 0 || blob.length > length) ? VALUE_CORRUPTED :
+           NO_ERROR;
 }
 
 /* Here are the actions. Each of them is a function without arguments. All
diff --git a/common/java/com/android/common/AndroidHttpClient.java b/common/java/com/android/common/AndroidHttpClient.java
index 6fa6da1..99faf6e 100644
--- a/common/java/com/android/common/AndroidHttpClient.java
+++ b/common/java/com/android/common/AndroidHttpClient.java
@@ -339,19 +339,9 @@
      * Shorter data will not be compressed.
      */
     public static long getMinGzipSize(ContentResolver resolver) {
-        String sMinGzipBytes = Settings.Gservices.getString(resolver,
-                Settings.Gservices.SYNC_MIN_GZIP_BYTES);
-
-        if (!TextUtils.isEmpty(sMinGzipBytes)) {
-            try {
-                return Long.parseLong(sMinGzipBytes);
-            } catch (NumberFormatException nfe) {
-                Log.w(TAG, "Unable to parse " +
-                        Settings.Gservices.SYNC_MIN_GZIP_BYTES + " " +
-                        sMinGzipBytes, nfe);
-            }
-        }
-        return DEFAULT_SYNC_MIN_GZIP_BYTES;
+        return Settings.Secure.getLong(resolver,
+                                       Settings.Secure.SYNC_MIN_GZIP_BYTES,
+                                       DEFAULT_SYNC_MIN_GZIP_BYTES);
     }
 
     /* cURL logging support. */
diff --git a/common/java/com/android/common/DNParser.java b/common/java/com/android/common/DNParser.java
new file mode 100644
index 0000000..32d57c0
--- /dev/null
+++ b/common/java/com/android/common/DNParser.java
@@ -0,0 +1,447 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.common;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A simple distinguished name(DN) parser.
+ *
+ * <p>This class is based on org.apache.harmony.security.x509.DNParser.  It's customized to remove
+ * external references which are unnecessary for our requirements.
+ *
+ * <p>This class is only meant for extracting a string value from a DN.  e.g. it doesn't support
+ * values in the hex-string style.
+ *
+ * <p>This class is used by {@link DomainNameValidator} only.  However, in order to make this
+ * class visible from unit tests, it's made public.
+ */
+public final class DNParser {
+    private static final String TAG = "DNParser";
+
+    /** DN to be parsed. */
+    private final String dn;
+
+    // length of distinguished name string
+    private final int length;
+
+    private int pos, beg, end;
+
+    // tmp vars to store positions of the currently parsed item
+    private int cur;
+
+    // distinguished name chars
+    private char[] chars;
+
+    /**
+     * Exception message thrown when we failed to parse DN, which shouldn't happen because we
+     * only handle DNs that {@link X500Principal#getName} returns, which shouldn't be malformed.
+     */
+    private static final String ERROR_PARSE_ERROR = "Failed to parse DN";
+
+    /**
+     * Constructor.
+     *
+     * @param principal - {@link X500Principal} to be parsed
+     */
+    public DNParser(X500Principal principal) {
+        this.dn = principal.getName(X500Principal.RFC2253);
+        this.length = dn.length();
+    }
+
+    // gets next attribute type: (ALPHA 1*keychar) / oid
+    private String nextAT() throws IOException {
+
+        // skip preceding space chars, they can present after
+        // comma or semicolon (compatibility with RFC 1779)
+        for (; pos < length && chars[pos] == ' '; pos++) {
+        }
+        if (pos == length) {
+            return null; // reached the end of DN
+        }
+
+        // mark the beginning of attribute type
+        beg = pos;
+
+        // attribute type chars
+        pos++;
+        for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
+            // we don't follow exact BNF syntax here:
+            // accept any char except space and '='
+        }
+        if (pos >= length) {
+            // unexpected end of DN
+            throw new IOException(ERROR_PARSE_ERROR);
+        }
+
+        // mark the end of attribute type
+        end = pos;
+
+        // skip trailing space chars between attribute type and '='
+        // (compatibility with RFC 1779)
+        if (chars[pos] == ' ') {
+            for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
+            }
+
+            if (chars[pos] != '=' || pos == length) {
+                // unexpected end of DN
+                throw new IOException(ERROR_PARSE_ERROR);
+            }
+        }
+
+        pos++; //skip '=' char
+
+        // skip space chars between '=' and attribute value
+        // (compatibility with RFC 1779)
+        for (; pos < length && chars[pos] == ' '; pos++) {
+        }
+
+        // in case of oid attribute type skip its prefix: "oid." or "OID."
+        // (compatibility with RFC 1779)
+        if ((end - beg > 4) && (chars[beg + 3] == '.')
+                && (chars[beg] == 'O' || chars[beg] == 'o')
+                && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
+                && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
+            beg += 4;
+        }
+
+        return new String(chars, beg, end - beg);
+    }
+
+    // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
+    private String quotedAV() throws IOException {
+
+        pos++;
+        beg = pos;
+        end = beg;
+        while (true) {
+
+            if (pos == length) {
+                // unexpected end of DN
+                throw new IOException(ERROR_PARSE_ERROR);
+            }
+
+            if (chars[pos] == '"') {
+                // enclosing quotation was found
+                pos++;
+                break;
+            } else if (chars[pos] == '\\') {
+                chars[end] = getEscaped();
+            } else {
+                // shift char: required for string with escaped chars
+                chars[end] = chars[pos];
+            }
+            pos++;
+            end++;
+        }
+
+        // skip trailing space chars before comma or semicolon.
+        // (compatibility with RFC 1779)
+        for (; pos < length && chars[pos] == ' '; pos++) {
+        }
+
+        return new String(chars, beg, end - beg);
+    }
+
+    // gets hex string attribute value: "#" hexstring
+    private String hexAV() throws IOException {
+
+        if (pos + 4 >= length) {
+            // encoded byte array  must be not less then 4 c
+            throw new IOException(ERROR_PARSE_ERROR);
+        }
+
+        beg = pos; // store '#' position
+        pos++;
+        while (true) {
+
+            // check for end of attribute value
+            // looks for space and component separators
+            if (pos == length || chars[pos] == '+' || chars[pos] == ','
+                    || chars[pos] == ';') {
+                end = pos;
+                break;
+            }
+
+            if (chars[pos] == ' ') {
+                end = pos;
+                pos++;
+                // skip trailing space chars before comma or semicolon.
+                // (compatibility with RFC 1779)
+                for (; pos < length && chars[pos] == ' '; pos++) {
+                }
+                break;
+            } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
+                chars[pos] += 32; //to low case
+            }
+
+            pos++;
+        }
+
+        // verify length of hex string
+        // encoded byte array  must be not less then 4 and must be even number
+        int hexLen = end - beg; // skip first '#' char
+        if (hexLen < 5 || (hexLen & 1) == 0) {
+            throw new IOException(ERROR_PARSE_ERROR);
+        }
+
+        // get byte encoding from string representation
+        byte[] encoded = new byte[hexLen / 2];
+        for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
+            encoded[i] = (byte) getByte(p);
+        }
+
+        return new String(chars, beg, hexLen);
+    }
+
+    // gets string attribute value: *( stringchar / pair )
+    private String escapedAV() throws IOException {
+
+        beg = pos;
+        end = pos;
+        while (true) {
+
+            if (pos >= length) {
+                // the end of DN has been found
+                return new String(chars, beg, end - beg);
+            }
+
+            switch (chars[pos]) {
+            case '+':
+            case ',':
+            case ';':
+                // separator char has beed found
+                return new String(chars, beg, end - beg);
+            case '\\':
+                // escaped char
+                chars[end++] = getEscaped();
+                pos++;
+                break;
+            case ' ':
+                // need to figure out whether space defines
+                // the end of attribute value or not
+                cur = end;
+
+                pos++;
+                chars[end++] = ' ';
+
+                for (; pos < length && chars[pos] == ' '; pos++) {
+                    chars[end++] = ' ';
+                }
+                if (pos == length || chars[pos] == ',' || chars[pos] == '+'
+                        || chars[pos] == ';') {
+                    // separator char or the end of DN has beed found
+                    return new String(chars, beg, cur - beg);
+                }
+                break;
+            default:
+                chars[end++] = chars[pos];
+                pos++;
+            }
+        }
+    }
+
+    // returns escaped char
+    private char getEscaped() throws IOException {
+
+        pos++;
+        if (pos == length) {
+            throw new IOException(ERROR_PARSE_ERROR);
+        }
+
+        switch (chars[pos]) {
+        case '"':
+        case '\\':
+        case ',':
+        case '=':
+        case '+':
+        case '<':
+        case '>':
+        case '#':
+        case ';':
+        case ' ':
+        case '*':
+        case '%':
+        case '_':
+            //FIXME: escaping is allowed only for leading or trailing space char
+            return chars[pos];
+        default:
+            // RFC doesn't explicitly say that escaped hex pair is
+            // interpreted as UTF-8 char. It only contains an example of such DN.
+            return getUTF8();
+        }
+    }
+
+    // decodes UTF-8 char
+    // see http://www.unicode.org for UTF-8 bit distribution table
+    private char getUTF8() throws IOException {
+
+        int res = getByte(pos);
+        pos++; //FIXME tmp
+
+        if (res < 128) { // one byte: 0-7F
+            return (char) res;
+        } else if (res >= 192 && res <= 247) {
+
+            int count;
+            if (res <= 223) { // two bytes: C0-DF
+                count = 1;
+                res = res & 0x1F;
+            } else if (res <= 239) { // three bytes: E0-EF
+                count = 2;
+                res = res & 0x0F;
+            } else { // four bytes: F0-F7
+                count = 3;
+                res = res & 0x07;
+            }
+
+            int b;
+            for (int i = 0; i < count; i++) {
+                pos++;
+                if (pos == length || chars[pos] != '\\') {
+                    return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+                }
+                pos++;
+
+                b = getByte(pos);
+                pos++; //FIXME tmp
+                if ((b & 0xC0) != 0x80) {
+                    return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+                }
+
+                res = (res << 6) + (b & 0x3F);
+            }
+            return (char) res;
+        } else {
+            return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+        }
+    }
+
+    // Returns byte representation of a char pair
+    // The char pair is composed of DN char in
+    // specified 'position' and the next char
+    // According to BNF syntax:
+    // hexchar    = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+    //                    / "a" / "b" / "c" / "d" / "e" / "f"
+    private int getByte(int position) throws IOException {
+
+        if ((position + 1) >= length) {
+            // to avoid ArrayIndexOutOfBoundsException
+            throw new IOException(ERROR_PARSE_ERROR);
+        }
+
+        int b1, b2;
+
+        b1 = chars[position];
+        if (b1 >= '0' && b1 <= '9') {
+            b1 = b1 - '0';
+        } else if (b1 >= 'a' && b1 <= 'f') {
+            b1 = b1 - 87; // 87 = 'a' - 10
+        } else if (b1 >= 'A' && b1 <= 'F') {
+            b1 = b1 - 55; // 55 = 'A' - 10
+        } else {
+            throw new IOException(ERROR_PARSE_ERROR);
+        }
+
+        b2 = chars[position + 1];
+        if (b2 >= '0' && b2 <= '9') {
+            b2 = b2 - '0';
+        } else if (b2 >= 'a' && b2 <= 'f') {
+            b2 = b2 - 87; // 87 = 'a' - 10
+        } else if (b2 >= 'A' && b2 <= 'F') {
+            b2 = b2 - 55; // 55 = 'A' - 10
+        } else {
+            throw new IOException(ERROR_PARSE_ERROR);
+        }
+
+        return (b1 << 4) + b2;
+    }
+
+    /**
+     * Parses the DN and returns the attribute value for an attribute type.
+     *
+     * @param attributeType attribute type to look for (e.g. "ca")
+     * @return value of the attribute that first found, or null if none found
+     */
+    public String find(String attributeType) {
+        try {
+            // Initialize internal state.
+            pos = 0;
+            beg = 0;
+            end = 0;
+            cur = 0;
+            chars = dn.toCharArray();
+
+            String attType = nextAT();
+            if (attType == null) {
+                return null;
+            }
+            while (true) {
+                String attValue = "";
+
+                if (pos == length) {
+                    return null;
+                }
+
+                switch (chars[pos]) {
+                case '"':
+                    attValue = quotedAV();
+                    break;
+                case '#':
+                    attValue = hexAV();
+                    break;
+                case '+':
+                case ',':
+                case ';': // compatibility with RFC 1779: semicolon can separate RDNs
+                    //empty attribute value
+                    break;
+                default:
+                    attValue = escapedAV();
+                }
+
+                if (attributeType.equalsIgnoreCase(attType)) {
+                    return attValue;
+                }
+
+                if (pos >= length) {
+                    return null;
+                }
+
+                if (chars[pos] == ',' || chars[pos] == ';') {
+                } else if (chars[pos] != '+') {
+                    throw new IOException(ERROR_PARSE_ERROR);
+                }
+
+                pos++;
+                attType = nextAT();
+                if (attType == null) {
+                    throw new IOException(ERROR_PARSE_ERROR);
+                }
+            }
+        } catch (IOException e) {
+            // Parse error shouldn't happen, because we only handle DNs that
+            // X500Principal.getName() returns, which shouldn't be malformed.
+            Log.e(TAG, "Failed to parse DN: " + dn);
+            return null;
+        }
+    }
+}
diff --git a/common/java/com/android/common/DomainNameValidator.java b/common/java/com/android/common/DomainNameValidator.java
new file mode 100644
index 0000000..ad44a7d
--- /dev/null
+++ b/common/java/com/android/common/DomainNameValidator.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.common;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.security.auth.x500.X500Principal;
+
+public class DomainNameValidator {
+    private final static String TAG = "DomainNameValidator";
+
+    private static final boolean DEBUG = false;
+    private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static Pattern QUICK_IP_PATTERN;
+    static {
+        try {
+            QUICK_IP_PATTERN = Pattern.compile("^[a-f0-9\\.:]+$");
+        } catch (PatternSyntaxException e) {}
+    }
+
+    private static final int ALT_DNS_NAME = 2;
+    private static final int ALT_IPA_NAME = 7;
+
+    /**
+     * Checks the site certificate against the domain name of the site being visited
+     * @param certificate The certificate to check
+     * @param thisDomain The domain name of the site being visited
+     * @return True iff if there is a domain match as specified by RFC2818
+     */
+    public static boolean match(X509Certificate certificate, String thisDomain) {
+        if (certificate == null || thisDomain == null || thisDomain.length() == 0) {
+            return false;
+        }
+
+        thisDomain = thisDomain.toLowerCase();
+        if (!isIpAddress(thisDomain)) {
+            return matchDns(certificate, thisDomain);
+        } else {
+            return matchIpAddress(certificate, thisDomain);
+        }
+    }
+
+    /**
+     * @return True iff the domain name is specified as an IP address
+     */
+    private static boolean isIpAddress(String domain) {
+        boolean rval = (domain != null && domain.length() != 0);
+        if (rval) {
+            try {
+                // do a quick-dirty IP match first to avoid DNS lookup
+                rval = QUICK_IP_PATTERN.matcher(domain).matches();
+                if (rval) {
+                    rval = domain.equals(
+                        InetAddress.getByName(domain).getHostAddress());
+                }
+            } catch (UnknownHostException e) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                  errorMessage = "unknown host exception";
+                }
+
+                if (LOG_ENABLED) {
+                    Log.v(TAG, "DomainNameValidator.isIpAddress(): " + errorMessage);
+                }
+
+                rval = false;
+            }
+        }
+
+        return rval;
+    }
+
+    /**
+     * Checks the site certificate against the IP domain name of the site being visited
+     * @param certificate The certificate to check
+     * @param thisDomain The DNS domain name of the site being visited
+     * @return True iff if there is a domain match as specified by RFC2818
+     */
+    private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) {
+        if (LOG_ENABLED) {
+            Log.v(TAG, "DomainNameValidator.matchIpAddress(): this domain: " + thisDomain);
+        }
+
+        try {
+            Collection subjectAltNames = certificate.getSubjectAlternativeNames();
+            if (subjectAltNames != null) {
+                Iterator i = subjectAltNames.iterator();
+                while (i.hasNext()) {
+                    List altNameEntry = (List)(i.next());
+                    if (altNameEntry != null && 2 <= altNameEntry.size()) {
+                        Integer altNameType = (Integer)(altNameEntry.get(0));
+                        if (altNameType != null) {
+                            if (altNameType.intValue() == ALT_IPA_NAME) {
+                                String altName = (String)(altNameEntry.get(1));
+                                if (altName != null) {
+                                    if (LOG_ENABLED) {
+                                        Log.v(TAG, "alternative IP: " + altName);
+                                    }
+                                    if (thisDomain.equalsIgnoreCase(altName)) {
+                                        return true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (CertificateParsingException e) {}
+
+        return false;
+    }
+
+    /**
+     * Checks the site certificate against the DNS domain name of the site being visited
+     * @param certificate The certificate to check
+     * @param thisDomain The DNS domain name of the site being visited
+     * @return True iff if there is a domain match as specified by RFC2818
+     */
+    private static boolean matchDns(X509Certificate certificate, String thisDomain) {
+        boolean hasDns = false;
+        try {
+            Collection subjectAltNames = certificate.getSubjectAlternativeNames();
+            if (subjectAltNames != null) {
+                Iterator i = subjectAltNames.iterator();
+                while (i.hasNext()) {
+                    List altNameEntry = (List)(i.next());
+                    if (altNameEntry != null && 2 <= altNameEntry.size()) {
+                        Integer altNameType = (Integer)(altNameEntry.get(0));
+                        if (altNameType != null) {
+                            if (altNameType.intValue() == ALT_DNS_NAME) {
+                                hasDns = true;
+                                String altName = (String)(altNameEntry.get(1));
+                                if (altName != null) {
+                                    if (matchDns(thisDomain, altName)) {
+                                        return true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (CertificateParsingException e) {
+            // one way we can get here is if an alternative name starts with
+            // '*' character, which is contrary to one interpretation of the
+            // spec (a valid DNS name must start with a letter); there is no
+            // good way around this, and in order to be compatible we proceed
+            // to check the common name (ie, ignore alternative names)
+            if (LOG_ENABLED) {
+                String errorMessage = e.getMessage();
+                if (errorMessage == null) {
+                    errorMessage = "failed to parse certificate";
+                }
+
+                Log.v(TAG, "DomainNameValidator.matchDns(): " + errorMessage);
+            }
+        }
+
+        if (!hasDns) {
+            final String cn = new DNParser(certificate.getSubjectX500Principal())
+                    .find("cn");
+            if (LOG_ENABLED) {
+                Log.v(TAG, "Validating subject: DN:"
+                        + certificate.getSubjectX500Principal().getName(X500Principal.CANONICAL)
+                        + "  CN:" + cn);
+            }
+            if (cn != null) {
+                return matchDns(thisDomain, cn);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param thisDomain The domain name of the site being visited
+     * @param thatDomain The domain name from the certificate
+     * @return True iff thisDomain matches thatDomain as specified by RFC2818
+     */
+    // not private for testing
+    public static boolean matchDns(String thisDomain, String thatDomain) {
+        if (LOG_ENABLED) {
+            Log.v(TAG, "DomainNameValidator.matchDns():" +
+                      " this domain: " + thisDomain +
+                      " that domain: " + thatDomain);
+        }
+
+        if (thisDomain == null || thisDomain.length() == 0 ||
+            thatDomain == null || thatDomain.length() == 0) {
+            return false;
+        }
+
+        thatDomain = thatDomain.toLowerCase();
+
+        // (a) domain name strings are equal, ignoring case: X matches X
+        boolean rval = thisDomain.equals(thatDomain);
+        if (!rval) {
+            String[] thisDomainTokens = thisDomain.split("\\.");
+            String[] thatDomainTokens = thatDomain.split("\\.");
+
+            int thisDomainTokensNum = thisDomainTokens.length;
+            int thatDomainTokensNum = thatDomainTokens.length;
+
+            // (b) OR thatHost is a '.'-suffix of thisHost: Z.Y.X matches X
+            if (thisDomainTokensNum >= thatDomainTokensNum) {
+                for (int i = thatDomainTokensNum - 1; i >= 0; --i) {
+                    rval = thisDomainTokens[i].equals(thatDomainTokens[i]);
+                    if (!rval) {
+                        // (c) OR we have a special *-match:
+                        // *.Y.X matches Z.Y.X but *.X doesn't match Z.Y.X
+                        rval = (i == 0 && thisDomainTokensNum == thatDomainTokensNum);
+                        if (rval) {
+                            rval = thatDomainTokens[0].equals("*");
+                            if (!rval) {
+                                // (d) OR we have a *-component match:
+                                // f*.com matches foo.com but not bar.com
+                                rval = domainTokenMatch(
+                                    thisDomainTokens[0], thatDomainTokens[0]);
+                            }
+                        }
+                        break;
+                    }
+                }
+            } else {
+              // (e) OR thatHost has a '*.'-prefix of thisHost:
+              // *.Y.X matches Y.X
+              rval = thatDomain.equals("*." + thisDomain);
+            }
+        }
+
+        return rval;
+    }
+
+    /**
+     * @param thisDomainToken The domain token from the current domain name
+     * @param thatDomainToken The domain token from the certificate
+     * @return True iff thisDomainToken matches thatDomainToken, using the
+     * wildcard match as specified by RFC2818-3.1. For example, f*.com must
+     * match foo.com but not bar.com
+     */
+    private static boolean domainTokenMatch(String thisDomainToken, String thatDomainToken) {
+        if (thisDomainToken != null && thatDomainToken != null) {
+            int starIndex = thatDomainToken.indexOf('*');
+            if (starIndex >= 0) {
+                if (thatDomainToken.length() - 1 <= thisDomainToken.length()) {
+                    String prefix = thatDomainToken.substring(0,  starIndex);
+                    String suffix = thatDomainToken.substring(starIndex + 1);
+
+                    return thisDomainToken.startsWith(prefix) && thisDomainToken.endsWith(suffix);
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/common/tests/src/com/android/common/DNParserTest.java b/common/tests/src/com/android/common/DNParserTest.java
new file mode 100644
index 0000000..34b140a
--- /dev/null
+++ b/common/tests/src/com/android/common/DNParserTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.common;
+
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.TestCase;
+
+public class DNParserTest extends TestCase {
+    public void testFind() {
+        checkFind("", "cn", null);
+        checkFind("ou=xxx", "cn", null);
+        checkFind("ou=xxx,cn=xxx", "cn", "xxx");
+        checkFind("ou=xxx+cn=yyy,cn=zzz+cn=abc", "cn", "yyy");
+        checkFind("2.5.4.3=a,ou=xxx", "cn", "a"); // OID
+        checkFind("cn=a,cn=b", "cn", "a");
+        checkFind("ou=Cc,ou=Bb,ou=Aa", "ou", "Cc");
+        checkFind("cn=imap.gmail.com", "cn", "imap.gmail.com");
+
+        // Quoted string (see http://www.ietf.org/rfc/rfc2253.txt)
+        checkFind("o=\"\\\" a ,=<>#;\"", "o", "\" a ,=<>#;");
+        checkFind("o=abc\\,def", "o", "abc,def");
+
+        // UTF-8 (example in rfc 2253)
+        checkFind("cn=Lu\\C4\\8Di\\C4\\87", "cn", "\u004c\u0075\u010d\u0069\u0107");
+
+        // whitespaces
+        checkFind("ou=a, o=  a  b  ,cn=x", "o", "a  b");
+        checkFind("o=\"  a  b  \" ,cn=x", "o", "  a  b  ");
+    }
+
+    private void checkFind(String dn, String attrType, String expected) {
+        String actual = new DNParser(new X500Principal(dn)).find(attrType);
+        assertEquals("dn:" + dn + "  attr:" + attrType, expected, actual);
+    }
+}
diff --git a/common/tests/src/com/android/common/DomainNameValidatorTest.java b/common/tests/src/com/android/common/DomainNameValidatorTest.java
new file mode 100644
index 0000000..4fdd4cd
--- /dev/null
+++ b/common/tests/src/com/android/common/DomainNameValidatorTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.common;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.TestCase;
+
+public class DomainNameValidatorTest extends TestCase {
+    private static final int ALT_UNKNOWN = 0;
+    private static final int ALT_DNS_NAME = 2;
+    private static final int ALT_IPA_NAME = 7;
+
+    /**
+     * Tests {@link DomainNameValidator#match}
+     */
+    public void testMatch() {
+        // TODO Use actual X509Certificate objects, instead of StubX509Certificate.
+        // Comment in DomainNameValidator suggests X509Certificate fails to parse a certificate
+        // if subject alternative names contain a domain name that begins with '*'.
+        // This test won't cover this kind of errors.
+
+        checkMatch("11", new StubX509Certificate("cn=imap.g.com"), "imap.g.com", true);
+        checkMatch("12", new StubX509Certificate("cn=imap2.g.com"), "imap.g.com", false);
+        checkMatch("13", new StubX509Certificate("cn=sub.imap.g.com"), "imap.g.com", false);
+
+        // If a subjectAltName extension of type dNSName is present, that MUST
+        // be used as the identity
+        checkMatch("21", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com")
+                , "imap.g.com", false);
+        checkMatch("22", new StubX509Certificate("cn=imap.g.com") // This cn should be ignored
+                .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com")
+                , "imap.g.com", false);
+        checkMatch("23", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+                , "imap.g.com", true);
+
+        // With wildcards
+        checkMatch("24", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "*.g.com")
+                , "imap.g.com", true);
+
+
+        // host name is ip address
+        checkMatch("31", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+                , "1.2.3.4", true);
+        checkMatch("32", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+                , "1.2.3.5", false);
+        checkMatch("32", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "192.168.100.1")
+                , "192.168.100.1", true);
+
+        // Has unknown subject alternative names
+        checkMatch("41", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+                .addSubjectAlternativeName(ALT_UNKNOWN,  "random string 2")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+                , "imap.g.com", true);
+
+        checkMatch("42", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+                , "2.33.44.55", true);
+
+        checkMatch("43", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+                , "g.com", false);
+
+        checkMatch("44", new StubX509Certificate("")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+                .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+                .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+                .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+                , "2.33.44.1", false);
+    }
+
+    private void checkMatch(String message, X509Certificate certificate, String thisDomain,
+            boolean expected) {
+        Boolean actual = DomainNameValidator.match(certificate, thisDomain);
+        assertEquals(message, (Object) expected, (Object) actual);
+    }
+
+    /**
+     * Tests {@link DomainNameValidator#matchDns}
+     */
+    public void testMatchDns() {
+        checkMatchDns("11", "a.b.c.d", "a.b.c.d", true);
+        checkMatchDns("12", "a.b.c.d", "*.b.c.d", true);
+        checkMatchDns("13", "b.c.d", "*.b.c.d", true);
+        checkMatchDns("14", "b.c.d", "b*.c.d", true);
+
+        checkMatchDns("15", "a.b.c.d", "*.*.c.d", false);
+        checkMatchDns("16", "a.b.c.d", "*.c.d", false);
+
+        checkMatchDns("21", "imap.google.com", "imap.google.com", true);
+        checkMatchDns("22", "imap2.google.com", "imap.google.com", false);
+        checkMatchDns("23", "imap.google.com", "*.google.com", true);
+        checkMatchDns("24", "imap2.google.com", "*.google.com", true);
+        checkMatchDns("25", "imap.google.com", "*.googl.com", false);
+        checkMatchDns("26", "imap2.google2.com", "*.google3.com", false);
+        checkMatchDns("27", "imap.google.com", "ima*.google.com", true);
+        checkMatchDns("28", "imap.google.com", "imap*.google.com", true);
+        checkMatchDns("29", "imap.google.com", "*.imap.google.com", true);
+
+        checkMatchDns("41", "imap.google.com", "a*.google.com", false);
+        checkMatchDns("42", "imap.google.com", "ix*.google.com", false);
+
+        checkMatchDns("51", "imap.google.com", "iMap.Google.Com", true);
+    }
+
+    private void checkMatchDns(String message, String thisDomain, String thatDomain,
+            boolean expected) {
+        boolean actual = DomainNameValidator.matchDns(thisDomain, thatDomain);
+        assertEquals(message, expected, actual);
+    }
+
+    /**
+     * Minimal {@link X509Certificate} implementation for {@link DomainNameValidator}.
+     */
+    private static class StubX509Certificate extends X509Certificate {
+        private final X500Principal subjectX500Principal;
+        private Collection<List<?>> subjectAlternativeNames;
+
+        public StubX509Certificate(String subjectDn) {
+            subjectX500Principal = new X500Principal(subjectDn);
+            subjectAlternativeNames = null;
+        }
+
+        public StubX509Certificate addSubjectAlternativeName(int type, String name) {
+            if (subjectAlternativeNames == null) {
+                subjectAlternativeNames = new ArrayList<List<?>>();
+            }
+            LinkedList<Object> entry = new LinkedList<Object>();
+            entry.add(type);
+            entry.add(name);
+            subjectAlternativeNames.add(entry);
+            return this;
+        }
+
+        @Override
+        public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
+            return subjectAlternativeNames;
+        }
+
+        @Override
+        public X500Principal getSubjectX500Principal() {
+            return subjectX500Principal;
+        }
+
+        @Override
+        public void checkValidity() throws CertificateExpiredException,
+                CertificateNotYetValidException {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public void checkValidity(Date date) throws CertificateExpiredException,
+                CertificateNotYetValidException {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public int getBasicConstraints() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public Principal getIssuerDN() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public boolean[] getIssuerUniqueID() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public boolean[] getKeyUsage() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public Date getNotAfter() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public Date getNotBefore() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public BigInteger getSerialNumber() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public String getSigAlgName() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public String getSigAlgOID() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public byte[] getSigAlgParams() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public byte[] getSignature() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public Principal getSubjectDN() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public boolean[] getSubjectUniqueID() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public byte[] getTBSCertificate() throws CertificateEncodingException {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public int getVersion() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public byte[] getEncoded() throws CertificateEncodingException {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public PublicKey getPublicKey() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public String toString() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+                InvalidKeyException, NoSuchProviderException, SignatureException {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        @Override
+        public void verify(PublicKey key, String sigProvider) throws CertificateException,
+                NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+                SignatureException {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        public Set<String> getCriticalExtensionOIDs() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        public byte[] getExtensionValue(String oid) {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        public Set<String> getNonCriticalExtensionOIDs() {
+            throw new RuntimeException("Method not implemented");
+        }
+
+        public boolean hasUnsupportedCriticalExtension() {
+            throw new RuntimeException("Method not implemented");
+        }
+    }
+}
diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp
index 2024cc0..ecfe1e0 100644
--- a/libs/audioflinger/AudioFlinger.cpp
+++ b/libs/audioflinger/AudioFlinger.cpp
@@ -932,6 +932,8 @@
     result.append(buffer);
     snprintf(buffer, SIZE, "blocked in write: %d\n", mInWrite);
     result.append(buffer);
+    snprintf(buffer, SIZE, "suspend count: %d\n", mSuspended);
+    result.append(buffer);
     write(fd, result.string(), result.size());
 
     dumpBase(fd, args);
@@ -1344,7 +1346,7 @@
         if (cblk->framesReady() && (track->isReady() || track->isStopped()) &&
                 !track->isPaused())
         {
-            //LOGV("track %d u=%08x, s=%08x [OK]", track->name(), cblk->user, cblk->server);
+            //LOGV("track %d u=%08x, s=%08x [OK] on thread %p", track->name(), cblk->user, cblk->server, this);
 
             // compute volume for this track
             int16_t left, right;
@@ -1400,7 +1402,7 @@
             track->mRetryCount = kMaxTrackRetries;
             mixerStatus = MIXER_TRACKS_READY;
         } else {
-            //LOGV("track %d u=%08x, s=%08x [NOT READY]", track->name(), cblk->user, cblk->server);
+            //LOGV("track %d u=%08x, s=%08x [NOT READY] on thread %p", track->name(), cblk->user, cblk->server, this);
             if (track->isStopped()) {
                 track->reset();
             }
@@ -1914,7 +1916,7 @@
 // ----------------------------------------------------------------------------
 
 AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::MixerThread* mainThread, int id)
-    :   MixerThread(audioFlinger, mainThread->getOutput(), id)
+    :   MixerThread(audioFlinger, mainThread->getOutput(), id), mWaitTimeMs(UINT_MAX)
 {
     mType = PlaybackThread::DUPLICATING;
     addOutputTrack(mainThread);
@@ -1952,6 +1954,7 @@
 
             if (checkForNewParameters_l()) {
                 mixBufferSize = mFrameCount*mFrameSize;
+                updateWaitTime();
                 activeSleepTime = activeSleepTimeUs();
                 idleSleepTime = idleSleepTimeUs();
             }
@@ -2003,7 +2006,11 @@
 
         if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
             // mix buffers...
-            mAudioMixer->process(curBuf);
+            if (outputsReady(outputTracks)) {
+                mAudioMixer->process(curBuf);
+            } else {
+                memset(curBuf, 0, mixBufferSize);
+            }
             sleepTime = 0;
             writeFrames = mFrameCount;
         } else {
@@ -2054,6 +2061,7 @@
 {
     int frameCount = (3 * mFrameCount * mSampleRate) / thread->sampleRate();
     OutputTrack *outputTrack = new OutputTrack((ThreadBase *)thread,
+                                            this,
                                             mSampleRate,
                                             mFormat,
                                             mChannelCount,
@@ -2062,6 +2070,7 @@
         thread->setStreamVolume(AudioSystem::NUM_STREAM_TYPES, 1.0f);
         mOutputTracks.add(outputTrack);
         LOGV("addOutputTrack() track %p, on thread %p", outputTrack, thread);
+        updateWaitTime();
     }
 }
 
@@ -2072,12 +2081,50 @@
         if (mOutputTracks[i]->thread() == (ThreadBase *)thread) {
             mOutputTracks[i]->destroy();
             mOutputTracks.removeAt(i);
+            updateWaitTime();
             return;
         }
     }
     LOGV("removeOutputTrack(): unkonwn thread: %p", thread);
 }
 
+void AudioFlinger::DuplicatingThread::updateWaitTime()
+{
+    mWaitTimeMs = UINT_MAX;
+    for (size_t i = 0; i < mOutputTracks.size(); i++) {
+        sp<ThreadBase> strong = mOutputTracks[i]->thread().promote();
+        if (strong != NULL) {
+            uint32_t waitTimeMs = (strong->frameCount() * 2 * 1000) / strong->sampleRate();
+            if (waitTimeMs < mWaitTimeMs) {
+                mWaitTimeMs = waitTimeMs;
+            }
+        }
+    }
+}
+
+
+bool AudioFlinger::DuplicatingThread::outputsReady(SortedVector< sp<OutputTrack> > &outputTracks)
+{
+    for (size_t i = 0; i < outputTracks.size(); i++) {
+        sp <ThreadBase> thread = outputTracks[i]->thread().promote();
+        if (thread == 0) {
+            LOGW("DuplicatingThread::outputsReady() could not promote thread on output track %p", outputTracks[i].get());
+            return false;
+        }
+        PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
+        if (playbackThread->standby() && !playbackThread->isSuspended()) {
+            LOGV("DuplicatingThread output track %p on thread %p Not Ready", outputTracks[i].get(), thread.get());
+            return false;
+        }
+    }
+    return true;
+}
+
+uint32_t AudioFlinger::DuplicatingThread::activeSleepTimeUs()
+{
+    return (mWaitTimeMs * 1000) / 2;
+}
+
 // ----------------------------------------------------------------------------
 
 // TrackBase constructor must be called with AudioFlinger::mLock held
@@ -2616,12 +2663,13 @@
 
 AudioFlinger::PlaybackThread::OutputTrack::OutputTrack(
             const wp<ThreadBase>& thread,
+            DuplicatingThread *sourceThread,
             uint32_t sampleRate,
             int format,
             int channelCount,
             int frameCount)
     :   Track(thread, NULL, AudioSystem::NUM_STREAM_TYPES, sampleRate, format, channelCount, frameCount, NULL),
-    mActive(false)
+    mActive(false), mSourceThread(sourceThread)
 {
 
     PlaybackThread *playbackThread = (PlaybackThread *)thread.unsafe_get();
@@ -2630,10 +2678,9 @@
         mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t);
         mCblk->volume[0] = mCblk->volume[1] = 0x1000;
         mOutBuffer.frameCount = 0;
-        mWaitTimeMs = (playbackThread->frameCount() * 2 * 1000) / playbackThread->sampleRate();
         playbackThread->mTracks.add(this);
-        LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, mCblk->frameCount %d, mCblk->sampleRate %d, mCblk->channels %d mBufferEnd %p mWaitTimeMs %d",
-                mCblk, mBuffer, mCblk->buffers, mCblk->frameCount, mCblk->sampleRate, mCblk->channels, mBufferEnd, mWaitTimeMs);
+        LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, mCblk->frameCount %d, mCblk->sampleRate %d, mCblk->channels %d mBufferEnd %p",
+                mCblk, mBuffer, mCblk->buffers, mCblk->frameCount, mCblk->sampleRate, mCblk->channels, mBufferEnd);
     } else {
         LOGW("Error creating output track on thread %p", playbackThread);
     }
@@ -2673,7 +2720,7 @@
     inBuffer.frameCount = frames;
     inBuffer.i16 = data;
 
-    uint32_t waitTimeLeftMs = mWaitTimeMs;
+    uint32_t waitTimeLeftMs = mSourceThread->waitTimeMs();
 
     if (!mActive && frames != 0) {
         start();
@@ -2712,12 +2759,11 @@
             mOutBuffer.frameCount = pInBuffer->frameCount;
             nsecs_t startTime = systemTime();
             if (obtainBuffer(&mOutBuffer, waitTimeLeftMs) == (status_t)AudioTrack::NO_MORE_BUFFERS) {
-                LOGV ("OutputTrack::write() %p no more output buffers", this);
+                LOGV ("OutputTrack::write() %p thread %p no more output buffers", this, mThread.unsafe_get());
                 outputBufferFull = true;
                 break;
             }
             uint32_t waitTimeMs = (uint32_t)ns2ms(systemTime() - startTime);
-            LOGV("OutputTrack::write() to thread %p waitTimeMs %d waitTimeLeftMs %d", mThread.unsafe_get(), waitTimeMs, waitTimeLeftMs);
             if (waitTimeLeftMs >= waitTimeMs) {
                 waitTimeLeftMs -= waitTimeMs;
             } else {
@@ -2738,7 +2784,7 @@
                 mBufferQueue.removeAt(0);
                 delete [] pInBuffer->mBuffer;
                 delete pInBuffer;
-                LOGV("OutputTrack::write() %p released overflow buffer %d", this, mBufferQueue.size());
+                LOGV("OutputTrack::write() %p thread %p released overflow buffer %d", this, mThread.unsafe_get(), mBufferQueue.size());
             } else {
                 break;
             }
@@ -2747,16 +2793,19 @@
 
     // If we could not write all frames, allocate a buffer and queue it for next time.
     if (inBuffer.frameCount) {
-        if (mBufferQueue.size() < kMaxOverFlowBuffers) {
-            pInBuffer = new Buffer;
-            pInBuffer->mBuffer = new int16_t[inBuffer.frameCount * channels];
-            pInBuffer->frameCount = inBuffer.frameCount;
-            pInBuffer->i16 = pInBuffer->mBuffer;
-            memcpy(pInBuffer->raw, inBuffer.raw, inBuffer.frameCount * channels * sizeof(int16_t));
-            mBufferQueue.add(pInBuffer);
-            LOGV("OutputTrack::write() %p adding overflow buffer %d", this, mBufferQueue.size());
-        } else {
-            LOGW("OutputTrack::write() %p no more overflow buffers", this);
+        sp<ThreadBase> thread = mThread.promote();
+        if (thread != 0 && !thread->standby()) {
+            if (mBufferQueue.size() < kMaxOverFlowBuffers) {
+                pInBuffer = new Buffer;
+                pInBuffer->mBuffer = new int16_t[inBuffer.frameCount * channels];
+                pInBuffer->frameCount = inBuffer.frameCount;
+                pInBuffer->i16 = pInBuffer->mBuffer;
+                memcpy(pInBuffer->raw, inBuffer.raw, inBuffer.frameCount * channels * sizeof(int16_t));
+                mBufferQueue.add(pInBuffer);
+                LOGV("OutputTrack::write() %p thread %p adding overflow buffer %d", this, mThread.unsafe_get(), mBufferQueue.size());
+            } else {
+                LOGW("OutputTrack::write() %p thread %p no more overflow buffers", mThread.unsafe_get(), this);
+            }
         }
     }
 
diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h
index 8c29da8..12c90eb 100644
--- a/libs/audioflinger/AudioFlinger.h
+++ b/libs/audioflinger/AudioFlinger.h
@@ -20,6 +20,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <limits.h>
 
 #include <media/IAudioFlinger.h>
 #include <media/IAudioFlingerClient.h>
@@ -208,6 +209,7 @@
     class PlaybackThread;
     class MixerThread;
     class DirectOutputThread;
+    class DuplicatingThread;
     class Track;
     class RecordTrack;
 
@@ -324,6 +326,7 @@
                     void        sendConfigEvent_l(int event, int param = 0);
                     void        processConfigEvents();
                     int         id() const { return mId;}
+                    bool        standby() { return mStandby; }
 
         mutable     Mutex                   mLock;
 
@@ -452,6 +455,7 @@
             };
 
                                 OutputTrack(  const wp<ThreadBase>& thread,
+                                        DuplicatingThread *sourceThread,
                                         uint32_t sampleRate,
                                         int format,
                                         int channelCount,
@@ -471,13 +475,12 @@
             void                clearBufferQueue();
 
             // Maximum number of pending buffers allocated by OutputTrack::write()
-            static const uint8_t kMaxOverFlowBuffers = 3;
+            static const uint8_t kMaxOverFlowBuffers = 10;
 
             Vector < Buffer* >          mBufferQueue;
             AudioBufferProvider::Buffer mOutBuffer;
-            uint32_t                    mWaitTimeMs;
             bool                        mActive;
-
+            DuplicatingThread*          mSourceThread;
         };  // end of OutputTrack
 
         PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id);
@@ -520,6 +523,7 @@
         virtual     int         type() const { return mType; }
                     void        suspend() { mSuspended++; }
                     void        restore() { if (mSuspended) mSuspended--; }
+                    bool        isSuspended() { return (mSuspended != 0); }
         virtual     String8     getParameters(const String8& keys);
         virtual     void        audioConfigChanged(int event, int param = 0);
 
@@ -635,9 +639,16 @@
         virtual     bool        threadLoop();
                     void        addOutputTrack(MixerThread* thread);
                     void        removeOutputTrack(MixerThread* thread);
+                    uint32_t    waitTimeMs() { return mWaitTimeMs; }
+    protected:
+        virtual     uint32_t    activeSleepTimeUs();
 
     private:
+                    bool        outputsReady(SortedVector< sp<OutputTrack> > &outputTracks);
+                    void        updateWaitTime();
+
         SortedVector < sp<OutputTrack> >  mOutputTracks;
+                    uint32_t    mWaitTimeMs;
     };
 
               PlaybackThread *checkPlaybackThread_l(int output) const;
diff --git a/opengl/include/ETC1/etc1.h b/opengl/include/ETC1/etc1.h
new file mode 100644
index 0000000..0d38905
--- /dev/null
+++ b/opengl/include/ETC1/etc1.h
@@ -0,0 +1,106 @@
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __etc1_h__
+#define __etc1_h__
+
+#define ETC1_ENCODED_BLOCK_SIZE 8
+#define ETC1_DECODED_BLOCK_SIZE 48
+
+#ifndef ETC1_RGB8_OES
+#define ETC1_RGB8_OES 0x8D64
+#endif
+
+typedef unsigned char etc1_byte;
+typedef int etc1_bool;
+typedef unsigned int etc1_uint32;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Encode a block of pixels.
+//
+// pIn is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a
+// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
+// value of pixel (x, y).
+//
+// validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether
+// the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing.
+//
+// pOut is an ETC1 compressed version of the data.
+
+void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 validPixelMask, etc1_byte* pOut);
+
+// Decode a block of pixels.
+//
+// pIn is an ETC1 compressed version of the data.
+//
+// pOut is a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a
+// 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
+// value of pixel (x, y).
+
+void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut);
+
+// Return the size of the encoded image data (does not include size of PKM header).
+
+etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height);
+
+// Encode an entire image.
+// pIn - pointer to the image data. Formatted such that
+//       pixel (x,y) is at pIn + pixelSize * x + stride * y;
+// pOut - pointer to encoded data. Must be large enough to store entire encoded image.
+// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image.
+// returns non-zero if there is an error.
+
+int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height,
+        etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut);
+
+// Decode an entire image.
+// pIn - pointer to encoded data.
+// pOut - pointer to the image data. Will be written such that
+//        pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be
+//        large enough to store entire image.
+// pixelSize can be 2 or 3. 2 is an GL_UNSIGNED_SHORT_5_6_5 image, 3 is a GL_BYTE RGB image.
+// returns non-zero if there is an error.
+
+int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut,
+        etc1_uint32 width, etc1_uint32 height,
+        etc1_uint32 pixelSize, etc1_uint32 stride);
+
+// Size of a PKM header, in bytes.
+
+#define ETC_PKM_HEADER_SIZE 16
+
+// Format a PKM header
+
+void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height);
+
+// Check if a PKM header is correctly formatted.
+
+etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader);
+
+// Read the image width from a PKM header
+
+etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader);
+
+// Read the image height from a PKM header
+
+etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/opengl/libagl/fixed_asm.S b/opengl/libagl/fixed_asm.S
index 6cbc56f..05044f2 100644
--- a/opengl/libagl/fixed_asm.S
+++ b/opengl/libagl/fixed_asm.S
@@ -20,7 +20,9 @@
     .align
     
     .global gglFloatToFixed
+    .type gglFloatToFixed, %function
     .global gglFloatToFixedFast
+    .type gglFloatToFixedFast, %function
 
 
 /*
diff --git a/opengl/libagl/iterators.S b/opengl/libagl/iterators.S
index daf2937..8c86482 100644
--- a/opengl/libagl/iterators.S
+++ b/opengl/libagl/iterators.S
@@ -21,6 +21,7 @@
     .arm
     
     .global iterators0032
+    .type iterators0032, %function
 
 /*
  * iterators0032
diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk
index 6d20e80..7353385 100644
--- a/opengl/libs/Android.mk
+++ b/opengl/libs/Android.mk
@@ -120,3 +120,33 @@
 endif
 
 include $(BUILD_SHARED_LIBRARY)
+
+###############################################################################
+# Build the ETC1 host static library
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= 		\
+	ETC1/etc1.cpp 	\
+#
+
+LOCAL_LDLIBS := -lpthread -ldl
+LOCAL_MODULE:= libETC1
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+###############################################################################
+# Build the ETC1 device library
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= 		\
+	ETC1/etc1.cpp 	\
+#
+
+LOCAL_LDLIBS := -lpthread -ldl
+LOCAL_MODULE:= libETC1
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/opengl/libs/ETC1/etc1.cpp b/opengl/libs/ETC1/etc1.cpp
new file mode 100644
index 0000000..5ed2c3c
--- /dev/null
+++ b/opengl/libs/ETC1/etc1.cpp
@@ -0,0 +1,670 @@
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <ETC1/etc1.h>
+
+#include <string.h>
+
+/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt
+
+ The number of bits that represent a 4x4 texel block is 64 bits if
+ <internalformat> is given by ETC1_RGB8_OES.
+
+ The data for a block is a number of bytes,
+
+ {q0, q1, q2, q3, q4, q5, q6, q7}
+
+ where byte q0 is located at the lowest memory address and q7 at
+ the highest. The 64 bits specifying the block is then represented
+ by the following 64 bit integer:
+
+ int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7;
+
+ ETC1_RGB8_OES:
+
+ a) bit layout in bits 63 through 32 if diffbit = 0
+
+ 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48
+ -----------------------------------------------
+ | base col1 | base col2 | base col1 | base col2 |
+ | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)|
+ -----------------------------------------------
+
+ 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32
+ ---------------------------------------------------
+ | base col1 | base col2 | table  | table  |diff|flip|
+ | B1 (4bits)| B2 (4bits)| cw 1   | cw 2   |bit |bit |
+ ---------------------------------------------------
+
+
+ b) bit layout in bits 63 through 32 if diffbit = 1
+
+ 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48
+ -----------------------------------------------
+ | base col1    | dcol 2 | base col1    | dcol 2 |
+ | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    |
+ -----------------------------------------------
+
+ 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32
+ ---------------------------------------------------
+ | base col 1   | dcol 2 | table  | table  |diff|flip|
+ | B1' (5 bits) | dB2    | cw 1   | cw 2   |bit |bit |
+ ---------------------------------------------------
+
+
+ c) bit layout in bits 31 through 0 (in both cases)
+
+ 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
+ -----------------------------------------------
+ |       most significant pixel index bits       |
+ | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a|
+ -----------------------------------------------
+
+ 15 14 13 12 11 10  9  8  7  6  5  4  3   2   1  0
+ --------------------------------------------------
+ |         least significant pixel index bits       |
+ | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a |
+ --------------------------------------------------
+
+
+ Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures:
+
+ table codeword                modifier table
+ ------------------        ----------------------
+ 0                     -8  -2  2   8
+ 1                    -17  -5  5  17
+ 2                    -29  -9  9  29
+ 3                    -42 -13 13  42
+ 4                    -60 -18 18  60
+ 5                    -80 -24 24  80
+ 6                   -106 -33 33 106
+ 7                   -183 -47 47 183
+
+
+ Add table 3.17.3 Mapping from pixel index values to modifier values for
+ ETC1 compressed textures:
+
+ pixel index value
+ ---------------
+ msb     lsb           resulting modifier value
+ -----   -----          -------------------------
+ 1       1            -b (large negative value)
+ 1       0            -a (small negative value)
+ 0       0             a (small positive value)
+ 0       1             b (large positive value)
+
+
+ */
+
+static const int kModifierTable[] = {
+/* 0 */2, 8, -2, -8,
+/* 1 */5, 17, -5, -17,
+/* 2 */9, 29, -9, -29,
+/* 3 */13, 42, -13, -42,
+/* 4 */18, 60, -18, -60,
+/* 5 */24, 80, -24, -80,
+/* 6 */33, 106, -33, -106,
+/* 7 */47, 183, -47, -183 };
+
+static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 };
+
+static inline etc1_byte clamp(int x) {
+    return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0);
+}
+
+static
+inline int convert4To8(int b) {
+    int c = b & 0xf;
+    return (c << 4) | c;
+}
+
+static
+inline int convert5To8(int b) {
+    int c = b & 0x1f;
+    return (c << 3) | (c >> 2);
+}
+
+static
+inline int convert6To8(int b) {
+    int c = b & 0x3f;
+    return (c << 2) | (c >> 4);
+}
+
+static
+inline int divideBy255(int d) {
+    return (d + 128 + (d >> 8)) >> 8;
+}
+
+static
+inline int convert8To4(int b) {
+    int c = b & 0xff;
+    return divideBy255(b * 15);
+}
+
+static
+inline int convert8To5(int b) {
+    int c = b & 0xff;
+    return divideBy255(b * 31);
+}
+
+static
+inline int convertDiff(int base, int diff) {
+    return convert5To8((0x1f & base) + kLookup[0x7 & diff]);
+}
+
+static
+void decode_subblock(etc1_byte* pOut, int r, int g, int b, const int* table,
+        etc1_uint32 low, bool second, bool flipped) {
+    int baseX = 0;
+    int baseY = 0;
+    if (second) {
+        if (flipped) {
+            baseY = 2;
+        } else {
+            baseX = 2;
+        }
+    }
+    for (int i = 0; i < 8; i++) {
+        int x, y;
+        if (flipped) {
+            x = baseX + (i >> 1);
+            y = baseY + (i & 1);
+        } else {
+            x = baseX + (i >> 2);
+            y = baseY + (i & 3);
+        }
+        int k = y + (x * 4);
+        int offset = ((low >> k) & 1) | ((low >> (k + 15)) & 2);
+        int delta = table[offset];
+        etc1_byte* q = pOut + 3 * (x + 4 * y);
+        *q++ = clamp(r + delta);
+        *q++ = clamp(g + delta);
+        *q++ = clamp(b + delta);
+    }
+}
+
+// Input is an ETC1 compressed version of the data.
+// Output is a 4 x 4 square of 3-byte pixels in form R, G, B
+
+void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut) {
+    etc1_uint32 high = (pIn[0] << 24) | (pIn[1] << 16) | (pIn[2] << 8) | pIn[3];
+    etc1_uint32 low = (pIn[4] << 24) | (pIn[5] << 16) | (pIn[6] << 8) | pIn[7];
+    int r1, r2, g1, g2, b1, b2;
+    if (high & 2) {
+        // differential
+        int rBase = high >> 27;
+        int gBase = high >> 19;
+        int bBase = high >> 11;
+        r1 = convert5To8(rBase);
+        r2 = convertDiff(rBase, high >> 24);
+        g1 = convert5To8(gBase);
+        g2 = convertDiff(gBase, high >> 16);
+        b1 = convert5To8(bBase);
+        b2 = convertDiff(bBase, high >> 8);
+    } else {
+        // not differential
+        r1 = convert4To8(high >> 28);
+        r2 = convert4To8(high >> 24);
+        g1 = convert4To8(high >> 20);
+        g2 = convert4To8(high >> 16);
+        b1 = convert4To8(high >> 12);
+        b2 = convert4To8(high >> 8);
+    }
+    int tableIndexA = 7 & (high >> 5);
+    int tableIndexB = 7 & (high >> 2);
+    const int* tableA = kModifierTable + tableIndexA * 4;
+    const int* tableB = kModifierTable + tableIndexB * 4;
+    bool flipped = (high & 1) != 0;
+    decode_subblock(pOut, r1, g1, b1, tableA, low, false, flipped);
+    decode_subblock(pOut, r2, g2, b2, tableB, low, true, flipped);
+}
+
+typedef struct {
+    etc1_uint32 high;
+    etc1_uint32 low;
+    etc1_uint32 score; // Lower is more accurate
+} etc_compressed;
+
+static
+inline void take_best(etc_compressed* a, const etc_compressed* b) {
+    if (a->score > b->score) {
+        *a = *b;
+    }
+}
+
+static
+void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask,
+        etc1_byte* pColors, bool flipped, bool second) {
+    int r = 0;
+    int g = 0;
+    int b = 0;
+
+    if (flipped) {
+        int by = 0;
+        if (second) {
+            by = 2;
+        }
+        for (int y = 0; y < 2; y++) {
+            int yy = by + y;
+            for (int x = 0; x < 4; x++) {
+                int i = x + 4 * yy;
+                if (inMask & (1 << i)) {
+                    const etc1_byte* p = pIn + i * 3;
+                    r += *(p++);
+                    g += *(p++);
+                    b += *(p++);
+                }
+            }
+        }
+    } else {
+        int bx = 0;
+        if (second) {
+            bx = 2;
+        }
+        for (int y = 0; y < 4; y++) {
+            for (int x = 0; x < 2; x++) {
+                int xx = bx + x;
+                int i = xx + 4 * y;
+                if (inMask & (1 << i)) {
+                    const etc1_byte* p = pIn + i * 3;
+                    r += *(p++);
+                    g += *(p++);
+                    b += *(p++);
+                }
+            }
+        }
+    }
+    pColors[0] = (etc1_byte)((r + 4) >> 3);
+    pColors[1] = (etc1_byte)((g + 4) >> 3);
+    pColors[2] = (etc1_byte)((b + 4) >> 3);
+}
+
+static
+inline int square(int x) {
+    return x * x;
+}
+
+static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors,
+        const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex,
+        const int* pModifierTable) {
+    etc1_uint32 bestScore = ~0;
+    int bestIndex = 0;
+    int pixelR = pIn[0];
+    int pixelG = pIn[1];
+    int pixelB = pIn[2];
+    int r = pBaseColors[0];
+    int g = pBaseColors[1];
+    int b = pBaseColors[2];
+    for (int i = 0; i < 4; i++) {
+        int modifier = pModifierTable[i];
+        int decodedG = clamp(g + modifier);
+        etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG));
+        if (score >= bestScore) {
+            continue;
+        }
+        int decodedR = clamp(r + modifier);
+        score += (etc1_uint32) (3 * square(decodedR - pixelR));
+        if (score >= bestScore) {
+            continue;
+        }
+        int decodedB = clamp(b + modifier);
+        score += (etc1_uint32) square(decodedB - pixelB);
+        if (score < bestScore) {
+            bestScore = score;
+            bestIndex = i;
+        }
+    }
+    etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1))
+            << bitIndex;
+    *pLow |= lowMask;
+    return bestScore;
+}
+
+static
+void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask,
+        etc_compressed* pCompressed, bool flipped, bool second,
+        const etc1_byte* pBaseColors, const int* pModifierTable) {
+    int score = pCompressed->score;
+    if (flipped) {
+        int by = 0;
+        if (second) {
+            by = 2;
+        }
+        for (int y = 0; y < 2; y++) {
+            int yy = by + y;
+            for (int x = 0; x < 4; x++) {
+                int i = x + 4 * yy;
+                if (inMask & (1 << i)) {
+                    score += chooseModifier(pBaseColors, pIn + i * 3,
+                            &pCompressed->low, yy + x * 4, pModifierTable);
+                }
+            }
+        }
+    } else {
+        int bx = 0;
+        if (second) {
+            bx = 2;
+        }
+        for (int y = 0; y < 4; y++) {
+            for (int x = 0; x < 2; x++) {
+                int xx = bx + x;
+                int i = xx + 4 * y;
+                if (inMask & (1 << i)) {
+                    score += chooseModifier(pBaseColors, pIn + i * 3,
+                            &pCompressed->low, y + xx * 4, pModifierTable);
+                }
+            }
+        }
+    }
+    pCompressed->score = score;
+}
+
+static bool inRange4bitSigned(int color) {
+    return color >= -4 && color <= 3;
+}
+
+static void etc_encodeBaseColors(etc1_byte* pBaseColors,
+        const etc1_byte* pColors, etc_compressed* pCompressed) {
+    int r1, g1, b1, r2, g2, b2; // 8 bit base colors for sub-blocks
+    bool differential;
+    {
+        int r51 = convert8To5(pColors[0]);
+        int g51 = convert8To5(pColors[1]);
+        int b51 = convert8To5(pColors[2]);
+        int r52 = convert8To5(pColors[3]);
+        int g52 = convert8To5(pColors[4]);
+        int b52 = convert8To5(pColors[5]);
+
+        r1 = convert5To8(r51);
+        g1 = convert5To8(g51);
+        b1 = convert5To8(b51);
+
+        int dr = r52 - r51;
+        int dg = g52 - g51;
+        int db = b52 - b51;
+
+        differential = inRange4bitSigned(dr) && inRange4bitSigned(dg)
+                && inRange4bitSigned(db);
+        if (differential) {
+            r2 = convert5To8(r51 + dr);
+            g2 = convert5To8(g51 + dg);
+            b2 = convert5To8(b51 + db);
+            pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19)
+                    | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2;
+        }
+    }
+
+    if (!differential) {
+        int r41 = convert8To4(pColors[0]);
+        int g41 = convert8To4(pColors[1]);
+        int b41 = convert8To4(pColors[2]);
+        int r42 = convert8To4(pColors[3]);
+        int g42 = convert8To4(pColors[4]);
+        int b42 = convert8To4(pColors[5]);
+        r1 = convert4To8(r41);
+        g1 = convert4To8(g41);
+        b1 = convert4To8(b41);
+        r2 = convert4To8(r42);
+        g2 = convert4To8(g42);
+        b2 = convert4To8(b42);
+        pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42
+                << 16) | (b41 << 12) | (b42 << 8);
+    }
+    pBaseColors[0] = r1;
+    pBaseColors[1] = g1;
+    pBaseColors[2] = b1;
+    pBaseColors[3] = r2;
+    pBaseColors[4] = g2;
+    pBaseColors[5] = b2;
+}
+
+static
+void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask,
+        const etc1_byte* pColors, etc_compressed* pCompressed, bool flipped) {
+    pCompressed->score = ~0;
+    pCompressed->high = (flipped ? 1 : 0);
+    pCompressed->low = 0;
+
+    etc1_byte pBaseColors[6];
+
+    etc_encodeBaseColors(pBaseColors, pColors, pCompressed);
+
+    int originalHigh = pCompressed->high;
+
+    const int* pModifierTable = kModifierTable;
+    for (int i = 0; i < 8; i++, pModifierTable += 4) {
+        etc_compressed temp;
+        temp.score = 0;
+        temp.high = originalHigh | (i << 5);
+        temp.low = 0;
+        etc_encode_subblock_helper(pIn, inMask, &temp, flipped, false,
+                pBaseColors, pModifierTable);
+        take_best(pCompressed, &temp);
+    }
+    pModifierTable = kModifierTable;
+    etc_compressed firstHalf = *pCompressed;
+    for (int i = 0; i < 8; i++, pModifierTable += 4) {
+        etc_compressed temp;
+        temp.score = firstHalf.score;
+        temp.high = firstHalf.high | (i << 2);
+        temp.low = firstHalf.low;
+        etc_encode_subblock_helper(pIn, inMask, &temp, flipped, true,
+                pBaseColors + 3, pModifierTable);
+        if (i == 0) {
+            *pCompressed = temp;
+        } else {
+            take_best(pCompressed, &temp);
+        }
+    }
+}
+
+static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) {
+    pOut[0] = (etc1_byte)(d >> 24);
+    pOut[1] = (etc1_byte)(d >> 16);
+    pOut[2] = (etc1_byte)(d >> 8);
+    pOut[3] = (etc1_byte) d;
+}
+
+// Input is a 4 x 4 square of 3-byte pixels in form R, G, B
+// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y)
+// pixel is valid or not. Invalid pixel color values are ignored when compressing.
+// Output is an ETC1 compressed version of the data.
+
+void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask,
+        etc1_byte* pOut) {
+    etc1_byte colors[6];
+    etc1_byte flippedColors[6];
+    etc_average_colors_subblock(pIn, inMask, colors, false, false);
+    etc_average_colors_subblock(pIn, inMask, colors + 3, false, true);
+    etc_average_colors_subblock(pIn, inMask, flippedColors, true, false);
+    etc_average_colors_subblock(pIn, inMask, flippedColors + 3, true, true);
+
+    etc_compressed a, b;
+    etc_encode_block_helper(pIn, inMask, colors, &a, false);
+    etc_encode_block_helper(pIn, inMask, flippedColors, &b, true);
+    take_best(&a, &b);
+    writeBigEndian(pOut, a.high);
+    writeBigEndian(pOut + 4, a.low);
+}
+
+// Return the size of the encoded image data (does not include size of PKM header).
+
+etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height) {
+    return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1;
+}
+
+// Encode an entire image.
+// pIn - pointer to the image data. Formatted such that the Red component of
+//       pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset;
+// pOut - pointer to encoded data. Must be large enough to store entire encoded image.
+
+int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height,
+        etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut) {
+    if (pixelSize < 2 || pixelSize > 3) {
+        return -1;
+    }
+    static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff };
+    static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777,
+            0xffff };
+    etc1_byte block[ETC1_DECODED_BLOCK_SIZE];
+    etc1_byte encoded[ETC1_ENCODED_BLOCK_SIZE];
+
+    etc1_uint32 encodedWidth = (width + 3) & ~3;
+    etc1_uint32 encodedHeight = (height + 3) & ~3;
+
+    for (etc1_uint32 y = 0; y < encodedHeight; y += 4) {
+        etc1_uint32 yEnd = height - y;
+        if (yEnd > 4) {
+            yEnd = 4;
+        }
+        int ymask = kYMask[yEnd];
+        for (etc1_uint32 x = 0; x < encodedWidth; x += 4) {
+            etc1_uint32 xEnd = width - x;
+            if (xEnd > 4) {
+                xEnd = 4;
+            }
+            int mask = ymask & kXMask[xEnd];
+            for (etc1_uint32 cy = 0; cy < yEnd; cy++) {
+                etc1_byte* q = block + (cy * 4) * 3;
+                const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy);
+                if (pixelSize == 3) {
+                    memcpy(q, p, xEnd * 3);
+                } else {
+                    for (etc1_uint32 cx = 0; cx < xEnd; cx++) {
+                        int pixel = (p[1] << 8) | p[0];
+                        *q++ = convert5To8(pixel >> 11);
+                        *q++ = convert6To8(pixel >> 5);
+                        *q++ = convert5To8(pixel);
+                        p += pixelSize;
+                    }
+                }
+            }
+            etc1_encode_block(block, mask, encoded);
+            memcpy(pOut, encoded, sizeof(encoded));
+            pOut += sizeof(encoded);
+        }
+    }
+    return 0;
+}
+
+// Decode an entire image.
+// pIn - pointer to encoded data.
+// pOut - pointer to the image data. Will be written such that the Red component of
+//       pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset. Must be
+//        large enough to store entire image.
+
+
+int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut,
+        etc1_uint32 width, etc1_uint32 height,
+        etc1_uint32 pixelSize, etc1_uint32 stride) {
+    if (pixelSize < 2 || pixelSize > 3) {
+        return -1;
+    }
+    etc1_byte block[ETC1_DECODED_BLOCK_SIZE];
+
+    etc1_uint32 encodedWidth = (width + 3) & ~3;
+    etc1_uint32 encodedHeight = (height + 3) & ~3;
+
+    for (etc1_uint32 y = 0; y < encodedHeight; y += 4) {
+        etc1_uint32 yEnd = height - y;
+        if (yEnd > 4) {
+            yEnd = 4;
+        }
+        for (etc1_uint32 x = 0; x < encodedWidth; x += 4) {
+            etc1_uint32 xEnd = width - x;
+            if (xEnd > 4) {
+                xEnd = 4;
+            }
+            etc1_decode_block(pIn, block);
+            pIn += ETC1_ENCODED_BLOCK_SIZE;
+            for (etc1_uint32 cy = 0; cy < yEnd; cy++) {
+                const etc1_byte* q = block + (cy * 4) * 3;
+                etc1_byte* p = pOut + pixelSize * x + stride * (y + cy);
+                if (pixelSize == 3) {
+                    memcpy(p, q, xEnd * 3);
+                } else {
+                    for (etc1_uint32 cx = 0; cx < xEnd; cx++) {
+                        etc1_byte r = *q++;
+                        etc1_byte g = *q++;
+                        etc1_byte b = *q++;
+                        etc1_uint32 pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
+                        *p++ = (etc1_byte) pixel;
+                        *p++ = (etc1_byte) (pixel >> 8);
+                    }
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+static const char kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' };
+
+static const etc1_uint32 ETC1_PKM_FORMAT_OFFSET = 6;
+static const etc1_uint32 ETC1_PKM_ENCODED_WIDTH_OFFSET = 8;
+static const etc1_uint32 ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10;
+static const etc1_uint32 ETC1_PKM_WIDTH_OFFSET = 12;
+static const etc1_uint32 ETC1_PKM_HEIGHT_OFFSET = 14;
+
+static const etc1_uint32 ETC1_RGB_NO_MIPMAPS = 0;
+
+static void writeBEUint16(etc1_byte* pOut, etc1_uint32 data) {
+    pOut[0] = (etc1_byte) (data >> 8);
+    pOut[1] = (etc1_byte) data;
+}
+
+static etc1_uint32 readBEUint16(const etc1_byte* pIn) {
+    return (pIn[0] << 8) | pIn[1];
+}
+
+// Format a PKM header
+
+void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height) {
+    memcpy(pHeader, kMagic, sizeof(kMagic));
+    etc1_uint32 encodedWidth = (width + 3) & ~3;
+    etc1_uint32 encodedHeight = (height + 3) & ~3;
+    writeBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS);
+    writeBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth);
+    writeBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight);
+    writeBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET, width);
+    writeBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET, height);
+}
+
+// Check if a PKM header is correctly formatted.
+
+etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader) {
+    if (memcmp(pHeader, kMagic, sizeof(kMagic))) {
+        return false;
+    }
+    etc1_uint32 format = readBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET);
+    etc1_uint32 encodedWidth = readBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET);
+    etc1_uint32 encodedHeight = readBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET);
+    etc1_uint32 width = readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET);
+    etc1_uint32 height = readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET);
+    return format == ETC1_RGB_NO_MIPMAPS &&
+            encodedWidth >= width && encodedWidth - width < 4 &&
+            encodedHeight >= height && encodedHeight - height < 4;
+}
+
+// Read the image width from a PKM header
+
+etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader) {
+    return readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET);
+}
+
+// Read the image height from a PKM header
+
+etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader){
+    return readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET);
+}
diff --git a/opengl/tests/fillrate/Android.mk b/opengl/tests/fillrate/Android.mk
index a7d30c2..191c59b 100644
--- a/opengl/tests/fillrate/Android.mk
+++ b/opengl/tests/fillrate/Android.mk
@@ -6,6 +6,7 @@
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
+	libutils \
     libEGL \
     libGLESv1_CM \
     libui
diff --git a/opengl/tests/finish/Android.mk b/opengl/tests/finish/Android.mk
index 5620814..aa607c6 100644
--- a/opengl/tests/finish/Android.mk
+++ b/opengl/tests/finish/Android.mk
@@ -6,6 +6,7 @@
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
+	libutils \
     libEGL \
     libGLESv1_CM \
     libui
diff --git a/opengl/tests/gl2_jni/Android.mk b/opengl/tests/gl2_jni/Android.mk
index 81247df..384966c 100644
--- a/opengl/tests/gl2_jni/Android.mk
+++ b/opengl/tests/gl2_jni/Android.mk
@@ -11,7 +11,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := user
+LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/opengl/tests/gl_basic/gl_basic.cpp b/opengl/tests/gl_basic/gl_basic.cpp
index 7dc2378..feb964a 100644
--- a/opengl/tests/gl_basic/gl_basic.cpp
+++ b/opengl/tests/gl_basic/gl_basic.cpp
@@ -1,32 +1,39 @@
 // Simple OpenGL ES 1.x application showing how to initialize and draw something.
 
-#include <EGL/egl.h>

+#include <EGL/egl.h>
+
 #include <GLES/gl.h>
 #include <GLES/glext.h>
 
 #include <ui/FramebufferNativeWindow.h>
 #include <ui/EGLUtils.h>
 
-#include <stdio.h>

+#include <stdio.h>
+
 #include <stdlib.h>
 #include <math.h>
 
 using namespace android;
-

-EGLDisplay eglDisplay;

-EGLSurface eglSurface;

-EGLContext eglContext;

-GLuint texture;

-

+
+EGLDisplay eglDisplay;
+EGLSurface eglSurface;
+EGLContext eglContext;
+GLuint texture;
+
 #define FIXED_ONE 0x10000
-#define ITERATIONS 50

-

-int init_gl_surface(void);

-void free_gl_surface(void);

-void init_scene(void);

-void render();

+#define ITERATIONS 50
+
+int init_gl_surface(void);
+void free_gl_surface(void);
+void init_scene(void);
+void render();
 void create_texture(void);
-int readTimer(void);

+int readTimer(void);
+
+static void printGLString(const char *name, GLenum s) {
+    const char *v = (const char *) glGetString(s);
+    fprintf(stderr, "GL %s = %s\n", name, v);
+}
 
 static void gluLookAt(float eyeX, float eyeY, float eyeZ,
         float centerX, float centerY, float centerZ, float upX, float upY,
@@ -87,7 +94,6 @@
     glTranslatef(-eyeX, -eyeY, -eyeZ);
 }
 
-
 void printEGLConfiguration(EGLDisplay dpy, EGLConfig config) {
 
 #define X(VAL) {VAL, #VAL}
@@ -183,125 +189,129 @@
     free(configs);
     return true;
 }
-

-int main(int argc, char **argv)

-{

+
+int main(int argc, char **argv)
+{
     int q;
-    int start, end;

-
+    int start, end;
     printf("Initializing EGL...\n");
-

-    if(!init_gl_surface())

-    {

-        printf("GL initialisation failed - exiting\n");

-        return 0;

-    }

-

-    init_scene();

-

-    create_texture();

-

+    if(!init_gl_surface())
+    {
+        printf("GL initialisation failed - exiting\n");
+        return 0;
+    }
+    init_scene();
+    create_texture();
     printf("Running...\n");
-
     while(true) {
         render();
-    }

-

-    free_gl_surface();

-

-    return 0;

-}

-

-int init_gl_surface(void)

-{

-    EGLint numConfigs = 1;

-    EGLConfig myConfig = {0};

-    EGLint attrib[] =

-    {

-            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,

-            EGL_NONE

-    };

-

-    if ( (eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY )

-    {

-        printf("eglGetDisplay failed\n");

-        return 0;

     }
-

-    if ( eglInitialize(eglDisplay, NULL, NULL) != EGL_TRUE )

-    {

-        printf("eglInitialize failed\n");

-        return 0;

+    free_gl_surface();
+    return 0;
+}
+
+int init_gl_surface(void)
+{
+    EGLint numConfigs = 1;
+    EGLConfig myConfig = {0};
+    EGLint attrib[] =
+    {
+            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+            EGL_NONE
+    };
+
+    if ( (eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY )
+    {
+        printf("eglGetDisplay failed\n");
+        return 0;
+    }
+    
+    if ( eglInitialize(eglDisplay, NULL, NULL) != EGL_TRUE )
+    {
+        printf("eglInitialize failed\n");
+        return 0;
     }
 
     if (! printEGLConfigurations(eglDisplay)) {
         printf("printEGLConfigurations failed.\n");
         return 0;
-    }

-    EGLNativeWindowType window = android_createDisplaySurface();

-    EGLUtils::selectConfigForNativeWindow(eglDisplay, attrib, window, &myConfig);

-

+    }
+
+    EGLNativeWindowType window = android_createDisplaySurface();
+    EGLUtils::selectConfigForNativeWindow(eglDisplay, attrib, window, &myConfig);
+
     if ( (eglSurface = eglCreateWindowSurface(eglDisplay, myConfig,
-            window, 0)) == EGL_NO_SURFACE )

-    {

-        printf("eglCreateWindowSurface failed\n");

-        return 0;

-    }

-

-    if ( (eglContext = eglCreateContext(eglDisplay, myConfig, 0, 0)) == EGL_NO_CONTEXT )

-    {

-        printf("eglCreateContext failed\n");

-        return 0;

-    }

-

-    if ( eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) != EGL_TRUE )

-    {

-        printf("eglMakeCurrent failed\n");

-        return 0;

-    }

-

-    return 1;

-}

-

-void free_gl_surface(void)

-{

-    if (eglDisplay != EGL_NO_DISPLAY)

-    {

-        eglMakeCurrent( EGL_NO_DISPLAY, EGL_NO_SURFACE,

-                EGL_NO_SURFACE, EGL_NO_CONTEXT );

-        eglDestroyContext( eglDisplay, eglContext );

-        eglDestroySurface( eglDisplay, eglSurface );

-        eglTerminate( eglDisplay );

-        eglDisplay = EGL_NO_DISPLAY;

-    }

-}

-

-void init_scene(void)

-{

+            window, 0)) == EGL_NO_SURFACE )
+    {
+        printf("eglCreateWindowSurface failed\n");
+        return 0;
+    }
+
+    if ( (eglContext = eglCreateContext(eglDisplay, myConfig, 0, 0)) == EGL_NO_CONTEXT )
+    {
+        printf("eglCreateContext failed\n");
+        return 0;
+    }
+
+    if ( eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) != EGL_TRUE )
+    {
+        printf("eglMakeCurrent failed\n");
+        return 0;
+    }
+    
+    int w, h;
+
+    eglQuerySurface(eglDisplay, eglSurface, EGL_WIDTH, &w);
+    checkEglError("eglQuerySurface");
+    eglQuerySurface(eglDisplay, eglSurface, EGL_HEIGHT, &h);
+    checkEglError("eglQuerySurface");
+    GLint dim = w < h ? w : h;
+    
+    fprintf(stderr, "Window dimensions: %d x %d\n", w, h);
+
+    printGLString("Version", GL_VERSION);
+    printGLString("Vendor", GL_VENDOR);
+    printGLString("Renderer", GL_RENDERER);
+    printGLString("Extensions", GL_EXTENSIONS);
+
+    return 1;
+}
+
+void free_gl_surface(void)
+{
+    if (eglDisplay != EGL_NO_DISPLAY)
+    {
+        eglMakeCurrent( EGL_NO_DISPLAY, EGL_NO_SURFACE,
+                EGL_NO_SURFACE, EGL_NO_CONTEXT );
+        eglDestroyContext( eglDisplay, eglContext );
+        eglDestroySurface( eglDisplay, eglSurface );
+        eglTerminate( eglDisplay );
+        eglDisplay = EGL_NO_DISPLAY;
+    }
+}
+
+void init_scene(void)
+{
     glDisable(GL_DITHER);
     glEnable(GL_CULL_FACE);
-
     float ratio = 320.0f / 480.0f;
     glViewport(0, 0, 320, 480);
-
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     glFrustumf(-ratio, ratio, -1, 1, 1, 10);
-
-    glMatrixMode(GL_MODELVIEW);

+    glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     gluLookAt(
             0, 0, 3,  // eye
             0, 0, 0,  // center
             0, 1, 0); // up
-

-    glEnable(GL_TEXTURE_2D);

-    glEnableClientState(GL_VERTEX_ARRAY);

+    glEnable(GL_TEXTURE_2D);
+    glEnableClientState(GL_VERTEX_ARRAY);
     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-}

-

-void create_texture(void)

-{

+}
+
+void create_texture(void)
+{
     const unsigned int on = 0xff0000ff;
     const unsigned int off = 0xffffffff;
     const unsigned int pixels[] =
@@ -314,44 +324,42 @@
             off, on, off, on, off, on, off, on,
             on, off, on, off, on, off, on, off,
             off, on, off, on, off, on, off, on,
-    };

-    glGenTextures(1, &texture);

-    glBindTexture(GL_TEXTURE_2D, texture);

-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

-    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

-    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

-}

-

-void render()

-{

+    };
+
+    glGenTextures(1, &texture);
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+}
+
+void render()
+{
     int i, j;
-    int quads = 1;

-

-    const GLfloat vertices[] = {

-            -1,  -1,  0,

-             1,  -1,  0,

-             1,   1,  0,

-            -1,   1,  0

-    };

-

-    const GLfixed texCoords[] = {

-            0,            0,

-            FIXED_ONE,    0,

-            FIXED_ONE,    FIXED_ONE,

-            0,            FIXED_ONE

-    };

-

+    int quads = 1;
+
+    const GLfloat vertices[] = {
+            -1,  -1,  0,
+             1,  -1,  0,
+             1,   1,  0,
+            -1,   1,  0
+    };
+
+    const GLfixed texCoords[] = {
+            0,            0,
+            FIXED_ONE,    0,
+            FIXED_ONE,    FIXED_ONE,
+            0,            FIXED_ONE
+    };
+
     const GLushort indices[] = { 0, 1, 2,  0, 2, 3 };
-

-    glVertexPointer(3, GL_FLOAT, 0, vertices);

+
+    glVertexPointer(3, GL_FLOAT, 0, vertices);
     glTexCoordPointer(2, GL_FIXED, 0, texCoords);
-
     glClearColor(1.0, 1.0, 1.0, 1.0);
-
     int nelem = sizeof(indices)/sizeof(indices[0]);
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
     glDrawElements(GL_TRIANGLES, nelem, GL_UNSIGNED_SHORT, indices);
-    eglSwapBuffers(eglDisplay, eglSurface);

-}

-
+    eglSwapBuffers(eglDisplay, eglSurface);
+}
diff --git a/opengl/tests/gl_jni/Android.mk b/opengl/tests/gl_jni/Android.mk
index 4029fa1..f1bd31d 100644
--- a/opengl/tests/gl_jni/Android.mk
+++ b/opengl/tests/gl_jni/Android.mk
@@ -11,7 +11,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := user
+LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/opengl/tests/gldual/Android.mk b/opengl/tests/gldual/Android.mk
index e73c249..995a5d7 100644
--- a/opengl/tests/gldual/Android.mk
+++ b/opengl/tests/gldual/Android.mk
@@ -11,7 +11,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := user
+LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/opengl/tests/gldual/res/layout/gldual_activity.xml b/opengl/tests/gldual/res/layout/gldual_activity.xml
index f2d59c7..d75acbc 100644
--- a/opengl/tests/gldual/res/layout/gldual_activity.xml
+++ b/opengl/tests/gldual/res/layout/gldual_activity.xml
@@ -17,14 +17,14 @@
 <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:layout_width="match_parent"
+    android:layout_height="match_parent">
   <android.opengl.GLSurfaceView android:id="@+id/gl1"
-	android:layout_width="fill_parent"
+	android:layout_width="match_parent"
 	    android:layout_height="0dip"
 	    android:layout_weight="1" />
   <com.android.gldual.GLDualGL2View android:id="@+id/gl2"
-	android:layout_width="fill_parent"
+	android:layout_width="match_parent"
             android:layout_height="0dip"
             android:layout_weight="1" />
 </LinearLayout>
diff --git a/opengl/tests/swapinterval/Android.mk b/opengl/tests/swapinterval/Android.mk
index 619447c..9a4145e 100644
--- a/opengl/tests/swapinterval/Android.mk
+++ b/opengl/tests/swapinterval/Android.mk
@@ -6,6 +6,7 @@
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
+	libutils \
     libEGL \
     libGLESv1_CM \
     libui
diff --git a/vpn/java/android/net/vpn/VpnManager.java b/vpn/java/android/net/vpn/VpnManager.java
index 6df612e..ce40b5d 100644
--- a/vpn/java/android/net/vpn/VpnManager.java
+++ b/vpn/java/android/net/vpn/VpnManager.java
@@ -16,11 +16,15 @@
 
 package android.net.vpn;
 
+import java.io.File;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.os.Environment;
+import android.os.SystemProperties;
 import android.util.Log;
 
 /**
@@ -65,7 +69,7 @@
     /** Error code to indicate a successful connection. */
     public static final int VPN_ERROR_NO_ERROR = 0;
 
-    public static final String PROFILES_PATH = "/data/misc/vpn/profiles";
+    public static final String PROFILES_PATH = "/misc/vpn/profiles";
 
     private static final String PACKAGE_PREFIX =
             VpnManager.class.getPackage().getName() + ".";
@@ -77,7 +81,13 @@
     private static final String ACTION_VPN_SETTINGS =
             PACKAGE_PREFIX + "SETTINGS";
 
-    private static final String TAG = VpnManager.class.getSimpleName();
+    public static final String TAG = VpnManager.class.getSimpleName();
+
+    // TODO(oam): Test VPN when EFS is enabled (will do later)...
+    public static String getProfilePath() {
+        // This call will return the correct path if Encrypted FS is enabled or not.
+        return Environment.getSecureDataDirectory().getPath() + PROFILES_PATH;
+    }
 
     /**
      * Returns all supported VPN types.