am 3db1ebaf: am 4ed2b1c3: Pass touch event time from Java to WebKit

Merge commit '3db1ebaf1cc2a097845f217626e31d69e156b73d'

* commit '3db1ebaf1cc2a097845f217626e31d69e156b73d':
  Pass touch event time from Java to WebKit
diff --git a/awt/javax/imageio/metadata/IIOMetadataNode.java b/awt/javax/imageio/metadata/IIOMetadataNode.java
index adc6d67..efbaae8 100644
--- a/awt/javax/imageio/metadata/IIOMetadataNode.java
+++ b/awt/javax/imageio/metadata/IIOMetadataNode.java
@@ -27,6 +27,8 @@
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
+import org.w3c.dom.TypeInfo;
+import org.w3c.dom.UserDataHandler;
 
 //???AWT
 //import org.w3c.dom.TypeInfo;
@@ -924,6 +926,14 @@
         return userObject;
     }
 
+    public TypeInfo getSchemaTypeInfo() {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object setUserData(String key, Object data, UserDataHandler handler) {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Sets the user object associated with this node.
      * 
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index 6419a5c..405d471 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
@@ -198,7 +197,7 @@
 static sp<MediaPlayer> newMediaPlayer(const char *file)
 {
     sp<MediaPlayer> mp = new MediaPlayer();
-    if (mp->setDataSource(file) == NO_ERROR) {
+    if (mp->setDataSource(file, NULL /* headers */) == NO_ERROR) {
         mp->setAudioStreamType(AudioSystem::ENFORCED_AUDIBLE);
         mp->prepare();
     } else {
@@ -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;
@@ -236,8 +235,9 @@
 
     // Callback is disabled by default
     mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
+    mOrientation = 0;
     cameraService->incUsers();
-    LOGD("Client::Client X (pid %d)", callingPid);
+    LOGV("Client::Client X (pid %d)", callingPid);
 }
 
 status_t CameraService::Client::checkPid()
@@ -253,7 +253,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 +267,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 +286,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 +310,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 +344,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 +373,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 +397,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 +421,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 +438,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 +487,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 +512,7 @@
 
     default: // CAMERA_PREVIEW_MODE
         if (mSurface == 0) {
-            LOGD("mSurface is not set yet.");
+            LOGV("mSurface is not set yet.");
         }
         return startPreviewMode();
     }
@@ -520,7 +520,7 @@
 
 status_t CameraService::Client::startRecordingMode()
 {
-    LOGD("startRecordingMode (pid %d)", getCallingPid());
+    LOGV("startRecordingMode (pid %d)", getCallingPid());
 
     status_t ret = UNKNOWN_ERROR;
 
@@ -547,7 +547,7 @@
 
 status_t CameraService::Client::setOverlay()
 {
-    LOGD("setOverlay");
+    LOGV("setOverlay");
     int w, h;
     CameraParameters params(mHardware->getParameters());
     params.getPreviewSize(&w, &h);
@@ -571,9 +571,10 @@
             // wait in the createOverlay call if the previous overlay is in the 
             // process of being destroyed.
             for (int retry = 0; retry < 50; ++retry) {
-                mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT);
+                mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT,
+                                                      mOrientation);
                 if (mOverlayRef != NULL) break;
-                LOGD("Overlay create failed - retrying");
+                LOGW("Overlay create failed - retrying");
                 usleep(20000);
             }
             if ( mOverlayRef.get() == NULL )
@@ -602,15 +603,9 @@
     CameraParameters params(mHardware->getParameters());
     params.getPreviewSize(&w, &h);
 
-    uint32_t transform = 0;
-    if (params.getOrientation() ==
-        CameraParameters::CAMERA_ORIENTATION_PORTRAIT) {
-      LOGV("portrait mode");
-      transform = ISurface::BufferHeap::ROT_90;
-    }
     ISurface::BufferHeap buffers(w, h, w, h,
                                  PIXEL_FORMAT_YCbCr_420_SP,
-                                 transform,
+                                 mOrientation,
                                  0,
                                  mHardware->getPreviewHeap());
 
@@ -623,7 +618,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 +654,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 +682,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 +696,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 +713,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 +732,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 +800,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 +820,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 +828,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 +844,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 +861,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();
@@ -920,12 +915,6 @@
     if (mSurface != 0 && !mUseOverlay) {
         int w, h;
         CameraParameters params(mHardware->getParameters());
-        uint32_t transform = 0;
-        if (params.getOrientation() == CameraParameters::CAMERA_ORIENTATION_PORTRAIT) {
-            LOGV("portrait mode");
-            transform = ISurface::BufferHeap::ROT_90;
-        }
-
         if (size == NULL) {
             params.getPictureSize(&w, &h);
         } else {
@@ -933,10 +922,10 @@
             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());
+            PIXEL_FORMAT_YCbCr_420_SP, mOrientation, 0, mHardware->getRawHeap());
 
         mSurface->registerBuffers(buffers);
     }
@@ -952,7 +941,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 +1178,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();
@@ -1201,6 +1190,7 @@
     }
 
     CameraParameters p(params);
+
     return mHardware->setParameters(p);
 }
 
@@ -1215,17 +1205,41 @@
     }
 
     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;
 
+    if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) {
+        // The orientation cannot be set during preview.
+        if (mHardware->previewEnabled()) {
+            return INVALID_OPERATION;
+        }
+        switch (arg1) {
+            case 0:
+                mOrientation = ISurface::BufferHeap::ROT_0;
+                break;
+            case 90:
+                mOrientation = ISurface::BufferHeap::ROT_90;
+                break;
+            case 180:
+                mOrientation = ISurface::BufferHeap::ROT_180;
+                break;
+            case 270:
+                mOrientation = ISurface::BufferHeap::ROT_270;
+                break;
+            default:
+                return BAD_VALUE;
+        }
+        return OK;
+    }
+
     if (mHardware == 0) {
         LOGE("mHardware is NULL, returning.");
         return INVALID_OPERATION;
@@ -1327,7 +1341,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 +1349,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 +1359,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/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h
index 3e3e54f..b3d20f6 100644
--- a/camera/libcameraservice/CameraService.h
+++ b/camera/libcameraservice/CameraService.h
@@ -182,6 +182,7 @@
                     sp<CameraService>           mCameraService;
                     sp<ISurface>                mSurface;
                     int                         mPreviewCallbackFlag;
+                    int                         mOrientation;
 
                     sp<MediaPlayer>             mMediaPlayerClick;
                     sp<MediaPlayer>             mMediaPlayerBeep;
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/camera/tests/CameraServiceTest/CameraServiceTest.cpp b/camera/tests/CameraServiceTest/CameraServiceTest.cpp
index 29320e0..f89d9d3 100644
--- a/camera/tests/CameraServiceTest/CameraServiceTest.cpp
+++ b/camera/tests/CameraServiceTest/CameraServiceTest.cpp
@@ -283,7 +283,7 @@
     virtual void postBuffer(ssize_t offset);
     virtual void unregisterBuffers();
     virtual sp<OverlayRef> createOverlay(
-            uint32_t w, uint32_t h, int32_t format);
+            uint32_t w, uint32_t h, int32_t format, int32_t orientation);
     virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage);
 
     // new functions
@@ -346,7 +346,8 @@
     }
 }
 
-sp<OverlayRef> MSurface::createOverlay(uint32_t w, uint32_t h, int32_t format) {
+sp<OverlayRef> MSurface::createOverlay(uint32_t w, uint32_t h, int32_t format,
+        int32_t orientation) {
     // We don't expect this to be called in current hardware.
     ASSERT(0);
     sp<OverlayRef> dummy;
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..25dc007
--- /dev/null
+++ b/common/java/com/android/common/DomainNameValidator.java
@@ -0,0 +1,275 @@
+/*
+ * 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) {
+            String errorMessage = e.getMessage();
+            if (errorMessage == null) {
+                errorMessage = "failed to parse certificate";
+            }
+
+            Log.w(TAG, "DomainNameValidator.matchDns(): " + errorMessage);
+            return false;
+        }
+
+        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/java/com/android/common/Patterns.java b/common/java/com/android/common/Patterns.java
index 2eab3e1..24a18c0 100644
--- a/common/java/com/android/common/Patterns.java
+++ b/common/java/com/android/common/Patterns.java
@@ -122,7 +122,7 @@
 
     public static final Pattern EMAIL_ADDRESS
         = Pattern.compile(
-            "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
+            "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
             "\\@" +
             "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
             "(" +
diff --git a/common/java/com/android/common/speech/LoggingEvents.java b/common/java/com/android/common/speech/LoggingEvents.java
new file mode 100644
index 0000000..3b3ecb8
--- /dev/null
+++ b/common/java/com/android/common/speech/LoggingEvents.java
@@ -0,0 +1,130 @@
+/*
+ * 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.speech;
+
+/**
+ * Logging event constants used for Voice Search and VoiceIME. These are the
+ * keys and values of extras to be specified in logging broadcast intents.
+ * This class is used by clients of the android.speech APIs to log how the
+ * user interacts with the IME settings and speech recognition result.
+ */
+public class LoggingEvents {
+    // The name of the broadcast intent for logging.
+    public static final String ACTION_LOG_EVENT = "com.android.common.speech.LOG_EVENT";
+
+    // The extra key used for the name of the app being logged.
+    public static final String EXTRA_APP_NAME = "app_name";
+
+    // The extra key used for the name of the app issuing the VoiceSearch
+    // or VoiceIME request
+    public static final String EXTRA_CALLING_APP_NAME = "";
+
+    // The extra key used for the event value. The possible event values depend
+    // on the app being logged for, and are defined in the subclasses below.
+    public static final String EXTRA_EVENT = "extra_event";
+
+    // The extra key used to log the time in milliseconds at which the EXTRA_EVENT
+    // occurred in the client.
+    public static final String EXTRA_TIMESTAMP = "timestamp";
+
+    // The extra key used (with a boolean value of 'true') as a way to trigger a
+    // flush of the log events to the server.
+    public static final String EXTRA_FLUSH = "flush";
+
+    /**
+     * Logging event constants for voice search. Below are the extra values for
+     * {@link LoggingEvents#EXTRA_EVENT}, clustered with keys to additional
+     * extras for some events that need to be included as additional fields in
+     * the event. Note that this is not representative of *all* voice search
+     * events - only the ones that need to be reported from outside the voice
+     * search app, such as from Browser.
+     */
+    public class VoiceSearch {
+        // The app name to be used for logging VoiceSearch events.
+        public static final String APP_NAME = "googlemobile";
+
+        public static final int RETRY = 0;
+
+        public static final int N_BEST_REVEAL = 1;
+
+        public static final int N_BEST_CHOOSE = 2;
+        public static final String EXTRA_N_BEST_CHOOSE_INDEX = "index";  // value should be int
+
+        public static final int QUERY_UPDATED = 3;
+        public static final String EXTRA_QUERY_UPDATED_VALUE = "value";  // value should be String
+    }
+
+    /**
+     * Logging event constants for VoiceIME. Below are the extra values for
+     * {@link LoggingEvents#EXTRA_EVENT}, clustered with keys to additional
+     * extras for some events that need to be included as additional fields in
+     * the event.
+     */
+    public class VoiceIme {
+        // The app name to be used for logging VoiceIME events.
+        public static final String APP_NAME = "voiceime";
+
+        public static final int KEYBOARD_WARNING_DIALOG_SHOWN = 0;
+
+        public static final int KEYBOARD_WARNING_DIALOG_DISMISSED = 1;
+
+        public static final int KEYBOARD_WARNING_DIALOG_OK = 2;
+
+        public static final int KEYBOARD_WARNING_DIALOG_CANCEL = 3;
+
+        public static final int SETTINGS_WARNING_DIALOG_SHOWN = 4;
+
+        public static final int SETTINGS_WARNING_DIALOG_DISMISSED = 5;
+
+        public static final int SETTINGS_WARNING_DIALOG_OK = 6;
+
+        public static final int SETTINGS_WARNING_DIALOG_CANCEL = 7;
+
+        public static final int SWIPE_HINT_DISPLAYED = 8;
+
+        public static final int PUNCTUATION_HINT_DISPLAYED = 9;
+
+        public static final int CANCEL_DURING_LISTENING = 10;
+
+        public static final int CANCEL_DURING_WORKING = 11;
+
+        public static final int CANCEL_DURING_ERROR = 12;
+
+        public static final int ERROR = 13;
+        public static final String EXTRA_ERROR_CODE = "code";  // value should be int
+
+        public static final int START = 14;
+        public static final String EXTRA_START_LOCALE = "locale";  // value should be String
+        public static final String EXTRA_START_SWIPE = "swipe";  // value should be boolean
+
+        public static final int VOICE_INPUT_DELIVERED = 15;
+
+        public static final int N_BEST_CHOOSE = 16;
+        public static final String EXTRA_N_BEST_CHOOSE_INDEX = "index";  // value should be int
+
+        public static final int TEXT_MODIFIED = 17;
+
+        public static final int INPUT_ENDED = 18;
+
+        public static final int VOICE_INPUT_SETTING_ENABLED = 19;
+
+        public static final int VOICE_INPUT_SETTING_DISABLED = 20;
+
+        public static final int IME_TEXT_ACCEPTED = 21;
+    }
+
+}
diff --git a/common/java/com/android/common/userhappiness/UserHappinessSignals.java b/common/java/com/android/common/userhappiness/UserHappinessSignals.java
new file mode 100644
index 0000000..347bdaa
--- /dev/null
+++ b/common/java/com/android/common/userhappiness/UserHappinessSignals.java
@@ -0,0 +1,45 @@
+/*
+ * 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.userhappiness;
+
+import android.content.Intent;
+import android.content.Context;
+import com.android.common.speech.LoggingEvents;
+
+/**
+ * Metrics for User Happiness are recorded here. Each app can define when to
+ * call these User Happiness metrics.
+ */
+public class UserHappinessSignals {
+
+    /**
+     *  Log when a user "accepted" IME text. Each application can define what
+     *  it means to "accept" text. In the case of Gmail, pressing the "Send"
+     *  button indicates text acceptance. We broadcast this information to
+     *  VoiceSearch LoggingEvents and use it to aggregate VoiceIME Happiness Metrics
+     */
+    public static void userAcceptedImeText(Context context) {
+        // Create a Voice IME Logging intent.
+        Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
+        i.putExtra(LoggingEvents.EXTRA_APP_NAME, LoggingEvents.VoiceIme.APP_NAME);
+        i.putExtra(LoggingEvents.EXTRA_EVENT, LoggingEvents.VoiceIme.IME_TEXT_ACCEPTED);
+        i.putExtra(LoggingEvents.EXTRA_CALLING_APP_NAME, context.getPackageName());
+        i.putExtra(LoggingEvents.EXTRA_TIMESTAMP, System.currentTimeMillis());
+        context.sendBroadcast(i);
+    }
+
+}
diff --git a/common/java/com/android/common/widget/NumberPicker.java b/common/java/com/android/common/widget/NumberPicker.java
deleted file mode 100644
index 64b436f..0000000
--- a/common/java/com/android/common/widget/NumberPicker.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.common.widget;
-
-import android.content.Context;
-import android.os.Handler;
-import android.text.InputFilter;
-import android.text.InputType;
-import android.text.Spanned;
-import android.text.method.NumberKeyListener;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.View.OnLongClickListener;
-import android.widget.TextView;
-import android.widget.LinearLayout;
-import android.widget.EditText;
-
-import com.android.internal.R;
-
-public class NumberPicker extends LinearLayout implements OnClickListener,
-        OnFocusChangeListener, OnLongClickListener {
-
-    public interface OnChangedListener {
-        void onChanged(NumberPicker picker, int oldVal, int newVal);
-    }
-
-    public interface Formatter {
-        String toString(int value);
-    }
-
-    /*
-     * Use a custom NumberPicker formatting callback to use two-digit
-     * minutes strings like "01".  Keeping a static formatter etc. is the
-     * most efficient way to do this; it avoids creating temporary objects
-     * on every call to format().
-     */
-    public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER =
-            new NumberPicker.Formatter() {
-                final StringBuilder mBuilder = new StringBuilder();
-                final java.util.Formatter mFmt = new java.util.Formatter(mBuilder);
-                final Object[] mArgs = new Object[1];
-                public String toString(int value) {
-                    mArgs[0] = value;
-                    mBuilder.delete(0, mBuilder.length());
-                    mFmt.format("%02d", mArgs);
-                    return mFmt.toString();
-                }
-        };
-
-    private final Handler mHandler;
-    private final Runnable mRunnable = new Runnable() {
-        public void run() {
-            if (mIncrement) {
-                changeCurrent(mCurrent + 1);
-                mHandler.postDelayed(this, mSpeed);
-            } else if (mDecrement) {
-                changeCurrent(mCurrent - 1);
-                mHandler.postDelayed(this, mSpeed);
-            }
-        }
-    };
-
-    private final EditText mText;
-    private final InputFilter mNumberInputFilter;
-
-    private String[] mDisplayedValues;
-    protected int mStart;
-    protected int mEnd;
-    protected int mCurrent;
-    protected int mPrevious;
-    private OnChangedListener mListener;
-    private Formatter mFormatter;
-    private long mSpeed = 300;
-
-    private boolean mIncrement;
-    private boolean mDecrement;
-
-    public NumberPicker(Context context) {
-        this(context, null);
-    }
-
-    public NumberPicker(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    @SuppressWarnings({"UnusedDeclaration"})
-    public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs);
-        setOrientation(VERTICAL);
-        LayoutInflater inflater =
-                (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.number_picker, this, true);
-        mHandler = new Handler();
-        InputFilter inputFilter = new NumberPickerInputFilter();
-        mNumberInputFilter = new NumberRangeKeyListener();
-        mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
-        mIncrementButton.setOnClickListener(this);
-        mIncrementButton.setOnLongClickListener(this);
-        mIncrementButton.setNumberPicker(this);
-        mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
-        mDecrementButton.setOnClickListener(this);
-        mDecrementButton.setOnLongClickListener(this);
-        mDecrementButton.setNumberPicker(this);
-
-        mText = (EditText) findViewById(R.id.timepicker_input);
-        mText.setOnFocusChangeListener(this);
-        mText.setFilters(new InputFilter[] {inputFilter});
-        mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
-
-        if (!isEnabled()) {
-            setEnabled(false);
-        }
-    }
-
-    @Override
-    public void setEnabled(boolean enabled) {
-        super.setEnabled(enabled);
-        mIncrementButton.setEnabled(enabled);
-        mDecrementButton.setEnabled(enabled);
-        mText.setEnabled(enabled);
-    }
-
-    public void setOnChangeListener(OnChangedListener listener) {
-        mListener = listener;
-    }
-
-    public void setFormatter(Formatter formatter) {
-        mFormatter = formatter;
-    }
-
-    /**
-     * Set the range of numbers allowed for the number picker. The current
-     * value will be automatically set to the start.
-     *
-     * @param start the start of the range (inclusive)
-     * @param end the end of the range (inclusive)
-     */
-    public void setRange(int start, int end) {
-        mStart = start;
-        mEnd = end;
-        mCurrent = start;
-        updateView();
-    }
-
-    /**
-     * Set the range of numbers allowed for the number picker. The current
-     * value will be automatically set to the start. Also provide a mapping
-     * for values used to display to the user.
-     *
-     * @param start the start of the range (inclusive)
-     * @param end the end of the range (inclusive)
-     * @param displayedValues the values displayed to the user.
-     */
-    public void setRange(int start, int end, String[] displayedValues) {
-        mDisplayedValues = displayedValues;
-        mStart = start;
-        mEnd = end;
-        mCurrent = start;
-        updateView();
-    }
-
-    public void setCurrent(int current) {
-        mCurrent = current;
-        updateView();
-    }
-
-    /**
-     * The speed (in milliseconds) at which the numbers will scroll
-     * when the the +/- buttons are longpressed. Default is 300ms.
-     */
-    public void setSpeed(long speed) {
-        mSpeed = speed;
-    }
-
-    public void onClick(View v) {
-        validateInput(mText);
-        if (!mText.hasFocus()) mText.requestFocus();
-
-        // now perform the increment/decrement
-        if (R.id.increment == v.getId()) {
-            changeCurrent(mCurrent + 1);
-        } else if (R.id.decrement == v.getId()) {
-            changeCurrent(mCurrent - 1);
-        }
-    }
-
-    private String formatNumber(int value) {
-        return (mFormatter != null)
-                ? mFormatter.toString(value)
-                : String.valueOf(value);
-    }
-
-    protected void changeCurrent(int current) {
-
-        // Wrap around the values if we go past the start or end
-        if (current > mEnd) {
-            current = mStart;
-        } else if (current < mStart) {
-            current = mEnd;
-        }
-        mPrevious = mCurrent;
-        mCurrent = current;
-        notifyChange();
-        updateView();
-    }
-
-    protected void notifyChange() {
-        if (mListener != null) {
-            mListener.onChanged(this, mPrevious, mCurrent);
-        }
-    }
-
-    protected void updateView() {
-
-        /* If we don't have displayed values then use the
-         * current number else find the correct value in the
-         * displayed values for the current number.
-         */
-        if (mDisplayedValues == null) {
-            mText.setText(formatNumber(mCurrent));
-        } else {
-            mText.setText(mDisplayedValues[mCurrent - mStart]);
-        }
-        mText.setSelection(mText.getText().length());
-    }
-
-    private void validateCurrentView(CharSequence str) {
-        int val = getSelectedPos(str.toString());
-        if ((val >= mStart) && (val <= mEnd)) {
-            if (mCurrent != val) {
-                mPrevious = mCurrent;
-                mCurrent = val;
-                notifyChange();
-            }
-        }
-        updateView();
-    }
-
-    public void onFocusChange(View v, boolean hasFocus) {
-
-        /* When focus is lost check that the text field
-         * has valid values.
-         */
-        if (!hasFocus) {
-            validateInput(v);
-        }
-    }
-
-    private void validateInput(View v) {
-        String str = String.valueOf(((TextView) v).getText());
-        if ("".equals(str)) {
-
-            // Restore to the old value as we don't allow empty values
-            updateView();
-        } else {
-
-            // Check the new value and ensure it's in range
-            validateCurrentView(str);
-        }
-    }
-
-    /**
-     * We start the long click here but rely on the {@link NumberPickerButton}
-     * to inform us when the long click has ended.
-     */
-    public boolean onLongClick(View v) {
-
-        /* The text view may still have focus so clear it's focus which will
-         * trigger the on focus changed and any typed values to be pulled.
-         */
-        mText.clearFocus();
-
-        if (R.id.increment == v.getId()) {
-            mIncrement = true;
-            mHandler.post(mRunnable);
-        } else if (R.id.decrement == v.getId()) {
-            mDecrement = true;
-            mHandler.post(mRunnable);
-        }
-        return true;
-    }
-
-    public void cancelIncrement() {
-        mIncrement = false;
-    }
-
-    public void cancelDecrement() {
-        mDecrement = false;
-    }
-
-    private static final char[] DIGIT_CHARACTERS = new char[] {
-        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
-    };
-
-    private NumberPickerButton mIncrementButton;
-    private NumberPickerButton mDecrementButton;
-
-    private class NumberPickerInputFilter implements InputFilter {
-        public CharSequence filter(CharSequence source, int start, int end,
-                Spanned dest, int dstart, int dend) {
-            if (mDisplayedValues == null) {
-                return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
-            }
-            CharSequence filtered = String.valueOf(source.subSequence(start, end));
-            String result = String.valueOf(dest.subSequence(0, dstart))
-                    + filtered
-                    + dest.subSequence(dend, dest.length());
-            String str = String.valueOf(result).toLowerCase();
-            for (String val : mDisplayedValues) {
-                val = val.toLowerCase();
-                if (val.startsWith(str)) {
-                    return filtered;
-                }
-            }
-            return "";
-        }
-    }
-
-    private class NumberRangeKeyListener extends NumberKeyListener {
-
-        // XXX This doesn't allow for range limits when controlled by a
-        // soft input method!
-        public int getInputType() {
-            return InputType.TYPE_CLASS_NUMBER;
-        }
-
-        @Override
-        protected char[] getAcceptedChars() {
-            return DIGIT_CHARACTERS;
-        }
-
-        @Override
-        public CharSequence filter(CharSequence source, int start, int end,
-                Spanned dest, int dstart, int dend) {
-
-            CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
-            if (filtered == null) {
-                filtered = source.subSequence(start, end);
-            }
-
-            String result = String.valueOf(dest.subSequence(0, dstart))
-                    + filtered
-                    + dest.subSequence(dend, dest.length());
-
-            if ("".equals(result)) {
-                return result;
-            }
-            int val = getSelectedPos(result);
-
-            /* Ensure the user can't type in a value greater
-             * than the max allowed. We have to allow less than min
-             * as the user might want to delete some numbers
-             * and then type a new number.
-             */
-            if (val > mEnd) {
-                return "";
-            } else {
-                return filtered;
-            }
-        }
-    }
-
-    private int getSelectedPos(String str) {
-        if (mDisplayedValues == null) {
-            return Integer.parseInt(str);
-        } else {
-            for (int i = 0; i < mDisplayedValues.length; i++) {
-
-                /* Don't force the user to type in jan when ja will do */
-                str = str.toLowerCase();
-                if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
-                    return mStart + i;
-                }
-            }
-
-            /* The user might have typed in a number into the month field i.e.
-             * 10 instead of OCT so support that too.
-             */
-            try {
-                return Integer.parseInt(str);
-            } catch (NumberFormatException e) {
-
-                /* Ignore as if it's not a number we don't care */
-            }
-        }
-        return mStart;
-    }
-
-    /**
-     * @return the current value.
-     */
-    public int getCurrent() {
-        return mCurrent;
-    }
-}
diff --git a/common/java/com/android/common/widget/NumberPickerButton.java b/common/java/com/android/common/widget/NumberPickerButton.java
deleted file mode 100644
index f6b6d5d..0000000
--- a/common/java/com/android/common/widget/NumberPickerButton.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.common.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.widget.ImageButton;
-
-import com.android.internal.R;
-
-/**
- * This class exists purely to cancel long click events.
- */
-public class NumberPickerButton extends ImageButton {
-
-    private NumberPicker mNumberPicker;
-
-    public NumberPickerButton(Context context, AttributeSet attrs,
-            int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public NumberPickerButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public NumberPickerButton(Context context) {
-        super(context);
-    }
-
-    public void setNumberPicker(NumberPicker picker) {
-        mNumberPicker = picker;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        cancelLongpressIfRequired(event);
-        return super.onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onTrackballEvent(MotionEvent event) {
-        cancelLongpressIfRequired(event);
-        return super.onTrackballEvent(event);
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
-                || (keyCode == KeyEvent.KEYCODE_ENTER)) {
-            cancelLongpress();
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    private void cancelLongpressIfRequired(MotionEvent event) {
-        if ((event.getAction() == MotionEvent.ACTION_CANCEL)
-                || (event.getAction() == MotionEvent.ACTION_UP)) {
-            cancelLongpress();
-        }
-    }
-
-    private void cancelLongpress() {
-        if (R.id.increment == getId()) {
-            mNumberPicker.cancelIncrement();
-        } else if (R.id.decrement == getId()) {
-            mNumberPicker.cancelDecrement();
-        }
-    }
-}
diff --git a/common/tests/res/raw/alt_ip_only.crt b/common/tests/res/raw/alt_ip_only.crt
new file mode 100644
index 0000000..3ac9f5a
--- /dev/null
+++ b/common/tests/res/raw/alt_ip_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsjCCAZqgAwIBAgIJALrC37YAXFIeMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIxMzk0NloYDzIwNjQxMDE1MjEzOTQ2WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALr8s/4Abpby
+IYks5YCJE2nbWH7kj6XbwnRzsVP9RVC33bPoQ1M+2ZY24HqkigjQS/HEXR0s0bYh
+dewNUnTj1uGyGs6cYzsbu7x114vmVYqjxUo3hKjwfYiPeF6f3IE1vpLI7I2G32gq
+Zwm9c1/vXNHIdWQxCpFcuPA8P3YGfoApFX4pQPFplBUNAQqnjdmA68cbxxMC+1F3
+mX42D7iIEVwyVpah5HjyxjIZQlf3X7QBj0bCmkL+ibIHTALrkNNwNM6i4xzYLz/5
+14GkN9ncHY87eSOk6r53ptER6mQMhCe9qPRjSHnpWTTyj6IXTaYe+dDQw657B80w
+cSHL7Ed25zUCAwEAAaMTMBEwDwYDVR0RBAgwBocEwKgKATANBgkqhkiG9w0BAQUF
+AAOCAQEAgrwrtOWZT3fbi1AafpGaAiOBWSJqYqRhtQy0AfiZBxv1U0XaYqmZmpnq
+DVAqr0NkljowD28NBrxIFO5gBNum2ZOPDl2/5vjFn+IirUCJ9u9wS7zYkTCW2lQR
+xE7Ic3mfWv7wUbKDfjlWqP1IDHUxwkrBTAl+HnwOPiaKKk1ttwcrgS8AHlqASe03
+mlwnvJ+Stk54IneRaegL0L93sNAy63RZqnPCTxGz7eHcFwX8Jdr4sbxTxQqV6pIc
+WPjHQcWfpkFzAF5wyOq0kveVfx0g5xPhOVDd+U+q7WastbXICpCoHp9FxISmZVik
+sAyifp8agkYdzaSh55fFmKXlFnRsQw==
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_alt_only.crt b/common/tests/res/raw/subject_alt_only.crt
new file mode 100644
index 0000000..d5808fb
--- /dev/null
+++ b/common/tests/res/raw/subject_alt_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvTCCAaWgAwIBAgIJALbA0TZk2YmNMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIwNTg1NFoYDzIwNjQxMDE1MjA1ODU0WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEg6acVC9V4
+xNGoLNVLPbqBc8IvMvcsc88dF6MW3d9VagX3aeWU8c79tI/KOV/1AOakH7WYxw/w
+yD8aOX7+9BK1Hu0qKKKbSM+ycqaMthXd6xytrNDsIx5WiGUz8zTko0Gk3orIR7p7
+rPcNzB/zwtESkscqPv85aEn7S/yClNkzLfEzm3CtaYOc0tfhBMyzi/ipXzGMxUmx
+PvOLr3v/Oz5pZEQw7Kxlm4+tAtn7bJlHziQ1UW4WPIy+T3hySBEpODFiqZi7Ok3X
+Zjxdii62fgo5B2Ee7q5Amo0mUIwcQTDjJ2CLAqzYnSh3tpiPJGjEIjmRyCoMQ1bx
+7D+y7nSPIq8CAwEAAaMeMBwwGgYDVR0RBBMwEYIPd3d3LmV4YW1wbGUuY29tMA0G
+CSqGSIb3DQEBBQUAA4IBAQBsGEh+nHc0l9FJTzWqvG3qs7i6XoJZdtThCDx4HjKJ
+8GMrJtreNN4JvIxn7KC+alVbnILjzCRO+c3rsnpxKBi5cp2imjuw5Kf/x2Seimb9
+UvZbaJvBVOzy4Q1IGef9bLy3wZzy2/WfBFyvPTAkgkRaX7LN2jnYOYVhNoNFrwqe
+EWxkA6fzrpyseUEFeGFFjGxRSRCDcQ25Eq6d9rkC1x21zNtt4QwZBO0wHrTy155M
+JPRynf9244Pn0Sr/wsnmdsTRFIFYynrc51hQ7DkwbUxpcaewkZzilru/SwZ3+pPT
+9JSqm5hJ1pg5WDlPkW7c/1VA0/141N52Q8MIU+2ZpuOj
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_only.crt b/common/tests/res/raw/subject_only.crt
new file mode 100644
index 0000000..11b34e7
--- /dev/null
+++ b/common/tests/res/raw/subject_only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbmgAwIBAgIJANCQbJPPw31SMA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjA1ODE4
+WhgPMjA2NDEwMTUyMDU4MThaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsdUJk
+4KxADA3vlDHxNbyC27Ozw4yiSVzPTHUct471YmdDRW3orO2P5a5hRnUGV70gjH9X
+MU4oeOdWYAgXB9pxfLyr6621k1+uNrmaZtzp0ECH9twcwxNJJFDZsN7o9vt7V6Ej
+NN9weeqDr/aeQXo07a12vyVfR6jWO8jHB0e4aemwZNoYjNvM69fivQTse2ZoRVfj
+eSHhjRTX6I8ry4a31Hwt+fT1QiWWNN6o7+WOtpJAhX3eg4smhSD1svi2kOT8tdUe
+NS4hWlmXmumU9G4tI8PBurcLNTm7PB2lUlbn/IV18WavqKE/Uy/1WgAx+a1EJNdp
+i07AG1PsqaONKkf1AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAJrNsuL7fZZNC8gL
+BdePJ7DYW2e7mXANU3bCBe2BZqmXKQxKwibZnEsqA+yMLqcSd8uxISlyHY2tw9wT
+4wB9KPIttfNLbwn/rk+MbOTHpvyF60d9WhJJVUkPBl8D4VuPSl+VnlA54kU9dtZN
++ZYdxYbNtSsI/Flz9SCoOV79W9GhN+uYJhv6RwyIMIHeMpZpyX1xSUVx5dZlmerQ
+WAUvghDH3fFRt2ZdnA4OXoKkTAaM3Pv7PUMsnah8bux6MQi0AuLMWFWOI1H34koH
+rs2oQLwOLnuifH52ey9+tJguabo+brlYYigAuWWFEzJfBzikDkIwnE/L7wlrypIk
+taXDWI4=
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_with_alt_names.crt b/common/tests/res/raw/subject_with_alt_names.crt
new file mode 100644
index 0000000..6963c7e
--- /dev/null
+++ b/common/tests/res/raw/subject_with_alt_names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBDCCAeygAwIBAgIJALv14qjcuhw9MA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjA1OTM4
+WhgPMjA2NDEwMTUyMDU5MzhaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCiTVgU
+kBO9KNYZZLmiPR0eBrk8u61CLnm35BGKW8EFpDaINLbbIFIQvqOMekURON/N+xFY
+D8roo7aFZVuHWAUqFcOJ4e6NmviK5qocLihtzAexsw4f4AzZxM3A8kcLlWLyAt7e
+EVLxhcMHogY7GaF6q+33Z8p+zp6x3tj07mwyPrriCLse2PeRsRunZl/fp/VvRlr6
+YbC7CbRrhnIv5nqohs8BsbBiiFpxQftsMQmiXhY2LUzqY2RXUIOw24fHjoQkHTL2
+4z5nUM3b6ueQe+CBnobUS6fzK/36Nct4dRpev9i/ORdRLuIDKJ+QR16G1V/BJYBR
+dAK+3iXvg6z8vP1XAgMBAAGjMTAvMC0GA1UdEQQmMCSCEHd3dzIuZXhhbXBsZS5j
+b22CEHd3dzMuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAJQNf38uXm3h
+0vsF+Yd6/HqM48Su7tWnTDAfTXnQZZkzjzITq3JXzquMXICktAVN2cLnT9zPfRAE
+8V8A3BNO5zXiR5W3o/mJP5HQ3/WxpzBGM2N+YmDCJyBoQrIVaAZaXAZUaBBvn5A+
+kEVfGWquwIFuvA67xegbJOCRLD4eUzRdNsn5+NFiakWO1tkFqEzqyQ0PNPviRjgu
+z9NxdPvd1JQOhydkucsPKJzlEBbGyL5QL/Jkot3Qy+FOeuNzgQUfAGtQgzRrsZDK
+hrTVypLSoRXuTB2aWilu4p6aNh84xTdyqo2avtNr2MiQMZIcdamBq8LdBIAShFXI
+h5G2eVGXH/Y=
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_with_wild_alt_name.crt b/common/tests/res/raw/subject_with_wild_alt_name.crt
new file mode 100644
index 0000000..19b1174
--- /dev/null
+++ b/common/tests/res/raw/subject_with_wild_alt_name.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8DCCAdigAwIBAgIJAL/oWJ64VAdXMA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjEwMDAx
+WhgPMjA2NDEwMTUyMTAwMDFaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbx1QB
+92iea7VybLYICA4MX4LWipYrRsgXUXQrcIQ3YLTQ9rH0VwScrHL4O4JDxgXCQnR+
+4VOzD42q1KXHJAqzqGUYCNPyvZEzkGCnQ4FBIUEmxZd5SNEefJVH3Z6GizYJomTh
+p78yDcoqymD9umxRC2cWFu8GscfFGMVyhsqLlOofu7UWOs22mkXPo43jDx+VOAoV
+n48YP3P57a2Eo0gcd4zVL00y62VegqBO/1LW38aTS7teiCBFc1TkNYa5I40yN9lP
+rB9ICHYQWyzf/7OxU9iauEK2w6DmSsQoLs9JzEhgeNZddkcc77ciSUCo2Hx0VpOJ
+BFyf2rbryJeAk+FDAgMBAAGjHTAbMBkGA1UdEQQSMBCCDiouZXhhbXBsZTIuY29t
+MA0GCSqGSIb3DQEBBQUAA4IBAQA2a14pRL+4laJ8sscQlucaDB/oSdb0cwhk4IkE
+kKl/ZKr6rKwPZ81sJRgzvI4imLbUAKt4AJHdpI9cIQUq1gw9bzil7LKwmFtFSPmC
+MYb1iadaYrvp7RE4yXrWCcSbU0hup9JQLHTrHLlqLtRuU48NHMvWYThBcS9Q/hQp
+nJ/JxYy3am99MHALWLAfuRxQXhE4C5utDmBwI2KD6A8SA30s+CnuegmkYScuSqBu
+Y3R0HZvKzNIU3pwAm69HCJoG+/9MZEIDJb0WJc5UygxDT45XE9zQMQe4dBOTaNXT
++ntgaB62kE10HzrzpqXAgoAWxWK4RzFcUpBWw9qYq9xOCewJ
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/wild_alt_name_only.crt b/common/tests/res/raw/wild_alt_name_only.crt
new file mode 100644
index 0000000..fafdebf
--- /dev/null
+++ b/common/tests/res/raw/wild_alt_name_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuzCCAaOgAwIBAgIJAP82tgcvmAGxMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIxMDAyN1oYDzIwNjQxMDE1MjEwMDI3WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALs528EQbcB1
+x4BwxthQBZrgDJzoO7KPV3dhGYoeP8EnRjapZm+T/sj9P/O4HvfxjnB+fsjYSdmE
+WWUtnFrP7wtG9DUC748Ea2PMV8WFhOG58dqBNIko5XzkHB7SxkNZD5S/0KQYMGLr
+rchDsDlmsEf2Qb6qiqpNEU70aSkExZJcH+B9nWdeBpsVFu7wtezwSWEc2NUa2bhW
+gcXQ/aafwHZ4o2PyGwy0sgS/UifqO9tEllC2tPleSNJOmYsVudv5Bz4Q0GG38BSz
+Pc0IcOoln0ZWpXbGr03V2vlXWCwzaFAl3I1T3O7YVqDiaSWoP+d0tHZzmw8aJLXd
+B+KaUUGxRPsCAwEAAaMcMBowGAYDVR0RBBEwD4INKi5leGFtcGxlLmNvbTANBgkq
+hkiG9w0BAQUFAAOCAQEAJbVan4QgJ0cvpJnK9UWIVJNC+UbP87RC5go2fQiTnmGv
+prOrIuMqz1+vGcpIheLTLctJRHPoadXq0+UbQEIaU3pQbY6C4nNdfl+hcvmJeqrt
+kOCcvmIamO68iNvTSeszuHuu4O38PefrW2Xd0nn7bjFZrzBzHFhTudmnqNliP3ue
+KKQpqkUt5lCytnH8V/u/UCWdvVx5LnUa2XFGVLi3ongBIojW5fvF+yxn9ADqxdrI
+va++ow5r1VxQXFJc0ZPzsDo+6TlktoDHaRQJGMqQomqHWT4i7F5UZgf6BHGfEUPU
+qep+GsF3QRHSBtpObWkVDZNFvky3a1iZ2q25+hFIqQ==
+-----END CERTIFICATE-----
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..b825be4
--- /dev/null
+++ b/common/tests/src/com/android/common/DomainNameValidatorTest.java
@@ -0,0 +1,399 @@
+/*
+ * 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 com.android.common.tests.R;
+
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+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.CertificateFactory;
+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;
+
+public class DomainNameValidatorTest extends AndroidTestCase {
+    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}, using a simple {@link X509Certificate}
+     * implementation.
+     */
+    public void testMatch() {
+        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);
+    }
+
+    /**
+     * Test {@link DomainNameValidator#match} with actual certificates.
+     */
+    public void testWithActualCert() throws Exception {
+        // subject_only
+        //
+        // subject: C=JP, CN=www.example.com
+        // subject alt names: n/a
+        checkWithActualCert("11", R.raw.subject_only, "www.example.com", true);
+        checkWithActualCert("12", R.raw.subject_only, "www2.example.com", false);
+
+        // subject_alt_only
+        //
+        // subject: C=JP (no CN)
+        // subject alt names: DNS:www.example.com
+        checkWithActualCert("21", R.raw.subject_alt_only, "www.example.com", true);
+        checkWithActualCert("22", R.raw.subject_alt_only, "www2.example.com", false);
+
+        // subject_with_alt_names
+        //
+        // subject: C=JP, CN=www.example.com
+        // subject alt names: DNS:www2.example.com, DNS:www3.example.com
+        // * Subject should be ignored, because it has subject alt names.
+        checkWithActualCert("31", R.raw.subject_with_alt_names, "www.example.com", false);
+        checkWithActualCert("32", R.raw.subject_with_alt_names, "www2.example.com", true);
+        checkWithActualCert("33", R.raw.subject_with_alt_names, "www3.example.com", true);
+        checkWithActualCert("34", R.raw.subject_with_alt_names, "www4.example.com", false);
+
+        // subject_with_wild_alt_name
+        //
+        // subject: C=JP, CN=www.example.com
+        // subject alt names: DNS:*.example2.com
+        // * Subject should be ignored, because it has subject alt names.
+        checkWithActualCert("41", R.raw.subject_with_wild_alt_name, "www.example.com", false);
+        checkWithActualCert("42", R.raw.subject_with_wild_alt_name, "www2.example.com", false);
+        checkWithActualCert("43", R.raw.subject_with_wild_alt_name, "www.example2.com", true);
+        checkWithActualCert("44", R.raw.subject_with_wild_alt_name, "abc.example2.com", true);
+        checkWithActualCert("45", R.raw.subject_with_wild_alt_name, "www.example3.com", false);
+
+        // wild_alt_name_only
+        //
+        // subject: C=JP
+        // subject alt names: DNS:*.example.com
+        checkWithActualCert("51", R.raw.wild_alt_name_only, "www.example.com", true);
+        checkWithActualCert("52", R.raw.wild_alt_name_only, "www2.example.com", true);
+        checkWithActualCert("53", R.raw.wild_alt_name_only, "www.example2.com", false);
+
+        // wild_alt_name_only
+        //
+        // subject: C=JP
+        // subject alt names: IP Address:192.168.10.1
+        checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.1", true);
+        checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.2", false);
+    }
+
+    private void checkWithActualCert(String message, int resId, String domain,
+            boolean expected) throws Exception {
+        CertificateFactory factory = CertificateFactory.getInstance("X509");
+        InputStream certStream = getContext().getResources().openRawResource(resId);
+        X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream);
+
+        checkMatch(message, certificate, domain, expected);
+    }
+
+    /**
+     * 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/include/binder/MemoryDealer.h b/include/binder/MemoryDealer.h
index 03ac70a..170f20d 100644
--- a/include/binder/MemoryDealer.h
+++ b/include/binder/MemoryDealer.h
@@ -22,232 +22,35 @@
 #include <sys/types.h>
 
 #include <binder/IMemory.h>
-#include <utils/threads.h>
 #include <binder/MemoryHeapBase.h>
 
 namespace android {
 // ----------------------------------------------------------------------------
-class String8;
 
-/*
- * interface for implementing a "heap". A heap basically provides
- * the IMemoryHeap interface for cross-process sharing and the
- * ability to map/unmap pages within the heap.
- */
-class HeapInterface : public virtual BnMemoryHeap
-{
-public:
-    // all values must be page-aligned
-    virtual sp<IMemory> mapMemory(size_t offset, size_t size) = 0;
-
-    HeapInterface();
-protected:
-    virtual ~HeapInterface();
-};
-
-// ----------------------------------------------------------------------------
-
-/*
- * interface for implementing an allocator. An allocator provides
- * methods for allocating and freeing memory blocks and dumping
- * its state.
- */
-class AllocatorInterface : public RefBase
-{
-public:
-    enum {
-        PAGE_ALIGNED = 0x00000001
-    };
-
-    virtual size_t      allocate(size_t size, uint32_t flags = 0) = 0;
-    virtual status_t    deallocate(size_t offset) = 0;
-    virtual size_t      size() const = 0;
-    virtual void        dump(const char* what, uint32_t flags = 0) const = 0;
-    virtual void        dump(String8& res,
-            const char* what, uint32_t flags = 0) const = 0;
-
-    AllocatorInterface();
-protected:
-    virtual ~AllocatorInterface();
-};
-
-// ----------------------------------------------------------------------------
-
-/*
- * concrete implementation of HeapInterface on top of mmap() 
- */
-class SharedHeap : public HeapInterface, public MemoryHeapBase
-{
-public:
-                        SharedHeap();
-                        SharedHeap(size_t size, uint32_t flags = 0, char const * name = NULL);
-    virtual             ~SharedHeap();
-    virtual sp<IMemory> mapMemory(size_t offset, size_t size);
-};
-
-// ----------------------------------------------------------------------------
-
-/*
- * A simple templatized doubly linked-list implementation
- */
-
-template <typename NODE>
-class LinkedList
-{
-    NODE*  mFirst;
-    NODE*  mLast;
-
-public:
-                LinkedList() : mFirst(0), mLast(0) { }
-    bool        isEmpty() const { return mFirst == 0; }
-    NODE const* head() const { return mFirst; }
-    NODE*       head() { return mFirst; }
-    NODE const* tail() const { return mLast; }
-    NODE*       tail() { return mLast; }
-
-    void insertAfter(NODE* node, NODE* newNode) {
-        newNode->prev = node;
-        newNode->next = node->next;
-        if (node->next == 0) mLast = newNode;
-        else                 node->next->prev = newNode;
-        node->next = newNode;
-    }
-
-    void insertBefore(NODE* node, NODE* newNode) {
-         newNode->prev = node->prev;
-         newNode->next = node;
-         if (node->prev == 0)   mFirst = newNode;
-         else                   node->prev->next = newNode;
-         node->prev = newNode;
-    }
-
-    void insertHead(NODE* newNode) {
-        if (mFirst == 0) {
-            mFirst = mLast = newNode;
-            newNode->prev = newNode->next = 0;
-        } else {
-            newNode->prev = 0;
-            newNode->next = mFirst;
-            mFirst->prev = newNode;
-            mFirst = newNode;
-        }
-    }
-    
-    void insertTail(NODE* newNode) {
-        if (mLast == 0) {
-            insertHead(newNode);
-        } else {
-            newNode->prev = mLast;
-            newNode->next = 0;
-            mLast->next = newNode;
-            mLast = newNode;
-        }
-    }
-
-    NODE* remove(NODE* node) {
-        if (node->prev == 0)    mFirst = node->next;
-        else                    node->prev->next = node->next;
-        if (node->next == 0)    mLast = node->prev;
-        else                    node->next->prev = node->prev;
-        return node;
-    }
-};
-
-
-/*
- * concrete implementation of AllocatorInterface using a simple
- * best-fit allocation scheme
- */
-class SimpleBestFitAllocator : public AllocatorInterface
-{
-public:
-
-                        SimpleBestFitAllocator(size_t size);
-    virtual             ~SimpleBestFitAllocator();
-
-    virtual size_t      allocate(size_t size, uint32_t flags = 0);
-    virtual status_t    deallocate(size_t offset);
-    virtual size_t      size() const;
-    virtual void        dump(const char* what, uint32_t flags = 0) const;
-    virtual void        dump(String8& res,
-            const char* what, uint32_t flags = 0) const;
-
-private:
-
-    struct chunk_t {
-        chunk_t(size_t start, size_t size) 
-            : start(start), size(size), free(1), prev(0), next(0) {
-        }
-        size_t              start;
-        size_t              size : 28;
-        int                 free : 4;
-        mutable chunk_t*    prev;
-        mutable chunk_t*    next;
-    };
-
-    ssize_t  alloc(size_t size, uint32_t flags);
-    chunk_t* dealloc(size_t start);
-    void     dump_l(const char* what, uint32_t flags = 0) const;
-    void     dump_l(String8& res, const char* what, uint32_t flags = 0) const;
-
-    static const int    kMemoryAlign;
-    mutable Mutex       mLock;
-    LinkedList<chunk_t> mList;
-    size_t              mHeapSize;
-};
+class SimpleBestFitAllocator;
 
 // ----------------------------------------------------------------------------
 
 class MemoryDealer : public RefBase
 {
 public:
+    MemoryDealer(size_t size, const char* name = 0);
 
-    enum {
-        READ_ONLY = MemoryHeapBase::READ_ONLY,
-        PAGE_ALIGNED = AllocatorInterface::PAGE_ALIGNED
-    };
-
-    // creates a memory dealer with the SharedHeap and SimpleBestFitAllocator
-    MemoryDealer(size_t size, uint32_t flags = 0, const char* name = 0);
-
-    // provide a custom heap but use the SimpleBestFitAllocator
-    MemoryDealer(const sp<HeapInterface>& heap);
-
-    // provide both custom heap and allocotar
-    MemoryDealer(
-            const sp<HeapInterface>& heap,
-            const sp<AllocatorInterface>& allocator);
-
-    virtual sp<IMemory> allocate(size_t size, uint32_t flags = 0);
+    virtual sp<IMemory> allocate(size_t size);
     virtual void        deallocate(size_t offset);
-    virtual void        dump(const char* what, uint32_t flags = 0) const;
-
+    virtual void        dump(const char* what) const;
 
     sp<IMemoryHeap> getMemoryHeap() const { return heap(); }
-    sp<AllocatorInterface> getAllocator() const { return allocator(); }
 
 protected:
     virtual ~MemoryDealer();
 
-private:    
-    const sp<HeapInterface>&        heap() const;
-    const sp<AllocatorInterface>&   allocator() const;
+private:
+    const sp<IMemoryHeap>&      heap() const;
+    SimpleBestFitAllocator*     allocator() const;
 
-    class Allocation : public BnMemory {
-    public:
-        Allocation(const sp<MemoryDealer>& dealer,
-                ssize_t offset, size_t size, const sp<IMemory>& memory);
-        virtual ~Allocation();
-        virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const;
-    private:
-        sp<MemoryDealer>        mDealer;
-        ssize_t                 mOffset;
-        size_t                  mSize;
-        sp<IMemory>             mMemory;
-    };
-
-    sp<HeapInterface>           mHeap;
-    sp<AllocatorInterface>      mAllocator;
+    sp<IMemoryHeap>             mHeap;
+    SimpleBestFitAllocator*     mAllocator;
 };
 
 
diff --git a/include/binder/MemoryHeapPmem.h b/include/binder/MemoryHeapPmem.h
index dbf26ff..aac164f 100644
--- a/include/binder/MemoryHeapPmem.h
+++ b/include/binder/MemoryHeapPmem.h
@@ -20,10 +20,10 @@
 #include <stdlib.h>
 #include <stdint.h>
 
-#include <binder/MemoryDealer.h>
 #include <binder/MemoryHeapBase.h>
 #include <binder/IMemory.h>
 #include <utils/SortedVector.h>
+#include <utils/threads.h>
 
 namespace android {
 
@@ -31,7 +31,7 @@
 
 // ---------------------------------------------------------------------------
 
-class MemoryHeapPmem : public HeapInterface, public MemoryHeapBase
+class MemoryHeapPmem : public MemoryHeapBase
 {
 public:
     class MemoryPmem : public BnMemory {
diff --git a/include/private/opengles/gl_context.h b/include/private/opengles/gl_context.h
index 26cde38..e0deba9 100644
--- a/include/private/opengles/gl_context.h
+++ b/include/private/opengles/gl_context.h
@@ -36,7 +36,12 @@
 
 namespace android {
 
-const unsigned int OGLES_NUM_COMPRESSED_TEXTURE_FORMATS = 10;
+
+const unsigned int OGLES_NUM_COMPRESSED_TEXTURE_FORMATS = 10
+#ifdef GL_OES_compressed_ETC1_RGB8_texture
+        + 1
+#endif
+        ;
 
 class EGLTextureObject;
 class EGLSurfaceManager;
diff --git a/include/ui/Camera.h b/include/ui/Camera.h
index 5219772..c506fb8 100644
--- a/include/ui/Camera.h
+++ b/include/ui/Camera.h
@@ -82,6 +82,7 @@
 enum {
     CAMERA_CMD_START_SMOOTH_ZOOM     = 1,
     CAMERA_CMD_STOP_SMOOTH_ZOOM      = 2,
+    CAMERA_CMD_SET_DISPLAY_ORIENTATION = 3,
 };
 
 // camera fatal errors
@@ -209,4 +210,3 @@
 }; // namespace android
 
 #endif
-
diff --git a/include/ui/CameraParameters.h b/include/ui/CameraParameters.h
index 9e4e140..2c29bfb 100644
--- a/include/ui/CameraParameters.h
+++ b/include/ui/CameraParameters.h
@@ -29,19 +29,15 @@
     CameraParameters(const String8 &params) { unflatten(params); }
     ~CameraParameters();
 
-    enum {
-        CAMERA_ORIENTATION_UNKNOWN = 0,
-        CAMERA_ORIENTATION_PORTRAIT = 1,
-        CAMERA_ORIENTATION_LANDSCAPE = 2,
-    };
-
     String8 flatten() const;
     void unflatten(const String8 &params);
 
     void set(const char *key, const char *value);
     void set(const char *key, int value);
+    void setFloat(const char *key, float value);
     const char *get(const char *key) const;
     int getInt(const char *key) const;
+    float getFloat(const char *key) const;
 
     /* preview-size=176x144 */
     void setPreviewSize(int width, int height);
@@ -63,9 +59,6 @@
     void setPictureFormat(const char *format);
     const char *getPictureFormat() const;
 
-    int getOrientation() const;
-    void setOrientation(int orientation);
-
     void dump() const;
     status_t dump(int fd, const Vector<String16>& args) const;
 
@@ -109,9 +102,10 @@
     // The height (in pixels) of EXIF thumbnail in Jpeg picture.
     // Example value: "384". Read/write.
     static const char KEY_JPEG_THUMBNAIL_HEIGHT[];
-    // Supported EXIF thumbnail sizes (width x height).
-    // Example value: "512x384,320x240". Read only.
-    static const char KEY_SUPPORTED_THUMBNAIL_SIZES[];
+    // Supported EXIF thumbnail sizes (width x height). 0x0 means not thumbnail
+    // in EXIF.
+    // Example value: "512x384,320x240,0x0". Read only.
+    static const char KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES[];
     // The quality of the EXIF thumbnail in Jpeg picture. The range is 1 to 100,
     // with 100 being the best.
     // Example value: "90". Read/write.
@@ -184,6 +178,16 @@
     // Supported focus modes.
     // Example value: "auto,macro,fixed". Read only.
     static const char KEY_SUPPORTED_FOCUS_MODES[];
+    // Focal length in millimeter.
+    // Example value: "4.31". Read only.
+    static const char KEY_FOCAL_LENGTH[];
+    // Horizontal angle of view in degrees.
+    // Example value: "54.8". Read only.
+    static const char KEY_HORIZONTAL_VIEW_ANGLE[];
+    // Vertical angle of view in degrees.
+    // Example value: "42.5". Read only.
+    static const char KEY_VERTICAL_VIEW_ANGLE[];
+
 
         // Values for white balance settings.
     static const char WHITE_BALANCE_AUTO[];
diff --git a/include/ui/ISurface.h b/include/ui/ISurface.h
index 2ca0026..c7f181c 100644
--- a/include/ui/ISurface.h
+++ b/include/ui/ISurface.h
@@ -55,8 +55,11 @@
     class BufferHeap {
     public:
         enum {
-            /* rotate source image 90 degrees */
+            /* rotate source image */
+            ROT_0     = 0,
             ROT_90    = HAL_TRANSFORM_ROT_90,
+            ROT_180   = HAL_TRANSFORM_ROT_180,
+            ROT_270   = HAL_TRANSFORM_ROT_270,
         };
         BufferHeap();
         
@@ -86,7 +89,7 @@
     virtual void unregisterBuffers() = 0;
     
     virtual sp<OverlayRef> createOverlay(
-            uint32_t w, uint32_t h, int32_t format) = 0;
+            uint32_t w, uint32_t h, int32_t format, int32_t orientation) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/libs/audioflinger/A2dpAudioInterface.cpp b/libs/audioflinger/A2dpAudioInterface.cpp
index c07bbfe..747d0e4 100644
--- a/libs/audioflinger/A2dpAudioInterface.cpp
+++ b/libs/audioflinger/A2dpAudioInterface.cpp
@@ -457,4 +457,10 @@
     return NO_ERROR;
 }
 
+status_t A2dpAudioInterface::A2dpAudioStreamOut::getRenderPosition(uint32_t *driverFrames)
+{
+    //TODO: enable when supported by driver
+    return INVALID_OPERATION;
+}
+
 }; // namespace android
diff --git a/libs/audioflinger/A2dpAudioInterface.h b/libs/audioflinger/A2dpAudioInterface.h
index 530e432..48154f9 100644
--- a/libs/audioflinger/A2dpAudioInterface.h
+++ b/libs/audioflinger/A2dpAudioInterface.h
@@ -93,6 +93,7 @@
         virtual status_t    dump(int fd, const Vector<String16>& args);
         virtual status_t    setParameters(const String8& keyValuePairs);
         virtual String8     getParameters(const String8& keys);
+        virtual status_t    getRenderPosition(uint32_t *dspFrames);
 
     private:
         friend class A2dpAudioInterface;
diff --git a/libs/audioflinger/Android.mk b/libs/audioflinger/Android.mk
index f5c03bb..b68bfc1 100644
--- a/libs/audioflinger/Android.mk
+++ b/libs/audioflinger/Android.mk
@@ -47,7 +47,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=               \
-    AudioPolicyManagerGeneric.cpp
+    AudioPolicyManagerBase.cpp
 
 LOCAL_SHARED_LIBRARIES := \
     libcutils \
@@ -60,7 +60,7 @@
  LOCAL_SHARED_LIBRARIES += libdl
 endif
 
-LOCAL_MODULE:= libaudiopolicygeneric
+LOCAL_MODULE:= libaudiopolicybase
 
 ifeq ($(BOARD_HAVE_BLUETOOTH),true)
   LOCAL_CFLAGS += -DWITH_A2DP
@@ -70,7 +70,7 @@
   LOCAL_CFLAGS += -DAUDIO_POLICY_TEST
 endif
 
-include $(BUILD_SHARED_LIBRARY)
+include $(BUILD_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 
@@ -87,11 +87,10 @@
     libutils \
 	libbinder \
     libmedia \
-    libhardware_legacy \
-    libaudiopolicygeneric
+    libhardware_legacy
 
 ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true)
-  LOCAL_STATIC_LIBRARIES += libaudiointerface
+  LOCAL_STATIC_LIBRARIES += libaudiointerface libaudiopolicybase
   LOCAL_CFLAGS += -DGENERIC_AUDIO
 else
   LOCAL_SHARED_LIBRARIES += libaudio libaudiopolicy
diff --git a/libs/audioflinger/AudioDumpInterface.cpp b/libs/audioflinger/AudioDumpInterface.cpp
index 858e5aa..30e2bc9 100644
--- a/libs/audioflinger/AudioDumpInterface.cpp
+++ b/libs/audioflinger/AudioDumpInterface.cpp
@@ -379,6 +379,12 @@
     }
 }
 
+status_t AudioStreamOutDump::getRenderPosition(uint32_t *dspFrames)
+{
+    if (mFinalStream != 0 ) return mFinalStream->getRenderPosition(dspFrames);
+    return INVALID_OPERATION;
+}
+
 // ----------------------------------------------------------------------------
 
 AudioStreamInDump::AudioStreamInDump(AudioDumpInterface *interface,
diff --git a/libs/audioflinger/AudioDumpInterface.h b/libs/audioflinger/AudioDumpInterface.h
index 1136ce1..5b9a6b1 100644
--- a/libs/audioflinger/AudioDumpInterface.h
+++ b/libs/audioflinger/AudioDumpInterface.h
@@ -56,8 +56,9 @@
     void                Close(void);
     AudioStreamOut*     finalStream() { return mFinalStream; }
     uint32_t            device() { return mDevice; }
-
     int                 getId()  { return mId; }
+    virtual status_t    getRenderPosition(uint32_t *dspFrames);
+
 private:
     AudioDumpInterface *mInterface;
     int                  mId;
diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp
index 2024cc0..9d52882 100644
--- a/libs/audioflinger/AudioFlinger.cpp
+++ b/libs/audioflinger/AudioFlinger.cpp
@@ -544,11 +544,11 @@
     return mStreamTypes[stream].mute;
 }
 
-bool AudioFlinger::isMusicActive() const
+bool AudioFlinger::isStreamActive(int stream) const
 {
     Mutex::Autolock _l(mLock);
     for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) {
-        if (mPlaybackThreads.valueAt(i)->isMusicActive()) {
+        if (mPlaybackThreads.valueAt(i)->isStreamActive(stream)) {
             return true;
         }
     }
@@ -633,6 +633,20 @@
     return ret;
 }
 
+status_t AudioFlinger::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output)
+{
+    status_t status;
+
+    Mutex::Autolock _l(mLock);
+
+    PlaybackThread *playbackThread = checkPlaybackThread_l(output);
+    if (playbackThread != NULL) {
+        return playbackThread->getRenderPosition(halFrames, dspFrames);
+    }
+
+    return BAD_VALUE;
+}
+
 void AudioFlinger::registerClient(const sp<IAudioFlingerClient>& client)
 {
 
@@ -932,6 +946,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);
@@ -1069,7 +1085,7 @@
     return mStreamTypes[stream].mute;
 }
 
-bool AudioFlinger::PlaybackThread::isMusicActive() const
+bool AudioFlinger::PlaybackThread::isStreamActive(int stream) const
 {
     Mutex::Autolock _l(mLock);
     size_t count = mActiveTracks.size();
@@ -1077,7 +1093,7 @@
         sp<Track> t = mActiveTracks[i].promote();
         if (t == 0) continue;
         Track* const track = t.get();
-        if (t->type() == AudioSystem::MUSIC)
+        if (t->type() == stream)
             return true;
     }
     return false;
@@ -1164,6 +1180,19 @@
     memset(mMixBuffer, 0, mFrameCount * 2 * sizeof(int16_t));
 }
 
+status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames)
+{
+    if (halFrames == 0 || dspFrames == 0) {
+        return BAD_VALUE;
+    }
+    if (mOutput == 0) {
+        return INVALID_OPERATION;
+    }
+    *halFrames = mBytesWritten/mOutput->frameSize();
+
+    return mOutput->getRenderPosition(dspFrames);
+}
+
 // ----------------------------------------------------------------------------
 
 AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id)
@@ -1288,8 +1317,9 @@
         if (sleepTime == 0) {
             mLastWriteTime = systemTime();
             mInWrite = true;
+            mBytesWritten += mixBufferSize;
             int bytesWritten = (int)mOutput->write(curBuf, mixBufferSize);
-            if (bytesWritten > 0) mBytesWritten += bytesWritten;
+            if (bytesWritten < 0) mBytesWritten -= mixBufferSize;
             mNumWrites++;
             mInWrite = false;
             nsecs_t now = systemTime();
@@ -1344,7 +1374,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 +1430,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();
             }
@@ -1810,8 +1840,9 @@
         if (sleepTime == 0) {
             mLastWriteTime = systemTime();
             mInWrite = true;
+            mBytesWritten += mixBufferSize;
             int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize);
-            if (bytesWritten) mBytesWritten += bytesWritten;
+            if (bytesWritten < 0) mBytesWritten -= mixBufferSize;
             mNumWrites++;
             mInWrite = false;
             mStandby = false;
@@ -1914,7 +1945,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 +1983,7 @@
 
             if (checkForNewParameters_l()) {
                 mixBufferSize = mFrameCount*mFrameSize;
+                updateWaitTime();
                 activeSleepTime = activeSleepTimeUs();
                 idleSleepTime = idleSleepTimeUs();
             }
@@ -2003,7 +2035,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 +2090,7 @@
 {
     int frameCount = (3 * mFrameCount * mSampleRate) / thread->sampleRate();
     OutputTrack *outputTrack = new OutputTrack((ThreadBase *)thread,
+                                            this,
                                             mSampleRate,
                                             mFormat,
                                             mChannelCount,
@@ -2062,6 +2099,7 @@
         thread->setStreamVolume(AudioSystem::NUM_STREAM_TYPES, 1.0f);
         mOutputTracks.add(outputTrack);
         LOGV("addOutputTrack() track %p, on thread %p", outputTrack, thread);
+        updateWaitTime();
     }
 }
 
@@ -2072,12 +2110,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 +2692,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 +2707,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 +2749,7 @@
     inBuffer.frameCount = frames;
     inBuffer.i16 = data;
 
-    uint32_t waitTimeLeftMs = mWaitTimeMs;
+    uint32_t waitTimeLeftMs = mSourceThread->waitTimeMs();
 
     if (!mActive && frames != 0) {
         start();
@@ -2712,12 +2788,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 +2813,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 +2822,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);
+            }
         }
     }
 
@@ -2851,7 +2929,7 @@
 AudioFlinger::Client::Client(const sp<AudioFlinger>& audioFlinger, pid_t pid)
     :   RefBase(),
         mAudioFlinger(audioFlinger),
-        mMemoryDealer(new MemoryDealer(1024*1024)),
+        mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")),
         mPid(pid)
 {
     // 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer
diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h
index 8c29da8..44da9ed 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>
@@ -99,7 +100,7 @@
     virtual     status_t    setMicMute(bool state);
     virtual     bool        getMicMute() const;
 
-    virtual     bool        isMusicActive() const;
+    virtual     bool        isStreamActive(int stream) const;
 
     virtual     status_t    setParameters(int ioHandle, const String8& keyValuePairs);
     virtual     String8     getParameters(int ioHandle, const String8& keys);
@@ -135,6 +136,8 @@
 
     virtual status_t setVoiceVolume(float volume);
 
+    virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output);
+
     // IBinder::DeathRecipient
     virtual     void        binderDied(const wp<IBinder>& who);
 
@@ -208,6 +211,7 @@
     class PlaybackThread;
     class MixerThread;
     class DirectOutputThread;
+    class DuplicatingThread;
     class Track;
     class RecordTrack;
 
@@ -324,6 +328,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 +457,7 @@
             };
 
                                 OutputTrack(  const wp<ThreadBase>& thread,
+                                        DuplicatingThread *sourceThread,
                                         uint32_t sampleRate,
                                         int format,
                                         int channelCount,
@@ -471,13 +477,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);
@@ -503,7 +508,7 @@
         virtual     float       streamVolume(int stream) const;
         virtual     bool        streamMute(int stream) const;
 
-                    bool        isMusicActive() const;
+                    bool        isStreamActive(int stream) const;
 
                     sp<Track>   createTrack_l(
                                     const sp<AudioFlinger::Client>& client,
@@ -520,8 +525,10 @@
         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);
+        virtual     status_t    getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames);
 
         struct  stream_type_t {
             stream_type_t()
@@ -635,9 +642,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/libs/audioflinger/AudioHardwareGeneric.cpp b/libs/audioflinger/AudioHardwareGeneric.cpp
index 57874f3..d63c031 100644
--- a/libs/audioflinger/AudioHardwareGeneric.cpp
+++ b/libs/audioflinger/AudioHardwareGeneric.cpp
@@ -298,6 +298,11 @@
     return param.toString();
 }
 
+status_t AudioStreamOutGeneric::getRenderPosition(uint32_t *dspFrames)
+{
+    return INVALID_OPERATION;
+}
+
 // ----------------------------------------------------------------------------
 
 // record functions
@@ -310,9 +315,8 @@
         uint32_t *pRate,
         AudioSystem::audio_in_acoustics acoustics)
 {
-    // FIXME: remove logging
     if (pFormat == 0 || pChannels == 0 || pRate == 0) return BAD_VALUE;
-    LOGD("AudioStreamInGeneric::set(%p, %d, %d, %d, %u)", hw, fd, *pFormat, *pChannels, *pRate);
+    LOGV("AudioStreamInGeneric::set(%p, %d, %d, %d, %u)", hw, fd, *pFormat, *pChannels, *pRate);
     // check values
     if ((*pFormat != format()) ||
         (*pChannels != channels()) ||
@@ -332,14 +336,10 @@
 
 AudioStreamInGeneric::~AudioStreamInGeneric()
 {
-    // FIXME: remove logging
-    LOGD("AudioStreamInGeneric destructor");
 }
 
 ssize_t AudioStreamInGeneric::read(void* buffer, ssize_t bytes)
 {
-    // FIXME: remove logging
-    LOGD("AudioStreamInGeneric::read(%p, %d) from fd %d", buffer, (int)bytes, mFd);
     AutoMutex lock(mLock);
     if (mFd < 0) {
         LOGE("Attempt to read from unopened device");
diff --git a/libs/audioflinger/AudioHardwareGeneric.h b/libs/audioflinger/AudioHardwareGeneric.h
index 42da413..95c7ea3 100644
--- a/libs/audioflinger/AudioHardwareGeneric.h
+++ b/libs/audioflinger/AudioHardwareGeneric.h
@@ -55,6 +55,7 @@
     virtual status_t    dump(int fd, const Vector<String16>& args);
     virtual status_t    setParameters(const String8& keyValuePairs);
     virtual String8     getParameters(const String8& keys);
+    virtual status_t    getRenderPosition(uint32_t *dspFrames);
 
 private:
     AudioHardwareGeneric *mAudioHardware;
diff --git a/libs/audioflinger/AudioHardwareStub.cpp b/libs/audioflinger/AudioHardwareStub.cpp
index ae391ee..ae215d1 100644
--- a/libs/audioflinger/AudioHardwareStub.cpp
+++ b/libs/audioflinger/AudioHardwareStub.cpp
@@ -158,6 +158,11 @@
     return param.toString();
 }
 
+status_t AudioStreamOutStub::getRenderPosition(uint32_t *dspFrames)
+{
+    return INVALID_OPERATION;
+}
+
 // ----------------------------------------------------------------------------
 
 status_t AudioStreamInStub::set(int *pFormat, uint32_t *pChannels, uint32_t *pRate,
diff --git a/libs/audioflinger/AudioHardwareStub.h b/libs/audioflinger/AudioHardwareStub.h
index 583f852..769ae3f 100644
--- a/libs/audioflinger/AudioHardwareStub.h
+++ b/libs/audioflinger/AudioHardwareStub.h
@@ -41,6 +41,7 @@
     virtual status_t    dump(int fd, const Vector<String16>& args);
     virtual status_t    setParameters(const String8& keyValuePairs) { return NO_ERROR;}
     virtual String8     getParameters(const String8& keys);
+    virtual status_t    getRenderPosition(uint32_t *dspFrames);
 };
 
 class AudioStreamInStub : public AudioStreamIn {
diff --git a/libs/audioflinger/AudioPolicyManagerBase.cpp b/libs/audioflinger/AudioPolicyManagerBase.cpp
new file mode 100644
index 0000000..096aa73
--- /dev/null
+++ b/libs/audioflinger/AudioPolicyManagerBase.cpp
@@ -0,0 +1,1924 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AudioPolicyManagerBase"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+#include <hardware_legacy/AudioPolicyManagerBase.h>
+#include <media/mediarecorder.h>
+
+namespace android {
+
+
+// ----------------------------------------------------------------------------
+// AudioPolicyInterface implementation
+// ----------------------------------------------------------------------------
+
+
+status_t AudioPolicyManagerBase::setDeviceConnectionState(AudioSystem::audio_devices device,
+                                                  AudioSystem::device_connection_state state,
+                                                  const char *device_address)
+{
+
+    LOGV("setDeviceConnectionState() device: %x, state %d, address %s", device, state, device_address);
+
+    // connect/disconnect only 1 device at a time
+    if (AudioSystem::popCount(device) != 1) return BAD_VALUE;
+
+    if (strlen(device_address) >= MAX_DEVICE_ADDRESS_LEN) {
+        LOGE("setDeviceConnectionState() invalid address: %s", device_address);
+        return BAD_VALUE;
+    }
+
+    // handle output devices
+    if (AudioSystem::isOutputDevice(device)) {
+
+#ifndef WITH_A2DP
+        if (AudioSystem::isA2dpDevice(device)) {
+            LOGE("setDeviceConnectionState() invalid device: %x", device);
+            return BAD_VALUE;
+        }
+#endif
+
+        switch (state)
+        {
+        // handle output device connection
+        case AudioSystem::DEVICE_STATE_AVAILABLE:
+            if (mAvailableOutputDevices & device) {
+                LOGW("setDeviceConnectionState() device already connected: %x", device);
+                return INVALID_OPERATION;
+            }
+            LOGV("setDeviceConnectionState() connecting device %x", device);
+
+            // register new device as available
+            mAvailableOutputDevices |= device;
+
+#ifdef WITH_A2DP
+            // handle A2DP device connection
+            if (AudioSystem::isA2dpDevice(device)) {
+                status_t status = handleA2dpConnection(device, device_address);
+                if (status != NO_ERROR) {
+                    mAvailableOutputDevices &= ~device;
+                    return status;
+                }
+            } else
+#endif
+            {
+                if (AudioSystem::isBluetoothScoDevice(device)) {
+                    LOGV("setDeviceConnectionState() BT SCO  device, address %s", device_address);
+                    // keep track of SCO device address
+                    mScoDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN);
+#ifdef WITH_A2DP
+                    if ((mA2dpDeviceAddress == mScoDeviceAddress) &&
+                        (mPhoneState != AudioSystem::MODE_NORMAL)) {
+                        mpClientInterface->suspendOutput(mA2dpOutput);
+                    }
+#endif
+                }
+            }
+            break;
+        // handle output device disconnection
+        case AudioSystem::DEVICE_STATE_UNAVAILABLE: {
+            if (!(mAvailableOutputDevices & device)) {
+                LOGW("setDeviceConnectionState() device not connected: %x", device);
+                return INVALID_OPERATION;
+            }
+
+
+            LOGV("setDeviceConnectionState() disconnecting device %x", device);
+            // remove device from available output devices
+            mAvailableOutputDevices &= ~device;
+
+#ifdef WITH_A2DP
+            // handle A2DP device disconnection
+            if (AudioSystem::isA2dpDevice(device)) {
+                status_t status = handleA2dpDisconnection(device, device_address);
+                if (status != NO_ERROR) {
+                    mAvailableOutputDevices |= device;
+                    return status;
+                }
+            } else
+#endif
+            {
+                if (AudioSystem::isBluetoothScoDevice(device)) {
+                    mScoDeviceAddress = "";
+#ifdef WITH_A2DP
+                    if ((mA2dpDeviceAddress == mScoDeviceAddress) &&
+                        (mPhoneState != AudioSystem::MODE_NORMAL)) {
+                        mpClientInterface->restoreOutput(mA2dpOutput);
+                    }
+#endif
+                }
+            }
+            } break;
+
+        default:
+            LOGE("setDeviceConnectionState() invalid state: %x", state);
+            return BAD_VALUE;
+        }
+
+        // request routing change if necessary
+        uint32_t newDevice = getNewDevice(mHardwareOutput, false);
+#ifdef WITH_A2DP
+        checkOutputForAllStrategies(newDevice);
+        // A2DP outputs must be closed after checkOutputForAllStrategies() is executed
+        if (state == AudioSystem::DEVICE_STATE_UNAVAILABLE && AudioSystem::isA2dpDevice(device)) {
+            closeA2dpOutputs();
+        }
+#endif
+        updateDeviceForStrategy();
+        setOutputDevice(mHardwareOutput, newDevice);
+
+        if (device == AudioSystem::DEVICE_OUT_WIRED_HEADSET) {
+            device = AudioSystem::DEVICE_IN_WIRED_HEADSET;
+        } else if (device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO ||
+                   device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET ||
+                   device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT) {
+            device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+        } else {
+            return NO_ERROR;
+        }
+    }
+    // handle input devices
+    if (AudioSystem::isInputDevice(device)) {
+
+        switch (state)
+        {
+        // handle input device connection
+        case AudioSystem::DEVICE_STATE_AVAILABLE: {
+            if (mAvailableInputDevices & device) {
+                LOGW("setDeviceConnectionState() device already connected: %d", device);
+                return INVALID_OPERATION;
+            }
+            mAvailableInputDevices |= device;
+            }
+            break;
+
+        // handle input device disconnection
+        case AudioSystem::DEVICE_STATE_UNAVAILABLE: {
+            if (!(mAvailableInputDevices & device)) {
+                LOGW("setDeviceConnectionState() device not connected: %d", device);
+                return INVALID_OPERATION;
+            }
+            mAvailableInputDevices &= ~device;
+            } break;
+
+        default:
+            LOGE("setDeviceConnectionState() invalid state: %x", state);
+            return BAD_VALUE;
+        }
+
+        audio_io_handle_t activeInput = getActiveInput();
+        if (activeInput != 0) {
+            AudioInputDescriptor *inputDesc = mInputs.valueFor(activeInput);
+            uint32_t newDevice = getDeviceForInputSource(inputDesc->mInputSource);
+            if (newDevice != inputDesc->mDevice) {
+                LOGV("setDeviceConnectionState() changing device from %x to %x for input %d",
+                        inputDesc->mDevice, newDevice, activeInput);
+                inputDesc->mDevice = newDevice;
+                AudioParameter param = AudioParameter();
+                param.addInt(String8(AudioParameter::keyRouting), (int)newDevice);
+                mpClientInterface->setParameters(activeInput, param.toString());
+            }
+        }
+
+        return NO_ERROR;
+    }
+
+    LOGW("setDeviceConnectionState() invalid device: %x", device);
+    return BAD_VALUE;
+}
+
+AudioSystem::device_connection_state AudioPolicyManagerBase::getDeviceConnectionState(AudioSystem::audio_devices device,
+                                                  const char *device_address)
+{
+    AudioSystem::device_connection_state state = AudioSystem::DEVICE_STATE_UNAVAILABLE;
+    String8 address = String8(device_address);
+    if (AudioSystem::isOutputDevice(device)) {
+        if (device & mAvailableOutputDevices) {
+#ifdef WITH_A2DP
+            if (AudioSystem::isA2dpDevice(device) &&
+                address != "" && mA2dpDeviceAddress != address) {
+                return state;
+            }
+#endif
+            if (AudioSystem::isBluetoothScoDevice(device) &&
+                address != "" && mScoDeviceAddress != address) {
+                return state;
+            }
+            state = AudioSystem::DEVICE_STATE_AVAILABLE;
+        }
+    } else if (AudioSystem::isInputDevice(device)) {
+        if (device & mAvailableInputDevices) {
+            state = AudioSystem::DEVICE_STATE_AVAILABLE;
+        }
+    }
+
+    return state;
+}
+
+void AudioPolicyManagerBase::setPhoneState(int state)
+{
+    LOGV("setPhoneState() state %d", state);
+    uint32_t newDevice = 0;
+    if (state < 0 || state >= AudioSystem::NUM_MODES) {
+        LOGW("setPhoneState() invalid state %d", state);
+        return;
+    }
+
+    if (state == mPhoneState ) {
+        LOGW("setPhoneState() setting same state %d", state);
+        return;
+    }
+
+    // if leaving call state, handle special case of active streams
+    // pertaining to sonification strategy see handleIncallSonification()
+    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+        LOGV("setPhoneState() in call state management: new state is %d", state);
+        for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+            handleIncallSonification(stream, false, true);
+        }
+    }
+
+    // store previous phone state for management of sonification strategy below
+    int oldState = mPhoneState;
+    mPhoneState = state;
+    bool force = false;
+
+    // are we entering or starting a call
+    if ((oldState != AudioSystem::MODE_IN_CALL) && (state == AudioSystem::MODE_IN_CALL)) {
+        LOGV("  Entering call in setPhoneState()");
+        // force routing command to audio hardware when starting a call
+        // even if no device change is needed
+        force = true;
+    } else if ((oldState == AudioSystem::MODE_IN_CALL) && (state != AudioSystem::MODE_IN_CALL)) {
+        LOGV("  Exiting call in setPhoneState()");
+        // force routing command to audio hardware when exiting a call
+        // even if no device change is needed
+        force = true;
+    }
+
+    // check for device and output changes triggered by new phone state
+    newDevice = getNewDevice(mHardwareOutput, false);
+#ifdef WITH_A2DP
+    checkOutputForAllStrategies(newDevice);
+    // suspend A2DP output if SCO device address is the same as A2DP device address.
+    // no need to check that a SCO device is actually connected as mScoDeviceAddress == ""
+    // if none is connected and the test below will fail.
+    if (mA2dpDeviceAddress == mScoDeviceAddress) {
+        if (oldState == AudioSystem::MODE_NORMAL) {
+            mpClientInterface->suspendOutput(mA2dpOutput);
+        } else if (state == AudioSystem::MODE_NORMAL) {
+            mpClientInterface->restoreOutput(mA2dpOutput);
+        }
+    }
+#endif
+    updateDeviceForStrategy();
+
+    AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
+
+    // force routing command to audio hardware when ending call
+    // even if no device change is needed
+    if (oldState == AudioSystem::MODE_IN_CALL && newDevice == 0) {
+        newDevice = hwOutputDesc->device();
+    }
+    // change routing is necessary
+    setOutputDevice(mHardwareOutput, newDevice, force);
+
+    // if entering in call state, handle special case of active streams
+    // pertaining to sonification strategy see handleIncallSonification()
+    if (state == AudioSystem::MODE_IN_CALL) {
+        LOGV("setPhoneState() in call state management: new state is %d", state);
+        for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+            handleIncallSonification(stream, true, true);
+        }
+    }
+
+    // Flag that ringtone volume must be limited to music volume until we exit MODE_RINGTONE
+    if (state == AudioSystem::MODE_RINGTONE &&
+        (hwOutputDesc->mRefCount[AudioSystem::MUSIC] ||
+        (systemTime() - mMusicStopTime) < seconds(SONIFICATION_HEADSET_MUSIC_DELAY))) {
+        mLimitRingtoneVolume = true;
+    } else {
+        mLimitRingtoneVolume = false;
+    }
+}
+
+void AudioPolicyManagerBase::setRingerMode(uint32_t mode, uint32_t mask)
+{
+    LOGV("setRingerMode() mode %x, mask %x", mode, mask);
+
+    mRingerMode = mode;
+}
+
+void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config)
+{
+    LOGV("setForceUse() usage %d, config %d, mPhoneState %d", usage, config, mPhoneState);
+
+    switch(usage) {
+    case AudioSystem::FOR_COMMUNICATION:
+        if (config != AudioSystem::FORCE_SPEAKER && config != AudioSystem::FORCE_BT_SCO &&
+            config != AudioSystem::FORCE_NONE) {
+            LOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config);
+            return;
+        }
+        mForceUse[usage] = config;
+        break;
+    case AudioSystem::FOR_MEDIA:
+        if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP &&
+            config != AudioSystem::FORCE_WIRED_ACCESSORY && config != AudioSystem::FORCE_NONE) {
+            LOGW("setForceUse() invalid config %d for FOR_MEDIA", config);
+            return;
+        }
+        mForceUse[usage] = config;
+        break;
+    case AudioSystem::FOR_RECORD:
+        if (config != AudioSystem::FORCE_BT_SCO && config != AudioSystem::FORCE_WIRED_ACCESSORY &&
+            config != AudioSystem::FORCE_NONE) {
+            LOGW("setForceUse() invalid config %d for FOR_RECORD", config);
+            return;
+        }
+        mForceUse[usage] = config;
+        break;
+    case AudioSystem::FOR_DOCK:
+        if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK &&
+            config != AudioSystem::FORCE_BT_DESK_DOCK && config != AudioSystem::FORCE_WIRED_ACCESSORY) {
+            LOGW("setForceUse() invalid config %d for FOR_DOCK", config);
+        }
+        mForceUse[usage] = config;
+        break;
+    default:
+        LOGW("setForceUse() invalid usage %d", usage);
+        break;
+    }
+
+    // check for device and output changes triggered by new phone state
+    uint32_t newDevice = getNewDevice(mHardwareOutput, false);
+#ifdef WITH_A2DP
+    checkOutputForAllStrategies(newDevice);
+#endif
+    updateDeviceForStrategy();
+    setOutputDevice(mHardwareOutput, newDevice);
+}
+
+AudioSystem::forced_config AudioPolicyManagerBase::getForceUse(AudioSystem::force_use usage)
+{
+    return mForceUse[usage];
+}
+
+void AudioPolicyManagerBase::setSystemProperty(const char* property, const char* value)
+{
+    LOGV("setSystemProperty() property %s, value %s", property, value);
+    if (strcmp(property, "ro.camera.sound.forced") == 0) {
+        if (atoi(value)) {
+            LOGV("ENFORCED_AUDIBLE cannot be muted");
+            mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = false;
+        } else {
+            LOGV("ENFORCED_AUDIBLE can be muted");
+            mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = true;
+        }
+    }
+}
+
+audio_io_handle_t AudioPolicyManagerBase::getOutput(AudioSystem::stream_type stream,
+                                    uint32_t samplingRate,
+                                    uint32_t format,
+                                    uint32_t channels,
+                                    AudioSystem::output_flags flags)
+{
+    audio_io_handle_t output = 0;
+    uint32_t latency = 0;
+    routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
+    uint32_t device = getDeviceForStrategy(strategy);
+    LOGV("getOutput() stream %d, samplingRate %d, format %d, channels %x, flags %x", stream, samplingRate, format, channels, flags);
+
+#ifdef AUDIO_POLICY_TEST
+    if (mCurOutput != 0) {
+        LOGV("getOutput() test output mCurOutput %d, samplingRate %d, format %d, channels %x, mDirectOutput %d",
+                mCurOutput, mTestSamplingRate, mTestFormat, mTestChannels, mDirectOutput);
+
+        if (mTestOutputs[mCurOutput] == 0) {
+            LOGV("getOutput() opening test output");
+            AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+            outputDesc->mDevice = mTestDevice;
+            outputDesc->mSamplingRate = mTestSamplingRate;
+            outputDesc->mFormat = mTestFormat;
+            outputDesc->mChannels = mTestChannels;
+            outputDesc->mLatency = mTestLatencyMs;
+            outputDesc->mFlags = (AudioSystem::output_flags)(mDirectOutput ? AudioSystem::OUTPUT_FLAG_DIRECT : 0);
+            outputDesc->mRefCount[stream] = 0;
+            mTestOutputs[mCurOutput] = mpClientInterface->openOutput(&outputDesc->mDevice,
+                                            &outputDesc->mSamplingRate,
+                                            &outputDesc->mFormat,
+                                            &outputDesc->mChannels,
+                                            &outputDesc->mLatency,
+                                            outputDesc->mFlags);
+            if (mTestOutputs[mCurOutput]) {
+                AudioParameter outputCmd = AudioParameter();
+                outputCmd.addInt(String8("set_id"),mCurOutput);
+                mpClientInterface->setParameters(mTestOutputs[mCurOutput],outputCmd.toString());
+                addOutput(mTestOutputs[mCurOutput], outputDesc);
+            }
+        }
+        return mTestOutputs[mCurOutput];
+    }
+#endif //AUDIO_POLICY_TEST
+
+    // open a direct output if:
+    // 1 a direct output is explicitely requested
+    // 2 the audio format is compressed
+    if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) ||
+         (format !=0 && !AudioSystem::isLinearPCM(format))) {
+
+        LOGV("getOutput() opening direct output device %x", device);
+        AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+        outputDesc->mDevice = device;
+        outputDesc->mSamplingRate = samplingRate;
+        outputDesc->mFormat = format;
+        outputDesc->mChannels = channels;
+        outputDesc->mLatency = 0;
+        outputDesc->mFlags = (AudioSystem::output_flags)(flags | AudioSystem::OUTPUT_FLAG_DIRECT);
+        outputDesc->mRefCount[stream] = 1;
+        output = mpClientInterface->openOutput(&outputDesc->mDevice,
+                                        &outputDesc->mSamplingRate,
+                                        &outputDesc->mFormat,
+                                        &outputDesc->mChannels,
+                                        &outputDesc->mLatency,
+                                        outputDesc->mFlags);
+
+        // only accept an output with the requeted parameters
+        if (output == 0 ||
+            (samplingRate != 0 && samplingRate != outputDesc->mSamplingRate) ||
+            (format != 0 && format != outputDesc->mFormat) ||
+            (channels != 0 && channels != outputDesc->mChannels)) {
+            LOGV("getOutput() failed opening direct output: samplingRate %d, format %d, channels %d",
+                    samplingRate, format, channels);
+            if (output != 0) {
+                mpClientInterface->closeOutput(output);
+            }
+            delete outputDesc;
+            return 0;
+        }
+        addOutput(output, outputDesc);
+        return output;
+    }
+
+    if (channels != 0 && channels != AudioSystem::CHANNEL_OUT_MONO &&
+        channels != AudioSystem::CHANNEL_OUT_STEREO) {
+        return 0;
+    }
+    // open a non direct output
+
+    // get which output is suitable for the specified stream. The actual routing change will happen
+    // when startOutput() will be called
+    uint32_t a2dpDevice = device & AudioSystem::DEVICE_OUT_ALL_A2DP;
+    if (AudioSystem::popCount((AudioSystem::audio_devices)device) == 2) {
+#ifdef WITH_A2DP
+        if (a2dpUsedForSonification() && a2dpDevice != 0) {
+            // if playing on 2 devices among which one is A2DP, use duplicated output
+            LOGV("getOutput() using duplicated output");
+            LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device in multiple %x selected but A2DP output not opened", device);
+            output = mDuplicatedOutput;
+        } else
+#endif
+        {
+            // if playing on 2 devices among which none is A2DP, use hardware output
+            output = mHardwareOutput;
+        }
+        LOGV("getOutput() using output %d for 2 devices %x", output, device);
+    } else {
+#ifdef WITH_A2DP
+        if (a2dpDevice != 0) {
+            // if playing on A2DP device, use a2dp output
+            LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device %x selected but A2DP output not opened", device);
+            output = mA2dpOutput;
+        } else
+#endif
+        {
+            // if playing on not A2DP device, use hardware output
+            output = mHardwareOutput;
+        }
+    }
+
+
+    LOGW_IF((output ==0), "getOutput() could not find output for stream %d, samplingRate %d, format %d, channels %x, flags %x",
+                stream, samplingRate, format, channels, flags);
+
+    return output;
+}
+
+status_t AudioPolicyManagerBase::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
+{
+    LOGV("startOutput() output %d, stream %d", output, stream);
+    ssize_t index = mOutputs.indexOfKey(output);
+    if (index < 0) {
+        LOGW("startOutput() unknow output %d", output);
+        return BAD_VALUE;
+    }
+
+    AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
+    routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
+
+#ifdef WITH_A2DP
+    if (mA2dpOutput != 0  && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) {
+        setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput);
+    }
+#endif
+
+    // incremenent usage count for this stream on the requested output:
+    // NOTE that the usage count is the same for duplicated output and hardware output which is
+    // necassary for a correct control of hardware output routing by startOutput() and stopOutput()
+    outputDesc->changeRefCount(stream, 1);
+
+    setOutputDevice(output, getNewDevice(output));
+
+    // handle special case for sonification while in call
+    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+        handleIncallSonification(stream, true, false);
+    }
+
+    // apply volume rules for current stream and device if necessary
+    checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, outputDesc->device());
+
+    return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
+{
+    LOGV("stopOutput() output %d, stream %d", output, stream);
+    ssize_t index = mOutputs.indexOfKey(output);
+    if (index < 0) {
+        LOGW("stopOutput() unknow output %d", output);
+        return BAD_VALUE;
+    }
+
+    AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
+    routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
+
+    // handle special case for sonification while in call
+    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+        handleIncallSonification(stream, false, false);
+    }
+
+    if (outputDesc->mRefCount[stream] > 0) {
+        // decrement usage count of this stream on the output
+        outputDesc->changeRefCount(stream, -1);
+        // store time at which the last music track was stopped - see computeVolume()
+        if (stream == AudioSystem::MUSIC) {
+            mMusicStopTime = systemTime();
+        }
+
+        setOutputDevice(output, getNewDevice(output));
+
+#ifdef WITH_A2DP
+        if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) {
+            setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput, mOutputs.valueFor(mHardwareOutput)->mLatency*2);
+        }
+#endif
+        return NO_ERROR;
+    } else {
+        LOGW("stopOutput() refcount is already 0 for output %d", output);
+        return INVALID_OPERATION;
+    }
+}
+
+void AudioPolicyManagerBase::releaseOutput(audio_io_handle_t output)
+{
+    LOGV("releaseOutput() %d", output);
+    ssize_t index = mOutputs.indexOfKey(output);
+    if (index < 0) {
+        LOGW("releaseOutput() releasing unknown output %d", output);
+        return;
+    }
+
+#ifdef AUDIO_POLICY_TEST
+    int testIndex = testOutputIndex(output);
+    if (testIndex != 0) {
+        AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
+        if (outputDesc->refCount() == 0) {
+            mpClientInterface->closeOutput(output);
+            delete mOutputs.valueAt(index);
+            mOutputs.removeItem(output);
+            mTestOutputs[testIndex] = 0;
+        }
+        return;
+    }
+#endif //AUDIO_POLICY_TEST
+
+    if (mOutputs.valueAt(index)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT) {
+        mpClientInterface->closeOutput(output);
+        delete mOutputs.valueAt(index);
+        mOutputs.removeItem(output);
+    }
+}
+
+audio_io_handle_t AudioPolicyManagerBase::getInput(int inputSource,
+                                    uint32_t samplingRate,
+                                    uint32_t format,
+                                    uint32_t channels,
+                                    AudioSystem::audio_in_acoustics acoustics)
+{
+    audio_io_handle_t input = 0;
+    uint32_t device = getDeviceForInputSource(inputSource);
+
+    LOGV("getInput() inputSource %d, samplingRate %d, format %d, channels %x, acoustics %x", inputSource, samplingRate, format, channels, acoustics);
+
+    if (device == 0) {
+        return 0;
+    }
+
+    // adapt channel selection to input source
+    switch(inputSource) {
+    case AUDIO_SOURCE_VOICE_UPLINK:
+        channels = AudioSystem::CHANNEL_IN_VOICE_UPLINK;
+        break;
+    case AUDIO_SOURCE_VOICE_DOWNLINK:
+        channels = AudioSystem::CHANNEL_IN_VOICE_DNLINK;
+        break;
+    case AUDIO_SOURCE_VOICE_CALL:
+        channels = (AudioSystem::CHANNEL_IN_VOICE_UPLINK | AudioSystem::CHANNEL_IN_VOICE_DNLINK);
+        break;
+    default:
+        break;
+    }
+
+    AudioInputDescriptor *inputDesc = new AudioInputDescriptor();
+
+    inputDesc->mInputSource = inputSource;
+    inputDesc->mDevice = device;
+    inputDesc->mSamplingRate = samplingRate;
+    inputDesc->mFormat = format;
+    inputDesc->mChannels = channels;
+    inputDesc->mAcoustics = acoustics;
+    inputDesc->mRefCount = 0;
+    input = mpClientInterface->openInput(&inputDesc->mDevice,
+                                    &inputDesc->mSamplingRate,
+                                    &inputDesc->mFormat,
+                                    &inputDesc->mChannels,
+                                    inputDesc->mAcoustics);
+
+    // only accept input with the exact requested set of parameters
+    if (input == 0 ||
+        (samplingRate != inputDesc->mSamplingRate) ||
+        (format != inputDesc->mFormat) ||
+        (channels != inputDesc->mChannels)) {
+        LOGV("getInput() failed opening input: samplingRate %d, format %d, channels %d",
+                samplingRate, format, channels);
+        if (input != 0) {
+            mpClientInterface->closeInput(input);
+        }
+        delete inputDesc;
+        return 0;
+    }
+    mInputs.add(input, inputDesc);
+    return input;
+}
+
+status_t AudioPolicyManagerBase::startInput(audio_io_handle_t input)
+{
+    LOGV("startInput() input %d", input);
+    ssize_t index = mInputs.indexOfKey(input);
+    if (index < 0) {
+        LOGW("startInput() unknow input %d", input);
+        return BAD_VALUE;
+    }
+    AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
+
+#ifdef AUDIO_POLICY_TEST
+    if (mTestInput == 0)
+#endif //AUDIO_POLICY_TEST
+    {
+        // refuse 2 active AudioRecord clients at the same time
+        if (getActiveInput() != 0) {
+            LOGW("startInput() input %d failed: other input already started", input);
+            return INVALID_OPERATION;
+        }
+    }
+
+    AudioParameter param = AudioParameter();
+    param.addInt(String8(AudioParameter::keyRouting), (int)inputDesc->mDevice);
+
+    // use Voice Recognition mode or not for this input based on input source
+    int vr_enabled = inputDesc->mInputSource == AUDIO_SOURCE_VOICE_RECOGNITION ? 1 : 0;
+    param.addInt(String8("vr_mode"), vr_enabled);
+    LOGV("AudioPolicyManager::startInput(%d), setting vr_mode to %d", inputDesc->mInputSource, vr_enabled);
+
+    mpClientInterface->setParameters(input, param.toString());
+
+    inputDesc->mRefCount = 1;
+    return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::stopInput(audio_io_handle_t input)
+{
+    LOGV("stopInput() input %d", input);
+    ssize_t index = mInputs.indexOfKey(input);
+    if (index < 0) {
+        LOGW("stopInput() unknow input %d", input);
+        return BAD_VALUE;
+    }
+    AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
+
+    if (inputDesc->mRefCount == 0) {
+        LOGW("stopInput() input %d already stopped", input);
+        return INVALID_OPERATION;
+    } else {
+        AudioParameter param = AudioParameter();
+        param.addInt(String8(AudioParameter::keyRouting), 0);
+        mpClientInterface->setParameters(input, param.toString());
+        inputDesc->mRefCount = 0;
+        return NO_ERROR;
+    }
+}
+
+void AudioPolicyManagerBase::releaseInput(audio_io_handle_t input)
+{
+    LOGV("releaseInput() %d", input);
+    ssize_t index = mInputs.indexOfKey(input);
+    if (index < 0) {
+        LOGW("releaseInput() releasing unknown input %d", input);
+        return;
+    }
+    mpClientInterface->closeInput(input);
+    delete mInputs.valueAt(index);
+    mInputs.removeItem(input);
+    LOGV("releaseInput() exit");
+}
+
+void AudioPolicyManagerBase::initStreamVolume(AudioSystem::stream_type stream,
+                                            int indexMin,
+                                            int indexMax)
+{
+    LOGV("initStreamVolume() stream %d, min %d, max %d", stream , indexMin, indexMax);
+    if (indexMin < 0 || indexMin >= indexMax) {
+        LOGW("initStreamVolume() invalid index limits for stream %d, min %d, max %d", stream , indexMin, indexMax);
+        return;
+    }
+    mStreams[stream].mIndexMin = indexMin;
+    mStreams[stream].mIndexMax = indexMax;
+}
+
+status_t AudioPolicyManagerBase::setStreamVolumeIndex(AudioSystem::stream_type stream, int index)
+{
+
+    if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) {
+        return BAD_VALUE;
+    }
+
+    // Force max volume if stream cannot be muted
+    if (!mStreams[stream].mCanBeMuted) index = mStreams[stream].mIndexMax;
+
+    LOGV("setStreamVolumeIndex() stream %d, index %d", stream, index);
+    mStreams[stream].mIndexCur = index;
+
+    // compute and apply stream volume on all outputs according to connected device
+    status_t status = NO_ERROR;
+    for (size_t i = 0; i < mOutputs.size(); i++) {
+        status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), mOutputs.valueAt(i)->device());
+        if (volStatus != NO_ERROR) {
+            status = volStatus;
+        }
+    }
+    return status;
+}
+
+status_t AudioPolicyManagerBase::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index)
+{
+    if (index == 0) {
+        return BAD_VALUE;
+    }
+    LOGV("getStreamVolumeIndex() stream %d", stream);
+    *index =  mStreams[stream].mIndexCur;
+    return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::dump(int fd)
+{
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    String8 result;
+
+    snprintf(buffer, SIZE, "\nAudioPolicyManager Dump: %p\n", this);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Hardware Output: %d\n", mHardwareOutput);
+    result.append(buffer);
+#ifdef WITH_A2DP
+    snprintf(buffer, SIZE, " A2DP Output: %d\n", mA2dpOutput);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Duplicated Output: %d\n", mDuplicatedOutput);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " A2DP device address: %s\n", mA2dpDeviceAddress.string());
+    result.append(buffer);
+#endif
+    snprintf(buffer, SIZE, " SCO device address: %s\n", mScoDeviceAddress.string());
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Output devices: %08x\n", mAvailableOutputDevices);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Input devices: %08x\n", mAvailableInputDevices);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Phone state: %d\n", mPhoneState);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Ringer mode: %d\n", mRingerMode);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Force use for communications %d\n", mForceUse[AudioSystem::FOR_COMMUNICATION]);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Force use for media %d\n", mForceUse[AudioSystem::FOR_MEDIA]);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Force use for record %d\n", mForceUse[AudioSystem::FOR_RECORD]);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Force use for dock %d\n", mForceUse[AudioSystem::FOR_DOCK]);
+    result.append(buffer);
+    write(fd, result.string(), result.size());
+
+    snprintf(buffer, SIZE, "\nOutputs dump:\n");
+    write(fd, buffer, strlen(buffer));
+    for (size_t i = 0; i < mOutputs.size(); i++) {
+        snprintf(buffer, SIZE, "- Output %d dump:\n", mOutputs.keyAt(i));
+        write(fd, buffer, strlen(buffer));
+        mOutputs.valueAt(i)->dump(fd);
+    }
+
+    snprintf(buffer, SIZE, "\nInputs dump:\n");
+    write(fd, buffer, strlen(buffer));
+    for (size_t i = 0; i < mInputs.size(); i++) {
+        snprintf(buffer, SIZE, "- Input %d dump:\n", mInputs.keyAt(i));
+        write(fd, buffer, strlen(buffer));
+        mInputs.valueAt(i)->dump(fd);
+    }
+
+    snprintf(buffer, SIZE, "\nStreams dump:\n");
+    write(fd, buffer, strlen(buffer));
+    snprintf(buffer, SIZE, " Stream  Index Min  Index Max  Index Cur  Can be muted\n");
+    write(fd, buffer, strlen(buffer));
+    for (size_t i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
+        snprintf(buffer, SIZE, " %02d", i);
+        mStreams[i].dump(buffer + 3, SIZE);
+        write(fd, buffer, strlen(buffer));
+    }
+
+    return NO_ERROR;
+}
+
+// ----------------------------------------------------------------------------
+// AudioPolicyManagerBase
+// ----------------------------------------------------------------------------
+
+AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clientInterface)
+    :
+#ifdef AUDIO_POLICY_TEST
+    Thread(false),
+#endif //AUDIO_POLICY_TEST
+    mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false)
+{
+    mpClientInterface = clientInterface;
+
+    for (int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) {
+        mForceUse[i] = AudioSystem::FORCE_NONE;
+    }
+
+    // devices available by default are speaker, ear piece and microphone
+    mAvailableOutputDevices = AudioSystem::DEVICE_OUT_EARPIECE |
+                        AudioSystem::DEVICE_OUT_SPEAKER;
+    mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC;
+
+#ifdef WITH_A2DP
+    mA2dpOutput = 0;
+    mDuplicatedOutput = 0;
+    mA2dpDeviceAddress = String8("");
+#endif
+    mScoDeviceAddress = String8("");
+
+    // open hardware output
+    AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+    outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
+    mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
+                                    &outputDesc->mSamplingRate,
+                                    &outputDesc->mFormat,
+                                    &outputDesc->mChannels,
+                                    &outputDesc->mLatency,
+                                    outputDesc->mFlags);
+
+    if (mHardwareOutput == 0) {
+        LOGE("Failed to initialize hardware output stream, samplingRate: %d, format %d, channels %d",
+                outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
+    } else {
+        addOutput(mHardwareOutput, outputDesc);
+        setOutputDevice(mHardwareOutput, (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER, true);
+    }
+
+    updateDeviceForStrategy();
+#ifdef AUDIO_POLICY_TEST
+    AudioParameter outputCmd = AudioParameter();
+    outputCmd.addInt(String8("set_id"), 0);
+    mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
+
+    mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER;
+    mTestSamplingRate = 44100;
+    mTestFormat = AudioSystem::PCM_16_BIT;
+    mTestChannels =  AudioSystem::CHANNEL_OUT_STEREO;
+    mTestLatencyMs = 0;
+    mCurOutput = 0;
+    mDirectOutput = false;
+    for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
+        mTestOutputs[i] = 0;
+    }
+
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    snprintf(buffer, SIZE, "AudioPolicyManagerTest");
+    run(buffer, ANDROID_PRIORITY_AUDIO);
+#endif //AUDIO_POLICY_TEST
+}
+
+AudioPolicyManagerBase::~AudioPolicyManagerBase()
+{
+#ifdef AUDIO_POLICY_TEST
+    exit();
+#endif //AUDIO_POLICY_TEST
+   for (size_t i = 0; i < mOutputs.size(); i++) {
+        mpClientInterface->closeOutput(mOutputs.keyAt(i));
+        delete mOutputs.valueAt(i);
+   }
+   mOutputs.clear();
+   for (size_t i = 0; i < mInputs.size(); i++) {
+        mpClientInterface->closeInput(mInputs.keyAt(i));
+        delete mInputs.valueAt(i);
+   }
+   mInputs.clear();
+}
+
+#ifdef AUDIO_POLICY_TEST
+bool AudioPolicyManagerBase::threadLoop()
+{
+    LOGV("entering threadLoop()");
+    while (!exitPending())
+    {
+        String8 command;
+        int valueInt;
+        String8 value;
+
+        Mutex::Autolock _l(mLock);
+        mWaitWorkCV.waitRelative(mLock, milliseconds(50));
+
+        command = mpClientInterface->getParameters(0, String8("test_cmd_policy"));
+        AudioParameter param = AudioParameter(command);
+
+        if (param.getInt(String8("test_cmd_policy"), valueInt) == NO_ERROR &&
+            valueInt != 0) {
+            LOGV("Test command %s received", command.string());
+            String8 target;
+            if (param.get(String8("target"), target) != NO_ERROR) {
+                target = "Manager";
+            }
+            if (param.getInt(String8("test_cmd_policy_output"), valueInt) == NO_ERROR) {
+                param.remove(String8("test_cmd_policy_output"));
+                mCurOutput = valueInt;
+            }
+            if (param.get(String8("test_cmd_policy_direct"), value) == NO_ERROR) {
+                param.remove(String8("test_cmd_policy_direct"));
+                if (value == "false") {
+                    mDirectOutput = false;
+                } else if (value == "true") {
+                    mDirectOutput = true;
+                }
+            }
+            if (param.getInt(String8("test_cmd_policy_input"), valueInt) == NO_ERROR) {
+                param.remove(String8("test_cmd_policy_input"));
+                mTestInput = valueInt;
+            }
+
+            if (param.get(String8("test_cmd_policy_format"), value) == NO_ERROR) {
+                param.remove(String8("test_cmd_policy_format"));
+                int format = AudioSystem::INVALID_FORMAT;
+                if (value == "PCM 16 bits") {
+                    format = AudioSystem::PCM_16_BIT;
+                } else if (value == "PCM 8 bits") {
+                    format = AudioSystem::PCM_8_BIT;
+                } else if (value == "Compressed MP3") {
+                    format = AudioSystem::MP3;
+                }
+                if (format != AudioSystem::INVALID_FORMAT) {
+                    if (target == "Manager") {
+                        mTestFormat = format;
+                    } else if (mTestOutputs[mCurOutput] != 0) {
+                        AudioParameter outputParam = AudioParameter();
+                        outputParam.addInt(String8("format"), format);
+                        mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
+                    }
+                }
+            }
+            if (param.get(String8("test_cmd_policy_channels"), value) == NO_ERROR) {
+                param.remove(String8("test_cmd_policy_channels"));
+                int channels = 0;
+
+                if (value == "Channels Stereo") {
+                    channels =  AudioSystem::CHANNEL_OUT_STEREO;
+                } else if (value == "Channels Mono") {
+                    channels =  AudioSystem::CHANNEL_OUT_MONO;
+                }
+                if (channels != 0) {
+                    if (target == "Manager") {
+                        mTestChannels = channels;
+                    } else if (mTestOutputs[mCurOutput] != 0) {
+                        AudioParameter outputParam = AudioParameter();
+                        outputParam.addInt(String8("channels"), channels);
+                        mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
+                    }
+                }
+            }
+            if (param.getInt(String8("test_cmd_policy_sampleRate"), valueInt) == NO_ERROR) {
+                param.remove(String8("test_cmd_policy_sampleRate"));
+                if (valueInt >= 0 && valueInt <= 96000) {
+                    int samplingRate = valueInt;
+                    if (target == "Manager") {
+                        mTestSamplingRate = samplingRate;
+                    } else if (mTestOutputs[mCurOutput] != 0) {
+                        AudioParameter outputParam = AudioParameter();
+                        outputParam.addInt(String8("sampling_rate"), samplingRate);
+                        mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
+                    }
+                }
+            }
+
+            if (param.get(String8("test_cmd_policy_reopen"), value) == NO_ERROR) {
+                param.remove(String8("test_cmd_policy_reopen"));
+
+                mpClientInterface->closeOutput(mHardwareOutput);
+                delete mOutputs.valueFor(mHardwareOutput);
+                mOutputs.removeItem(mHardwareOutput);
+
+                AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+                outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
+                mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
+                                                &outputDesc->mSamplingRate,
+                                                &outputDesc->mFormat,
+                                                &outputDesc->mChannels,
+                                                &outputDesc->mLatency,
+                                                outputDesc->mFlags);
+                if (mHardwareOutput == 0) {
+                    LOGE("Failed to reopen hardware output stream, samplingRate: %d, format %d, channels %d",
+                            outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
+                } else {
+                    AudioParameter outputCmd = AudioParameter();
+                    outputCmd.addInt(String8("set_id"), 0);
+                    mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
+                    addOutput(mHardwareOutput, outputDesc);
+                }
+            }
+
+
+            mpClientInterface->setParameters(0, String8("test_cmd_policy="));
+        }
+    }
+    return false;
+}
+
+void AudioPolicyManagerBase::exit()
+{
+    {
+        AutoMutex _l(mLock);
+        requestExit();
+        mWaitWorkCV.signal();
+    }
+    requestExitAndWait();
+}
+
+int AudioPolicyManagerBase::testOutputIndex(audio_io_handle_t output)
+{
+    for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
+        if (output == mTestOutputs[i]) return i;
+    }
+    return 0;
+}
+#endif //AUDIO_POLICY_TEST
+
+// ---
+
+void AudioPolicyManagerBase::addOutput(audio_io_handle_t id, AudioOutputDescriptor *outputDesc)
+{
+    outputDesc->mId = id;
+    mOutputs.add(id, outputDesc);
+}
+
+
+#ifdef WITH_A2DP
+status_t AudioPolicyManagerBase::handleA2dpConnection(AudioSystem::audio_devices device,
+                                                 const char *device_address)
+{
+    // when an A2DP device is connected, open an A2DP and a duplicated output
+    LOGV("opening A2DP output for device %s", device_address);
+    AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+    outputDesc->mDevice = device;
+    mA2dpOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
+                                            &outputDesc->mSamplingRate,
+                                            &outputDesc->mFormat,
+                                            &outputDesc->mChannels,
+                                            &outputDesc->mLatency,
+                                            outputDesc->mFlags);
+    if (mA2dpOutput) {
+        // add A2DP output descriptor
+        addOutput(mA2dpOutput, outputDesc);
+        // set initial stream volume for A2DP device
+        applyStreamVolumes(mA2dpOutput, device);
+        if (a2dpUsedForSonification()) {
+            mDuplicatedOutput = mpClientInterface->openDuplicateOutput(mA2dpOutput, mHardwareOutput);
+        }
+        if (mDuplicatedOutput != 0 ||
+            !a2dpUsedForSonification()) {
+            // If both A2DP and duplicated outputs are open, send device address to A2DP hardware
+            // interface
+            AudioParameter param;
+            param.add(String8("a2dp_sink_address"), String8(device_address));
+            mpClientInterface->setParameters(mA2dpOutput, param.toString());
+            mA2dpDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN);
+
+            if (a2dpUsedForSonification()) {
+                // add duplicated output descriptor
+                AudioOutputDescriptor *dupOutputDesc = new AudioOutputDescriptor();
+                dupOutputDesc->mOutput1 = mOutputs.valueFor(mHardwareOutput);
+                dupOutputDesc->mOutput2 = mOutputs.valueFor(mA2dpOutput);
+                dupOutputDesc->mSamplingRate = outputDesc->mSamplingRate;
+                dupOutputDesc->mFormat = outputDesc->mFormat;
+                dupOutputDesc->mChannels = outputDesc->mChannels;
+                dupOutputDesc->mLatency = outputDesc->mLatency;
+                addOutput(mDuplicatedOutput, dupOutputDesc);
+                applyStreamVolumes(mDuplicatedOutput, device);
+            }
+        } else {
+            LOGW("getOutput() could not open duplicated output for %d and %d",
+                    mHardwareOutput, mA2dpOutput);
+            mpClientInterface->closeOutput(mA2dpOutput);
+            mOutputs.removeItem(mA2dpOutput);
+            mA2dpOutput = 0;
+            delete outputDesc;
+            return NO_INIT;
+        }
+    } else {
+        LOGW("setDeviceConnectionState() could not open A2DP output for device %x", device);
+        delete outputDesc;
+        return NO_INIT;
+    }
+    AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
+
+    if (mA2dpDeviceAddress == mScoDeviceAddress) {
+        // It is normal to suspend twice if we are both in call,
+        // and have the hardware audio output routed to BT SCO
+        if (mPhoneState != AudioSystem::MODE_NORMAL) {
+            mpClientInterface->suspendOutput(mA2dpOutput);
+        }
+        if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)hwOutputDesc->device())) {
+            mpClientInterface->suspendOutput(mA2dpOutput);
+        }
+    }
+
+    if (!a2dpUsedForSonification()) {
+        // mute music on A2DP output if a notification or ringtone is playing
+        uint32_t refCount = hwOutputDesc->strategyRefCount(STRATEGY_SONIFICATION);
+        for (uint32_t i = 0; i < refCount; i++) {
+            setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput);
+        }
+    }
+    return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devices device,
+                                                    const char *device_address)
+{
+    if (mA2dpOutput == 0) {
+        LOGW("setDeviceConnectionState() disconnecting A2DP and no A2DP output!");
+        return INVALID_OPERATION;
+    }
+
+    if (mA2dpDeviceAddress != device_address) {
+        LOGW("setDeviceConnectionState() disconnecting unknow A2DP sink address %s", device_address);
+        return INVALID_OPERATION;
+    }
+
+    // mute media during 2 seconds to avoid outputing sound on hardware output while music stream
+    // is switched from A2DP output and before music is paused by music application
+    setStrategyMute(STRATEGY_MEDIA, true, mHardwareOutput);
+    setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, 2000);
+
+    if (!a2dpUsedForSonification()) {
+        // unmute music on A2DP output if a notification or ringtone is playing
+        uint32_t refCount = mOutputs.valueFor(mHardwareOutput)->strategyRefCount(STRATEGY_SONIFICATION);
+        for (uint32_t i = 0; i < refCount; i++) {
+            setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput);
+        }
+    }
+    mA2dpDeviceAddress = "";
+    return NO_ERROR;
+}
+
+void AudioPolicyManagerBase::closeA2dpOutputs()
+{
+    LOGV("setDeviceConnectionState() closing A2DP and duplicated output!");
+
+    if (mDuplicatedOutput != 0) {
+        mpClientInterface->closeOutput(mDuplicatedOutput);
+        delete mOutputs.valueFor(mDuplicatedOutput);
+        mOutputs.removeItem(mDuplicatedOutput);
+        mDuplicatedOutput = 0;
+    }
+    if (mA2dpOutput != 0) {
+        AudioParameter param;
+        param.add(String8("closing"), String8("true"));
+        mpClientInterface->setParameters(mA2dpOutput, param.toString());
+        mpClientInterface->closeOutput(mA2dpOutput);
+        delete mOutputs.valueFor(mA2dpOutput);
+        mOutputs.removeItem(mA2dpOutput);
+        mA2dpOutput = 0;
+    }
+}
+
+void AudioPolicyManagerBase::checkOutputForStrategy(routing_strategy strategy, uint32_t &newDevice)
+{
+    uint32_t prevDevice = getDeviceForStrategy(strategy);
+    uint32_t curDevice = getDeviceForStrategy(strategy, false);
+    bool a2dpWasUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(prevDevice & ~AudioSystem::DEVICE_OUT_SPEAKER));
+    bool a2dpIsUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(curDevice & ~AudioSystem::DEVICE_OUT_SPEAKER));
+    AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
+    AudioOutputDescriptor *a2dpOutputDesc;
+
+    if (a2dpWasUsed && !a2dpIsUsed) {
+        bool dupUsed = a2dpUsedForSonification() && a2dpWasUsed && (AudioSystem::popCount(prevDevice) == 2);
+
+        if (dupUsed) {
+            LOGV("checkOutputForStrategy() moving strategy %d to duplicated", strategy);
+            a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput);
+        } else {
+            LOGV("checkOutputForStrategy() moving strategy %d to a2dp", strategy);
+            a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput);
+        }
+
+        for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+            if (getStrategy((AudioSystem::stream_type)i) == strategy) {
+                mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput);
+                int refCount = a2dpOutputDesc->mRefCount[i];
+                // in the case of duplicated output, the ref count is first incremented
+                // and then decremented on hardware output tus keeping its value
+                hwOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount);
+                a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount);
+            }
+        }
+        // do not change newDevice is it was already set before this call by a previous call to
+        // getNewDevice() or checkOutputForStrategy() for a strategy with higher priority
+        if (newDevice == 0 && hwOutputDesc->isUsedByStrategy(strategy)) {
+            newDevice = getDeviceForStrategy(strategy, false);
+        }
+    }
+    if (a2dpIsUsed && !a2dpWasUsed) {
+        bool dupUsed = a2dpUsedForSonification() && a2dpIsUsed && (AudioSystem::popCount(curDevice) == 2);
+        audio_io_handle_t a2dpOutput;
+
+        if (dupUsed) {
+            LOGV("checkOutputForStrategy() moving strategy %d from duplicated", strategy);
+            a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput);
+            a2dpOutput = mDuplicatedOutput;
+        } else {
+            LOGV("checkOutputForStrategy() moving strategy %d from a2dp", strategy);
+            a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput);
+            a2dpOutput = mA2dpOutput;
+        }
+
+        for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+            if (getStrategy((AudioSystem::stream_type)i) == strategy) {
+                mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, a2dpOutput);
+                int refCount = hwOutputDesc->mRefCount[i];
+                // in the case of duplicated output, the ref count is first incremented
+                // and then decremented on hardware output tus keeping its value
+                a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount);
+                hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount);
+            }
+        }
+    }
+}
+
+void AudioPolicyManagerBase::checkOutputForAllStrategies(uint32_t &newDevice)
+{
+    // Check strategies in order of priority so that once newDevice is set
+    // for a given strategy it is not modified by subsequent calls to
+    // checkOutputForStrategy()
+    checkOutputForStrategy(STRATEGY_PHONE, newDevice);
+    checkOutputForStrategy(STRATEGY_SONIFICATION, newDevice);
+    checkOutputForStrategy(STRATEGY_MEDIA, newDevice);
+    checkOutputForStrategy(STRATEGY_DTMF, newDevice);
+}
+
+#endif
+
+uint32_t AudioPolicyManagerBase::getNewDevice(audio_io_handle_t output, bool fromCache)
+{
+    uint32_t device = 0;
+
+    AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+    // check the following by order of priority to request a routing change if necessary:
+    // 1: we are in call or the strategy phone is active on the hardware output:
+    //      use device for strategy phone
+    // 2: the strategy sonification is active on the hardware output:
+    //      use device for strategy sonification
+    // 3: the strategy media is active on the hardware output:
+    //      use device for strategy media
+    // 4: the strategy DTMF is active on the hardware output:
+    //      use device for strategy DTMF
+    if (mPhoneState == AudioSystem::MODE_IN_CALL ||
+        outputDesc->isUsedByStrategy(STRATEGY_PHONE)) {
+        device = getDeviceForStrategy(STRATEGY_PHONE, fromCache);
+    } else if (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION)) {
+        device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache);
+    } else if (outputDesc->isUsedByStrategy(STRATEGY_MEDIA)) {
+        device = getDeviceForStrategy(STRATEGY_MEDIA, fromCache);
+    } else if (outputDesc->isUsedByStrategy(STRATEGY_DTMF)) {
+        device = getDeviceForStrategy(STRATEGY_DTMF, fromCache);
+    }
+
+    LOGV("getNewDevice() selected device %x", device);
+    return device;
+}
+
+AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy(AudioSystem::stream_type stream)
+{
+    // stream to strategy mapping
+    switch (stream) {
+    case AudioSystem::VOICE_CALL:
+    case AudioSystem::BLUETOOTH_SCO:
+        return STRATEGY_PHONE;
+    case AudioSystem::RING:
+    case AudioSystem::NOTIFICATION:
+    case AudioSystem::ALARM:
+    case AudioSystem::ENFORCED_AUDIBLE:
+        return STRATEGY_SONIFICATION;
+    case AudioSystem::DTMF:
+        return STRATEGY_DTMF;
+    default:
+        LOGE("unknown stream type");
+    case AudioSystem::SYSTEM:
+        // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs
+        // while key clicks are played produces a poor result
+    case AudioSystem::TTS:
+    case AudioSystem::MUSIC:
+        return STRATEGY_MEDIA;
+    }
+}
+
+uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, bool fromCache)
+{
+    uint32_t device = 0;
+
+    if (fromCache) {
+        LOGV("getDeviceForStrategy() from cache strategy %d, device %x", strategy, mDeviceForStrategy[strategy]);
+        return mDeviceForStrategy[strategy];
+    }
+
+    switch (strategy) {
+    case STRATEGY_DTMF:
+        if (mPhoneState != AudioSystem::MODE_IN_CALL) {
+            // when off call, DTMF strategy follows the same rules as MEDIA strategy
+            device = getDeviceForStrategy(STRATEGY_MEDIA, false);
+            break;
+        }
+        // when in call, DTMF and PHONE strategies follow the same rules
+        // FALL THROUGH
+
+    case STRATEGY_PHONE:
+        // for phone strategy, we first consider the forced use and then the available devices by order
+        // of priority
+        switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) {
+        case AudioSystem::FORCE_BT_SCO:
+            if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) {
+                device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+                if (device) break;
+            }
+            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+            if (device) break;
+            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO;
+            if (device) break;
+            // if SCO device is requested but no SCO device is available, fall back to default case
+            // FALL THROUGH
+
+        default:    // FORCE_NONE
+            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
+            if (device) break;
+            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
+            if (device) break;
+            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_EARPIECE;
+            if (device == 0) {
+                LOGE("getDeviceForStrategy() earpiece device not found");
+            }
+            break;
+
+        case AudioSystem::FORCE_SPEAKER:
+            if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) {
+                device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+                if (device) break;
+            }
+            device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
+            if (device == 0) {
+                LOGE("getDeviceForStrategy() speaker device not found");
+            }
+            break;
+        }
+    break;
+
+    case STRATEGY_SONIFICATION:
+
+        // If incall, just select the STRATEGY_PHONE device: The rest of the behavior is handled by
+        // handleIncallSonification().
+        if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+            device = getDeviceForStrategy(STRATEGY_PHONE, false);
+            break;
+        }
+        device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
+        if (device == 0) {
+            LOGE("getDeviceForStrategy() speaker device not found");
+        }
+        // The second device used for sonification is the same as the device used by media strategy
+        // FALL THROUGH
+
+    case STRATEGY_MEDIA: {
+        uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL;
+#ifdef WITH_A2DP
+        if (mA2dpOutput != 0) {
+            if (strategy == STRATEGY_SONIFICATION && !a2dpUsedForSonification()) {
+                break;
+            }
+            if (device2 == 0) {
+                device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP;
+            }
+            if (device2 == 0) {
+                device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
+            }
+            if (device2 == 0) {
+                device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
+            }
+        }
+#endif
+        if (device2 == 0) {
+            device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
+        }
+        if (device2 == 0) {
+            device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
+        }
+        if (device2 == 0) {
+            device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
+        }
+
+        // device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION, 0 otherwise
+        device |= device2;
+        if (device == 0) {
+            LOGE("getDeviceForStrategy() speaker device not found");
+        }
+        } break;
+
+    default:
+        LOGW("getDeviceForStrategy() unknown strategy: %d", strategy);
+        break;
+    }
+
+    LOGV("getDeviceForStrategy() strategy %d, device %x", strategy, device);
+    return device;
+}
+
+void AudioPolicyManagerBase::updateDeviceForStrategy()
+{
+    for (int i = 0; i < NUM_STRATEGIES; i++) {
+        mDeviceForStrategy[i] = getDeviceForStrategy((routing_strategy)i, false);
+    }
+}
+
+void AudioPolicyManagerBase::setOutputDevice(audio_io_handle_t output, uint32_t device, bool force, int delayMs)
+{
+    LOGV("setOutputDevice() output %d device %x delayMs %d", output, device, delayMs);
+    AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+
+
+    if (outputDesc->isDuplicated()) {
+        setOutputDevice(outputDesc->mOutput1->mId, device, force, delayMs);
+        setOutputDevice(outputDesc->mOutput2->mId, device, force, delayMs);
+        return;
+    }
+#ifdef WITH_A2DP
+    // filter devices according to output selected
+    if (output == mHardwareOutput) {
+        device &= ~AudioSystem::DEVICE_OUT_ALL_A2DP;
+    } else {
+        device &= AudioSystem::DEVICE_OUT_ALL_A2DP;
+    }
+#endif
+
+    uint32_t prevDevice = (uint32_t)outputDesc->device();
+    // Do not change the routing if:
+    //  - the requestede device is 0
+    //  - the requested device is the same as current device and force is not specified.
+    // Doing this check here allows the caller to call setOutputDevice() without conditions
+    if (device == 0 ||
+        (device == prevDevice && !force)) {
+        LOGV("setOutputDevice() setting same device %x or null device for output %d", device, output);
+        return;
+    }
+
+    outputDesc->mDevice = device;
+    // mute media streams if both speaker and headset are selected
+    if (output == mHardwareOutput && AudioSystem::popCount(device) == 2) {
+        setStrategyMute(STRATEGY_MEDIA, true, output);
+        // wait for the PCM output buffers to empty before proceeding with the rest of the command
+        usleep(outputDesc->mLatency*2*1000);
+    }
+#ifdef WITH_A2DP
+    // suspend A2D output if SCO device is selected
+    if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)device)) {
+         if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
+             mpClientInterface->suspendOutput(mA2dpOutput);
+         }
+    }
+#endif
+    // do the routing
+    AudioParameter param = AudioParameter();
+    param.addInt(String8(AudioParameter::keyRouting), (int)device);
+    mpClientInterface->setParameters(mHardwareOutput, param.toString(), delayMs);
+    // update stream volumes according to new device
+    applyStreamVolumes(output, device, delayMs);
+
+#ifdef WITH_A2DP
+    // if disconnecting SCO device, restore A2DP output
+    if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)prevDevice)) {
+         if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
+             LOGV("restore A2DP output");
+             mpClientInterface->restoreOutput(mA2dpOutput);
+         }
+    }
+#endif
+    // if changing from a combined headset + speaker route, unmute media streams
+    if (output == mHardwareOutput && AudioSystem::popCount(prevDevice) == 2) {
+        setStrategyMute(STRATEGY_MEDIA, false, output, delayMs);
+    }
+}
+
+uint32_t AudioPolicyManagerBase::getDeviceForInputSource(int inputSource)
+{
+    uint32_t device;
+
+    switch(inputSource) {
+    case AUDIO_SOURCE_DEFAULT:
+    case AUDIO_SOURCE_MIC:
+    case AUDIO_SOURCE_VOICE_RECOGNITION:
+        if (mForceUse[AudioSystem::FOR_RECORD] == AudioSystem::FORCE_BT_SCO &&
+            mAvailableInputDevices & AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET) {
+            device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+        } else if (mAvailableInputDevices & AudioSystem::DEVICE_IN_WIRED_HEADSET) {
+            device = AudioSystem::DEVICE_IN_WIRED_HEADSET;
+        } else {
+            device = AudioSystem::DEVICE_IN_BUILTIN_MIC;
+        }
+        break;
+    case AUDIO_SOURCE_CAMCORDER:
+        if (hasBackMicrophone()) {
+            device = AudioSystem::DEVICE_IN_BACK_MIC;
+        } else {
+            device = AudioSystem::DEVICE_IN_BUILTIN_MIC;
+        }
+        break;
+    case AUDIO_SOURCE_VOICE_UPLINK:
+    case AUDIO_SOURCE_VOICE_DOWNLINK:
+    case AUDIO_SOURCE_VOICE_CALL:
+        device = AudioSystem::DEVICE_IN_VOICE_CALL;
+        break;
+    default:
+        LOGW("getInput() invalid input source %d", inputSource);
+        device = 0;
+        break;
+    }
+    LOGV("getDeviceForInputSource()input source %d, device %08x", inputSource, device);
+    return device;
+}
+
+audio_io_handle_t AudioPolicyManagerBase::getActiveInput()
+{
+    for (size_t i = 0; i < mInputs.size(); i++) {
+        if (mInputs.valueAt(i)->mRefCount > 0) {
+            return mInputs.keyAt(i);
+        }
+    }
+    return 0;
+}
+
+float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_handle_t output, uint32_t device)
+{
+    float volume = 1.0;
+    AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+    StreamDescriptor &streamDesc = mStreams[stream];
+
+    if (device == 0) {
+        device = outputDesc->device();
+    }
+
+    int volInt = (100 * (index - streamDesc.mIndexMin)) / (streamDesc.mIndexMax - streamDesc.mIndexMin);
+    volume = AudioSystem::linearToLog(volInt);
+
+    // if a heaset is connected, apply the following rules to ring tones and notifications
+    // to avoid sound level bursts in user's ears:
+    // - always attenuate ring tones and notifications volume by 6dB
+    // - if music is playing, always limit the volume to current music volume,
+    // with a minimum threshold at -36dB so that notification is always perceived.
+    if ((device &
+        (AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP |
+        AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+        AudioSystem::DEVICE_OUT_WIRED_HEADSET |
+        AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) &&
+        (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) &&
+        streamDesc.mCanBeMuted) {
+        volume *= SONIFICATION_HEADSET_VOLUME_FACTOR;
+        // when the phone is ringing we must consider that music could have been paused just before
+        // by the music application and behave as if music was active if the last music track was
+        // just stopped
+        if (outputDesc->mRefCount[AudioSystem::MUSIC] || mLimitRingtoneVolume) {
+            float musicVol = computeVolume(AudioSystem::MUSIC, mStreams[AudioSystem::MUSIC].mIndexCur, output, device);
+            float minVol = (musicVol > SONIFICATION_HEADSET_VOLUME_MIN) ? musicVol : SONIFICATION_HEADSET_VOLUME_MIN;
+            if (volume > minVol) {
+                volume = minVol;
+                LOGV("computeVolume limiting volume to %f musicVol %f", minVol, musicVol);
+            }
+        }
+    }
+
+    return volume;
+}
+
+status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_io_handle_t output, uint32_t device, int delayMs, bool force)
+{
+
+    // do not change actual stream volume if the stream is muted
+    if (mOutputs.valueFor(output)->mMuteCount[stream] != 0) {
+        LOGV("checkAndSetVolume() stream %d muted count %d", stream, mOutputs.valueFor(output)->mMuteCount[stream]);
+        return NO_ERROR;
+    }
+
+    // do not change in call volume if bluetooth is connected and vice versa
+    if ((stream == AudioSystem::VOICE_CALL && mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) ||
+        (stream == AudioSystem::BLUETOOTH_SCO && mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO)) {
+        LOGV("checkAndSetVolume() cannot set stream %d volume with force use = %d for comm",
+             stream, mForceUse[AudioSystem::FOR_COMMUNICATION]);
+        return INVALID_OPERATION;
+    }
+
+    float volume = computeVolume(stream, index, output, device);
+    // do not set volume if the float value did not change
+    if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || force) {
+        mOutputs.valueFor(output)->mCurVolume[stream] = volume;
+        LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs);
+        if (stream == AudioSystem::VOICE_CALL ||
+            stream == AudioSystem::DTMF ||
+            stream == AudioSystem::BLUETOOTH_SCO) {
+            float voiceVolume = -1.0;
+            // offset value to reflect actual hardware volume that never reaches 0
+            // 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java)
+            volume = 0.01 + 0.99 * volume;
+            if (stream == AudioSystem::VOICE_CALL) {
+                voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
+            } else if (stream == AudioSystem::BLUETOOTH_SCO) {
+                voiceVolume = 1.0;
+            }
+            if (voiceVolume >= 0 && output == mHardwareOutput) {
+                mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
+            }
+        }
+        mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs);
+    }
+
+    return NO_ERROR;
+}
+
+void AudioPolicyManagerBase::applyStreamVolumes(audio_io_handle_t output, uint32_t device, int delayMs)
+{
+    LOGV("applyStreamVolumes() for output %d and device %x", output, device);
+
+    for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+        checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, device, delayMs);
+    }
+}
+
+void AudioPolicyManagerBase::setStrategyMute(routing_strategy strategy, bool on, audio_io_handle_t output, int delayMs)
+{
+    LOGV("setStrategyMute() strategy %d, mute %d, output %d", strategy, on, output);
+    for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+        if (getStrategy((AudioSystem::stream_type)stream) == strategy) {
+            setStreamMute(stream, on, output, delayMs);
+        }
+    }
+}
+
+void AudioPolicyManagerBase::setStreamMute(int stream, bool on, audio_io_handle_t output, int delayMs)
+{
+    StreamDescriptor &streamDesc = mStreams[stream];
+    AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+
+    LOGV("setStreamMute() stream %d, mute %d, output %d, mMuteCount %d", stream, on, output, outputDesc->mMuteCount[stream]);
+
+    if (on) {
+        if (outputDesc->mMuteCount[stream] == 0) {
+            if (streamDesc.mCanBeMuted) {
+                checkAndSetVolume(stream, 0, output, outputDesc->device(), delayMs);
+            }
+        }
+        // increment mMuteCount after calling checkAndSetVolume() so that volume change is not ignored
+        outputDesc->mMuteCount[stream]++;
+    } else {
+        if (outputDesc->mMuteCount[stream] == 0) {
+            LOGW("setStreamMute() unmuting non muted stream!");
+            return;
+        }
+        if (--outputDesc->mMuteCount[stream] == 0) {
+            checkAndSetVolume(stream, streamDesc.mIndexCur, output, outputDesc->device(), delayMs);
+        }
+    }
+}
+
+void AudioPolicyManagerBase::handleIncallSonification(int stream, bool starting, bool stateChange)
+{
+    // if the stream pertains to sonification strategy and we are in call we must
+    // mute the stream if it is low visibility. If it is high visibility, we must play a tone
+    // in the device used for phone strategy and play the tone if the selected device does not
+    // interfere with the device used for phone strategy
+    // if stateChange is true, we are called from setPhoneState() and we must mute or unmute as
+    // many times as there are active tracks on the output
+
+    if (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) {
+        AudioOutputDescriptor *outputDesc = mOutputs.valueFor(mHardwareOutput);
+        LOGV("handleIncallSonification() stream %d starting %d device %x stateChange %d",
+                stream, starting, outputDesc->mDevice, stateChange);
+        if (outputDesc->mRefCount[stream]) {
+            int muteCount = 1;
+            if (stateChange) {
+                muteCount = outputDesc->mRefCount[stream];
+            }
+            if (AudioSystem::isLowVisibility((AudioSystem::stream_type)stream)) {
+                LOGV("handleIncallSonification() low visibility, muteCount %d", muteCount);
+                for (int i = 0; i < muteCount; i++) {
+                    setStreamMute(stream, starting, mHardwareOutput);
+                }
+            } else {
+                LOGV("handleIncallSonification() high visibility");
+                if (outputDesc->device() & getDeviceForStrategy(STRATEGY_PHONE)) {
+                    LOGV("handleIncallSonification() high visibility muted, muteCount %d", muteCount);
+                    for (int i = 0; i < muteCount; i++) {
+                        setStreamMute(stream, starting, mHardwareOutput);
+                    }
+                }
+                if (starting) {
+                    mpClientInterface->startTone(ToneGenerator::TONE_SUP_CALL_WAITING, AudioSystem::VOICE_CALL);
+                } else {
+                    mpClientInterface->stopTone();
+                }
+            }
+        }
+    }
+}
+
+// --- AudioOutputDescriptor class implementation
+
+AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor()
+    : mId(0), mSamplingRate(0), mFormat(0), mChannels(0), mLatency(0),
+    mFlags((AudioSystem::output_flags)0), mDevice(0), mOutput1(0), mOutput2(0)
+{
+    // clear usage count for all stream types
+    for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
+        mRefCount[i] = 0;
+        mCurVolume[i] = -1.0;
+        mMuteCount[i] = 0;
+    }
+}
+
+uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::device()
+{
+    uint32_t device = 0;
+    if (isDuplicated()) {
+        device = mOutput1->mDevice | mOutput2->mDevice;
+    } else {
+        device = mDevice;
+    }
+    return device;
+}
+
+void AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta)
+{
+    // forward usage count change to attached outputs
+    if (isDuplicated()) {
+        mOutput1->changeRefCount(stream, delta);
+        mOutput2->changeRefCount(stream, delta);
+    }
+    if ((delta + (int)mRefCount[stream]) < 0) {
+        LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]);
+        mRefCount[stream] = 0;
+        return;
+    }
+    mRefCount[stream] += delta;
+    LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]);
+}
+
+uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::refCount()
+{
+    uint32_t refcount = 0;
+    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+        refcount += mRefCount[i];
+    }
+    return refcount;
+}
+
+uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::strategyRefCount(routing_strategy strategy)
+{
+    uint32_t refCount = 0;
+    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+        if (getStrategy((AudioSystem::stream_type)i) == strategy) {
+            refCount += mRefCount[i];
+        }
+    }
+    return refCount;
+}
+
+
+status_t AudioPolicyManagerBase::AudioOutputDescriptor::dump(int fd)
+{
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    String8 result;
+
+    snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Format: %d\n", mFormat);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Latency: %d\n", mLatency);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Flags %08x\n", mFlags);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Devices %08x\n", device());
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Stream volume refCount muteCount\n");
+    result.append(buffer);
+    for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
+        snprintf(buffer, SIZE, " %02d     %.03f     %02d       %02d\n", i, mCurVolume[i], mRefCount[i], mMuteCount[i]);
+        result.append(buffer);
+    }
+    write(fd, result.string(), result.size());
+
+    return NO_ERROR;
+}
+
+// --- AudioInputDescriptor class implementation
+
+AudioPolicyManagerBase::AudioInputDescriptor::AudioInputDescriptor()
+    : mSamplingRate(0), mFormat(0), mChannels(0),
+     mAcoustics((AudioSystem::audio_in_acoustics)0), mDevice(0), mRefCount(0)
+{
+}
+
+status_t AudioPolicyManagerBase::AudioInputDescriptor::dump(int fd)
+{
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    String8 result;
+
+    snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Format: %d\n", mFormat);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Acoustics %08x\n", mAcoustics);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Devices %08x\n", mDevice);
+    result.append(buffer);
+    snprintf(buffer, SIZE, " Ref Count %d\n", mRefCount);
+    result.append(buffer);
+    write(fd, result.string(), result.size());
+
+    return NO_ERROR;
+}
+
+// --- StreamDescriptor class implementation
+
+void AudioPolicyManagerBase::StreamDescriptor::dump(char* buffer, size_t size)
+{
+    snprintf(buffer, size, "      %02d         %02d         %02d         %d\n",
+            mIndexMin,
+            mIndexMax,
+            mIndexCur,
+            mCanBeMuted);
+}
+
+
+}; // namespace android
diff --git a/libs/audioflinger/AudioPolicyManagerGeneric.cpp b/libs/audioflinger/AudioPolicyManagerGeneric.cpp
deleted file mode 100644
index 8cfc204..0000000
--- a/libs/audioflinger/AudioPolicyManagerGeneric.cpp
+++ /dev/null
@@ -1,945 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AudioPolicyManagerGeneric"
-//#define LOG_NDEBUG 0
-#include <utils/Log.h>
-#include "AudioPolicyManagerGeneric.h"
-#include <media/mediarecorder.h>
-
-namespace android {
-
-
-// ----------------------------------------------------------------------------
-// AudioPolicyInterface implementation
-// ----------------------------------------------------------------------------
-
-
-status_t AudioPolicyManagerGeneric::setDeviceConnectionState(AudioSystem::audio_devices device,
-                                                  AudioSystem::device_connection_state state,
-                                                  const char *device_address)
-{
-
-    LOGV("setDeviceConnectionState() device: %x, state %d, address %s", device, state, device_address);
-
-    // connect/disconnect only 1 device at a time
-    if (AudioSystem::popCount(device) != 1) return BAD_VALUE;
-
-    if (strlen(device_address) >= MAX_DEVICE_ADDRESS_LEN) {
-        LOGE("setDeviceConnectionState() invalid address: %s", device_address);
-        return BAD_VALUE;
-    }
-
-    // handle output devices
-    if (AudioSystem::isOutputDevice(device)) {
-        switch (state)
-        {
-        // handle output device connection
-        case AudioSystem::DEVICE_STATE_AVAILABLE:
-            if (mAvailableOutputDevices & device) {
-                LOGW("setDeviceConnectionState() device already connected: %x", device);
-                return INVALID_OPERATION;
-            }
-            LOGV("setDeviceConnectionState() connecting device %x", device);
-
-            // register new device as available
-            mAvailableOutputDevices |= device;
-            break;
-        // handle output device disconnection
-        case AudioSystem::DEVICE_STATE_UNAVAILABLE:
-            if (!(mAvailableOutputDevices & device)) {
-                LOGW("setDeviceConnectionState() device not connected: %x", device);
-                return INVALID_OPERATION;
-            }
-            LOGV("setDeviceConnectionState() disconnecting device %x", device);
-            // remove device from available output devices
-            mAvailableOutputDevices &= ~device;
-            break;
-
-        default:
-            LOGE("setDeviceConnectionState() invalid state: %x", state);
-            return BAD_VALUE;
-        }
-        return NO_ERROR;
-    }
-    // handle input devices
-    if (AudioSystem::isInputDevice(device)) {
-        switch (state)
-        {
-        // handle input device connection
-        case AudioSystem::DEVICE_STATE_AVAILABLE:
-            if (mAvailableInputDevices & device) {
-                LOGW("setDeviceConnectionState() device already connected: %d", device);
-                return INVALID_OPERATION;
-            }
-            mAvailableInputDevices |= device;
-            break;
-
-        // handle input device disconnection
-        case AudioSystem::DEVICE_STATE_UNAVAILABLE:
-            if (!(mAvailableInputDevices & device)) {
-                LOGW("setDeviceConnectionState() device not connected: %d", device);
-                return INVALID_OPERATION;
-            }
-            mAvailableInputDevices &= ~device;
-            break;
-
-        default:
-            LOGE("setDeviceConnectionState() invalid state: %x", state);
-            return BAD_VALUE;
-        }
-        return NO_ERROR;
-    }
-
-    LOGW("setDeviceConnectionState() invalid device: %x", device);
-    return BAD_VALUE;
-}
-
-AudioSystem::device_connection_state AudioPolicyManagerGeneric::getDeviceConnectionState(AudioSystem::audio_devices device,
-                                                  const char *device_address)
-{
-    AudioSystem::device_connection_state state = AudioSystem::DEVICE_STATE_UNAVAILABLE;
-    String8 address = String8(device_address);
-    if (AudioSystem::isOutputDevice(device)) {
-        if (device & mAvailableOutputDevices) {
-            state = AudioSystem::DEVICE_STATE_AVAILABLE;
-        }
-    } else if (AudioSystem::isInputDevice(device)) {
-        if (device & mAvailableInputDevices) {
-            state = AudioSystem::DEVICE_STATE_AVAILABLE;
-        }
-    }
-
-    return state;
-}
-
-void AudioPolicyManagerGeneric::setPhoneState(int state)
-{
-    LOGV("setPhoneState() state %d", state);
-    uint32_t newDevice = 0;
-    if (state < 0 || state >= AudioSystem::NUM_MODES) {
-        LOGW("setPhoneState() invalid state %d", state);
-        return;
-    }
-
-    if (state == mPhoneState ) {
-        LOGW("setPhoneState() setting same state %d", state);
-        return;
-    }
-    // store previous phone state for management of sonification strategy below
-    int oldState = mPhoneState;
-    mPhoneState = state;
-
-    // if leaving or entering in call state, handle special case of active streams
-    // pertaining to sonification strategy see handleIncallSonification()
-    if (state == AudioSystem::MODE_IN_CALL ||
-        oldState == AudioSystem::MODE_IN_CALL) {
-        bool starting = (state == AudioSystem::MODE_IN_CALL) ? true : false;
-        LOGV("setPhoneState() in call state management: new state is %d", state);
-        for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
-            handleIncallSonification(stream, starting);
-        }
-    }
-}
-
-void AudioPolicyManagerGeneric::setRingerMode(uint32_t mode, uint32_t mask)
-{
-    LOGV("setRingerMode() mode %x, mask %x", mode, mask);
-
-    mRingerMode = mode;
-}
-
-void AudioPolicyManagerGeneric::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config)
-{
-    LOGV("setForceUse) usage %d, config %d, mPhoneState %d", usage, config, mPhoneState);
-    mForceUse[usage] = config;
-}
-
-AudioSystem::forced_config AudioPolicyManagerGeneric::getForceUse(AudioSystem::force_use usage)
-{
-    return mForceUse[usage];
-}
-
-void AudioPolicyManagerGeneric::setSystemProperty(const char* property, const char* value)
-{
-    LOGV("setSystemProperty() property %s, value %s", property, value);
-    if (strcmp(property, "ro.camera.sound.forced") == 0) {
-        if (atoi(value)) {
-            LOGV("ENFORCED_AUDIBLE cannot be muted");
-            mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = false;
-        } else {
-            LOGV("ENFORCED_AUDIBLE can be muted");
-            mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = true;
-        }
-    }
-}
-
-audio_io_handle_t AudioPolicyManagerGeneric::getOutput(AudioSystem::stream_type stream,
-                                    uint32_t samplingRate,
-                                    uint32_t format,
-                                    uint32_t channels,
-                                    AudioSystem::output_flags flags)
-{
-    LOGV("getOutput() stream %d, samplingRate %d, format %d, channels %x, flags %x", stream, samplingRate, format, channels, flags);
-
-#ifdef AUDIO_POLICY_TEST
-    if (mCurOutput != 0) {
-        LOGV("getOutput() test output mCurOutput %d, samplingRate %d, format %d, channels %x, mDirectOutput %d",
-                mCurOutput, mTestSamplingRate, mTestFormat, mTestChannels, mDirectOutput);
-
-        if (mTestOutputs[mCurOutput] == 0) {
-            LOGV("getOutput() opening test output");
-            AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
-            outputDesc->mDevice = mTestDevice;
-            outputDesc->mSamplingRate = mTestSamplingRate;
-            outputDesc->mFormat = mTestFormat;
-            outputDesc->mChannels = mTestChannels;
-            outputDesc->mLatency = mTestLatencyMs;
-            outputDesc->mFlags = (AudioSystem::output_flags)(mDirectOutput ? AudioSystem::OUTPUT_FLAG_DIRECT : 0);
-            outputDesc->mRefCount[stream] = 0;
-            mTestOutputs[mCurOutput] = mpClientInterface->openOutput(&outputDesc->mDevice,
-                                            &outputDesc->mSamplingRate,
-                                            &outputDesc->mFormat,
-                                            &outputDesc->mChannels,
-                                            &outputDesc->mLatency,
-                                            outputDesc->mFlags);
-            if (mTestOutputs[mCurOutput]) {
-                AudioParameter outputCmd = AudioParameter();
-                outputCmd.addInt(String8("set_id"),mCurOutput);
-                mpClientInterface->setParameters(mTestOutputs[mCurOutput],outputCmd.toString());
-                mOutputs.add(mTestOutputs[mCurOutput], outputDesc);
-            }
-        }
-        return mTestOutputs[mCurOutput];
-    }
-#endif //AUDIO_POLICY_TEST
-    if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) ||
-        (format != 0 && !AudioSystem::isLinearPCM(format)) ||
-        (channels != 0 && channels != AudioSystem::CHANNEL_OUT_MONO && channels != AudioSystem::CHANNEL_OUT_STEREO)) {
-        return 0;
-    }
-
-    return mHardwareOutput;
-}
-
-status_t AudioPolicyManagerGeneric::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
-{
-    LOGV("startOutput() output %d, stream %d", output, stream);
-    ssize_t index = mOutputs.indexOfKey(output);
-    if (index < 0) {
-        LOGW("startOutput() unknow output %d", output);
-        return BAD_VALUE;
-    }
-
-    AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
-
-    // handle special case for sonification while in call
-    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
-        handleIncallSonification(stream, true);
-    }
-
-    // incremenent usage count for this stream on the requested output:
-    outputDesc->changeRefCount(stream, 1);
-    return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
-{
-    LOGV("stopOutput() output %d, stream %d", output, stream);
-    ssize_t index = mOutputs.indexOfKey(output);
-    if (index < 0) {
-        LOGW("stopOutput() unknow output %d", output);
-        return BAD_VALUE;
-    }
-
-    AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
-
-    // handle special case for sonification while in call
-    if (mPhoneState == AudioSystem::MODE_IN_CALL) {
-        handleIncallSonification(stream, false);
-    }
-
-    if (outputDesc->isUsedByStream(stream)) {
-        // decrement usage count of this stream on the output
-        outputDesc->changeRefCount(stream, -1);
-        return NO_ERROR;
-    } else {
-        LOGW("stopOutput() refcount is already 0 for output %d", output);
-        return INVALID_OPERATION;
-    }
-}
-
-void AudioPolicyManagerGeneric::releaseOutput(audio_io_handle_t output)
-{
-    LOGV("releaseOutput() %d", output);
-    ssize_t index = mOutputs.indexOfKey(output);
-    if (index < 0) {
-        LOGW("releaseOutput() releasing unknown output %d", output);
-        return;
-    }
-
-#ifdef AUDIO_POLICY_TEST
-    int testIndex = testOutputIndex(output);
-    if (testIndex != 0) {
-        AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
-        if (outputDesc->refCount() == 0) {
-            mpClientInterface->closeOutput(output);
-            delete mOutputs.valueAt(index);
-            mOutputs.removeItem(output);
-            mTestOutputs[testIndex] = 0;
-        }
-    }
-#endif //AUDIO_POLICY_TEST
-}
-
-audio_io_handle_t AudioPolicyManagerGeneric::getInput(int inputSource,
-                                    uint32_t samplingRate,
-                                    uint32_t format,
-                                    uint32_t channels,
-                                    AudioSystem::audio_in_acoustics acoustics)
-{
-    audio_io_handle_t input = 0;
-    uint32_t device;
-
-    LOGV("getInput() inputSource %d, samplingRate %d, format %d, channels %x, acoustics %x", inputSource, samplingRate, format, channels, acoustics);
-
-    AudioInputDescriptor *inputDesc = new AudioInputDescriptor();
-    inputDesc->mDevice = AudioSystem::DEVICE_IN_BUILTIN_MIC;
-    inputDesc->mSamplingRate = samplingRate;
-    inputDesc->mFormat = format;
-    inputDesc->mChannels = channels;
-    inputDesc->mAcoustics = acoustics;
-    inputDesc->mRefCount = 0;
-    input = mpClientInterface->openInput(&inputDesc->mDevice,
-                                    &inputDesc->mSamplingRate,
-                                    &inputDesc->mFormat,
-                                    &inputDesc->mChannels,
-                                    inputDesc->mAcoustics);
-
-    // only accept input with the exact requested set of parameters
-    if ((samplingRate != inputDesc->mSamplingRate) ||
-        (format != inputDesc->mFormat) ||
-        (channels != inputDesc->mChannels)) {
-        LOGV("getOutput() failed opening input: samplingRate %d, format %d, channels %d",
-                samplingRate, format, channels);
-        mpClientInterface->closeInput(input);
-        delete inputDesc;
-        return 0;
-    }
-    mInputs.add(input, inputDesc);
-    return input;
-}
-
-status_t AudioPolicyManagerGeneric::startInput(audio_io_handle_t input)
-{
-    LOGV("startInput() input %d", input);
-    ssize_t index = mInputs.indexOfKey(input);
-    if (index < 0) {
-        LOGW("startInput() unknow input %d", input);
-        return BAD_VALUE;
-    }
-    AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
-
-#ifdef AUDIO_POLICY_TEST
-    if (mTestInput == 0)
-#endif //AUDIO_POLICY_TEST
-    {
-        // refuse 2 active AudioRecord clients at the same time
-        for (size_t i = 0; i < mInputs.size(); i++) {
-            if (mInputs.valueAt(i)->mRefCount > 0) {
-                LOGW("startInput() input %d, other input %d already started", input, mInputs.keyAt(i));
-                return INVALID_OPERATION;
-            }
-        }
-    }
-
-    inputDesc->mRefCount = 1;
-    return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::stopInput(audio_io_handle_t input)
-{
-    LOGV("stopInput() input %d", input);
-    ssize_t index = mInputs.indexOfKey(input);
-    if (index < 0) {
-        LOGW("stopInput() unknow input %d", input);
-        return BAD_VALUE;
-    }
-    AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
-
-    if (inputDesc->mRefCount == 0) {
-        LOGW("stopInput() input %d already stopped", input);
-        return INVALID_OPERATION;
-    } else {
-        inputDesc->mRefCount = 0;
-        return NO_ERROR;
-    }
-}
-
-void AudioPolicyManagerGeneric::releaseInput(audio_io_handle_t input)
-{
-    LOGV("releaseInput() %d", input);
-    ssize_t index = mInputs.indexOfKey(input);
-    if (index < 0) {
-        LOGW("releaseInput() releasing unknown input %d", input);
-        return;
-    }
-    mpClientInterface->closeInput(input);
-    delete mInputs.valueAt(index);
-    mInputs.removeItem(input);
-}
-
-
-
-void AudioPolicyManagerGeneric::initStreamVolume(AudioSystem::stream_type stream,
-                                            int indexMin,
-                                            int indexMax)
-{
-    LOGV("initStreamVolume() stream %d, min %d, max %d", stream , indexMin, indexMax);
-    mStreams[stream].mIndexMin = indexMin;
-    mStreams[stream].mIndexMax = indexMax;
-}
-
-status_t AudioPolicyManagerGeneric::setStreamVolumeIndex(AudioSystem::stream_type stream, int index)
-{
-
-    if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) {
-        return BAD_VALUE;
-    }
-
-    LOGV("setStreamVolumeIndex() stream %d, index %d", stream, index);
-    mStreams[stream].mIndexCur = index;
-
-    // do not change actual stream volume if the stream is muted
-    if (mStreams[stream].mMuteCount != 0) {
-        return NO_ERROR;
-    }
-
-    // Do not changed in call volume if bluetooth is connected and vice versa
-    if ((stream == AudioSystem::VOICE_CALL && mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) ||
-        (stream == AudioSystem::BLUETOOTH_SCO && mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO)) {
-        LOGV("setStreamVolumeIndex() cannot set stream %d volume with force use = %d for comm",
-             stream, mForceUse[AudioSystem::FOR_COMMUNICATION]);
-        return INVALID_OPERATION;
-    }
-
-    // compute and apply stream volume on all outputs according to connected device
-    for (size_t i = 0; i < mOutputs.size(); i++) {
-        AudioOutputDescriptor *outputDesc = mOutputs.valueAt(i);
-        uint32_t device = outputDesc->device();
-
-        float volume = computeVolume((int)stream, index, device);
-
-        LOGV("setStreamVolume() for output %d stream %d, volume %f", mOutputs.keyAt(i), stream, volume);
-        mpClientInterface->setStreamVolume(stream, volume, mOutputs.keyAt(i));
-    }
-    return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index)
-{
-    if (index == 0) {
-        return BAD_VALUE;
-    }
-    LOGV("getStreamVolumeIndex() stream %d", stream);
-    *index =  mStreams[stream].mIndexCur;
-    return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::dump(int fd)
-{
-    const size_t SIZE = 256;
-    char buffer[SIZE];
-    String8 result;
-
-    snprintf(buffer, SIZE, "\nAudioPolicyManager Dump: %p\n", this);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Hardware Output: %d\n", mHardwareOutput);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Output devices: %08x\n", mAvailableOutputDevices);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Input devices: %08x\n", mAvailableInputDevices);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Phone state: %d\n", mPhoneState);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Ringer mode: %d\n", mRingerMode);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Force use for communications %d\n", mForceUse[AudioSystem::FOR_COMMUNICATION]);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Force use for media %d\n", mForceUse[AudioSystem::FOR_MEDIA]);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Force use for record %d\n", mForceUse[AudioSystem::FOR_RECORD]);
-    result.append(buffer);
-    write(fd, result.string(), result.size());
-
-    snprintf(buffer, SIZE, "\nOutputs dump:\n");
-    write(fd, buffer, strlen(buffer));
-    for (size_t i = 0; i < mOutputs.size(); i++) {
-        snprintf(buffer, SIZE, "- Output %d dump:\n", mOutputs.keyAt(i));
-        write(fd, buffer, strlen(buffer));
-        mOutputs.valueAt(i)->dump(fd);
-    }
-
-    snprintf(buffer, SIZE, "\nInputs dump:\n");
-    write(fd, buffer, strlen(buffer));
-    for (size_t i = 0; i < mInputs.size(); i++) {
-        snprintf(buffer, SIZE, "- Input %d dump:\n", mInputs.keyAt(i));
-        write(fd, buffer, strlen(buffer));
-        mInputs.valueAt(i)->dump(fd);
-    }
-
-    snprintf(buffer, SIZE, "\nStreams dump:\n");
-    write(fd, buffer, strlen(buffer));
-    snprintf(buffer, SIZE, " Stream  Index Min  Index Max  Index Cur  Mute Count  Can be muted\n");
-    write(fd, buffer, strlen(buffer));
-    for (size_t i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
-        snprintf(buffer, SIZE, " %02d", i);
-        mStreams[i].dump(buffer + 3, SIZE);
-        write(fd, buffer, strlen(buffer));
-    }
-
-    return NO_ERROR;
-}
-
-// ----------------------------------------------------------------------------
-// AudioPolicyManagerGeneric
-// ----------------------------------------------------------------------------
-
-// ---  class factory
-
-AudioPolicyManagerGeneric::AudioPolicyManagerGeneric(AudioPolicyClientInterface *clientInterface)
-    :
-#ifdef AUDIO_POLICY_TEST
-    Thread(false),
-#endif //AUDIO_POLICY_TEST
-    mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0)
-{
-    mpClientInterface = clientInterface;
-
-    for (int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) {
-        mForceUse[i] = AudioSystem::FORCE_NONE;
-    }
-
-    // devices available by default are speaker, ear piece and microphone
-    mAvailableOutputDevices = AudioSystem::DEVICE_OUT_SPEAKER;
-    mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC;
-
-    // open hardware output
-    AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
-    outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
-    mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
-                                    &outputDesc->mSamplingRate,
-                                    &outputDesc->mFormat,
-                                    &outputDesc->mChannels,
-                                    &outputDesc->mLatency,
-                                    outputDesc->mFlags);
-
-    if (mHardwareOutput == 0) {
-        LOGE("Failed to initialize hardware output stream, samplingRate: %d, format %d, channels %d",
-                outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
-    } else {
-        mOutputs.add(mHardwareOutput, outputDesc);
-    }
-
-#ifdef AUDIO_POLICY_TEST
-    AudioParameter outputCmd = AudioParameter();
-    outputCmd.addInt(String8("set_id"), 0);
-    mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
-
-    mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER;
-    mTestSamplingRate = 44100;
-    mTestFormat = AudioSystem::PCM_16_BIT;
-    mTestChannels =  AudioSystem::CHANNEL_OUT_STEREO;
-    mTestLatencyMs = 0;
-    mCurOutput = 0;
-    mDirectOutput = false;
-    for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
-        mTestOutputs[i] = 0;
-    }
-
-    const size_t SIZE = 256;
-    char buffer[SIZE];
-    snprintf(buffer, SIZE, "AudioPolicyManagerTest");
-    run(buffer, ANDROID_PRIORITY_AUDIO);
-#endif //AUDIO_POLICY_TEST
-}
-
-AudioPolicyManagerGeneric::~AudioPolicyManagerGeneric()
-{
-#ifdef AUDIO_POLICY_TEST
-    exit();
-#endif //AUDIO_POLICY_TEST
-
-   for (size_t i = 0; i < mOutputs.size(); i++) {
-        mpClientInterface->closeOutput(mOutputs.keyAt(i));
-        delete mOutputs.valueAt(i);
-   }
-   mOutputs.clear();
-   for (size_t i = 0; i < mInputs.size(); i++) {
-        mpClientInterface->closeInput(mInputs.keyAt(i));
-        delete mInputs.valueAt(i);
-   }
-   mInputs.clear();
-}
-
-#ifdef AUDIO_POLICY_TEST
-bool AudioPolicyManagerGeneric::threadLoop()
-{
-    LOGV("entering threadLoop()");
-    while (!exitPending())
-    {
-        String8 command;
-        int valueInt;
-        String8 value;
-
-        Mutex::Autolock _l(mLock);
-        mWaitWorkCV.waitRelative(mLock, milliseconds(50));
-
-        command = mpClientInterface->getParameters(0, String8("test_cmd_policy"));
-        AudioParameter param = AudioParameter(command);
-
-        if (param.getInt(String8("test_cmd_policy"), valueInt) == NO_ERROR &&
-            valueInt != 0) {
-            LOGV("Test command %s received", command.string());
-            String8 target;
-            if (param.get(String8("target"), target) != NO_ERROR) {
-                target = "Manager";
-            }
-            if (param.getInt(String8("test_cmd_policy_output"), valueInt) == NO_ERROR) {
-                param.remove(String8("test_cmd_policy_output"));
-                mCurOutput = valueInt;
-            }
-            if (param.get(String8("test_cmd_policy_direct"), value) == NO_ERROR) {
-                param.remove(String8("test_cmd_policy_direct"));
-                if (value == "false") {
-                    mDirectOutput = false;
-                } else if (value == "true") {
-                    mDirectOutput = true;
-                }
-            }
-            if (param.getInt(String8("test_cmd_policy_input"), valueInt) == NO_ERROR) {
-                param.remove(String8("test_cmd_policy_input"));
-                mTestInput = valueInt;
-            }
-
-            if (param.get(String8("test_cmd_policy_format"), value) == NO_ERROR) {
-                param.remove(String8("test_cmd_policy_format"));
-                int format = AudioSystem::INVALID_FORMAT;
-                if (value == "PCM 16 bits") {
-                    format = AudioSystem::PCM_16_BIT;
-                } else if (value == "PCM 8 bits") {
-                    format = AudioSystem::PCM_8_BIT;
-                } else if (value == "Compressed MP3") {
-                    format = AudioSystem::MP3;
-                }
-                if (format != AudioSystem::INVALID_FORMAT) {
-                    if (target == "Manager") {
-                        mTestFormat = format;
-                    } else if (mTestOutputs[mCurOutput] != 0) {
-                        AudioParameter outputParam = AudioParameter();
-                        outputParam.addInt(String8("format"), format);
-                        mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
-                    }
-                }
-            }
-            if (param.get(String8("test_cmd_policy_channels"), value) == NO_ERROR) {
-                param.remove(String8("test_cmd_policy_channels"));
-                int channels = 0;
-
-                if (value == "Channels Stereo") {
-                    channels =  AudioSystem::CHANNEL_OUT_STEREO;
-                } else if (value == "Channels Mono") {
-                    channels =  AudioSystem::CHANNEL_OUT_MONO;
-                }
-                if (channels != 0) {
-                    if (target == "Manager") {
-                        mTestChannels = channels;
-                    } else if (mTestOutputs[mCurOutput] != 0) {
-                        AudioParameter outputParam = AudioParameter();
-                        outputParam.addInt(String8("channels"), channels);
-                        mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
-                    }
-                }
-            }
-            if (param.getInt(String8("test_cmd_policy_sampleRate"), valueInt) == NO_ERROR) {
-                param.remove(String8("test_cmd_policy_sampleRate"));
-                if (valueInt >= 0 && valueInt <= 96000) {
-                    int samplingRate = valueInt;
-                    if (target == "Manager") {
-                        mTestSamplingRate = samplingRate;
-                    } else if (mTestOutputs[mCurOutput] != 0) {
-                        AudioParameter outputParam = AudioParameter();
-                        outputParam.addInt(String8("sampling_rate"), samplingRate);
-                        mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
-                    }
-                }
-            }
-
-            if (param.get(String8("test_cmd_policy_reopen"), value) == NO_ERROR) {
-                param.remove(String8("test_cmd_policy_reopen"));
-
-                mpClientInterface->closeOutput(mHardwareOutput);
-                delete mOutputs.valueFor(mHardwareOutput);
-                mOutputs.removeItem(mHardwareOutput);
-
-                AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
-                outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
-                mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
-                                                &outputDesc->mSamplingRate,
-                                                &outputDesc->mFormat,
-                                                &outputDesc->mChannels,
-                                                &outputDesc->mLatency,
-                                                outputDesc->mFlags);
-                if (mHardwareOutput == 0) {
-                    LOGE("Failed to reopen hardware output stream, samplingRate: %d, format %d, channels %d",
-                            outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
-                } else {
-                    AudioParameter outputCmd = AudioParameter();
-                    outputCmd.addInt(String8("set_id"), 0);
-                    mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
-                    mOutputs.add(mHardwareOutput, outputDesc);
-                }
-            }
-
-
-            mpClientInterface->setParameters(0, String8("test_cmd_policy="));
-        }
-    }
-    return false;
-}
-
-void AudioPolicyManagerGeneric::exit()
-{
-    {
-        AutoMutex _l(mLock);
-        requestExit();
-        mWaitWorkCV.signal();
-    }
-    requestExitAndWait();
-}
-
-int AudioPolicyManagerGeneric::testOutputIndex(audio_io_handle_t output)
-{
-    for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
-        if (output == mTestOutputs[i]) return i;
-    }
-    return 0;
-}
-#endif //AUDIO_POLICY_TEST
-
-// ---
-
-AudioPolicyManagerGeneric::routing_strategy AudioPolicyManagerGeneric::getStrategy(AudioSystem::stream_type stream)
-{
-    // stream to strategy mapping
-    switch (stream) {
-    case AudioSystem::VOICE_CALL:
-    case AudioSystem::BLUETOOTH_SCO:
-        return STRATEGY_PHONE;
-    case AudioSystem::RING:
-    case AudioSystem::NOTIFICATION:
-    case AudioSystem::ALARM:
-    case AudioSystem::ENFORCED_AUDIBLE:
-        return STRATEGY_SONIFICATION;
-    case AudioSystem::DTMF:
-        return STRATEGY_DTMF;
-    default:
-        LOGE("unknown stream type");
-    case AudioSystem::SYSTEM:
-        // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs
-        // while key clicks are played produces a poor result
-    case AudioSystem::TTS:
-    case AudioSystem::MUSIC:
-        return STRATEGY_MEDIA;
-    }
-}
-
-
-float AudioPolicyManagerGeneric::computeVolume(int stream, int index, uint32_t device)
-{
-    float volume = 1.0;
-
-    StreamDescriptor &streamDesc = mStreams[stream];
-
-    // Force max volume if stream cannot be muted
-    if (!streamDesc.mCanBeMuted) index = streamDesc.mIndexMax;
-
-    int volInt = (100 * (index - streamDesc.mIndexMin)) / (streamDesc.mIndexMax - streamDesc.mIndexMin);
-    volume = AudioSystem::linearToLog(volInt);
-
-    return volume;
-}
-
-void AudioPolicyManagerGeneric::setStreamMute(int stream, bool on, audio_io_handle_t output)
-{
-    LOGV("setStreamMute() stream %d, mute %d, output %d", stream, on, output);
-
-    StreamDescriptor &streamDesc = mStreams[stream];
-
-    if (on) {
-        if (streamDesc.mMuteCount++ == 0) {
-            if (streamDesc.mCanBeMuted) {
-                mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, 0, output);
-            }
-        }
-    } else {
-        if (streamDesc.mMuteCount == 0) {
-            LOGW("setStreamMute() unmuting non muted stream!");
-            return;
-        }
-        if (--streamDesc.mMuteCount == 0) {
-            uint32_t device = mOutputs.valueFor(output)->mDevice;
-            float volume = computeVolume(stream, streamDesc.mIndexCur, device);
-            mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output);
-        }
-    }
-}
-
-void AudioPolicyManagerGeneric::handleIncallSonification(int stream, bool starting)
-{
-    // if the stream pertains to sonification strategy and we are in call we must
-    // mute the stream if it is low visibility. If it is high visibility, we must play a tone
-    // in the device used for phone strategy and play the tone if the selected device does not
-    // interfere with the device used for phone strategy
-    if (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) {
-        AudioOutputDescriptor *outputDesc = mOutputs.valueFor(mHardwareOutput);
-        LOGV("handleIncallSonification() stream %d starting %d device %x", stream, starting, outputDesc->mDevice);
-        if (outputDesc->isUsedByStream((AudioSystem::stream_type)stream)) {
-            if (AudioSystem::isLowVisibility((AudioSystem::stream_type)stream)) {
-                LOGV("handleIncallSonification() low visibility");
-                setStreamMute(stream, starting, mHardwareOutput);
-            } else {
-                if (starting) {
-                    mpClientInterface->startTone(ToneGenerator::TONE_SUP_CALL_WAITING, AudioSystem::VOICE_CALL);
-                } else {
-                    mpClientInterface->stopTone();
-                }
-            }
-        }
-    }
-}
-
-
-// --- AudioOutputDescriptor class implementation
-
-AudioPolicyManagerGeneric::AudioOutputDescriptor::AudioOutputDescriptor()
-    : mSamplingRate(0), mFormat(0), mChannels(0), mLatency(0),
-    mFlags((AudioSystem::output_flags)0), mDevice(0)
-{
-    // clear usage count for all stream types
-    for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
-        mRefCount[i] = 0;
-    }
-}
-
-uint32_t AudioPolicyManagerGeneric::AudioOutputDescriptor::device()
-{
-    return mDevice;
-}
-
-void AudioPolicyManagerGeneric::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta)
-{
-    if ((delta + (int)mRefCount[stream]) < 0) {
-        LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]);
-        mRefCount[stream] = 0;
-        return;
-    }
-    mRefCount[stream] += delta;
-    LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]);
-}
-
-uint32_t AudioPolicyManagerGeneric::AudioOutputDescriptor::refCount()
-{
-    uint32_t refcount = 0;
-    for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
-        refcount += mRefCount[i];
-    }
-    return refcount;
-}
-
-status_t AudioPolicyManagerGeneric::AudioOutputDescriptor::dump(int fd)
-{
-    const size_t SIZE = 256;
-    char buffer[SIZE];
-    String8 result;
-
-    snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Format: %d\n", mFormat);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Latency: %d\n", mLatency);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Flags %08x\n", mFlags);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Devices %08x\n", mDevice);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Stream refCount\n");
-    result.append(buffer);
-    for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
-        snprintf(buffer, SIZE, " %02d     %d\n", i, mRefCount[i]);
-        result.append(buffer);
-    }
-    write(fd, result.string(), result.size());
-
-    return NO_ERROR;
-}
-
-// --- AudioInputDescriptor class implementation
-
-AudioPolicyManagerGeneric::AudioInputDescriptor::AudioInputDescriptor()
-    : mSamplingRate(0), mFormat(0), mChannels(0),
-     mAcoustics((AudioSystem::audio_in_acoustics)0), mDevice(0), mRefCount(0)
-{
-}
-
-status_t AudioPolicyManagerGeneric::AudioInputDescriptor::dump(int fd)
-{
-    const size_t SIZE = 256;
-    char buffer[SIZE];
-    String8 result;
-
-    snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Format: %d\n", mFormat);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Acoustics %08x\n", mAcoustics);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Devices %08x\n", mDevice);
-    result.append(buffer);
-    snprintf(buffer, SIZE, " Ref Count %d\n", mRefCount);
-    result.append(buffer);
-    write(fd, result.string(), result.size());
-
-    return NO_ERROR;
-}
-
-// --- StreamDescriptor class implementation
-
-void AudioPolicyManagerGeneric::StreamDescriptor::dump(char* buffer, size_t size)
-{
-    snprintf(buffer, size, "      %02d         %02d         %02d         %02d          %d\n",
-            mIndexMin,
-            mIndexMax,
-            mIndexCur,
-            mMuteCount,
-            mCanBeMuted);
-}
-
-}; // namespace android
diff --git a/libs/audioflinger/AudioPolicyManagerGeneric.h b/libs/audioflinger/AudioPolicyManagerGeneric.h
deleted file mode 100644
index 4997cdf..0000000
--- a/libs/audioflinger/AudioPolicyManagerGeneric.h
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <hardware_legacy/AudioPolicyInterface.h>
-#include <utils/threads.h>
-
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-
-#define MAX_DEVICE_ADDRESS_LEN 20
-#define NUM_TEST_OUTPUTS 5
-
-class AudioPolicyManagerGeneric: public AudioPolicyInterface
-#ifdef AUDIO_POLICY_TEST
-    , public Thread
-#endif //AUDIO_POLICY_TEST
-{
-
-public:
-                AudioPolicyManagerGeneric(AudioPolicyClientInterface *clientInterface);
-        virtual ~AudioPolicyManagerGeneric();
-
-        // AudioPolicyInterface
-        virtual status_t setDeviceConnectionState(AudioSystem::audio_devices device,
-                                                          AudioSystem::device_connection_state state,
-                                                          const char *device_address);
-        virtual AudioSystem::device_connection_state getDeviceConnectionState(AudioSystem::audio_devices device,
-                                                                              const char *device_address);
-        virtual void setPhoneState(int state);
-        virtual void setRingerMode(uint32_t mode, uint32_t mask);
-        virtual void setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config);
-        virtual AudioSystem::forced_config getForceUse(AudioSystem::force_use usage);
-        virtual void setSystemProperty(const char* property, const char* value);
-        virtual audio_io_handle_t getOutput(AudioSystem::stream_type stream,
-                                            uint32_t samplingRate,
-                                            uint32_t format,
-                                            uint32_t channels,
-                                            AudioSystem::output_flags flags);
-        virtual status_t startOutput(audio_io_handle_t output, AudioSystem::stream_type stream);
-        virtual status_t stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream);
-        virtual void releaseOutput(audio_io_handle_t output);
-        virtual audio_io_handle_t getInput(int inputSource,
-                                            uint32_t samplingRate,
-                                            uint32_t format,
-                                            uint32_t channels,
-                                            AudioSystem::audio_in_acoustics acoustics);
-        // indicates to the audio policy manager that the input starts being used.
-        virtual status_t startInput(audio_io_handle_t input);
-        // indicates to the audio policy manager that the input stops being used.
-        virtual status_t stopInput(audio_io_handle_t input);
-        virtual void releaseInput(audio_io_handle_t input);
-        virtual void initStreamVolume(AudioSystem::stream_type stream,
-                                                    int indexMin,
-                                                    int indexMax);
-        virtual status_t setStreamVolumeIndex(AudioSystem::stream_type stream, int index);
-        virtual status_t getStreamVolumeIndex(AudioSystem::stream_type stream, int *index);
-
-        virtual status_t dump(int fd);
-
-private:
-
-        enum routing_strategy {
-            STRATEGY_MEDIA,
-            STRATEGY_PHONE,
-            STRATEGY_SONIFICATION,
-            STRATEGY_DTMF,
-            NUM_STRATEGIES
-        };
-
-        // descriptor for audio outputs. Used to maintain current configuration of each opened audio output
-        // and keep track of the usage of this output by each audio stream type.
-        class AudioOutputDescriptor
-        {
-        public:
-            AudioOutputDescriptor();
-
-            status_t    dump(int fd);
-
-            uint32_t device();
-            void changeRefCount(AudioSystem::stream_type, int delta);
-            bool isUsedByStream(AudioSystem::stream_type stream) { return mRefCount[stream] > 0 ? true : false; }
-            uint32_t refCount();
-
-            uint32_t mSamplingRate;             //
-            uint32_t mFormat;                   //
-            uint32_t mChannels;                 // output configuration
-            uint32_t mLatency;                  //
-            AudioSystem::output_flags mFlags;   //
-            uint32_t mDevice;                   // current device this output is routed to
-            uint32_t mRefCount[AudioSystem::NUM_STREAM_TYPES]; // number of streams of each type using this output
-        };
-
-        // descriptor for audio inputs. Used to maintain current configuration of each opened audio input
-        // and keep track of the usage of this input.
-        class AudioInputDescriptor
-        {
-        public:
-            AudioInputDescriptor();
-
-            status_t    dump(int fd);
-
-            uint32_t mSamplingRate;                     //
-            uint32_t mFormat;                           // input configuration
-            uint32_t mChannels;                         //
-            AudioSystem::audio_in_acoustics mAcoustics; //
-            uint32_t mDevice;                           // current device this input is routed to
-            uint32_t mRefCount;                         // number of AudioRecord clients using this output
-        };
-
-        // stream descriptor used for volume control
-        class StreamDescriptor
-        {
-        public:
-            StreamDescriptor()
-            :   mIndexMin(0), mIndexMax(1), mIndexCur(1), mMuteCount(0), mCanBeMuted(true) {}
-
-            void dump(char* buffer, size_t size);
-
-            int mIndexMin;      // min volume index
-            int mIndexMax;      // max volume index
-            int mIndexCur;      // current volume index
-            int mMuteCount;     // mute request counter
-            bool mCanBeMuted;   // true is the stream can be muted
-        };
-
-        // return the strategy corresponding to a given stream type
-        static routing_strategy getStrategy(AudioSystem::stream_type stream);
-        // return the output handle of an output routed to the specified device, 0 if no output
-        // is routed to the device
-        float computeVolume(int stream, int index, uint32_t device);
-        // Mute or unmute the stream on the specified output
-        void setStreamMute(int stream, bool on, audio_io_handle_t output);
-        // handle special cases for sonification strategy while in call: mute streams or replace by
-        // a special tone in the device used for communication
-        void handleIncallSonification(int stream, bool starting);
-
-
-#ifdef AUDIO_POLICY_TEST
-        virtual     bool        threadLoop();
-                    void        exit();
-        int testOutputIndex(audio_io_handle_t output);
-#endif //AUDIO_POLICY_TEST
-
-
-        AudioPolicyClientInterface *mpClientInterface;  // audio policy client interface
-        audio_io_handle_t mHardwareOutput;              // hardware output handler
-
-        KeyedVector<audio_io_handle_t, AudioOutputDescriptor *> mOutputs;   // list ot output descritors
-        KeyedVector<audio_io_handle_t, AudioInputDescriptor *> mInputs;     // list of input descriptors
-        uint32_t mAvailableOutputDevices;                                   // bit field of all available output devices
-        uint32_t mAvailableInputDevices;                                    // bit field of all available input devices
-        int mPhoneState;                                                    // current phone state
-        uint32_t                 mRingerMode;                               // current ringer mode
-        AudioSystem::forced_config mForceUse[AudioSystem::NUM_FORCE_USE];   // current forced use configuration
-
-        StreamDescriptor mStreams[AudioSystem::NUM_STREAM_TYPES];           // stream descriptors for volume control
-
-#ifdef AUDIO_POLICY_TEST
-        Mutex   mLock;
-        Condition mWaitWorkCV;
-
-        int             mCurOutput;
-        bool            mDirectOutput;
-        audio_io_handle_t mTestOutputs[NUM_TEST_OUTPUTS];
-        int             mTestInput;
-        uint32_t        mTestDevice;
-        uint32_t        mTestSamplingRate;
-        uint32_t        mTestFormat;
-        uint32_t        mTestChannels;
-        uint32_t        mTestLatencyMs;
-#endif //AUDIO_POLICY_TEST
-
-};
-
-};
diff --git a/libs/audioflinger/AudioPolicyService.cpp b/libs/audioflinger/AudioPolicyService.cpp
index aa48019..bb3905c 100644
--- a/libs/audioflinger/AudioPolicyService.cpp
+++ b/libs/audioflinger/AudioPolicyService.cpp
@@ -30,9 +30,10 @@
 #include <utils/String16.h>
 #include <utils/threads.h>
 #include "AudioPolicyService.h"
-#include "AudioPolicyManagerGeneric.h"
+#include <hardware_legacy/AudioPolicyManagerBase.h>
 #include <cutils/properties.h>
 #include <dlfcn.h>
+#include <hardware_legacy/power.h>
 
 // ----------------------------------------------------------------------------
 // the sim build doesn't have gettid
@@ -43,8 +44,9 @@
 
 namespace android {
 
-static const char* kDeadlockedString = "AudioPolicyService may be deadlocked\n";
-static const char* kCmdDeadlockedString = "AudioPolicyService command thread may be deadlocked\n";
+
+static const char *kDeadlockedString = "AudioPolicyService may be deadlocked\n";
+static const char *kCmdDeadlockedString = "AudioPolicyService command thread may be deadlocked\n";
 
 static const int kDumpLockRetries = 50;
 static const int kDumpLockSleep = 20000;
@@ -67,18 +69,18 @@
     char value[PROPERTY_VALUE_MAX];
 
     // start tone playback thread
-    mTonePlaybackThread = new AudioCommandThread();
+    mTonePlaybackThread = new AudioCommandThread(String8(""));
     // start audio commands thread
-    mAudioCommandThread = new AudioCommandThread();
+    mAudioCommandThread = new AudioCommandThread(String8("ApmCommandThread"));
 
 #if (defined GENERIC_AUDIO) || (defined AUDIO_POLICY_TEST)
-    mpPolicyManager = new AudioPolicyManagerGeneric(this);
+    mpPolicyManager = new AudioPolicyManagerBase(this);
     LOGV("build for GENERIC_AUDIO - using generic audio policy");
 #else
     // if running in emulation - use the emulator driver
     if (property_get("ro.kernel.qemu", value, 0)) {
         LOGV("Running in emulation - using generic audio policy");
-        mpPolicyManager = new AudioPolicyManagerGeneric(this);
+        mpPolicyManager = new AudioPolicyManagerBase(this);
     }
     else {
         LOGV("Using hardware specific audio policy");
@@ -556,8 +558,8 @@
 
 // -----------  AudioPolicyService::AudioCommandThread implementation ----------
 
-AudioPolicyService::AudioCommandThread::AudioCommandThread()
-    :   Thread(false)
+AudioPolicyService::AudioCommandThread::AudioCommandThread(String8 name)
+    : Thread(false), mName(name)
 {
     mpToneGenerator = NULL;
 }
@@ -565,18 +567,20 @@
 
 AudioPolicyService::AudioCommandThread::~AudioCommandThread()
 {
+    if (mName != "" && !mAudioCommands.isEmpty()) {
+        release_wake_lock(mName.string());
+    }
     mAudioCommands.clear();
     if (mpToneGenerator != NULL) delete mpToneGenerator;
 }
 
 void AudioPolicyService::AudioCommandThread::onFirstRef()
 {
-    const size_t SIZE = 256;
-    char buffer[SIZE];
-
-    snprintf(buffer, SIZE, "AudioCommandThread");
-
-    run(buffer, ANDROID_PRIORITY_AUDIO);
+    if (mName != "") {
+        run(mName.string(), ANDROID_PRIORITY_AUDIO);
+    } else {
+        run("AudioCommandThread", ANDROID_PRIORITY_AUDIO);
+    }
 }
 
 bool AudioPolicyService::AudioCommandThread::threadLoop()
@@ -657,6 +661,10 @@
                 break;
             }
         }
+        // release delayed commands wake lock
+        if (mName != "" && mAudioCommands.isEmpty()) {
+            release_wake_lock(mName.string());
+        }
         LOGV("AudioCommandThread() going to sleep");
         mWaitWorkCV.waitRelative(mLock, waitTime);
         LOGV("AudioCommandThread() waking up");
@@ -815,6 +823,11 @@
 
     command->mTime = systemTime() + milliseconds(delayMs);
 
+    // acquire wake lock to make sure delayed commands are processed
+    if (mName != "" && mAudioCommands.isEmpty()) {
+        acquire_wake_lock(PARTIAL_WAKE_LOCK, mName.string());
+    }
+
     // check same pending commands with later time stamps and eliminate them
     for (i = mAudioCommands.size()-1; i >= 0; i--) {
         AudioCommand *command2 = mAudioCommands[i];
@@ -883,7 +896,7 @@
     removedCommands.clear();
 
     // insert command at the right place according to its time stamp
-    LOGV("inserting command: %d at index %ld, num commands %d", command->mCommand, i+1, mAudioCommands.size());
+    LOGV("inserting command: %d at index %d, num commands %d", command->mCommand, (int)i+1, mAudioCommands.size());
     mAudioCommands.insertAt(command, i + 1);
 }
 
diff --git a/libs/audioflinger/AudioPolicyService.h b/libs/audioflinger/AudioPolicyService.h
index b9234ec..a13d0bd 100644
--- a/libs/audioflinger/AudioPolicyService.h
+++ b/libs/audioflinger/AudioPolicyService.h
@@ -132,7 +132,7 @@
             SET_VOICE_VOLUME
         };
 
-        AudioCommandThread ();
+        AudioCommandThread (String8 name);
         virtual             ~AudioCommandThread();
 
                     status_t    dump(int fd);
@@ -195,7 +195,8 @@
         Condition mWaitWorkCV;
         Vector <AudioCommand *> mAudioCommands; // list of pending commands
         ToneGenerator *mpToneGenerator;     // the tone generator
-        AudioCommand mLastCommand;
+        AudioCommand mLastCommand;          // last processed command (used by dump)
+        String8 mName;                      // string used by wake lock fo delayed commands
     };
 
     // Internal dump utilities.
diff --git a/libs/binder/MemoryDealer.cpp b/libs/binder/MemoryDealer.cpp
index d5ffe7f..18669f7 100644
--- a/libs/binder/MemoryDealer.cpp
+++ b/libs/binder/MemoryDealer.cpp
@@ -17,12 +17,13 @@
 #define LOG_TAG "MemoryDealer"
 
 #include <binder/MemoryDealer.h>
+#include <binder/IPCThreadState.h>
+#include <binder/MemoryBase.h>
 
 #include <utils/Log.h>
-#include <binder/IPCThreadState.h>
 #include <utils/SortedVector.h>
 #include <utils/String8.h>
-#include <binder/MemoryBase.h>
+#include <utils/threads.h>
 
 #include <stdint.h>
 #include <stdio.h>
@@ -40,90 +41,203 @@
 namespace android {
 // ----------------------------------------------------------------------------
 
-HeapInterface::HeapInterface() { }
-HeapInterface::~HeapInterface() { }
+/*
+ * A simple templatized doubly linked-list implementation
+ */
 
-// ----------------------------------------------------------------------------
+template <typename NODE>
+class LinkedList
+{
+    NODE*  mFirst;
+    NODE*  mLast;
 
-AllocatorInterface::AllocatorInterface() { }
-AllocatorInterface::~AllocatorInterface() { }
-
-// ----------------------------------------------------------------------------
-
-class SimpleMemory : public MemoryBase {
 public:
-    SimpleMemory(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
-    virtual ~SimpleMemory();
+                LinkedList() : mFirst(0), mLast(0) { }
+    bool        isEmpty() const { return mFirst == 0; }
+    NODE const* head() const { return mFirst; }
+    NODE*       head() { return mFirst; }
+    NODE const* tail() const { return mLast; }
+    NODE*       tail() { return mLast; }
+
+    void insertAfter(NODE* node, NODE* newNode) {
+        newNode->prev = node;
+        newNode->next = node->next;
+        if (node->next == 0) mLast = newNode;
+        else                 node->next->prev = newNode;
+        node->next = newNode;
+    }
+
+    void insertBefore(NODE* node, NODE* newNode) {
+         newNode->prev = node->prev;
+         newNode->next = node;
+         if (node->prev == 0)   mFirst = newNode;
+         else                   node->prev->next = newNode;
+         node->prev = newNode;
+    }
+
+    void insertHead(NODE* newNode) {
+        if (mFirst == 0) {
+            mFirst = mLast = newNode;
+            newNode->prev = newNode->next = 0;
+        } else {
+            newNode->prev = 0;
+            newNode->next = mFirst;
+            mFirst->prev = newNode;
+            mFirst = newNode;
+        }
+    }
+
+    void insertTail(NODE* newNode) {
+        if (mLast == 0) {
+            insertHead(newNode);
+        } else {
+            newNode->prev = mLast;
+            newNode->next = 0;
+            mLast->next = newNode;
+            mLast = newNode;
+        }
+    }
+
+    NODE* remove(NODE* node) {
+        if (node->prev == 0)    mFirst = node->next;
+        else                    node->prev->next = node->next;
+        if (node->next == 0)    mLast = node->prev;
+        else                    node->next->prev = node->prev;
+        return node;
+    }
 };
 
+// ----------------------------------------------------------------------------
+
+class Allocation : public MemoryBase {
+public:
+    Allocation(const sp<MemoryDealer>& dealer,
+            const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
+    virtual ~Allocation();
+private:
+    sp<MemoryDealer> mDealer;
+};
 
 // ----------------------------------------------------------------------------
 
-MemoryDealer::Allocation::Allocation(
-        const sp<MemoryDealer>& dealer, ssize_t offset, size_t size,
-        const sp<IMemory>& memory)
-    : mDealer(dealer), mOffset(offset), mSize(size), mMemory(memory) 
+class SimpleBestFitAllocator
 {
+    enum {
+        PAGE_ALIGNED = 0x00000001
+    };
+public:
+    SimpleBestFitAllocator(size_t size);
+    ~SimpleBestFitAllocator();
+
+    size_t      allocate(size_t size, uint32_t flags = 0);
+    status_t    deallocate(size_t offset);
+    size_t      size() const;
+    void        dump(const char* what) const;
+    void        dump(String8& res, const char* what) const;
+
+private:
+
+    struct chunk_t {
+        chunk_t(size_t start, size_t size)
+        : start(start), size(size), free(1), prev(0), next(0) {
+        }
+        size_t              start;
+        size_t              size : 28;
+        int                 free : 4;
+        mutable chunk_t*    prev;
+        mutable chunk_t*    next;
+    };
+
+    ssize_t  alloc(size_t size, uint32_t flags);
+    chunk_t* dealloc(size_t start);
+    void     dump_l(const char* what) const;
+    void     dump_l(String8& res, const char* what) const;
+
+    static const int    kMemoryAlign;
+    mutable Mutex       mLock;
+    LinkedList<chunk_t> mList;
+    size_t              mHeapSize;
+};
+
+// ----------------------------------------------------------------------------
+
+Allocation::Allocation(
+        const sp<MemoryDealer>& dealer,
+        const sp<IMemoryHeap>& heap, ssize_t offset, size_t size)
+    : MemoryBase(heap, offset, size), mDealer(dealer)
+{
+#ifndef NDEBUG
+    void* const start_ptr = (void*)(intptr_t(heap->base()) + offset);
+    memset(start_ptr, 0xda, size);
+#endif
 }
 
-MemoryDealer::Allocation::~Allocation()
+Allocation::~Allocation()
 {
-    if (mSize) {
+    size_t freedOffset = getOffset();
+    size_t freedSize   = getSize();
+    if (freedSize) {
         /* NOTE: it's VERY important to not free allocations of size 0 because
          * they're special as they don't have any record in the allocator
          * and could alias some real allocation (their offset is zero). */
-        mDealer->deallocate(mOffset);
-    }
-}
+        mDealer->deallocate(freedOffset);
 
-sp<IMemoryHeap> MemoryDealer::Allocation::getMemory(
-    ssize_t* offset, size_t* size) const
-{
-    return mMemory->getMemory(offset, size);
+        // keep the size to unmap in excess
+        size_t pagesize = getpagesize();
+        size_t start = freedOffset;
+        size_t end = start + freedSize;
+        start &= ~(pagesize-1);
+        end = (end + pagesize-1) & ~(pagesize-1);
+
+        // give back to the kernel the pages we don't need
+        size_t free_start = freedOffset;
+        size_t free_end = free_start + freedSize;
+        if (start < free_start)
+            start = free_start;
+        if (end > free_end)
+            end = free_end;
+        start = (start + pagesize-1) & ~(pagesize-1);
+        end &= ~(pagesize-1);
+
+        if (start < end) {
+            void* const start_ptr = (void*)(intptr_t(getHeap()->base()) + start);
+            size_t size = end-start;
+
+#ifndef NDEBUG
+            memset(start_ptr, 0xdf, size);
+#endif
+
+            // MADV_REMOVE is not defined on Dapper based Goobuntu
+#ifdef MADV_REMOVE
+            if (size) {
+                int err = madvise(start_ptr, size, MADV_REMOVE);
+                LOGW_IF(err, "madvise(%p, %u, MADV_REMOVE) returned %s",
+                        start_ptr, size, err<0 ? strerror(errno) : "Ok");
+            }
+#endif
+        }
+    }
 }
 
 // ----------------------------------------------------------------------------
 
-MemoryDealer::MemoryDealer(size_t size, uint32_t flags, const char* name)
-    : mHeap(new SharedHeap(size, flags, name)),
+MemoryDealer::MemoryDealer(size_t size, const char* name)
+    : mHeap(new MemoryHeapBase(size, 0, name)),
     mAllocator(new SimpleBestFitAllocator(size))
 {    
 }
 
-MemoryDealer::MemoryDealer(const sp<HeapInterface>& heap)
-    : mHeap(heap),
-    mAllocator(new SimpleBestFitAllocator(heap->virtualSize()))
-{
-}
-
-MemoryDealer::MemoryDealer( const sp<HeapInterface>& heap,
-        const sp<AllocatorInterface>& allocator)
-    : mHeap(heap), mAllocator(allocator)
-{
-}
-
 MemoryDealer::~MemoryDealer()
 {
+    delete mAllocator;
 }
 
-sp<IMemory> MemoryDealer::allocate(size_t size, uint32_t flags)
+sp<IMemory> MemoryDealer::allocate(size_t size)
 {
     sp<IMemory> memory;
-    const ssize_t offset = allocator()->allocate(size, flags);
+    const ssize_t offset = allocator()->allocate(size);
     if (offset >= 0) {
-        sp<IMemory> new_memory = heap()->mapMemory(offset, size);
-        if (new_memory != 0) {
-            memory = new Allocation(this, offset, size, new_memory);
-        } else {
-            LOGE("couldn't map [%8lx, %u]", offset, size);
-            if (size) {
-                /* NOTE: it's VERY important to not free allocations of size 0
-                 * because they're special as they don't have any record in the 
-                 * allocator and could alias some real allocation 
-                 * (their offset is zero). */
-                allocator()->deallocate(offset);
-            }
-        }        
+        memory = new Allocation(this, heap(), offset, size);
     }
     return memory;
 }
@@ -133,16 +247,16 @@
     allocator()->deallocate(offset);
 }
 
-void MemoryDealer::dump(const char* what, uint32_t flags) const
+void MemoryDealer::dump(const char* what) const
 {
-    allocator()->dump(what, flags);
+    allocator()->dump(what);
 }
 
-const sp<HeapInterface>& MemoryDealer::heap() const {
+const sp<IMemoryHeap>& MemoryDealer::heap() const {
     return mHeap;
 }
 
-const sp<AllocatorInterface>& MemoryDealer::allocator() const {
+SimpleBestFitAllocator* MemoryDealer::allocator() const {
     return mAllocator;
 }
 
@@ -287,28 +401,28 @@
     return 0;
 }
 
-void SimpleBestFitAllocator::dump(const char* what, uint32_t flags) const
+void SimpleBestFitAllocator::dump(const char* what) const
 {
     Mutex::Autolock _l(mLock);
-    dump_l(what, flags);
+    dump_l(what);
 }
 
-void SimpleBestFitAllocator::dump_l(const char* what, uint32_t flags) const
+void SimpleBestFitAllocator::dump_l(const char* what) const
 {
     String8 result;
-    dump_l(result, what, flags);
+    dump_l(result, what);
     LOGD("%s", result.string());
 }
 
 void SimpleBestFitAllocator::dump(String8& result,
-        const char* what, uint32_t flags) const
+        const char* what) const
 {
     Mutex::Autolock _l(mLock);
-    dump_l(result, what, flags);
+    dump_l(result, what);
 }
 
 void SimpleBestFitAllocator::dump_l(String8& result,
-        const char* what, uint32_t flags) const
+        const char* what) const
 {
     size_t size = 0;
     int32_t i = 0;
@@ -341,81 +455,10 @@
         i++;
         cur = cur->next;
     }
-    snprintf(buffer, SIZE, "  size allocated: %u (%u KB)\n", int(size), int(size/1024));
+    snprintf(buffer, SIZE,
+            "  size allocated: %u (%u KB)\n", int(size), int(size/1024));
     result.append(buffer);
 }
-        
-// ----------------------------------------------------------------------------
 
-SharedHeap::SharedHeap() 
-    : HeapInterface(), MemoryHeapBase() 
-{ 
-}
-
-SharedHeap::SharedHeap(size_t size, uint32_t flags, char const * name)
-    : MemoryHeapBase(size, flags, name)
-{
-}
-
-SharedHeap::~SharedHeap()
-{
-}
-
-sp<IMemory> SharedHeap::mapMemory(size_t offset, size_t size)
-{
-    return new SimpleMemory(this, offset, size);
-}
- 
-
-SimpleMemory::SimpleMemory(const sp<IMemoryHeap>& heap,
-        ssize_t offset, size_t size)
-    : MemoryBase(heap, offset, size)
-{
-#ifndef NDEBUG
-    void* const start_ptr = (void*)(intptr_t(heap->base()) + offset);
-    memset(start_ptr, 0xda, size);
-#endif
-}
-
-SimpleMemory::~SimpleMemory()
-{
-    size_t freedOffset = getOffset();
-    size_t freedSize   = getSize();
-
-    // keep the size to unmap in excess
-    size_t pagesize = getpagesize();
-    size_t start = freedOffset;
-    size_t end = start + freedSize;
-    start &= ~(pagesize-1);
-    end = (end + pagesize-1) & ~(pagesize-1);
-
-    // give back to the kernel the pages we don't need
-    size_t free_start = freedOffset;
-    size_t free_end = free_start + freedSize;
-    if (start < free_start)
-        start = free_start;
-    if (end > free_end)
-        end = free_end;
-    start = (start + pagesize-1) & ~(pagesize-1);
-    end &= ~(pagesize-1);    
-
-    if (start < end) {
-        void* const start_ptr = (void*)(intptr_t(getHeap()->base()) + start);
-        size_t size = end-start;
-
-#ifndef NDEBUG
-        memset(start_ptr, 0xdf, size);
-#endif
-
-        // MADV_REMOVE is not defined on Dapper based Goobuntu 
-#ifdef MADV_REMOVE 
-        if (size) {
-            int err = madvise(start_ptr, size, MADV_REMOVE);
-            LOGW_IF(err, "madvise(%p, %u, MADV_REMOVE) returned %s",
-                    start_ptr, size, err<0 ? strerror(errno) : "Ok");
-        }
-#endif
-    }
-}
 
 }; // namespace android
diff --git a/libs/binder/MemoryHeapPmem.cpp b/libs/binder/MemoryHeapPmem.cpp
index c660947..16e92f9 100644
--- a/libs/binder/MemoryHeapPmem.cpp
+++ b/libs/binder/MemoryHeapPmem.cpp
@@ -127,7 +127,7 @@
 
 MemoryHeapPmem::MemoryHeapPmem(const sp<MemoryHeapBase>& pmemHeap,
         uint32_t flags)
-    : HeapInterface(), MemoryHeapBase()
+    : MemoryHeapBase()
 {
     char const * const device = pmemHeap->getDevice();
 #if HAVE_ANDROID_OS
diff --git a/libs/surfaceflinger/LayerBase.cpp b/libs/surfaceflinger/LayerBase.cpp
index 8003d22..4d7bef8 100644
--- a/libs/surfaceflinger/LayerBase.cpp
+++ b/libs/surfaceflinger/LayerBase.cpp
@@ -444,12 +444,21 @@
         glLoadIdentity();
 
         // the texture's source is rotated
-        if (texture.transform == HAL_TRANSFORM_ROT_90) {
-            // TODO: handle the other orientations
-            glTranslatef(0, 1, 0);
-            glRotatef(-90, 0, 0, 1);
+        switch (texture.transform) {
+            case HAL_TRANSFORM_ROT_90:
+                glTranslatef(0, 1, 0);
+                glRotatef(-90, 0, 0, 1);
+                break;
+            case HAL_TRANSFORM_ROT_180:
+                glTranslatef(1, 1, 0);
+                glRotatef(-180, 0, 0, 1);
+                break;
+            case HAL_TRANSFORM_ROT_270:
+                glTranslatef(1, 0, 0);
+                glRotatef(-270, 0, 0, 1);
+                break;
         }
-        
+
         if (texture.NPOTAdjust) {
             glScalef(texture.wScale, texture.hScale, 1.0f);
         }
@@ -809,7 +818,7 @@
 }
 
 sp<OverlayRef> LayerBaseClient::Surface::createOverlay(
-        uint32_t w, uint32_t h, int32_t format) 
+        uint32_t w, uint32_t h, int32_t format, int32_t orientation)
 {
     return NULL;
 };
diff --git a/libs/surfaceflinger/LayerBase.h b/libs/surfaceflinger/LayerBase.h
index ed07b3f..f73ea0c 100644
--- a/libs/surfaceflinger/LayerBase.h
+++ b/libs/surfaceflinger/LayerBase.h
@@ -351,7 +351,7 @@
         virtual void postBuffer(ssize_t offset);
         virtual void unregisterBuffers();
         virtual sp<OverlayRef> createOverlay(uint32_t w, uint32_t h,
-                int32_t format);
+                int32_t format, int32_t orientation);
 
     protected:
         friend class LayerBaseClient;
diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp
index 2ff6167..2735aa2 100644
--- a/libs/surfaceflinger/LayerBuffer.cpp
+++ b/libs/surfaceflinger/LayerBuffer.cpp
@@ -182,14 +182,15 @@
 /**
  * This creates an "overlay" source for this surface
  */
-sp<OverlayRef> LayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t f)
+sp<OverlayRef> LayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t f,
+        int32_t orientation)
 {
     sp<OverlayRef> result;
     Mutex::Autolock _l(mLock);
     if (mSource != 0)
         return result;
 
-    sp<OverlaySource> source = new OverlaySource(*this, &result, w, h, f);
+    sp<OverlaySource> source = new OverlaySource(*this, &result, w, h, f, orientation);
     if (result != 0) {
         mSource = source;
     }
@@ -248,11 +249,11 @@
 }
 
 sp<OverlayRef> LayerBuffer::SurfaceLayerBuffer::createOverlay(
-        uint32_t w, uint32_t h, int32_t format) {
+        uint32_t w, uint32_t h, int32_t format, int32_t orientation) {
     sp<OverlayRef> result;
     sp<LayerBuffer> owner(getOwner());
     if (owner != 0)
-        result = owner->createOverlay(w, h, format);
+        result = owner->createOverlay(w, h, format, orientation);
     return result;
 }
 
@@ -261,7 +262,7 @@
 // ============================================================================
 
 LayerBuffer::Buffer::Buffer(const ISurface::BufferHeap& buffers, ssize_t offset)
-    : mBufferHeap(buffers)
+    : mBufferHeap(buffers), mSupportsCopybit(false)
 {
     NativeBuffer& src(mNativeBuffer);
     src.crop.l = 0;
@@ -283,10 +284,8 @@
                 offset, buffers.heap->base(),
                 &src.img.handle);
 
-        LOGE_IF(err, "CREATE_HANDLE_FROM_BUFFER (heapId=%d, size=%d, "
-             "offset=%ld, base=%p) failed (%s)",
-                buffers.heap->heapID(), buffers.heap->getSize(),
-                offset, buffers.heap->base(), strerror(-err));
+        // we can fail here is the passed buffer is purely software
+        mSupportsCopybit = (err == NO_ERROR);
     }
  }
 
@@ -372,8 +371,23 @@
 
 LayerBuffer::BufferSource::~BufferSource()
 {    
+    class MessageDestroyTexture : public MessageBase {
+        SurfaceFlinger* flinger;
+        GLuint name;
+    public:
+        MessageDestroyTexture(
+                SurfaceFlinger* flinger, GLuint name)
+            : flinger(flinger), name(name) { }
+        virtual bool handler() {
+            glDeleteTextures(1, &name);
+            return true;
+        }
+    };
+
     if (mTexture.name != -1U) {
-        glDeleteTextures(1, &mTexture.name);
+        // GL textures can only be destroyed from the GL thread
+        mLayer.mFlinger->mEventQueue.postMessage(
+                new MessageDestroyTexture(mLayer.mFlinger.get(), mTexture.name) );
     }
     if (mTexture.image != EGL_NO_IMAGE_KHR) {
         EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay());
@@ -453,7 +467,7 @@
 #if defined(EGL_ANDROID_image_native_buffer)
     if (mLayer.mFlags & DisplayHardware::DIRECT_TEXTURE) {
         copybit_device_t* copybit = mLayer.mBlitEngine;
-        if (copybit) {
+        if (copybit && ourBuffer->supportsCopybit()) {
             // create our EGLImageKHR the first time
             err = initTempBuffer();
             if (err == NO_ERROR) {
@@ -465,7 +479,9 @@
                 copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
                 err = copybit->stretch(copybit, &dst.img, &src.img,
                         &dst.crop, &src.crop, &clip);
-
+                if (err != NO_ERROR) {
+                    clearTempBufferImage();
+                }
             }
         } else {
             err = INVALID_OPERATION;
@@ -504,16 +520,21 @@
         int t = w; w = h; h = t;
     }
 
+    // we're in the copybit case, so make sure we can handle this blit
+    // we don't have to keep the aspect ratio here
+    copybit_device_t* copybit = mLayer.mBlitEngine;
+    const int down = copybit->get(copybit, COPYBIT_MINIFICATION_LIMIT);
+    const int up = copybit->get(copybit, COPYBIT_MAGNIFICATION_LIMIT);
+    if (buffers.w > w*down)     w = buffers.w / down;
+    else if (w > buffers.w*up)  w = buffers.w*up;
+    if (buffers.h > h*down)     h = buffers.h / down;
+    else if (h > buffers.h*up)  h = buffers.h*up;
+
     if (mTexture.image != EGL_NO_IMAGE_KHR) {
         // we have an EGLImage, make sure the needed size didn't change
         if (w!=mTexture.width || h!= mTexture.height) {
             // delete the EGLImage and texture
-            EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay());
-            glDeleteTextures(1, &mTexture.name);
-            eglDestroyImageKHR(dpy, mTexture.image);
-            Texture defaultTexture;
-            mTexture = defaultTexture;
-            mTempGraphicBuffer.clear();
+            clearTempBufferImage();
         } else {
             // we're good, we have an EGLImageKHR and it's (still) the
             // right size
@@ -560,13 +581,29 @@
     return err;
 }
 
+void LayerBuffer::BufferSource::clearTempBufferImage() const
+{
+    // delete the image
+    EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay());
+    eglDestroyImageKHR(dpy, mTexture.image);
+
+    // and the associated texture (recreate a name)
+    glDeleteTextures(1, &mTexture.name);
+    Texture defaultTexture;
+    mTexture = defaultTexture;
+    mTexture.name = mLayer.createTexture();
+
+    // and the associated buffer
+    mTempGraphicBuffer.clear();
+}
+
 // ---------------------------------------------------------------------------
 
 LayerBuffer::OverlaySource::OverlaySource(LayerBuffer& layer,
         sp<OverlayRef>* overlayRef, 
-        uint32_t w, uint32_t h, int32_t format)
+        uint32_t w, uint32_t h, int32_t format, int32_t orientation)
     : Source(layer), mVisibilityChanged(false),
-    mOverlay(0), mOverlayHandle(0), mOverlayDevice(0)
+    mOverlay(0), mOverlayHandle(0), mOverlayDevice(0), mOrientation(orientation)
 {
     overlay_control_device_t* overlay_dev = mLayer.mFlinger->getOverlayEngine();
     if (overlay_dev == NULL) {
@@ -648,8 +685,12 @@
             if (mOverlay) {
                 overlay_control_device_t* overlay_dev = mOverlayDevice;
                 overlay_dev->setPosition(overlay_dev, mOverlay, x,y,w,h);
+                // we need to combine the layer orientation and the
+                // user-requested orientation.
+                Transform finalTransform = Transform(mOrientation) *
+                        Transform(mLayer.getOrientation());
                 overlay_dev->setParameter(overlay_dev, mOverlay,
-                        OVERLAY_TRANSFORM, mLayer.getOrientation());
+                        OVERLAY_TRANSFORM, finalTransform.getOrientation());
                 overlay_dev->commit(overlay_dev, mOverlay);
             }
         }
diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h
index 2ca63ac..e03f92c 100644
--- a/libs/surfaceflinger/LayerBuffer.h
+++ b/libs/surfaceflinger/LayerBuffer.h
@@ -74,7 +74,8 @@
     status_t registerBuffers(const ISurface::BufferHeap& buffers);
     void postBuffer(ssize_t offset);
     void unregisterBuffers();
-    sp<OverlayRef> createOverlay(uint32_t w, uint32_t h, int32_t format);
+    sp<OverlayRef> createOverlay(uint32_t w, uint32_t h, int32_t format,
+            int32_t orientation);
     
     sp<Source> getSource() const;
     sp<Source> clearSource();
@@ -99,6 +100,9 @@
     class Buffer : public LightRefBase<Buffer> {
     public:
         Buffer(const ISurface::BufferHeap& buffers, ssize_t offset);
+        inline bool supportsCopybit() const {
+            return mSupportsCopybit;
+        }
         inline status_t getStatus() const {
             return mBufferHeap.heap!=0 ? NO_ERROR : NO_INIT;
         }
@@ -113,6 +117,7 @@
     private:
         ISurface::BufferHeap    mBufferHeap;
         NativeBuffer            mNativeBuffer;
+        bool                    mSupportsCopybit;
     };
 
     class BufferSource : public Source {
@@ -131,6 +136,7 @@
         virtual void destroy() { }
     private:
         status_t initTempBuffer() const;
+        void clearTempBufferImage() const;
         mutable Mutex                   mBufferSourceLock;
         sp<Buffer>                      mBuffer;
         status_t                        mStatus;
@@ -145,7 +151,7 @@
     public:
         OverlaySource(LayerBuffer& layer,
                 sp<OverlayRef>* overlayRef, 
-                uint32_t w, uint32_t h, int32_t format);
+                uint32_t w, uint32_t h, int32_t format, int32_t orientation);
         virtual ~OverlaySource();
         virtual void onDraw(const Region& clip) const;
         virtual void onTransaction(uint32_t flags);
@@ -178,6 +184,7 @@
         int32_t mFormat;
         int32_t mWidthStride;
         int32_t mHeightStride;
+        int32_t mOrientation;
         mutable Mutex mOverlaySourceLock;
         bool mInitialized;
     };
@@ -195,7 +202,7 @@
         virtual void unregisterBuffers();
         
         virtual sp<OverlayRef> createOverlay(
-                uint32_t w, uint32_t h, int32_t format);
+                uint32_t w, uint32_t h, int32_t format, int32_t orientation);
     private:
         sp<LayerBuffer> getOwner() const {
             return static_cast<LayerBuffer*>(Surface::getOwner().get());
diff --git a/libs/surfaceflinger/Transform.cpp b/libs/surfaceflinger/Transform.cpp
index 1501536..ab6f7ba 100644
--- a/libs/surfaceflinger/Transform.cpp
+++ b/libs/surfaceflinger/Transform.cpp
@@ -42,6 +42,17 @@
 {
 }
 
+Transform::Transform(int32_t flags) {
+    mTransform.reset();
+    int sx = (flags & FLIP_H) ? -1 : 1;
+    int sy = (flags & FLIP_V) ? -1 : 1;
+    if (flags & ROT_90) {
+        this->set(0, -sy, sx, 0);
+    } else {
+        this->set(sx, 0, 0, sy);
+    }
+}
+
 Transform::~Transform() {
 }
 
diff --git a/libs/surfaceflinger/Transform.h b/libs/surfaceflinger/Transform.h
index 78f5c19..ddab404 100644
--- a/libs/surfaceflinger/Transform.h
+++ b/libs/surfaceflinger/Transform.h
@@ -38,6 +38,7 @@
 public:
                     Transform();
                     Transform(const Transform&  other);
+                    Transform(int32_t flags);
                     ~Transform();
 
             enum orientation_flags {
diff --git a/libs/ui/CameraParameters.cpp b/libs/ui/CameraParameters.cpp
index 8f1749d..c4958a0 100644
--- a/libs/ui/CameraParameters.cpp
+++ b/libs/ui/CameraParameters.cpp
@@ -36,7 +36,7 @@
 const char CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS[] = "picture-format-values";
 const char CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH[] = "jpeg-thumbnail-width";
 const char CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT[] = "jpeg-thumbnail-height";
-const char CameraParameters::KEY_SUPPORTED_THUMBNAIL_SIZES[] = "jpeg-thumbnail-size-values";
+const char CameraParameters::KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES[] = "jpeg-thumbnail-size-values";
 const char CameraParameters::KEY_JPEG_THUMBNAIL_QUALITY[] = "jpeg-thumbnail-quality";
 const char CameraParameters::KEY_JPEG_QUALITY[] = "jpeg-quality";
 const char CameraParameters::KEY_ROTATION[] = "rotation";
@@ -56,6 +56,9 @@
 const char CameraParameters::KEY_SUPPORTED_FLASH_MODES[] = "flash-mode-values";
 const char CameraParameters::KEY_FOCUS_MODE[] = "focus-mode";
 const char CameraParameters::KEY_SUPPORTED_FOCUS_MODES[] = "focus-mode-values";
+const char CameraParameters::KEY_FOCAL_LENGTH[] = "focal-length";
+const char CameraParameters::KEY_HORIZONTAL_VIEW_ANGLE[] = "horizontal-view-angle";
+const char CameraParameters::KEY_VERTICAL_VIEW_ANGLE[] = "vertical-view-angle";
 
 // Values for white balance settings.
 const char CameraParameters::WHITE_BALANCE_AUTO[] = "auto";
@@ -121,9 +124,6 @@
 const char CameraParameters::FOCUS_MODE_MACRO[] = "macro";
 const char CameraParameters::FOCUS_MODE_FIXED[] = "fixed";
 
-static const char* portrait = "portrait";
-static const char* landscape = "landscape";
-
 CameraParameters::CameraParameters()
                 : mMap()
 {
@@ -209,6 +209,13 @@
     set(key, str);
 }
 
+void CameraParameters::setFloat(const char *key, float value)
+{
+    char str[16];  // 14 should be enough. We overestimate to be safe.
+    snprintf(str, sizeof(str), "%g", value);
+    set(key, str);
+}
+
 const char *CameraParameters::get(const char *key) const
 {
     String8 v = mMap.valueFor(String8(key));
@@ -225,6 +232,13 @@
     return strtol(v, 0, 0);
 }
 
+float CameraParameters::getFloat(const char *key) const
+{
+    const char *v = get(key);
+    if (v == 0) return -1;
+    return strtof(v, 0);
+}
+
 static int parse_size(const char *str, int &width, int &height)
 {
     // Find the width.
@@ -282,23 +296,6 @@
     set(KEY_PREVIEW_FORMAT, format);
 }
 
-int CameraParameters::getOrientation() const
-{
-    const char* orientation = get("orientation");
-    if (orientation && !strcmp(orientation, portrait))
-        return CAMERA_ORIENTATION_PORTRAIT;
-    return CAMERA_ORIENTATION_LANDSCAPE;
-}
-
-void CameraParameters::setOrientation(int orientation)
-{
-    if (orientation == CAMERA_ORIENTATION_PORTRAIT) {
-        set("orientation", portrait);
-    } else {
-        set("orientation", landscape);
-    }
-}
-
 const char *CameraParameters::getPreviewFormat() const
 {
     return get(KEY_PREVIEW_FORMAT);
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index e39a357..c4d4f99 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -500,7 +500,7 @@
     LOGV("Opening device: %s", deviceName);
 
     AutoMutex _l(mLock);
-    
+
     fd = open(deviceName, O_RDWR);
     if(fd < 0) {
         LOGE("could not open %s, %s\n", deviceName, strerror(errno));
diff --git a/libs/ui/ISurface.cpp b/libs/ui/ISurface.cpp
index 4fb38ed..6f3d762 100644
--- a/libs/ui/ISurface.cpp
+++ b/libs/ui/ISurface.cpp
@@ -115,13 +115,14 @@
     }
 
     virtual sp<OverlayRef> createOverlay(
-             uint32_t w, uint32_t h, int32_t format)
+             uint32_t w, uint32_t h, int32_t format, int32_t orientation)
     {
         Parcel data, reply;
         data.writeInterfaceToken(ISurface::getInterfaceDescriptor());
         data.writeInt32(w);
         data.writeInt32(h);
         data.writeInt32(format);
+        data.writeInt32(orientation);
         remote()->transact(CREATE_OVERLAY, data, &reply);
         return OverlayRef::readFromParcel(reply);
     }
@@ -173,7 +174,8 @@
             int w = data.readInt32();
             int h = data.readInt32();
             int f = data.readInt32();
-            sp<OverlayRef> o = createOverlay(w, h, f);
+            int orientation = data.readInt32();
+            sp<OverlayRef> o = createOverlay(w, h, f, orientation);
             return OverlayRef::writeToParcel(reply, o);
         } break;
         default:
diff --git a/mms-common/Android.mk b/mms-common/Android.mk
new file mode 100644
index 0000000..de994c0
--- /dev/null
+++ b/mms-common/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Note: the source code is in java/, not src/, because this code is also part of
+# the framework library, and build/core/pathmap.mk expects a java/ subdirectory.
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := mms-common
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Include this library in the build server's output directory
+$(call dist-for-goals, droid, $(LOCAL_BUILT_MODULE):mms-common.jar)
+
+# Build the test package
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/mms-common/java/com/android/mmscommon/CharacterSets.java b/mms-common/java/com/android/mmscommon/CharacterSets.java
new file mode 100644
index 0000000..f19b078
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/CharacterSets.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+public class CharacterSets {
+    /**
+     * IANA assigned MIB enum numbers.
+     *
+     * From wap-230-wsp-20010705-a.pdf
+     * Any-charset = <Octet 128>
+     * Equivalent to the special RFC2616 charset value "*"
+     */
+    public static final int ANY_CHARSET = 0x00;
+    public static final int US_ASCII    = 0x03;
+    public static final int ISO_8859_1  = 0x04;
+    public static final int ISO_8859_2  = 0x05;
+    public static final int ISO_8859_3  = 0x06;
+    public static final int ISO_8859_4  = 0x07;
+    public static final int ISO_8859_5  = 0x08;
+    public static final int ISO_8859_6  = 0x09;
+    public static final int ISO_8859_7  = 0x0A;
+    public static final int ISO_8859_8  = 0x0B;
+    public static final int ISO_8859_9  = 0x0C;
+    public static final int SHIFT_JIS   = 0x11;
+    public static final int UTF_8       = 0x6A;
+    public static final int BIG5        = 0x07EA;
+    public static final int UCS2        = 0x03E8;
+    public static final int UTF_16      = 0x03F7;
+
+    /**
+     * If the encoding of given data is unsupported, use UTF_8 to decode it.
+     */
+    public static final int DEFAULT_CHARSET = UTF_8;
+
+    /**
+     * Array of MIB enum numbers.
+     */
+    private static final int[] MIBENUM_NUMBERS = {
+        ANY_CHARSET,
+        US_ASCII,
+        ISO_8859_1,
+        ISO_8859_2,
+        ISO_8859_3,
+        ISO_8859_4,
+        ISO_8859_5,
+        ISO_8859_6,
+        ISO_8859_7,
+        ISO_8859_8,
+        ISO_8859_9,
+        SHIFT_JIS,
+        UTF_8,
+        BIG5,
+        UCS2,
+        UTF_16,
+    };
+
+    /**
+     * The Well-known-charset Mime name.
+     */
+    public static final String MIMENAME_ANY_CHARSET = "*";
+    public static final String MIMENAME_US_ASCII    = "us-ascii";
+    public static final String MIMENAME_ISO_8859_1  = "iso-8859-1";
+    public static final String MIMENAME_ISO_8859_2  = "iso-8859-2";
+    public static final String MIMENAME_ISO_8859_3  = "iso-8859-3";
+    public static final String MIMENAME_ISO_8859_4  = "iso-8859-4";
+    public static final String MIMENAME_ISO_8859_5  = "iso-8859-5";
+    public static final String MIMENAME_ISO_8859_6  = "iso-8859-6";
+    public static final String MIMENAME_ISO_8859_7  = "iso-8859-7";
+    public static final String MIMENAME_ISO_8859_8  = "iso-8859-8";
+    public static final String MIMENAME_ISO_8859_9  = "iso-8859-9";
+    public static final String MIMENAME_SHIFT_JIS   = "shift_JIS";
+    public static final String MIMENAME_UTF_8       = "utf-8";
+    public static final String MIMENAME_BIG5        = "big5";
+    public static final String MIMENAME_UCS2        = "iso-10646-ucs-2";
+    public static final String MIMENAME_UTF_16      = "utf-16";
+
+    public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
+
+    /**
+     * Array of the names of character sets.
+     */
+    private static final String[] MIME_NAMES = {
+        MIMENAME_ANY_CHARSET,
+        MIMENAME_US_ASCII,
+        MIMENAME_ISO_8859_1,
+        MIMENAME_ISO_8859_2,
+        MIMENAME_ISO_8859_3,
+        MIMENAME_ISO_8859_4,
+        MIMENAME_ISO_8859_5,
+        MIMENAME_ISO_8859_6,
+        MIMENAME_ISO_8859_7,
+        MIMENAME_ISO_8859_8,
+        MIMENAME_ISO_8859_9,
+        MIMENAME_SHIFT_JIS,
+        MIMENAME_UTF_8,
+        MIMENAME_BIG5,
+        MIMENAME_UCS2,
+        MIMENAME_UTF_16,
+    };
+
+    private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
+    private static final HashMap<String, Integer> NAME_TO_MIBENUM_MAP;
+
+    static {
+        // Create the HashMaps.
+        MIBENUM_TO_NAME_MAP = new HashMap<Integer, String>();
+        NAME_TO_MIBENUM_MAP = new HashMap<String, Integer>();
+        assert(MIBENUM_NUMBERS.length == MIME_NAMES.length);
+        int count = MIBENUM_NUMBERS.length - 1;
+        for(int i = 0; i <= count; i++) {
+            MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
+            NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
+        }
+    }
+
+    private CharacterSets() {} // Non-instantiatable
+
+    /**
+     * Map an MIBEnum number to the name of the charset which this number
+     * is assigned to by IANA.
+     *
+     * @param mibEnumValue An IANA assigned MIBEnum number.
+     * @return The name string of the charset.
+     * @throws UnsupportedEncodingException
+     */
+    public static String getMimeName(int mibEnumValue)
+            throws UnsupportedEncodingException {
+        String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
+        if (name == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return name;
+    }
+
+    /**
+     * Map a well-known charset name to its assigned MIBEnum number.
+     *
+     * @param mimeName The charset name.
+     * @return The MIBEnum number assigned by IANA for this charset.
+     * @throws UnsupportedEncodingException
+     */
+    public static int getMibEnumValue(String mimeName)
+            throws UnsupportedEncodingException {
+        if(null == mimeName) {
+            return -1;
+        }
+
+        Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
+        if (mibEnumValue == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return mibEnumValue;
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/ContentType.java b/mms-common/java/com/android/mmscommon/ContentType.java
new file mode 100644
index 0000000..ca449fe
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/ContentType.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon;
+
+import java.util.ArrayList;
+
+public class ContentType {
+    public static final String MMS_MESSAGE       = "application/vnd.wap.mms-message";
+    // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+    // Notification.ind, Delivery.ind).
+    public static final String MMS_GENERIC       = "application/vnd.wap.mms-generic";
+    public static final String MULTIPART_MIXED   = "application/vnd.wap.multipart.mixed";
+    public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+    public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative";
+
+    public static final String TEXT_PLAIN        = "text/plain";
+    public static final String TEXT_HTML         = "text/html";
+    public static final String TEXT_VCALENDAR    = "text/x-vCalendar";
+    public static final String TEXT_VCARD        = "text/x-vCard";
+
+    public static final String IMAGE_UNSPECIFIED = "image/*";
+    public static final String IMAGE_JPEG        = "image/jpeg";
+    public static final String IMAGE_JPG         = "image/jpg";
+    public static final String IMAGE_GIF         = "image/gif";
+    public static final String IMAGE_WBMP        = "image/vnd.wap.wbmp";
+    public static final String IMAGE_PNG         = "image/png";
+
+    public static final String AUDIO_UNSPECIFIED = "audio/*";
+    public static final String AUDIO_AAC         = "audio/aac";
+    public static final String AUDIO_AMR         = "audio/amr";
+    public static final String AUDIO_IMELODY     = "audio/imelody";
+    public static final String AUDIO_MID         = "audio/mid";
+    public static final String AUDIO_MIDI        = "audio/midi";
+    public static final String AUDIO_MP3         = "audio/mp3";
+    public static final String AUDIO_MPEG3       = "audio/mpeg3";
+    public static final String AUDIO_MPEG        = "audio/mpeg";
+    public static final String AUDIO_MPG         = "audio/mpg";
+    public static final String AUDIO_MP4         = "audio/mp4";
+    public static final String AUDIO_X_MID       = "audio/x-mid";
+    public static final String AUDIO_X_MIDI      = "audio/x-midi";
+    public static final String AUDIO_X_MP3       = "audio/x-mp3";
+    public static final String AUDIO_X_MPEG3     = "audio/x-mpeg3";
+    public static final String AUDIO_X_MPEG      = "audio/x-mpeg";
+    public static final String AUDIO_X_MPG       = "audio/x-mpg";
+    public static final String AUDIO_3GPP        = "audio/3gpp";
+    public static final String AUDIO_OGG         = "application/ogg";
+
+    public static final String VIDEO_UNSPECIFIED = "video/*";
+    public static final String VIDEO_3GPP        = "video/3gpp";
+    public static final String VIDEO_3G2         = "video/3gpp2";
+    public static final String VIDEO_H263        = "video/h263";
+    public static final String VIDEO_MP4         = "video/mp4";
+
+    public static final String APP_SMIL          = "application/smil";
+    public static final String APP_WAP_XHTML     = "application/vnd.wap.xhtml+xml";
+    public static final String APP_XHTML         = "application/xhtml+xml";
+
+    public static final String APP_DRM_CONTENT   = "application/vnd.oma.drm.content";
+    public static final String APP_DRM_MESSAGE   = "application/vnd.oma.drm.message";
+
+    private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
+
+    static {
+        sSupportedContentTypes.add(TEXT_PLAIN);
+        sSupportedContentTypes.add(TEXT_HTML);
+        sSupportedContentTypes.add(TEXT_VCALENDAR);
+        sSupportedContentTypes.add(TEXT_VCARD);
+
+        sSupportedContentTypes.add(IMAGE_JPEG);
+        sSupportedContentTypes.add(IMAGE_GIF);
+        sSupportedContentTypes.add(IMAGE_WBMP);
+        sSupportedContentTypes.add(IMAGE_PNG);
+        sSupportedContentTypes.add(IMAGE_JPG);
+        //supportedContentTypes.add(IMAGE_SVG); not yet supported.
+
+        sSupportedContentTypes.add(AUDIO_AAC);
+        sSupportedContentTypes.add(AUDIO_AMR);
+        sSupportedContentTypes.add(AUDIO_IMELODY);
+        sSupportedContentTypes.add(AUDIO_MID);
+        sSupportedContentTypes.add(AUDIO_MIDI);
+        sSupportedContentTypes.add(AUDIO_MP3);
+        sSupportedContentTypes.add(AUDIO_MPEG3);
+        sSupportedContentTypes.add(AUDIO_MPEG);
+        sSupportedContentTypes.add(AUDIO_MPG);
+        sSupportedContentTypes.add(AUDIO_X_MID);
+        sSupportedContentTypes.add(AUDIO_X_MIDI);
+        sSupportedContentTypes.add(AUDIO_X_MP3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG);
+        sSupportedContentTypes.add(AUDIO_X_MPG);
+        sSupportedContentTypes.add(AUDIO_3GPP);
+        sSupportedContentTypes.add(AUDIO_OGG);
+
+        sSupportedContentTypes.add(VIDEO_3GPP);
+        sSupportedContentTypes.add(VIDEO_3G2);
+        sSupportedContentTypes.add(VIDEO_H263);
+        sSupportedContentTypes.add(VIDEO_MP4);
+
+        sSupportedContentTypes.add(APP_SMIL);
+        sSupportedContentTypes.add(APP_WAP_XHTML);
+        sSupportedContentTypes.add(APP_XHTML);
+
+        sSupportedContentTypes.add(APP_DRM_CONTENT);
+        sSupportedContentTypes.add(APP_DRM_MESSAGE);
+
+        // add supported image types
+        sSupportedImageTypes.add(IMAGE_JPEG);
+        sSupportedImageTypes.add(IMAGE_GIF);
+        sSupportedImageTypes.add(IMAGE_WBMP);
+        sSupportedImageTypes.add(IMAGE_PNG);
+        sSupportedImageTypes.add(IMAGE_JPG);
+
+        // add supported audio types
+        sSupportedAudioTypes.add(AUDIO_AAC);
+        sSupportedAudioTypes.add(AUDIO_AMR);
+        sSupportedAudioTypes.add(AUDIO_IMELODY);
+        sSupportedAudioTypes.add(AUDIO_MID);
+        sSupportedAudioTypes.add(AUDIO_MIDI);
+        sSupportedAudioTypes.add(AUDIO_MP3);
+        sSupportedAudioTypes.add(AUDIO_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_MPEG);
+        sSupportedAudioTypes.add(AUDIO_MPG);
+        sSupportedAudioTypes.add(AUDIO_MP4);
+        sSupportedAudioTypes.add(AUDIO_X_MID);
+        sSupportedAudioTypes.add(AUDIO_X_MIDI);
+        sSupportedAudioTypes.add(AUDIO_X_MP3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG);
+        sSupportedAudioTypes.add(AUDIO_X_MPG);
+        sSupportedAudioTypes.add(AUDIO_3GPP);
+        sSupportedAudioTypes.add(AUDIO_OGG);
+
+        // add supported video types
+        sSupportedVideoTypes.add(VIDEO_3GPP);
+        sSupportedVideoTypes.add(VIDEO_3G2);
+        sSupportedVideoTypes.add(VIDEO_H263);
+        sSupportedVideoTypes.add(VIDEO_MP4);
+    }
+
+    // This class should never be instantiated.
+    private ContentType() {
+    }
+
+    public static boolean isSupportedType(String contentType) {
+        return (null != contentType) && sSupportedContentTypes.contains(contentType);
+    }
+
+    public static boolean isSupportedImageType(String contentType) {
+        return isImageType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isSupportedAudioType(String contentType) {
+        return isAudioType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isSupportedVideoType(String contentType) {
+        return isVideoType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isTextType(String contentType) {
+        return (null != contentType) && contentType.startsWith("text/");
+    }
+
+    public static boolean isImageType(String contentType) {
+        return (null != contentType) && contentType.startsWith("image/");
+    }
+
+    public static boolean isAudioType(String contentType) {
+        return (null != contentType) && contentType.startsWith("audio/");
+    }
+
+    public static boolean isVideoType(String contentType) {
+        return (null != contentType) && contentType.startsWith("video/");
+    }
+
+    public static boolean isDrmType(String contentType) {
+        return (null != contentType)
+                && (contentType.equals(APP_DRM_CONTENT)
+                        || contentType.equals(APP_DRM_MESSAGE));
+    }
+
+    public static boolean isUnspecified(String contentType) {
+        return (null != contentType) && contentType.endsWith("*");
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getImageTypes() {
+        return (ArrayList<String>) sSupportedImageTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getAudioTypes() {
+        return (ArrayList<String>) sSupportedAudioTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getVideoTypes() {
+        return (ArrayList<String>) sSupportedVideoTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getSupportedTypes() {
+        return (ArrayList<String>) sSupportedContentTypes.clone();
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/EncodedStringValue.java b/mms-common/java/com/android/mmscommon/EncodedStringValue.java
new file mode 100644
index 0000000..0a4424e
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/EncodedStringValue.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+public class EncodedStringValue implements Cloneable {
+    private static final String TAG = "EncodedStringValue";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    /**
+     * The Char-set value.
+     */
+    private int mCharacterSet;
+
+    /**
+     * The Text-string value.
+     */
+    private byte[] mData;
+
+    /**
+     * Constructor.
+     *
+     * @param charset the Char-set value
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    public EncodedStringValue(int charset, byte[] data) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        if(null == data) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mCharacterSet = charset;
+        mData = new byte[data.length];
+        System.arraycopy(data, 0, mData, 0, data.length);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    public EncodedStringValue(byte[] data) {
+        this(CharacterSets.DEFAULT_CHARSET, data);
+    }
+
+    public EncodedStringValue(String data) {
+        try {
+            mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+            mCharacterSet = CharacterSets.DEFAULT_CHARSET;
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "Default encoding must be supported.", e);
+        }
+    }
+
+    /**
+     * Get Char-set value.
+     *
+     * @return the value
+     */
+    public int getCharacterSet() {
+        return mCharacterSet;
+    }
+
+    /**
+     * Set Char-set value.
+     *
+     * @param charset the Char-set value
+     */
+    public void setCharacterSet(int charset) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        mCharacterSet = charset;
+    }
+
+    /**
+     * Get Text-string value.
+     *
+     * @return the value
+     */
+    public byte[] getTextString() {
+        byte[] byteArray = new byte[mData.length];
+
+        System.arraycopy(mData, 0, byteArray, 0, mData.length);
+        return byteArray;
+    }
+
+    /**
+     * Set Text-string value.
+     *
+     * @param textString the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    public void setTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mData = new byte[textString.length];
+        System.arraycopy(textString, 0, mData, 0, textString.length);
+    }
+
+    /**
+     * Convert this object to a {@link java.lang.String}. If the encoding of
+     * the EncodedStringValue is null or unsupported, it will be
+     * treated as iso-8859-1 encoding.
+     *
+     * @return The decoded String.
+     */
+    public String getString()  {
+        if (CharacterSets.ANY_CHARSET == mCharacterSet) {
+            return new String(mData); // system default encoding.
+        } else {
+            try {
+                String name = CharacterSets.getMimeName(mCharacterSet);
+                return new String(mData, name);
+            } catch (UnsupportedEncodingException e) {
+            	if (LOCAL_LOGV) {
+            		Log.v(TAG, e.getMessage(), e);
+            	}
+            	try {
+                    return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
+                } catch (UnsupportedEncodingException _) {
+                    return new String(mData); // system default encoding.
+                }
+            }
+        }
+    }
+
+    /**
+     * Append to Text-string.
+     *
+     * @param textString the textString to append
+     * @throws NullPointerException if the text String is null
+     *                      or an IOException occured.
+     */
+    public void appendTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("Text-string is null.");
+        }
+
+        if(null == mData) {
+            mData = new byte[textString.length];
+            System.arraycopy(textString, 0, mData, 0, textString.length);
+        } else {
+            ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
+            try {
+                newTextString.write(mData);
+                newTextString.write(textString);
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new NullPointerException(
+                        "appendTextString: failed when write a new Text-string");
+            }
+
+            mData = newTextString.toByteArray();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        super.clone();
+        int len = mData.length;
+        byte[] dstBytes = new byte[len];
+        System.arraycopy(mData, 0, dstBytes, 0, len);
+
+        try {
+            return new EncodedStringValue(mCharacterSet, dstBytes);
+        } catch (Exception e) {
+            Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
+            e.printStackTrace();
+            throw new CloneNotSupportedException(e.getMessage());
+        }
+    }
+
+    /**
+     * Split this encoded string around matches of the given pattern.
+     *
+     * @param pattern the delimiting pattern
+     * @return the array of encoded strings computed by splitting this encoded
+     *         string around matches of the given pattern
+     */
+    public EncodedStringValue[] split(String pattern) {
+        String[] temp = getString().split(pattern);
+        EncodedStringValue[] ret = new EncodedStringValue[temp.length];
+        for (int i = 0; i < ret.length; ++i) {
+            try {
+                ret[i] = new EncodedStringValue(mCharacterSet,
+                        temp[i].getBytes());
+            } catch (NullPointerException _) {
+                // Can't arrive here
+                return null;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Extract an EncodedStringValue[] from a given String.
+     */
+    public static EncodedStringValue[] extract(String src) {
+        String[] values = src.split(";");
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < values.length; i++) {
+            if (values[i].length() > 0) {
+                list.add(new EncodedStringValue(values[i]));
+            }
+        }
+
+        int len = list.size();
+        if (len > 0) {
+            return list.toArray(new EncodedStringValue[len]);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Concatenate an EncodedStringValue[] into a single String.
+     */
+    public static String concat(EncodedStringValue[] addr) {
+        StringBuilder sb = new StringBuilder();
+        int maxIndex = addr.length - 1;
+        for (int i = 0; i <= maxIndex; i++) {
+            sb.append(addr[i].getString());
+            if (i < maxIndex) {
+                sb.append(";");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public static EncodedStringValue copy(EncodedStringValue value) {
+        if (value == null) {
+            return null;
+        }
+
+        return new EncodedStringValue(value.mCharacterSet, value.mData);
+    }
+
+    public static EncodedStringValue[] encodeStrings(String[] array) {
+        int count = array.length;
+        if (count > 0) {
+            EncodedStringValue[] encodedArray = new EncodedStringValue[count];
+            for (int i = 0; i < count; i++) {
+                encodedArray[i] = new EncodedStringValue(array[i]);
+            }
+            return encodedArray;
+        }
+        return null;
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/InvalidHeaderValueException.java b/mms-common/java/com/android/mmscommon/InvalidHeaderValueException.java
new file mode 100644
index 0000000..34d5871
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/InvalidHeaderValueException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon;
+
+/**
+ * Thrown when an invalid header value was set.
+ */
+public class InvalidHeaderValueException extends MmsException {
+    private static final long serialVersionUID = -2053384496042052262L;
+
+    /**
+     * Constructs an InvalidHeaderValueException with no detailed message.
+     */
+    public InvalidHeaderValueException() {
+        super();
+    }
+
+    /**
+     * Constructs an InvalidHeaderValueException with the specified detailed message.
+     *
+     * @param message the detailed message.
+     */
+    public InvalidHeaderValueException(String message) {
+        super(message);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/MmsException.java b/mms-common/java/com/android/mmscommon/MmsException.java
new file mode 100644
index 0000000..296a2c3
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/MmsException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon;
+
+/**
+ * A generic exception that is thrown by the Mms client.
+ */
+public class MmsException extends Exception {
+    private static final long serialVersionUID = -7323249827281485390L;
+
+    /**
+     * Creates a new MmsException.
+     */
+    public MmsException() {
+        super();
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message.
+     *
+     * @param message the detail message.
+     */
+    public MmsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new MmsException with the specified cause.
+     *
+     * @param cause the cause.
+     */
+    public MmsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message and cause.
+     *
+     * @param message the detail message.
+     * @param cause the cause.
+     */
+    public MmsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/PduHeaders.java b/mms-common/java/com/android/mmscommon/PduHeaders.java
new file mode 100644
index 0000000..d8f1211
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/PduHeaders.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PduHeaders {
+    /**
+     * All pdu header fields.
+     */
+    public static final int BCC                             = 0x81;
+    public static final int CC                              = 0x82;
+    public static final int CONTENT_LOCATION                = 0x83;
+    public static final int CONTENT_TYPE                    = 0x84;
+    public static final int DATE                            = 0x85;
+    public static final int DELIVERY_REPORT                 = 0x86;
+    public static final int DELIVERY_TIME                   = 0x87;
+    public static final int EXPIRY                          = 0x88;
+    public static final int FROM                            = 0x89;
+    public static final int MESSAGE_CLASS                   = 0x8A;
+    public static final int MESSAGE_ID                      = 0x8B;
+    public static final int MESSAGE_TYPE                    = 0x8C;
+    public static final int MMS_VERSION                     = 0x8D;
+    public static final int MESSAGE_SIZE                    = 0x8E;
+    public static final int PRIORITY                        = 0x8F;
+
+    public static final int READ_REPLY                      = 0x90;
+    public static final int READ_REPORT                     = 0x90;
+    public static final int REPORT_ALLOWED                  = 0x91;
+    public static final int RESPONSE_STATUS                 = 0x92;
+    public static final int RESPONSE_TEXT                   = 0x93;
+    public static final int SENDER_VISIBILITY               = 0x94;
+    public static final int STATUS                          = 0x95;
+    public static final int SUBJECT                         = 0x96;
+    public static final int TO                              = 0x97;
+    public static final int TRANSACTION_ID                  = 0x98;
+    public static final int RETRIEVE_STATUS                 = 0x99;
+    public static final int RETRIEVE_TEXT                   = 0x9A;
+    public static final int READ_STATUS                     = 0x9B;
+    public static final int REPLY_CHARGING                  = 0x9C;
+    public static final int REPLY_CHARGING_DEADLINE         = 0x9D;
+    public static final int REPLY_CHARGING_ID               = 0x9E;
+    public static final int REPLY_CHARGING_SIZE             = 0x9F;
+
+    public static final int PREVIOUSLY_SENT_BY              = 0xA0;
+    public static final int PREVIOUSLY_SENT_DATE            = 0xA1;
+    public static final int STORE                           = 0xA2;
+    public static final int MM_STATE                        = 0xA3;
+    public static final int MM_FLAGS                        = 0xA4;
+    public static final int STORE_STATUS                    = 0xA5;
+    public static final int STORE_STATUS_TEXT               = 0xA6;
+    public static final int STORED                          = 0xA7;
+    public static final int ATTRIBUTES                      = 0xA8;
+    public static final int TOTALS                          = 0xA9;
+    public static final int MBOX_TOTALS                     = 0xAA;
+    public static final int QUOTAS                          = 0xAB;
+    public static final int MBOX_QUOTAS                     = 0xAC;
+    public static final int MESSAGE_COUNT                   = 0xAD;
+    public static final int CONTENT                         = 0xAE;
+    public static final int START                           = 0xAF;
+
+    public static final int ADDITIONAL_HEADERS              = 0xB0;
+    public static final int DISTRIBUTION_INDICATOR          = 0xB1;
+    public static final int ELEMENT_DESCRIPTOR              = 0xB2;
+    public static final int LIMIT                           = 0xB3;
+    public static final int RECOMMENDED_RETRIEVAL_MODE      = 0xB4;
+    public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
+    public static final int STATUS_TEXT                     = 0xB6;
+    public static final int APPLIC_ID                       = 0xB7;
+    public static final int REPLY_APPLIC_ID                 = 0xB8;
+    public static final int AUX_APPLIC_ID                   = 0xB9;
+    public static final int CONTENT_CLASS                   = 0xBA;
+    public static final int DRM_CONTENT                     = 0xBB;
+    public static final int ADAPTATION_ALLOWED              = 0xBC;
+    public static final int REPLACE_ID                      = 0xBD;
+    public static final int CANCEL_ID                       = 0xBE;
+    public static final int CANCEL_STATUS                   = 0xBF;
+
+    /**
+     * X-Mms-Message-Type field types.
+     */
+    public static final int MESSAGE_TYPE_SEND_REQ           = 0x80;
+    public static final int MESSAGE_TYPE_SEND_CONF          = 0x81;
+    public static final int MESSAGE_TYPE_NOTIFICATION_IND   = 0x82;
+    public static final int MESSAGE_TYPE_NOTIFYRESP_IND     = 0x83;
+    public static final int MESSAGE_TYPE_RETRIEVE_CONF      = 0x84;
+    public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND    = 0x85;
+    public static final int MESSAGE_TYPE_DELIVERY_IND       = 0x86;
+    public static final int MESSAGE_TYPE_READ_REC_IND       = 0x87;
+    public static final int MESSAGE_TYPE_READ_ORIG_IND      = 0x88;
+    public static final int MESSAGE_TYPE_FORWARD_REQ        = 0x89;
+    public static final int MESSAGE_TYPE_FORWARD_CONF       = 0x8A;
+    public static final int MESSAGE_TYPE_MBOX_STORE_REQ     = 0x8B;
+    public static final int MESSAGE_TYPE_MBOX_STORE_CONF    = 0x8C;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_REQ      = 0x8D;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_CONF     = 0x8E;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ    = 0x8F;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF   = 0x90;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_REQ    = 0x91;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_CONF   = 0x92;
+    public static final int MESSAGE_TYPE_MBOX_DESCR         = 0x93;
+    public static final int MESSAGE_TYPE_DELETE_REQ         = 0x94;
+    public static final int MESSAGE_TYPE_DELETE_CONF        = 0x95;
+    public static final int MESSAGE_TYPE_CANCEL_REQ         = 0x96;
+    public static final int MESSAGE_TYPE_CANCEL_CONF        = 0x97;
+
+    /**
+     *  X-Mms-Delivery-Report |
+     *  X-Mms-Read-Report |
+     *  X-Mms-Report-Allowed |
+     *  X-Mms-Sender-Visibility |
+     *  X-Mms-Store |
+     *  X-Mms-Stored |
+     *  X-Mms-Totals |
+     *  X-Mms-Quotas |
+     *  X-Mms-Distribution-Indicator |
+     *  X-Mms-DRM-Content |
+     *  X-Mms-Adaptation-Allowed |
+     *  field types.
+     */
+    public static final int VALUE_YES                       = 0x80;
+    public static final int VALUE_NO                        = 0x81;
+
+    /**
+     *  Delivery-Time |
+     *  Expiry and Reply-Charging-Deadline |
+     *  field type components.
+     */
+    public static final int VALUE_ABSOLUTE_TOKEN            = 0x80;
+    public static final int VALUE_RELATIVE_TOKEN            = 0x81;
+
+    /**
+     * X-Mms-MMS-Version field types.
+     */
+    public static final int MMS_VERSION_1_3                 = ((1 << 4) | 3);
+    public static final int MMS_VERSION_1_2                 = ((1 << 4) | 2);
+    public static final int MMS_VERSION_1_1                 = ((1 << 4) | 1);
+    public static final int MMS_VERSION_1_0                 = ((1 << 4) | 0);
+
+    // Current version is 1.2.
+    public static final int CURRENT_MMS_VERSION             = MMS_VERSION_1_2;
+
+    /**
+     *  From field type components.
+     */
+    public static final int FROM_ADDRESS_PRESENT_TOKEN      = 0x80;
+    public static final int FROM_INSERT_ADDRESS_TOKEN       = 0x81;
+
+    public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
+    public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
+
+    /**
+     *  X-Mms-Status Field.
+     */
+    public static final int STATUS_EXPIRED                  = 0x80;
+    public static final int STATUS_RETRIEVED                = 0x81;
+    public static final int STATUS_REJECTED                 = 0x82;
+    public static final int STATUS_DEFERRED                 = 0x83;
+    public static final int STATUS_UNRECOGNIZED             = 0x84;
+    public static final int STATUS_INDETERMINATE            = 0x85;
+    public static final int STATUS_FORWARDED                = 0x86;
+    public static final int STATUS_UNREACHABLE              = 0x87;
+
+    /**
+     *  MM-Flags field type components.
+     */
+    public static final int MM_FLAGS_ADD_TOKEN              = 0x80;
+    public static final int MM_FLAGS_REMOVE_TOKEN           = 0x81;
+    public static final int MM_FLAGS_FILTER_TOKEN           = 0x82;
+
+    /**
+     *  X-Mms-Message-Class field types.
+     */
+    public static final int MESSAGE_CLASS_PERSONAL          = 0x80;
+    public static final int MESSAGE_CLASS_ADVERTISEMENT     = 0x81;
+    public static final int MESSAGE_CLASS_INFORMATIONAL     = 0x82;
+    public static final int MESSAGE_CLASS_AUTO              = 0x83;
+
+    public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
+    public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
+    public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
+    public static final String MESSAGE_CLASS_AUTO_STR = "auto";
+
+    /**
+     *  X-Mms-Priority field types.
+     */
+    public static final int PRIORITY_LOW                    = 0x80;
+    public static final int PRIORITY_NORMAL                 = 0x81;
+    public static final int PRIORITY_HIGH                   = 0x82;
+
+    /**
+     *  X-Mms-Response-Status field types.
+     */
+    public static final int RESPONSE_STATUS_OK                   = 0x80;
+    public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED    = 0x81;
+    public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT     = 0x83;
+    public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND    = 0x85;
+    public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM      = 0x86;
+    public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
+    public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE  = 0x88;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE    = 0xC0;
+
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND         = 0xC2;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM           = 0xC3;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS           = 0xC4;
+
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE                             = 0xE0;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED                      = 0xE1;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT              = 0xE2;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED          = 0xE3;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND                   = 0xE4;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED                = 0xE5;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET  = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED    = 0xE8;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED        = 0xE9;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED        = 0xEA;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID                     = 0xEB;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_END                                 = 0xFF;
+
+    /**
+     *  X-Mms-Retrieve-Status field types.
+     */
+    public static final int RETRIEVE_STATUS_OK                                  = 0x80;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE             = 0xC0;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND   = 0xC1;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM     = 0xC2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE             = 0xE0;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED      = 0xE1;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND   = 0xE2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
+    public static final int RETRIEVE_STATUS_ERROR_END                           = 0xFF;
+
+    /**
+     *  X-Mms-Sender-Visibility field types.
+     */
+    public static final int SENDER_VISIBILITY_HIDE          = 0x80;
+    public static final int SENDER_VISIBILITY_SHOW          = 0x81;
+
+    /**
+     *  X-Mms-Read-Status field types.
+     */
+    public static final int READ_STATUS_READ                        = 0x80;
+    public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
+
+    /**
+     *  X-Mms-Cancel-Status field types.
+     */
+    public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
+    public static final int CANCEL_STATUS_REQUEST_CORRUPTED             = 0x81;
+
+    /**
+     *  X-Mms-Reply-Charging field types.
+     */
+    public static final int REPLY_CHARGING_REQUESTED           = 0x80;
+    public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
+    public static final int REPLY_CHARGING_ACCEPTED            = 0x82;
+    public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY  = 0x83;
+
+    /**
+     *  X-Mms-MM-State field types.
+     */
+    public static final int MM_STATE_DRAFT                  = 0x80;
+    public static final int MM_STATE_SENT                   = 0x81;
+    public static final int MM_STATE_NEW                    = 0x82;
+    public static final int MM_STATE_RETRIEVED              = 0x83;
+    public static final int MM_STATE_FORWARDED              = 0x84;
+
+    /**
+     * X-Mms-Recommended-Retrieval-Mode field types.
+     */
+    public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
+
+    /**
+     *  X-Mms-Content-Class field types.
+     */
+    public static final int CONTENT_CLASS_TEXT              = 0x80;
+    public static final int CONTENT_CLASS_IMAGE_BASIC       = 0x81;
+    public static final int CONTENT_CLASS_IMAGE_RICH        = 0x82;
+    public static final int CONTENT_CLASS_VIDEO_BASIC       = 0x83;
+    public static final int CONTENT_CLASS_VIDEO_RICH        = 0x84;
+    public static final int CONTENT_CLASS_MEGAPIXEL         = 0x85;
+    public static final int CONTENT_CLASS_CONTENT_BASIC     = 0x86;
+    public static final int CONTENT_CLASS_CONTENT_RICH      = 0x87;
+
+    /**
+     *  X-Mms-Store-Status field types.
+     */
+    public static final int STORE_STATUS_SUCCESS                                = 0x80;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE                = 0xC0;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM        = 0xC1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE                = 0xE0;
+    public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED         = 0xE1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND      = 0xE3;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL             = 0xE4;
+    public static final int STORE_STATUS_ERROR_END                              = 0xFF;
+
+    /**
+     * The map contains the value of all headers.
+     */
+    private HashMap<Integer, Object> mHeaderMap = null;
+
+    /**
+     * Constructor of PduHeaders.
+     */
+    public PduHeaders() {
+        mHeaderMap = new HashMap<Integer, Object>();
+    }
+
+    /**
+     * Get octet value by header field.
+     *
+     * @param field the field
+     * @return the octet value of the pdu header
+     *          with specified header field. Return 0 if
+     *          the value is not set.
+     */
+    public int getOctet(int field) {
+        Integer octet = (Integer) mHeaderMap.get(field);
+        if (null == octet) {
+            return 0;
+        }
+
+        return octet;
+    }
+
+    /**
+     * Set octet value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setOctet(int value, int field)
+            throws InvalidHeaderValueException{
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case REPORT_ALLOWED:
+            case ADAPTATION_ALLOWED:
+            case DELIVERY_REPORT:
+            case DRM_CONTENT:
+            case DISTRIBUTION_INDICATOR:
+            case QUOTAS:
+            case READ_REPORT:
+            case STORE:
+            case STORED:
+            case TOTALS:
+            case SENDER_VISIBILITY:
+                if ((VALUE_YES != value) && (VALUE_NO != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case READ_STATUS:
+                if ((READ_STATUS_READ != value) &&
+                        (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CANCEL_STATUS:
+                if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
+                        (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case PRIORITY:
+                if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case STATUS:
+                if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case REPLY_CHARGING:
+                if ((value < REPLY_CHARGING_REQUESTED)
+                        || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case MM_STATE:
+                if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RECOMMENDED_RETRIEVAL_MODE:
+                if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CONTENT_CLASS:
+                if ((value < CONTENT_CLASS_TEXT)
+                        || (value > CONTENT_CLASS_CONTENT_RICH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RETRIEVE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
+                if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
+                        (value <= RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < RETRIEVE_STATUS_OK) ||
+                        ((value > RETRIEVE_STATUS_OK) &&
+                                (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case STORE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
+                if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
+                        (value <= STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < STORE_STATUS_SUCCESS) ||
+                        ((value > STORE_STATUS_SUCCESS) &&
+                                (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case RESPONSE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
+                if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
+                        (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
+                        (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
+                        (value < RESPONSE_STATUS_OK) ||
+                        ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
+                                (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
+                    value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case MMS_VERSION:
+                if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
+                    value = CURRENT_MMS_VERSION; // Current version is the default value.
+                }
+                break;
+            case MESSAGE_TYPE:
+                if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            default:
+                // This header value should not be Octect.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get TextString value by header field.
+     *
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     */
+    public byte[] getTextString(int field) {
+        return (byte[]) mHeaderMap.get(field);
+    }
+
+    /**
+     * Set TextString value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTextString(byte[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case TRANSACTION_ID:
+            case REPLY_CHARGING_ID:
+            case AUX_APPLIC_ID:
+            case APPLIC_ID:
+            case REPLY_APPLIC_ID:
+            case MESSAGE_ID:
+            case REPLACE_ID:
+            case CANCEL_ID:
+            case CONTENT_LOCATION:
+            case MESSAGE_CLASS:
+            case CONTENT_TYPE:
+                break;
+            default:
+                // This header value should not be Text-String.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get EncodedStringValue value by header field.
+     *
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     */
+    public EncodedStringValue getEncodedStringValue(int field) {
+        return (EncodedStringValue) mHeaderMap.get(field);
+    }
+
+    /**
+     * Get TO, CC or BCC header value.
+     *
+     * @param field the field
+     * @return the EncodeStringValue array of the pdu header
+     *          with specified header field
+     */
+    public EncodedStringValue[] getEncodedStringValues(int field) {
+        ArrayList<EncodedStringValue> list =
+                (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            return null;
+        }
+        EncodedStringValue[] values = new EncodedStringValue[list.size()];
+        return list.toArray(values);
+    }
+
+    /**
+     * Set EncodedStringValue value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    public void setEncodedStringValue(EncodedStringValue value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case SUBJECT:
+            case RECOMMENDED_RETRIEVAL_MODE_TEXT:
+            case RETRIEVE_TEXT:
+            case STATUS_TEXT:
+            case STORE_STATUS_TEXT:
+            case RESPONSE_TEXT:
+            case FROM:
+            case PREVIOUSLY_SENT_BY:
+            case MM_FLAGS:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Set TO, CC or BCC header value.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value array of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    public void setEncodedStringValues(EncodedStringValue[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < value.length; i++) {
+            list.add(value[i]);
+        }
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Append one EncodedStringValue to another.
+     *
+     * @param value the EncodedStringValue to append
+     * @param field the field
+     * @throws NullPointerException if the value is null.
+     */
+    public void appendEncodedStringValue(EncodedStringValue value,
+                                    int field) {
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list =
+            (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            list  = new ArrayList<EncodedStringValue>();
+        }
+        list.add(value);
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Get LongInteger value by header field.
+     *
+     * @param field the field
+     * @return the LongInteger value of the pdu header
+     *          with specified header field. if return -1, the
+     *          field is not existed in pdu header.
+     */
+    public long getLongInteger(int field) {
+        Long longInteger = (Long) mHeaderMap.get(field);
+        if (null == longInteger) {
+            return -1;
+        }
+
+        return longInteger.longValue();
+    }
+
+    /**
+     * Set LongInteger value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     */
+    public void setLongInteger(long value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case DATE:
+            case REPLY_CHARGING_SIZE:
+            case MESSAGE_SIZE:
+            case MESSAGE_COUNT:
+            case START:
+            case LIMIT:
+            case DELIVERY_TIME:
+            case EXPIRY:
+            case REPLY_CHARGING_DEADLINE:
+            case PREVIOUSLY_SENT_DATE:
+                break;
+            default:
+                // This header value should not be LongInteger.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/ContentType.java b/mms-common/java/com/android/mmscommon/mms/ContentType.java
new file mode 100644
index 0000000..f21eba8
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/ContentType.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms;
+
+import java.util.ArrayList;
+
+public class ContentType {
+    public static final String MMS_MESSAGE       = "application/vnd.wap.mms-message";
+    // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+    // Notification.ind, Delivery.ind).
+    public static final String MMS_GENERIC       = "application/vnd.wap.mms-generic";
+    public static final String MULTIPART_MIXED   = "application/vnd.wap.multipart.mixed";
+    public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+    public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative";
+
+    public static final String TEXT_PLAIN        = "text/plain";
+    public static final String TEXT_HTML         = "text/html";
+    public static final String TEXT_VCALENDAR    = "text/x-vCalendar";
+    public static final String TEXT_VCARD        = "text/x-vCard";
+
+    public static final String IMAGE_UNSPECIFIED = "image/*";
+    public static final String IMAGE_JPEG        = "image/jpeg";
+    public static final String IMAGE_JPG         = "image/jpg";
+    public static final String IMAGE_GIF         = "image/gif";
+    public static final String IMAGE_WBMP        = "image/vnd.wap.wbmp";
+    public static final String IMAGE_PNG         = "image/png";
+
+    public static final String AUDIO_UNSPECIFIED = "audio/*";
+    public static final String AUDIO_AAC         = "audio/aac";
+    public static final String AUDIO_AMR         = "audio/amr";
+    public static final String AUDIO_IMELODY     = "audio/imelody";
+    public static final String AUDIO_MID         = "audio/mid";
+    public static final String AUDIO_MIDI        = "audio/midi";
+    public static final String AUDIO_MP3         = "audio/mp3";
+    public static final String AUDIO_MPEG3       = "audio/mpeg3";
+    public static final String AUDIO_MPEG        = "audio/mpeg";
+    public static final String AUDIO_MPG         = "audio/mpg";
+    public static final String AUDIO_MP4         = "audio/mp4";
+    public static final String AUDIO_X_MID       = "audio/x-mid";
+    public static final String AUDIO_X_MIDI      = "audio/x-midi";
+    public static final String AUDIO_X_MP3       = "audio/x-mp3";
+    public static final String AUDIO_X_MPEG3     = "audio/x-mpeg3";
+    public static final String AUDIO_X_MPEG      = "audio/x-mpeg";
+    public static final String AUDIO_X_MPG       = "audio/x-mpg";
+    public static final String AUDIO_3GPP        = "audio/3gpp";
+    public static final String AUDIO_OGG         = "application/ogg";
+
+    public static final String VIDEO_UNSPECIFIED = "video/*";
+    public static final String VIDEO_3GPP        = "video/3gpp";
+    public static final String VIDEO_3G2         = "video/3gpp2";
+    public static final String VIDEO_H263        = "video/h263";
+    public static final String VIDEO_MP4         = "video/mp4";
+
+    public static final String APP_SMIL          = "application/smil";
+    public static final String APP_WAP_XHTML     = "application/vnd.wap.xhtml+xml";
+    public static final String APP_XHTML         = "application/xhtml+xml";
+
+    public static final String APP_DRM_CONTENT   = "application/vnd.oma.drm.content";
+    public static final String APP_DRM_MESSAGE   = "application/vnd.oma.drm.message";
+
+    private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
+
+    static {
+        sSupportedContentTypes.add(TEXT_PLAIN);
+        sSupportedContentTypes.add(TEXT_HTML);
+        sSupportedContentTypes.add(TEXT_VCALENDAR);
+        sSupportedContentTypes.add(TEXT_VCARD);
+
+        sSupportedContentTypes.add(IMAGE_JPEG);
+        sSupportedContentTypes.add(IMAGE_GIF);
+        sSupportedContentTypes.add(IMAGE_WBMP);
+        sSupportedContentTypes.add(IMAGE_PNG);
+        sSupportedContentTypes.add(IMAGE_JPG);
+        //supportedContentTypes.add(IMAGE_SVG); not yet supported.
+
+        sSupportedContentTypes.add(AUDIO_AAC);
+        sSupportedContentTypes.add(AUDIO_AMR);
+        sSupportedContentTypes.add(AUDIO_IMELODY);
+        sSupportedContentTypes.add(AUDIO_MID);
+        sSupportedContentTypes.add(AUDIO_MIDI);
+        sSupportedContentTypes.add(AUDIO_MP3);
+        sSupportedContentTypes.add(AUDIO_MPEG3);
+        sSupportedContentTypes.add(AUDIO_MPEG);
+        sSupportedContentTypes.add(AUDIO_MPG);
+        sSupportedContentTypes.add(AUDIO_X_MID);
+        sSupportedContentTypes.add(AUDIO_X_MIDI);
+        sSupportedContentTypes.add(AUDIO_X_MP3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG);
+        sSupportedContentTypes.add(AUDIO_X_MPG);
+        sSupportedContentTypes.add(AUDIO_3GPP);
+        sSupportedContentTypes.add(AUDIO_OGG);
+
+        sSupportedContentTypes.add(VIDEO_3GPP);
+        sSupportedContentTypes.add(VIDEO_3G2);
+        sSupportedContentTypes.add(VIDEO_H263);
+        sSupportedContentTypes.add(VIDEO_MP4);
+
+        sSupportedContentTypes.add(APP_SMIL);
+        sSupportedContentTypes.add(APP_WAP_XHTML);
+        sSupportedContentTypes.add(APP_XHTML);
+
+        sSupportedContentTypes.add(APP_DRM_CONTENT);
+        sSupportedContentTypes.add(APP_DRM_MESSAGE);
+
+        // add supported image types
+        sSupportedImageTypes.add(IMAGE_JPEG);
+        sSupportedImageTypes.add(IMAGE_GIF);
+        sSupportedImageTypes.add(IMAGE_WBMP);
+        sSupportedImageTypes.add(IMAGE_PNG);
+        sSupportedImageTypes.add(IMAGE_JPG);
+
+        // add supported audio types
+        sSupportedAudioTypes.add(AUDIO_AAC);
+        sSupportedAudioTypes.add(AUDIO_AMR);
+        sSupportedAudioTypes.add(AUDIO_IMELODY);
+        sSupportedAudioTypes.add(AUDIO_MID);
+        sSupportedAudioTypes.add(AUDIO_MIDI);
+        sSupportedAudioTypes.add(AUDIO_MP3);
+        sSupportedAudioTypes.add(AUDIO_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_MPEG);
+        sSupportedAudioTypes.add(AUDIO_MPG);
+        sSupportedAudioTypes.add(AUDIO_MP4);
+        sSupportedAudioTypes.add(AUDIO_X_MID);
+        sSupportedAudioTypes.add(AUDIO_X_MIDI);
+        sSupportedAudioTypes.add(AUDIO_X_MP3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG);
+        sSupportedAudioTypes.add(AUDIO_X_MPG);
+        sSupportedAudioTypes.add(AUDIO_3GPP);
+        sSupportedAudioTypes.add(AUDIO_OGG);
+
+        // add supported video types
+        sSupportedVideoTypes.add(VIDEO_3GPP);
+        sSupportedVideoTypes.add(VIDEO_3G2);
+        sSupportedVideoTypes.add(VIDEO_H263);
+        sSupportedVideoTypes.add(VIDEO_MP4);
+    }
+
+    // This class should never be instantiated.
+    private ContentType() {
+    }
+
+    public static boolean isSupportedType(String contentType) {
+        return (null != contentType) && sSupportedContentTypes.contains(contentType);
+    }
+
+    public static boolean isSupportedImageType(String contentType) {
+        return isImageType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isSupportedAudioType(String contentType) {
+        return isAudioType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isSupportedVideoType(String contentType) {
+        return isVideoType(contentType) && isSupportedType(contentType);
+    }
+
+    public static boolean isTextType(String contentType) {
+        return (null != contentType) && contentType.startsWith("text/");
+    }
+
+    public static boolean isImageType(String contentType) {
+        return (null != contentType) && contentType.startsWith("image/");
+    }
+
+    public static boolean isAudioType(String contentType) {
+        return (null != contentType) && contentType.startsWith("audio/");
+    }
+
+    public static boolean isVideoType(String contentType) {
+        return (null != contentType) && contentType.startsWith("video/");
+    }
+
+    public static boolean isDrmType(String contentType) {
+        return (null != contentType)
+                && (contentType.equals(APP_DRM_CONTENT)
+                        || contentType.equals(APP_DRM_MESSAGE));
+    }
+
+    public static boolean isUnspecified(String contentType) {
+        return (null != contentType) && contentType.endsWith("*");
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getImageTypes() {
+        return (ArrayList<String>) sSupportedImageTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getAudioTypes() {
+        return (ArrayList<String>) sSupportedAudioTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getVideoTypes() {
+        return (ArrayList<String>) sSupportedVideoTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getSupportedTypes() {
+        return (ArrayList<String>) sSupportedContentTypes.clone();
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/AcknowledgeInd.java b/mms-common/java/com/android/mmscommon/mms/pdu/AcknowledgeInd.java
new file mode 100644
index 0000000..d1243b2
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/AcknowledgeInd.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+/**
+ * M-Acknowledge.ind PDU.
+ */
+public class AcknowledgeInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-Acknowledge.ind pdu.
+     *
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     */
+    public AcknowledgeInd(int mmsVersion, byte[] transactionId)
+            throws InvalidHeaderValueException {
+        super();
+
+        setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    AcknowledgeInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/Base64.java b/mms-common/java/com/android/mmscommon/mms/pdu/Base64.java
new file mode 100644
index 0000000..4c95dec
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/Base64.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+public class Base64 {
+    /**
+     * Used to get the number of Quadruples.
+     */
+    static final int FOURBYTE = 4;
+
+    /**
+     * Byte used to pad output.
+     */
+    static final byte PAD = (byte) '=';
+
+    /**
+     * The base length.
+     */
+    static final int BASELENGTH = 255;
+
+    // Create arrays to hold the base64 characters
+    private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+    // Populating the character arrays
+    static {
+        for (int i = 0; i < BASELENGTH; i++) {
+            base64Alphabet[i] = (byte) -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--) {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--) {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+        for (int i = '9'; i >= '0'; i--) {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param base64Data Byte array containing Base64 data
+     * @return Array containing decoded data.
+     */
+    public static byte[] decodeBase64(byte[] base64Data) {
+        // RFC 2045 requires that we discard ALL non-Base64 characters
+        base64Data = discardNonBase64(base64Data);
+
+        // handle the edge case, so we don't have to worry about it later
+        if (base64Data.length == 0) {
+            return new byte[0];
+        }
+
+        int numberQuadruple = base64Data.length / FOURBYTE;
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+        // Throw away anything not in base64Data
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        {
+            // this sizes the output array properly - rlw
+            int lastData = base64Data.length;
+            // ignore the '=' padding
+            while (base64Data[lastData - 1] == PAD) {
+                if (--lastData == 0) {
+                    return new byte[0];
+                }
+            }
+            decodedData = new byte[lastData - numberQuadruple];
+        }
+
+        for (int i = 0; i < numberQuadruple; i++) {
+            dataIndex = i * 4;
+            marker0 = base64Data[dataIndex + 2];
+            marker1 = base64Data[dataIndex + 3];
+
+            b1 = base64Alphabet[base64Data[dataIndex]];
+            b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+            if (marker0 != PAD && marker1 != PAD) {
+                //No PAD e.g 3cQl
+                b3 = base64Alphabet[marker0];
+                b4 = base64Alphabet[marker1];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+            } else if (marker0 == PAD) {
+                //Two PAD e.g. 3c[Pad][Pad]
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+            } else if (marker1 == PAD) {
+                //One PAD e.g. 3cQ[Pad]
+                b3 = base64Alphabet[marker0];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            }
+            encodedIndex += 3;
+        }
+        return decodedData;
+    }
+
+    /**
+     * Check octect wheter it is a base64 encoding.
+     *
+     * @param octect to be checked byte
+     * @return ture if it is base64 encoding, false otherwise.
+     */
+    private static boolean isBase64(byte octect) {
+        if (octect == PAD) {
+            return true;
+        } else if (base64Alphabet[octect] == -1) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Discards any characters outside of the base64 alphabet, per
+     * the requirements on page 25 of RFC 2045 - "Any characters
+     * outside of the base64 alphabet are to be ignored in base64
+     * encoded data."
+     *
+     * @param data The base-64 encoded data to groom
+     * @return The data, less non-base64 characters (see RFC 2045).
+     */
+    static byte[] discardNonBase64(byte[] data) {
+        byte groomedData[] = new byte[data.length];
+        int bytesCopied = 0;
+
+        for (int i = 0; i < data.length; i++) {
+            if (isBase64(data[i])) {
+                groomedData[bytesCopied++] = data[i];
+            }
+        }
+
+        byte packedData[] = new byte[bytesCopied];
+
+        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+        return packedData;
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/DeliveryInd.java b/mms-common/java/com/android/mmscommon/mms/pdu/DeliveryInd.java
new file mode 100644
index 0000000..e83729b
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/DeliveryInd.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+/**
+ * M-Delivery.Ind Pdu.
+ */
+public class DeliveryInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public DeliveryInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    DeliveryInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value, should not be null
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get Status value.
+     *
+     * @return the value
+     */
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Set Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public EncodedStringValue getStatusText() {return null;}
+     *     public void setStatusText(EncodedStringValue value) {}
+     */
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/GenericPdu.java b/mms-common/java/com/android/mmscommon/mms/pdu/GenericPdu.java
new file mode 100644
index 0000000..c38e502
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/GenericPdu.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+public class GenericPdu {
+    /**
+     * The headers of pdu.
+     */
+    PduHeaders mPduHeaders = null;
+
+    /**
+     * Constructor.
+     */
+    public GenericPdu() {
+        mPduHeaders = new PduHeaders();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param headers Headers for this PDU.
+     */
+    GenericPdu(PduHeaders headers) {
+        mPduHeaders = headers;
+    }
+
+    /**
+     * Get the headers of this PDU.
+     *
+     * @return A PduHeaders of this PDU.
+     */
+    PduHeaders getPduHeaders() {
+        return mPduHeaders;
+    }
+
+    /**
+     * Get X-Mms-Message-Type field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getMessageType() {
+        return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Set X-Mms-Message-Type field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    public void setMessageType(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Get X-Mms-MMS-Version field value.
+     *
+     * @return the X-Mms-MMS-Version value
+     */
+    public int getMmsVersion() {
+        return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
+    }
+
+    /**
+     * Set X-Mms-MMS-Version field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    public void setMmsVersion(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/MultimediaMessagePdu.java b/mms-common/java/com/android/mmscommon/mms/pdu/MultimediaMessagePdu.java
new file mode 100644
index 0000000..04fde2d
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/MultimediaMessagePdu.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+/**
+ * Multimedia message PDU.
+ */
+public class MultimediaMessagePdu extends GenericPdu{
+    /**
+     * The body.
+     */
+    private PduBody mMessageBody;
+
+    /**
+     * Constructor.
+     */
+    public MultimediaMessagePdu() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param header the header of this PDU
+     * @param body the body of this PDU
+     */
+    public MultimediaMessagePdu(PduHeaders header, PduBody body) {
+        super(header);
+        mMessageBody = body;
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    MultimediaMessagePdu(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get body of the PDU.
+     *
+     * @return the body
+     */
+    public PduBody getBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * Set body of the PDU.
+     *
+     * @param body the body
+     */
+    public void setBody(PduBody body) {
+        mMessageBody = body;
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Add a "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addTo(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Priority value.
+     *
+     * @return the value
+     */
+    public int getPriority() {
+        return mPduHeaders.getOctet(PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Set X-Mms-Priority value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setPriority(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value in seconds.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/NotificationInd.java b/mms-common/java/com/android/mmscommon/mms/pdu/NotificationInd.java
new file mode 100644
index 0000000..24f17b0
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/NotificationInd.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+/**
+ * M-Notification.ind PDU.
+ */
+public class NotificationInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public NotificationInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    NotificationInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Content-Class Value.
+     *
+     * @return the value
+     */
+    public int getContentClass() {
+        return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Content-Class Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setContentClass(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Content-Location value.
+     * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+     * Content-location-value = Uri-value
+     *
+     * @return the value
+     */
+    public byte[] getContentLocation() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Set X-Mms-Content-Location value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setContentLocation(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Message-Size value.
+     * Message-size-value = Long-integer
+     *
+     * @return the value
+     */
+    public long getMessageSize() {
+        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Set X-Mms-Message-Size value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    public void setMessageSize(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report Value.
+     *
+     * @return the value
+     */
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public ElementDescriptorValue getElementDescriptor() {return null;}
+     *     public void getElementDescriptor(ElementDescriptorValue value) {}
+     *
+     *     public byte getPriority() {return 0x00;}
+     *     public void setPriority(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalMode() {return 0x00;}
+     *     public void setRecommendedRetrievalMode(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalModeText() {return 0x00;}
+     *     public void setRecommendedRetrievalModeText(byte value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte getStored() {return 0x00;}
+     *     public void setStored(byte value) {}
+     */
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/NotifyRespInd.java b/mms-common/java/com/android/mmscommon/mms/pdu/NotifyRespInd.java
new file mode 100644
index 0000000..c2e2b26
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/NotifyRespInd.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+/**
+ * M-NofifyResp.ind PDU.
+ */
+public class NotifyRespInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-NotifyResp.ind pdu.
+     *
+     * @param mmsVersion current version of mms
+     * @param transactionId the transaction-id value
+     * @param status the status value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public NotifyRespInd(int mmsVersion,
+                         byte[] transactionId,
+                         int status) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+        setStatus(status);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    NotifyRespInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Status field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * GetX-Mms-Status field value.
+     *
+     * @return the X-Mms-Status value
+     */
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/PduBody.java b/mms-common/java/com/android/mmscommon/mms/pdu/PduBody.java
new file mode 100644
index 0000000..cc28d80
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/PduBody.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+public class PduBody {
+    private Vector<PduPart> mParts = null;
+
+    private Map<String, PduPart> mPartMapByContentId = null;
+    private Map<String, PduPart> mPartMapByContentLocation = null;
+    private Map<String, PduPart> mPartMapByName = null;
+    private Map<String, PduPart> mPartMapByFileName = null;
+
+    /**
+     * Constructor.
+     */
+    public PduBody() {
+        mParts = new Vector<PduPart>();
+
+        mPartMapByContentId = new HashMap<String, PduPart>();
+        mPartMapByContentLocation  = new HashMap<String, PduPart>();
+        mPartMapByName = new HashMap<String, PduPart>();
+        mPartMapByFileName = new HashMap<String, PduPart>();
+    }
+
+    private void putPartToMaps(PduPart part) {
+        // Put part to mPartMapByContentId.
+        byte[] contentId = part.getContentId();
+        if(null != contentId) {
+            mPartMapByContentId.put(new String(contentId), part);
+        }
+
+        // Put part to mPartMapByContentLocation.
+        byte[] contentLocation = part.getContentLocation();
+        if(null != contentLocation) {
+            String clc = new String(contentLocation);
+            mPartMapByContentLocation.put(clc, part);
+        }
+
+        // Put part to mPartMapByName.
+        byte[] name = part.getName();
+        if(null != name) {
+            String clc = new String(name);
+            mPartMapByName.put(clc, part);
+        }
+
+        // Put part to mPartMapByFileName.
+        byte[] fileName = part.getFilename();
+        if(null != fileName) {
+            String clc = new String(fileName);
+            mPartMapByFileName.put(clc, part);
+        }
+    }
+
+    /**
+     * Appends the specified part to the end of this body.
+     *
+     * @param part part to be appended
+     * @return true when success, false when fail
+     * @throws NullPointerException when part is null
+     */
+    public boolean addPart(PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        return mParts.add(part);
+    }
+
+    /**
+     * Inserts the specified part at the specified position.
+     *
+     * @param index index at which the specified part is to be inserted
+     * @param part part to be inserted
+     * @throws NullPointerException when part is null
+     */
+    public void addPart(int index, PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        mParts.add(index, part);
+    }
+
+    /**
+     * Removes the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    public PduPart removePart(int index) {
+        return mParts.remove(index);
+    }
+
+    /**
+     * Remove all of the parts.
+     */
+    public void removeAll() {
+        mParts.clear();
+    }
+
+    /**
+     * Get the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    public PduPart getPart(int index) {
+        return mParts.get(index);
+    }
+
+    /**
+     * Get the index of the specified part.
+     *
+     * @param part the part object
+     * @return index the index of the first occurrence of the part in this body
+     */
+    public int getPartIndex(PduPart part) {
+        return mParts.indexOf(part);
+    }
+
+    /**
+     * Get the number of parts.
+     *
+     * @return the number of parts
+     */
+    public int getPartsNum() {
+        return mParts.size();
+    }
+
+    /**
+     * Get pdu part by content id.
+     *
+     * @param cid the value of content id.
+     * @return the pdu part.
+     */
+    public PduPart getPartByContentId(String cid) {
+        return mPartMapByContentId.get(cid);
+    }
+
+    /**
+     * Get pdu part by Content-Location. Content-Location of part is
+     * the same as filename and name(param of content-type).
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    public PduPart getPartByContentLocation(String contentLocation) {
+        return mPartMapByContentLocation.get(contentLocation);
+    }
+
+    /**
+     * Get pdu part by name.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    public PduPart getPartByName(String name) {
+        return mPartMapByName.get(name);
+    }
+
+    /**
+     * Get pdu part by filename.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    public PduPart getPartByFileName(String filename) {
+        return mPartMapByFileName.get(filename);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/PduComposer.java b/mms-common/java/com/android/mmscommon/mms/pdu/PduComposer.java
new file mode 100644
index 0000000..bb3116d
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/PduComposer.java
@@ -0,0 +1,1184 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.PduHeaders;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.util.Log;
+import android.text.TextUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduComposer {
+    /**
+     * Address type.
+     */
+    static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
+    static private final int PDU_EMAIL_ADDRESS_TYPE = 2;
+    static private final int PDU_IPV4_ADDRESS_TYPE = 3;
+    static private final int PDU_IPV6_ADDRESS_TYPE = 4;
+    static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
+
+    /**
+     * Address regular expression string.
+     */
+    static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
+    static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
+            "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
+    static final String REGEXP_IPV6_ADDRESS_TYPE =
+        "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
+    static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
+            "[0-9]{1,3}\\.{1}[0-9]{1,3}";
+
+    /**
+     * The postfix strings of address.
+     */
+    static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
+    static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
+    static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
+
+    /**
+     * Error values.
+     */
+    static private final int PDU_COMPOSE_SUCCESS = 0;
+    static private final int PDU_COMPOSE_CONTENT_ERROR = 1;
+    static private final int PDU_COMPOSE_FIELD_NOT_SET = 2;
+    static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
+
+    /**
+     * WAP values defined in WSP spec.
+     */
+    static private final int QUOTED_STRING_FLAG = 34;
+    static private final int END_STRING_FLAG = 0;
+    static private final int LENGTH_QUOTE = 31;
+    static private final int TEXT_MAX = 127;
+    static private final int SHORT_INTEGER_MAX = 127;
+    static private final int LONG_INTEGER_LENGTH_MAX = 8;
+
+    /**
+     * Block size when read data from InputStream.
+     */
+    static private final int PDU_COMPOSER_BLOCK_SIZE = 1024;
+
+    /**
+     * The output message.
+     */
+    protected ByteArrayOutputStream mMessage = null;
+
+    /**
+     * The PDU.
+     */
+    private GenericPdu mPdu = null;
+
+    /**
+     * Current visiting position of the mMessage.
+     */
+    protected int mPosition = 0;
+
+    /**
+     * Message compose buffer stack.
+     */
+    private BufferStack mStack = null;
+
+    /**
+     * Content resolver.
+     */
+    private final ContentResolver mResolver;
+
+    /**
+     * Header of this pdu.
+     */
+    private PduHeaders mPduHeader = null;
+
+    /**
+     * Map of all content type
+     */
+    private static HashMap<String, Integer> mContentTypeMap = null;
+
+    static {
+        mContentTypeMap = new HashMap<String, Integer>();
+
+        int i;
+        for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
+            mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context the context
+     * @param pdu the pdu to be composed
+     */
+    public PduComposer(Context context, GenericPdu pdu) {
+        mPdu = pdu;
+        mResolver = context.getContentResolver();
+        mPduHeader = pdu.getPduHeaders();
+        mStack = new BufferStack();
+        mMessage = new ByteArrayOutputStream();
+        mPosition = 0;
+    }
+
+    /**
+     * Make the message. No need to check whether mandatory fields are set,
+     * because the constructors of outgoing pdus are taking care of this.
+     *
+     * @return OutputStream of maked message. Return null if
+     *         the PDU is invalid.
+     */
+    public byte[] make() {
+        // Get Message-type.
+        int type = mPdu.getMessageType();
+
+        /* make the message */
+        switch (type) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            default:
+                return null;
+        }
+
+        return mMessage.toByteArray();
+    }
+
+    /**
+     *  Copy buf to mMessage.
+     */
+    protected void arraycopy(byte[] buf, int pos, int length) {
+        mMessage.write(buf, pos, length);
+        mPosition = mPosition + length;
+    }
+
+    /**
+     * Append a byte to mMessage.
+     */
+    protected void append(int value) {
+        mMessage.write(value);
+        mPosition ++;
+    }
+
+    /**
+     * Append short integer value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendShortInteger(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-integer = OCTET
+         * ; Integers in range 0-127 shall be encoded as a one octet value
+         * ; with the most significant bit set to one (1xxx xxxx) and with
+         * ; the value in the remaining least significant bits.
+         * In our implementation, only low 7 bits are stored and otherwise
+         * bits are ignored.
+         */
+        append((value | 0x80) & 0xff);
+    }
+
+    /**
+     * Append an octet number between 128 and 255 into mMessage.
+     * NOTE:
+     * A value between 0 and 127 should be appended by using appendShortInteger.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendOctet(int number) {
+        append(number);
+    }
+
+    /**
+     * Append a short length into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendShortLength(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-length = <Any octet 0-30>
+         */
+        append(value);
+    }
+
+    /**
+     * Append long integer into mMessage. it's used for really long integers.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendLongInteger(long longInt) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Long-integer = Short-length Multi-octet-integer
+         * ; The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * ; The content octets shall be an unsigned integer value with the
+         * ; most significant octet encoded first (big-endian representation).
+         * ; The minimum number of octets must be used to encode the value.
+         */
+        int size;
+        long temp = longInt;
+
+        // Count the length of the long integer.
+        for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
+            temp = (temp >>> 8);
+        }
+
+        // Set Length.
+        appendShortLength(size);
+
+        // Count and set the long integer.
+        int i;
+        int shift = (size -1) * 8;
+
+        for (i = 0; i < size; i++) {
+            append((int)((longInt >>> shift) & 0xff));
+            shift = shift - 8;
+        }
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendTextString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255
+            append(TEXT_MAX);
+        }
+
+        arraycopy(text, 0, text.length);
+        append(0);
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendTextString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        appendTextString(str.getBytes());
+    }
+
+    /**
+     * Append encoded string value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendEncodedString(EncodedStringValue enStr) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(enStr != null);
+
+        int charset = enStr.getCharacterSet();
+        byte[] textString = enStr.getTextString();
+        if (null == textString) {
+            return;
+        }
+
+        /*
+         * In the implementation of EncodedStringValue, the charset field will
+         * never be 0. It will always be composed as
+         * Encoded-string-value = Value-length Char-set Text-string
+         */
+        mStack.newbuf();
+        PositionMarker start = mStack.mark();
+
+        appendShortInteger(charset);
+        appendTextString(textString);
+
+        int len = start.getLength();
+        mStack.pop();
+        appendValueLength(len);
+        mStack.copy();
+    }
+
+    /**
+     * Append uintvar integer into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendUintvarInteger(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * To encode a large unsigned integer, split it into 7-bit fragments
+         * and place them in the payloads of multiple octets. The most significant
+         * bits are placed in the first octets with the least significant bits
+         * ending up in the last octet. All octets MUST set the Continue bit to 1
+         * except the last octet, which MUST set the Continue bit to 0.
+         */
+        int i;
+        long max = SHORT_INTEGER_MAX;
+
+        for (i = 0; i < 5; i++) {
+            if (value < max) {
+                break;
+            }
+
+            max = (max << 7) | 0x7fl;
+        }
+
+        while(i > 0) {
+            long temp = value >>> (i * 7);
+            temp = temp & 0x7f;
+
+            append((int)((temp | 0x80) & 0xff));
+
+            i--;
+        }
+
+        append((int)(value & 0x7f));
+    }
+
+    /**
+     * Append date value into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendDateValue(long date) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Date-value = Long-integer
+         */
+        appendLongInteger(date);
+    }
+
+    /**
+     * Append value length to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendValueLength(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Value-length = Short-length | (Length-quote Length)
+         * ; Value length is used to indicate the length of the value to follow
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         */
+        if (value < LENGTH_QUOTE) {
+            appendShortLength((int) value);
+            return;
+        }
+
+        append(LENGTH_QUOTE);
+        appendUintvarInteger(value);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendQuotedString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        append(QUOTED_STRING_FLAG);
+        arraycopy(text, 0, text.length);
+        append(END_STRING_FLAG);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendQuotedString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        appendQuotedString(str.getBytes());
+    }
+
+    private EncodedStringValue appendAddressType(EncodedStringValue address) {
+        EncodedStringValue temp = null;
+
+        try {
+            int addressType = checkAddressType(address.getString());
+            temp = EncodedStringValue.copy(address);
+            if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
+                // Phone number.
+                temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
+            } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
+                // Ipv4 address.
+                temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
+            } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
+                // Ipv6 address.
+                temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
+            }
+        } catch (NullPointerException e) {
+            return null;
+        }
+
+        return temp;
+    }
+
+    /**
+     * Append header to mMessage.
+     */
+    private int appendHeader(int field) {
+        switch (field) {
+            case PduHeaders.MMS_VERSION:
+                appendOctet(field);
+
+                int version = mPduHeader.getOctet(field);
+                if (0 == version) {
+                    appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
+                } else {
+                    appendShortInteger(version);
+                }
+
+                break;
+
+            case PduHeaders.MESSAGE_ID:
+            case PduHeaders.TRANSACTION_ID:
+                byte[] textString = mPduHeader.getTextString(field);
+                if (null == textString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendTextString(textString);
+                break;
+
+            case PduHeaders.TO:
+            case PduHeaders.BCC:
+            case PduHeaders.CC:
+                EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
+
+                if (null == addr) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                EncodedStringValue temp;
+                for (int i = 0; i < addr.length; i++) {
+                    temp = appendAddressType(addr[i]);
+                    if (temp == null) {
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+
+                    appendOctet(field);
+                    appendEncodedString(temp);
+                }
+                break;
+
+            case PduHeaders.FROM:
+                // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
+                appendOctet(field);
+
+                EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
+                if ((from == null)
+                        || TextUtils.isEmpty(from.getString())
+                        || new String(from.getTextString()).equals(
+                                PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+                    // Length of from = 1
+                    append(1);
+                    // Insert-address-token = <Octet 129>
+                    append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
+                } else {
+                    mStack.newbuf();
+                    PositionMarker fstart = mStack.mark();
+
+                    // Address-present-token = <Octet 128>
+                    append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
+
+                    temp = appendAddressType(from);
+                    if (temp == null) {
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+
+                    appendEncodedString(temp);
+
+                    int flen = fstart.getLength();
+                    mStack.pop();
+                    appendValueLength(flen);
+                    mStack.copy();
+                }
+                break;
+
+            case PduHeaders.READ_STATUS:
+            case PduHeaders.STATUS:
+            case PduHeaders.REPORT_ALLOWED:
+            case PduHeaders.PRIORITY:
+            case PduHeaders.DELIVERY_REPORT:
+            case PduHeaders.READ_REPORT:
+                int octet = mPduHeader.getOctet(field);
+                if (0 == octet) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendOctet(octet);
+                break;
+
+            case PduHeaders.DATE:
+                long date = mPduHeader.getLongInteger(field);
+                if (-1 == date) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendDateValue(date);
+                break;
+
+            case PduHeaders.SUBJECT:
+                EncodedStringValue enString =
+                    mPduHeader.getEncodedStringValue(field);
+                if (null == enString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendEncodedString(enString);
+                break;
+
+            case PduHeaders.MESSAGE_CLASS:
+                byte[] messageClass = mPduHeader.getTextString(field);
+                if (null == messageClass) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
+                } else {
+                    appendTextString(messageClass);
+                }
+                break;
+
+            case PduHeaders.EXPIRY:
+                long expiry = mPduHeader.getLongInteger(field);
+                if (-1 == expiry) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+
+                mStack.newbuf();
+                PositionMarker expiryStart = mStack.mark();
+
+                append(PduHeaders.VALUE_RELATIVE_TOKEN);
+                appendLongInteger(expiry);
+
+                int expiryLength = expiryStart.getLength();
+                mStack.pop();
+                appendValueLength(expiryLength);
+                mStack.copy();
+                break;
+
+            default:
+                return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make ReadRec.Ind.
+     */
+    private int makeReadRecInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Message-ID
+        if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Optional
+        appendHeader(PduHeaders.DATE);
+
+        // X-Mms-Read-Status
+        if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Applic-ID Optional(not support)
+        // X-Mms-Reply-Applic-ID Optional(not support)
+        // X-Mms-Aux-Applic-Info Optional(not support)
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make NotifyResp.Ind.
+     */
+    private int makeNotifyResp() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //  X-Mms-Status
+        if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional (not support)
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Acknowledge.Ind.
+     */
+    private int makeAckInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //     X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional
+        appendHeader(PduHeaders.REPORT_ALLOWED);
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Send.req.
+     */
+    private int makeSendReqPdu() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+
+        // X-Mms-Transaction-ID
+        appendOctet(PduHeaders.TRANSACTION_ID);
+
+        byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
+        if (trid == null) {
+            // Transaction-ID should be set(by Transaction) before make().
+            throw new IllegalArgumentException("Transaction-ID is null.");
+        }
+        appendTextString(trid);
+
+        //  X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Date-value Optional.
+        appendHeader(PduHeaders.DATE);
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        boolean recipient = false;
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Cc
+        if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Bcc
+        if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Need at least one of "cc", "bcc" and "to".
+        if (false == recipient) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Subject Optional
+        appendHeader(PduHeaders.SUBJECT);
+
+        // X-Mms-Message-Class Optional
+        // Message-class-value = Class-identifier | Token-text
+        appendHeader(PduHeaders.MESSAGE_CLASS);
+
+        // X-Mms-Expiry Optional
+        appendHeader(PduHeaders.EXPIRY);
+
+        // X-Mms-Priority Optional
+        appendHeader(PduHeaders.PRIORITY);
+
+        // X-Mms-Delivery-Report Optional
+        appendHeader(PduHeaders.DELIVERY_REPORT);
+
+        // X-Mms-Read-Report Optional
+        appendHeader(PduHeaders.READ_REPORT);
+
+        //    Content-Type
+        appendOctet(PduHeaders.CONTENT_TYPE);
+
+        //  Message body
+        makeMessageBody();
+
+        return PDU_COMPOSE_SUCCESS;  // Composing the message is OK
+    }
+
+    /**
+     * Make message body.
+     */
+    private int makeMessageBody() {
+        // 1. add body informations
+        mStack.newbuf();  // Switching buffer because we need to
+
+        PositionMarker ctStart = mStack.mark();
+
+        // This contentTypeIdentifier should be used for type of attachment...
+        String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
+        Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
+        if (contentTypeIdentifier == null) {
+            // content type is mandatory
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        appendShortInteger(contentTypeIdentifier.intValue());
+
+        // content-type parameter: start
+        PduBody body = ((SendReq) mPdu).getBody();
+        if (null == body || body.getPartsNum() == 0) {
+            // empty message
+            appendUintvarInteger(0);
+            mStack.pop();
+            mStack.copy();
+            return PDU_COMPOSE_SUCCESS;
+        }
+
+        PduPart part;
+        try {
+            part = body.getPart(0);
+
+            byte[] start = part.getContentId();
+            if (start != null) {
+                appendOctet(PduPart.P_DEP_START);
+                if (('<' == start[0]) && ('>' == start[start.length - 1])) {
+                    appendTextString(start);
+                } else {
+                    appendTextString("<" + new String(start) + ">");
+                }
+            }
+
+            // content-type parameter: type
+            appendOctet(PduPart.P_CT_MR_TYPE);
+            appendTextString(part.getContentType());
+        }
+        catch (ArrayIndexOutOfBoundsException e){
+            e.printStackTrace();
+        }
+
+        int ctLength = ctStart.getLength();
+        mStack.pop();
+        appendValueLength(ctLength);
+        mStack.copy();
+
+        // 3. add content
+        int partNum = body.getPartsNum();
+        appendUintvarInteger(partNum);
+        for (int i = 0; i < partNum; i++) {
+            part = body.getPart(i);
+            mStack.newbuf();  // Leaving space for header lengh and data length
+            PositionMarker attachment = mStack.mark();
+
+            mStack.newbuf();  // Leaving space for Content-Type length
+            PositionMarker contentTypeBegin = mStack.mark();
+
+            byte[] partContentType = part.getContentType();
+
+            if (partContentType == null) {
+                // content type is mandatory
+                return PDU_COMPOSE_CONTENT_ERROR;
+            }
+
+            // content-type value
+            Integer partContentTypeIdentifier =
+                mContentTypeMap.get(new String(partContentType));
+            if (partContentTypeIdentifier == null) {
+                appendTextString(partContentType);
+            } else {
+                appendShortInteger(partContentTypeIdentifier.intValue());
+            }
+
+            /* Content-type parameter : name.
+             * The value of name, filename, content-location is the same.
+             * Just one of them is enough for this PDU.
+             */
+            byte[] name = part.getName();
+
+            if (null == name) {
+                name = part.getFilename();
+
+                if (null == name) {
+                    name = part.getContentLocation();
+
+                    if (null == name) {
+                        /* at lease one of name, filename, Content-location
+                         * should be available.
+                         */
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+                }
+            }
+            appendOctet(PduPart.P_DEP_NAME);
+            appendTextString(name);
+
+            // content-type parameter : charset
+            int charset = part.getCharset();
+            if (charset != 0) {
+                appendOctet(PduPart.P_CHARSET);
+                appendShortInteger(charset);
+            }
+
+            int contentTypeLength = contentTypeBegin.getLength();
+            mStack.pop();
+            appendValueLength(contentTypeLength);
+            mStack.copy();
+
+            // content id
+            byte[] contentId = part.getContentId();
+
+            if (null != contentId) {
+                appendOctet(PduPart.P_CONTENT_ID);
+                if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
+                    appendQuotedString(contentId);
+                } else {
+                    appendQuotedString("<" + new String(contentId) + ">");
+                }
+            }
+
+            // content-location
+            byte[] contentLocation = part.getContentLocation();
+            if (null != contentLocation) {
+            	appendOctet(PduPart.P_CONTENT_LOCATION);
+            	appendTextString(contentLocation);
+            }
+
+            // content
+            int headerLength = attachment.getLength();
+
+            int dataLength = 0; // Just for safety...
+            byte[] partData = part.getData();
+
+            if (partData != null) {
+                arraycopy(partData, 0, partData.length);
+                dataLength = partData.length;
+            } else {
+                InputStream cr;
+                try {
+                    byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
+                    cr = mResolver.openInputStream(part.getDataUri());
+                    int len = 0;
+                    while ((len = cr.read(buffer)) != -1) {
+                        mMessage.write(buffer, 0, len);
+                        mPosition += len;
+                        dataLength += len;
+                    }
+                } catch (FileNotFoundException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (IOException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (RuntimeException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                }
+            }
+
+            if (dataLength != (attachment.getLength() - headerLength)) {
+                throw new RuntimeException("BUG: Length sanity check failed");
+            }
+
+            mStack.pop();
+            appendUintvarInteger(headerLength);
+            appendUintvarInteger(dataLength);
+            mStack.copy();
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     *  Record current message informations.
+     */
+    static private class LengthRecordNode {
+        ByteArrayOutputStream currentMessage = null;
+        public int currentPosition = 0;
+
+        public LengthRecordNode next = null;
+    }
+
+    /**
+     * Mark current message position and stact size.
+     */
+    private class PositionMarker {
+        private int c_pos;   // Current position
+        private int currentStackSize;  // Current stack size
+
+        int getLength() {
+            // If these assert fails, likely that you are finding the
+            // size of buffer that is deep in BufferStack you can only
+            // find the length of the buffer that is on top
+            if (currentStackSize != mStack.stackSize) {
+                throw new RuntimeException("BUG: Invalid call to getLength()");
+            }
+
+            return mPosition - c_pos;
+        }
+    }
+
+    /**
+     * This implementation can be OPTIMIZED to use only
+     * 2 buffers. This optimization involves changing BufferStack
+     * only... Its usage (interface) will not change.
+     */
+    private class BufferStack {
+        private LengthRecordNode stack = null;
+        private LengthRecordNode toCopy = null;
+
+        int stackSize = 0;
+
+        /**
+         *  Create a new message buffer and push it into the stack.
+         */
+        void newbuf() {
+            // You can't create a new buff when toCopy != null
+            // That is after calling pop() and before calling copy()
+            // If you do, it is a bug
+            if (toCopy != null) {
+                throw new RuntimeException("BUG: Invalid newbuf() before copy()");
+            }
+
+            LengthRecordNode temp = new LengthRecordNode();
+
+            temp.currentMessage = mMessage;
+            temp.currentPosition = mPosition;
+
+            temp.next = stack;
+            stack = temp;
+
+            stackSize = stackSize + 1;
+
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        /**
+         *  Pop the message before and record current message in the stack.
+         */
+        void pop() {
+            ByteArrayOutputStream currentMessage = mMessage;
+            int currentPosition = mPosition;
+
+            mMessage = stack.currentMessage;
+            mPosition = stack.currentPosition;
+
+            toCopy = stack;
+            // Re using the top element of the stack to avoid memory allocation
+
+            stack = stack.next;
+            stackSize = stackSize - 1;
+
+            toCopy.currentMessage = currentMessage;
+            toCopy.currentPosition = currentPosition;
+        }
+
+        /**
+         *  Append current message to the message before.
+         */
+        void copy() {
+            arraycopy(toCopy.currentMessage.toByteArray(), 0,
+                    toCopy.currentPosition);
+
+            toCopy = null;
+        }
+
+        /**
+         *  Mark current message position
+         */
+        PositionMarker mark() {
+            PositionMarker m = new PositionMarker();
+
+            m.c_pos = mPosition;
+            m.currentStackSize = stackSize;
+
+            return m;
+        }
+    }
+
+    /**
+     * Check address type.
+     *
+     * @param address address string without the postfix stinng type,
+     *        such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
+     * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
+     *         PDU_EMAIL_ADDRESS_TYPE if it is email address,
+     *         PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
+     *         PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
+     *         PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
+     */
+    protected static int checkAddressType(String address) {
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
+         * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
+         * e-mail = mailbox; to the definition of mailbox as described in
+         * section 3.4 of [RFC2822], but excluding the
+         * obsolete definitions as indicated by the "obs-" prefix.
+         * device-address = ( global-phone-number "/TYPE=PLMN" )
+         * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
+         * / ( escaped-value "/TYPE=" address-type )
+         *
+         * global-phone-number = ["+"] 1*( DIGIT / written-sep )
+         * written-sep =("-"/".")
+         *
+         * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
+         *
+         * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
+         */
+
+        if (null == address) {
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+
+        if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
+            // Ipv4 address.
+            return PDU_IPV4_ADDRESS_TYPE;
+        }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
+            // Phone number.
+            return PDU_PHONE_NUMBER_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
+            // Email address.
+            return PDU_EMAIL_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
+            // Ipv6 address.
+            return PDU_IPV6_ADDRESS_TYPE;
+        } else {
+            // Unknown address.
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/PduContentTypes.java b/mms-common/java/com/android/mmscommon/mms/pdu/PduContentTypes.java
new file mode 100644
index 0000000..3f971fd
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/PduContentTypes.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+public class PduContentTypes {
+    /**
+     * All content types. From:
+     * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
+     */
+    static final String[] contentTypes = {
+        "*/*",                                        /* 0x00 */
+        "text/*",                                     /* 0x01 */
+        "text/html",                                  /* 0x02 */
+        "text/plain",                                 /* 0x03 */
+        "text/x-hdml",                                /* 0x04 */
+        "text/x-ttml",                                /* 0x05 */
+        "text/x-vCalendar",                           /* 0x06 */
+        "text/x-vCard",                               /* 0x07 */
+        "text/vnd.wap.wml",                           /* 0x08 */
+        "text/vnd.wap.wmlscript",                     /* 0x09 */
+        "text/vnd.wap.wta-event",                     /* 0x0A */
+        "multipart/*",                                /* 0x0B */
+        "multipart/mixed",                            /* 0x0C */
+        "multipart/form-data",                        /* 0x0D */
+        "multipart/byterantes",                       /* 0x0E */
+        "multipart/alternative",                      /* 0x0F */
+        "application/*",                              /* 0x10 */
+        "application/java-vm",                        /* 0x11 */
+        "application/x-www-form-urlencoded",          /* 0x12 */
+        "application/x-hdmlc",                        /* 0x13 */
+        "application/vnd.wap.wmlc",                   /* 0x14 */
+        "application/vnd.wap.wmlscriptc",             /* 0x15 */
+        "application/vnd.wap.wta-eventc",             /* 0x16 */
+        "application/vnd.wap.uaprof",                 /* 0x17 */
+        "application/vnd.wap.wtls-ca-certificate",    /* 0x18 */
+        "application/vnd.wap.wtls-user-certificate",  /* 0x19 */
+        "application/x-x509-ca-cert",                 /* 0x1A */
+        "application/x-x509-user-cert",               /* 0x1B */
+        "image/*",                                    /* 0x1C */
+        "image/gif",                                  /* 0x1D */
+        "image/jpeg",                                 /* 0x1E */
+        "image/tiff",                                 /* 0x1F */
+        "image/png",                                  /* 0x20 */
+        "image/vnd.wap.wbmp",                         /* 0x21 */
+        "application/vnd.wap.multipart.*",            /* 0x22 */
+        "application/vnd.wap.multipart.mixed",        /* 0x23 */
+        "application/vnd.wap.multipart.form-data",    /* 0x24 */
+        "application/vnd.wap.multipart.byteranges",   /* 0x25 */
+        "application/vnd.wap.multipart.alternative",  /* 0x26 */
+        "application/xml",                            /* 0x27 */
+        "text/xml",                                   /* 0x28 */
+        "application/vnd.wap.wbxml",                  /* 0x29 */
+        "application/x-x968-cross-cert",              /* 0x2A */
+        "application/x-x968-ca-cert",                 /* 0x2B */
+        "application/x-x968-user-cert",               /* 0x2C */
+        "text/vnd.wap.si",                            /* 0x2D */
+        "application/vnd.wap.sic",                    /* 0x2E */
+        "text/vnd.wap.sl",                            /* 0x2F */
+        "application/vnd.wap.slc",                    /* 0x30 */
+        "text/vnd.wap.co",                            /* 0x31 */
+        "application/vnd.wap.coc",                    /* 0x32 */
+        "application/vnd.wap.multipart.related",      /* 0x33 */
+        "application/vnd.wap.sia",                    /* 0x34 */
+        "text/vnd.wap.connectivity-xml",              /* 0x35 */
+        "application/vnd.wap.connectivity-wbxml",     /* 0x36 */
+        "application/pkcs7-mime",                     /* 0x37 */
+        "application/vnd.wap.hashed-certificate",     /* 0x38 */
+        "application/vnd.wap.signed-certificate",     /* 0x39 */
+        "application/vnd.wap.cert-response",          /* 0x3A */
+        "application/xhtml+xml",                      /* 0x3B */
+        "application/wml+xml",                        /* 0x3C */
+        "text/css",                                   /* 0x3D */
+        "application/vnd.wap.mms-message",            /* 0x3E */
+        "application/vnd.wap.rollover-certificate",   /* 0x3F */
+        "application/vnd.wap.locc+wbxml",             /* 0x40 */
+        "application/vnd.wap.loc+xml",                /* 0x41 */
+        "application/vnd.syncml.dm+wbxml",            /* 0x42 */
+        "application/vnd.syncml.dm+xml",              /* 0x43 */
+        "application/vnd.syncml.notification",        /* 0x44 */
+        "application/vnd.wap.xhtml+xml",              /* 0x45 */
+        "application/vnd.wv.csp.cir",                 /* 0x46 */
+        "application/vnd.oma.dd+xml",                 /* 0x47 */
+        "application/vnd.oma.drm.message",            /* 0x48 */
+        "application/vnd.oma.drm.content",            /* 0x49 */
+        "application/vnd.oma.drm.rights+xml",         /* 0x4A */
+        "application/vnd.oma.drm.rights+wbxml",       /* 0x4B */
+        "application/vnd.wv.csp+xml",                 /* 0x4C */
+        "application/vnd.wv.csp+wbxml",               /* 0x4D */
+        "application/vnd.syncml.ds.notification",     /* 0x4E */
+        "audio/*",                                    /* 0x4F */
+        "video/*",                                    /* 0x50 */
+        "application/vnd.oma.dd2+xml",                /* 0x51 */
+        "application/mikey"                           /* 0x52 */
+    };
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/PduParser.java b/mms-common/java/com/android/mmscommon/mms/pdu/PduParser.java
new file mode 100644
index 0000000..9253f83
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/PduParser.java
@@ -0,0 +1,1873 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.ContentType;
+import com.android.mmscommon.CharacterSets;
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduParser {
+    /**
+     *  The next are WAP values defined in WSP specification.
+     */
+    private static final int QUOTE = 127;
+    private static final int LENGTH_QUOTE = 31;
+    private static final int TEXT_MIN = 32;
+    private static final int TEXT_MAX = 127;
+    private static final int SHORT_INTEGER_MAX = 127;
+    private static final int SHORT_LENGTH_MAX = 30;
+    private static final int LONG_INTEGER_LENGTH_MAX = 8;
+    private static final int QUOTED_STRING_FLAG = 34;
+    private static final int END_STRING_FLAG = 0x00;
+    //The next two are used by the interface "parseWapString" to
+    //distinguish Text-String and Quoted-String.
+    private static final int TYPE_TEXT_STRING = 0;
+    private static final int TYPE_QUOTED_STRING = 1;
+    private static final int TYPE_TOKEN_STRING = 2;
+
+    /**
+     * Specify the part position.
+     */
+    private static final int THE_FIRST_PART = 0;
+    private static final int THE_LAST_PART = 1;
+
+    /**
+     * The pdu data.
+     */
+    private ByteArrayInputStream mPduDataStream = null;
+
+    /**
+     * Store pdu headers
+     */
+    private PduHeaders mHeaders = null;
+
+    /**
+     * Store pdu parts.
+     */
+    private PduBody mBody = null;
+
+    /**
+     * Store the "type" parameter in "Content-Type" header field.
+     */
+    private static byte[] mTypeParam = null;
+
+    /**
+     * Store the "start" parameter in "Content-Type" header field.
+     */
+    private static byte[] mStartParam = null;
+
+    /**
+     * The log tag.
+     */
+    private static final String LOG_TAG = "PduParser";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    /**
+     * Constructor.
+     *
+     * @param pduDataStream pdu data to be parsed
+     */
+    public PduParser(byte[] pduDataStream) {
+        mPduDataStream = new ByteArrayInputStream(pduDataStream);
+    }
+
+    /**
+     * Parse the pdu.
+     *
+     * @return the pdu structure if parsing successfully.
+     *         null if parsing error happened or mandatory fields are not set.
+     */
+    public GenericPdu parse(){
+        if (mPduDataStream == null) {
+            return null;
+        }
+
+        /* parse headers */
+        mHeaders = parseHeaders(mPduDataStream);
+        if (null == mHeaders) {
+            // Parse headers failed.
+            return null;
+        }
+
+        /* get the message type */
+        int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check mandatory header fields */
+        if (false == checkMandatoryHeader(mHeaders)) {
+            log("check mandatory headers failed!");
+            return null;
+        }
+
+        if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
+                (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
+            /* need to parse the parts */
+            mBody = parseParts(mPduDataStream);
+            if (null == mBody) {
+                // Parse parts failed.
+                return null;
+            }
+        }
+
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                SendReq sendReq = new SendReq(mHeaders, mBody);
+                return sendReq;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                SendConf sendConf = new SendConf(mHeaders);
+                return sendConf;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                NotificationInd notificationInd =
+                    new NotificationInd(mHeaders);
+                return notificationInd;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                NotifyRespInd notifyRespInd =
+                    new NotifyRespInd(mHeaders);
+                return notifyRespInd;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                RetrieveConf retrieveConf =
+                    new RetrieveConf(mHeaders, mBody);
+
+                byte[] contentType = retrieveConf.getContentType();
+                if (null == contentType) {
+                    return null;
+                }
+                String ctTypeStr = new String(contentType);
+                if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
+                        || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
+                        || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+                    // The MMS content type must be "application/vnd.wap.multipart.mixed"
+                    // or "application/vnd.wap.multipart.related"
+                    // or "application/vnd.wap.multipart.alternative"
+                    return retrieveConf;
+                }
+                return null;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                DeliveryInd deliveryInd =
+                    new DeliveryInd(mHeaders);
+                return deliveryInd;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                AcknowledgeInd acknowledgeInd =
+                    new AcknowledgeInd(mHeaders);
+                return acknowledgeInd;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                ReadOrigInd readOrigInd =
+                    new ReadOrigInd(mHeaders);
+                return readOrigInd;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                ReadRecInd readRecInd =
+                    new ReadRecInd(mHeaders);
+                return readRecInd;
+            default:
+                log("Parser doesn't support this message type in this version!");
+            return null;
+        }
+    }
+
+    /**
+     * Parse pdu headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return headers in PduHeaders structure, null when parse fail
+     */
+    protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
+        if (pduDataStream == null) {
+            return null;
+        }
+
+        boolean keepParsing = true;
+        PduHeaders headers = new PduHeaders();
+
+        while (keepParsing && (pduDataStream.available() > 0)) {
+            int headerField = extractByteValue(pduDataStream);
+            switch (headerField) {
+                case PduHeaders.MESSAGE_TYPE:
+                {
+                    int messageType = extractByteValue(pduDataStream);
+                    switch (messageType) {
+                        // We don't support these kind of messages now.
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                            return null;
+                    }
+                    try {
+                        headers.setOctet(messageType, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + messageType +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+                /* Octect value */
+                case PduHeaders.REPORT_ALLOWED:
+                case PduHeaders.ADAPTATION_ALLOWED:
+                case PduHeaders.DELIVERY_REPORT:
+                case PduHeaders.DRM_CONTENT:
+                case PduHeaders.DISTRIBUTION_INDICATOR:
+                case PduHeaders.QUOTAS:
+                case PduHeaders.READ_REPORT:
+                case PduHeaders.STORE:
+                case PduHeaders.STORED:
+                case PduHeaders.TOTALS:
+                case PduHeaders.SENDER_VISIBILITY:
+                case PduHeaders.READ_STATUS:
+                case PduHeaders.CANCEL_STATUS:
+                case PduHeaders.PRIORITY:
+                case PduHeaders.STATUS:
+                case PduHeaders.REPLY_CHARGING:
+                case PduHeaders.MM_STATE:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
+                case PduHeaders.CONTENT_CLASS:
+                case PduHeaders.RETRIEVE_STATUS:
+                case PduHeaders.STORE_STATUS:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.RESPONSE_STATUS:
+                {
+                    int value = extractByteValue(pduDataStream);
+
+                    try {
+                        headers.setOctet(value, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + value +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Long-Integer */
+                case PduHeaders.DATE:
+                case PduHeaders.REPLY_CHARGING_SIZE:
+                case PduHeaders.MESSAGE_SIZE:
+                {
+                    try {
+                        long value = parseLongInteger(pduDataStream);
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Integer-Value */
+                case PduHeaders.MESSAGE_COUNT:
+                case PduHeaders.START:
+                case PduHeaders.LIMIT:
+                {
+                    try {
+                        long value = parseIntegerValue(pduDataStream);
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Text-String */
+                case PduHeaders.TRANSACTION_ID:
+                case PduHeaders.REPLY_CHARGING_ID:
+                case PduHeaders.AUX_APPLIC_ID:
+                case PduHeaders.APPLIC_ID:
+                case PduHeaders.REPLY_APPLIC_ID:
+                    /**
+                     * The next three header fields are email addresses
+                     * as defined in RFC2822,
+                     * not including the characters "<" and ">"
+                     */
+                case PduHeaders.MESSAGE_ID:
+                case PduHeaders.REPLACE_ID:
+                case PduHeaders.CANCEL_ID:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.CONTENT_LOCATION:
+                {
+                    byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if (null != value) {
+                        try {
+                            headers.setTextString(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Encoded-string-value */
+                case PduHeaders.SUBJECT:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
+                case PduHeaders.RETRIEVE_TEXT:
+                case PduHeaders.STATUS_TEXT:
+                case PduHeaders.STORE_STATUS_TEXT:
+                    /* the next one is not support
+                     * M-Mbox-Delete.conf and M-Delete.conf now */
+                case PduHeaders.RESPONSE_TEXT:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        try {
+                            headers.setEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch (RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Addressing model */
+                case PduHeaders.BCC:
+                case PduHeaders.CC:
+                case PduHeaders.TO:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        byte[] address = value.getTextString();
+                        if (null != address) {
+                            String str = new String(address);
+                            int endIndex = str.indexOf("/");
+                            if (endIndex > 0) {
+                                str = str.substring(0, endIndex);
+                            }
+                            try {
+                                value.setTextString(str.getBytes());
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                                return null;
+                            }
+                        }
+
+                        try {
+                            headers.appendEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Value-length
+                 * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
+                case PduHeaders.DELIVERY_TIME:
+                case PduHeaders.EXPIRY:
+                case PduHeaders.REPLY_CHARGING_DEADLINE:
+                {
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Absolute-token or Relative-token */
+                    int token = extractByteValue(pduDataStream);
+
+                    /* Date-value or Delta-seconds-value */
+                    long timeValue;
+                    try {
+                        timeValue = parseLongInteger(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
+                        /* need to convert the Delta-seconds-value
+                         * into Date-value */
+                        timeValue = System.currentTimeMillis()/1000 + timeValue;
+                    }
+
+                    try {
+                        headers.setLongInteger(timeValue, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.FROM: {
+                    /* From-value =
+                     * Value-length
+                     * (Address-present-token Encoded-string-value | Insert-address-token)
+                     */
+                    EncodedStringValue from = null;
+                    parseValueLength(pduDataStream); /* parse value-length */
+
+                    /* Address-present-token or Insert-address-token */
+                    int fromToken = extractByteValue(pduDataStream);
+
+                    /* Address-present-token or Insert-address-token */
+                    if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
+                        /* Encoded-string-value */
+                        from = parseEncodedStringValue(pduDataStream);
+                        if (null != from) {
+                            byte[] address = from.getTextString();
+                            if (null != address) {
+                                String str = new String(address);
+                                int endIndex = str.indexOf("/");
+                                if (endIndex > 0) {
+                                    str = str.substring(0, endIndex);
+                                }
+                                try {
+                                    from.setTextString(str.getBytes());
+                                } catch(NullPointerException e) {
+                                    log("null pointer error!");
+                                    return null;
+                                }
+                            }
+                        }
+                    } else {
+                        try {
+                            from = new EncodedStringValue(
+                                    PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
+                        } catch(NullPointerException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+
+                    try {
+                        headers.setEncodedStringValue(from, PduHeaders.FROM);
+                    } catch(NullPointerException e) {
+                        log("null pointer error!");
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Encoded-String-Value header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MESSAGE_CLASS: {
+                    /* Message-class-value = Class-identifier | Token-text */
+                    pduDataStream.mark(1);
+                    int messageClass = extractByteValue(pduDataStream);
+
+                    if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
+                        /* Class-identifier */
+                        try {
+                            if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            }
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    } else {
+                        /* Token-text */
+                        pduDataStream.reset();
+                        byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != messageClassString) {
+                            try {
+                                headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                            } catch(RuntimeException e) {
+                                log(headerField + "is not Text-String header field!");
+                                return null;
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.MMS_VERSION: {
+                    int version = parseShortInteger(pduDataStream);
+
+                    try {
+                        headers.setOctet(version, PduHeaders.MMS_VERSION);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + version +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_BY: {
+                    /* Previously-sent-by-value =
+                     * Value-length Forwarded-count-value Encoded-string-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* parse Encoded-string-value */
+                    EncodedStringValue previouslySentBy =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != previouslySentBy) {
+                        try {
+                            headers.setEncodedStringValue(previouslySentBy,
+                                    PduHeaders.PREVIOUSLY_SENT_BY);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_DATE: {
+                    /* Previously-sent-date-value =
+                     * Value-length Forwarded-count-value Date-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* Date-value */
+                    try {
+                        long perviouslySentDate = parseLongInteger(pduDataStream);
+                        headers.setLongInteger(perviouslySentDate,
+                                PduHeaders.PREVIOUSLY_SENT_DATE);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MM_FLAGS: {
+                    /* MM-flags-value =
+                     * Value-length
+                     * ( Add-token | Remove-token | Filter-token )
+                     * Encoded-string-value
+                     */
+
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Add-token | Remove-token | Filter-token */
+                    extractByteValue(pduDataStream);
+
+                    /* Encoded-string-value */
+                    parseEncodedStringValue(pduDataStream);
+
+                    /* not store this header filed in "headers",
+                     * because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                /* Value-length
+                 * (Message-total-token | Size-total-token) Integer-Value */
+                case PduHeaders.MBOX_TOTALS:
+                case PduHeaders.MBOX_QUOTAS:
+                {
+                    /* Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Message-total-token | Size-total-token */
+                    extractByteValue(pduDataStream);
+
+                    /*Integer-Value*/
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* not store these headers filed in "headers",
+                    because now PduHeaders doesn't support them */
+                    break;
+                }
+
+                case PduHeaders.ELEMENT_DESCRIPTOR: {
+                    parseContentType(pduDataStream, null);
+
+                    /* not store this header filed in "headers",
+                    because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                case PduHeaders.CONTENT_TYPE: {
+                    HashMap<Integer, Object> map =
+                        new HashMap<Integer, Object>();
+                    byte[] contentType =
+                        parseContentType(pduDataStream, map);
+
+                    if (null != contentType) {
+                        try {
+                            headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+
+                    /* get start parameter */
+                    mStartParam = (byte[]) map.get(PduPart.P_START);
+
+                    /* get charset parameter */
+                    mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
+
+                    keepParsing = false;
+                    break;
+                }
+
+                case PduHeaders.CONTENT:
+                case PduHeaders.ADDITIONAL_HEADERS:
+                case PduHeaders.ATTRIBUTES:
+                default: {
+                    log("Unknown header");
+                }
+            }
+        }
+
+        return headers;
+    }
+
+    /**
+     * Parse pdu parts.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return parts in PduBody structure
+     */
+    protected static PduBody parseParts(ByteArrayInputStream pduDataStream) {
+        if (pduDataStream == null) {
+            return null;
+        }
+
+        int count = parseUnsignedInt(pduDataStream); // get the number of parts
+        PduBody body = new PduBody();
+
+        for (int i = 0 ; i < count ; i++) {
+            int headerLength = parseUnsignedInt(pduDataStream);
+            int dataLength = parseUnsignedInt(pduDataStream);
+            PduPart part = new PduPart();
+            int startPos = pduDataStream.available();
+            if (startPos <= 0) {
+                // Invalid part.
+                return null;
+            }
+
+            /* parse part's content-type */
+            HashMap<Integer, Object> map = new HashMap<Integer, Object>();
+            byte[] contentType = parseContentType(pduDataStream, map);
+            if (null != contentType) {
+                part.setContentType(contentType);
+            } else {
+                part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
+            }
+
+            /* get name parameter */
+            byte[] name = (byte[]) map.get(PduPart.P_NAME);
+            if (null != name) {
+                part.setName(name);
+            }
+
+            /* get charset parameter */
+            Integer charset = (Integer) map.get(PduPart.P_CHARSET);
+            if (null != charset) {
+                part.setCharset(charset);
+            }
+
+            /* parse part's headers */
+            int endPos = pduDataStream.available();
+            int partHeaderLen = headerLength - (startPos - endPos);
+            if (partHeaderLen > 0) {
+                if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
+                    // Parse part header faild.
+                    return null;
+                }
+            } else if (partHeaderLen < 0) {
+                // Invalid length of content-type.
+                return null;
+            }
+
+            /* FIXME: check content-id, name, filename and content location,
+             * if not set anyone of them, generate a default content-location
+             */
+            if ((null == part.getContentLocation())
+                    && (null == part.getName())
+                    && (null == part.getFilename())
+                    && (null == part.getContentId())) {
+                part.setContentLocation(Long.toOctalString(
+                        System.currentTimeMillis()).getBytes());
+            }
+
+            /* get part's data */
+            if (dataLength > 0) {
+                byte[] partData = new byte[dataLength];
+                pduDataStream.read(partData, 0, dataLength);
+                // Check Content-Transfer-Encoding.
+                byte[] partDataEncoding = part.getContentTransferEncoding();
+                if (null != partDataEncoding) {
+                    String encoding = new String(partDataEncoding);
+                    if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+                        // Decode "base64" into "binary".
+                        partData = Base64.decodeBase64(partData);
+                    } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+                        // Decode "quoted-printable" into "binary".
+                        partData = QuotedPrintable.decodeQuotedPrintable(partData);
+                    } else {
+                        // "binary" is the default encoding.
+                    }
+                }
+                if (null == partData) {
+                    log("Decode part data error!");
+                    return null;
+                }
+                part.setData(partData);
+            }
+
+            /* add this part to body */
+            if (THE_FIRST_PART == checkPartPosition(part)) {
+                /* this is the first part */
+                body.addPart(0, part);
+            } else {
+                /* add the part to the end */
+                body.addPart(part);
+            }
+        }
+
+        return body;
+    }
+
+    /**
+     * Log status.
+     *
+     * @param text log information
+     */
+    private static void log(String text) {
+        if (LOCAL_LOGV) {
+            Log.v(LOG_TAG, text);
+        }
+    }
+
+    /**
+     * Parse unsigned integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer, -1 when failed
+     */
+    protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * The maximum size of a uintvar is 32 bits.
+         * So it will be encoded in no more than 5 octets.
+         */
+        assert(null != pduDataStream);
+        int result = 0;
+        int temp = pduDataStream.read();
+        if (temp == -1) {
+            return temp;
+        }
+
+        while((temp & 0x80) != 0) {
+            result = result << 7;
+            result |= temp & 0x7F;
+            temp = pduDataStream.read();
+            if (temp == -1) {
+                return temp;
+            }
+        }
+
+        result = result << 7;
+        result |= temp & 0x7F;
+
+        return result;
+    }
+
+    /**
+     * Parse value length.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer
+     */
+    protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Value-length = Short-length | (Length-quote Length)
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         * Uintvar-integer = 1*5 OCTET
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+
+        if (first <= SHORT_LENGTH_MAX) {
+            return first;
+        } else if (first == LENGTH_QUOTE) {
+            return parseUnsignedInt(pduDataStream);
+        }
+
+        throw new RuntimeException ("Value length > LENGTH_QUOTE!");
+    }
+
+    /**
+     * Parse encoded string value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the EncodedStringValue
+     */
+    protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        EncodedStringValue returnValue = null;
+        int charset = 0;
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+
+        pduDataStream.reset();
+        if (first < TEXT_MIN) {
+            parseValueLength(pduDataStream);
+
+            charset = parseShortInteger(pduDataStream); //get the "Charset"
+        }
+
+        byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+        try {
+            if (0 != charset) {
+                returnValue = new EncodedStringValue(charset, textString);
+            } else {
+                returnValue = new EncodedStringValue(textString);
+            }
+        } catch(Exception e) {
+            return null;
+        }
+
+        return returnValue;
+    }
+
+    /**
+     * Parse Text-String or Quoted-String.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
+     * @return the string without End-of-string in byte array
+     */
+    protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Text-string = [Quote] *TEXT End-of-string
+         * If the first character in the TEXT is in the range of 128-255,
+         * a Quote character must precede it.
+         * Otherwise the Quote character must be omitted.
+         * The Quote is not part of the contents.
+         * Quote = <Octet 127>
+         * End-of-string = <Octet 0>
+         *
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         *
+         * Token-text = Token End-of-string
+         */
+
+        // Mark supposed beginning of Text-string
+        // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
+        pduDataStream.mark(1);
+
+        // Check first char
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        if ((TYPE_QUOTED_STRING == stringType) &&
+                (QUOTED_STRING_FLAG == temp)) {
+            // Mark again if QUOTED_STRING_FLAG and ignore it
+            pduDataStream.mark(1);
+        } else if ((TYPE_TEXT_STRING == stringType) &&
+                (QUOTE == temp)) {
+            // Mark again if QUOTE and ignore it
+            pduDataStream.mark(1);
+        } else {
+            // Otherwise go back to origin
+            pduDataStream.reset();
+        }
+
+        // We are now definitely at the beginning of string
+        /**
+         * Return *TOKEN or *TEXT (Text-String without QUOTE,
+         * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
+         */
+        return getWapString(pduDataStream, stringType);
+    }
+
+    /**
+     * Check TOKEN data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TOKEN, false when ch is not TOKEN
+     */
+    protected static boolean isTokenCharacter(int ch) {
+        /**
+         * Token      = 1*<any CHAR except CTLs or separators>
+         * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
+         *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
+         *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
+         *            | "{"(123) | "}"(125) | SP(32) | HT(9)
+         * CHAR       = <any US-ASCII character (octets 0 - 127)>
+         * CTL        = <any US-ASCII control character
+         *            (octets 0 - 31) and DEL (127)>
+         * SP         = <US-ASCII SP, space (32)>
+         * HT         = <US-ASCII HT, horizontal-tab (9)>
+         */
+        if((ch < 33) || (ch > 126)) {
+            return false;
+        }
+
+        switch(ch) {
+            case '"': /* '"' */
+            case '(': /* '(' */
+            case ')': /* ')' */
+            case ',': /* ',' */
+            case '/': /* '/' */
+            case ':': /* ':' */
+            case ';': /* ';' */
+            case '<': /* '<' */
+            case '=': /* '=' */
+            case '>': /* '>' */
+            case '?': /* '?' */
+            case '@': /* '@' */
+            case '[': /* '[' */
+            case '\\': /* '\' */
+            case ']': /* ']' */
+            case '{': /* '{' */
+            case '}': /* '}' */
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check TEXT data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TEXT, false when ch is not TEXT
+     */
+    protected static boolean isText(int ch) {
+        /**
+         * TEXT = <any OCTET except CTLs,
+         *      but including LWS>
+         * CTL  = <any US-ASCII control character
+         *      (octets 0 - 31) and DEL (127)>
+         * LWS  = [CRLF] 1*( SP | HT )
+         * CRLF = CR LF
+         * CR   = <US-ASCII CR, carriage return (13)>
+         * LF   = <US-ASCII LF, linefeed (10)>
+         */
+        if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
+            return true;
+        }
+
+        switch(ch) {
+            case '\t': /* '\t' */
+            case '\n': /* '\n' */
+            case '\r': /* '\r' */
+                return true;
+        }
+
+        return false;
+    }
+
+    protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        while((-1 != temp) && ('\0' != temp)) {
+            // check each of the character
+            if (stringType == TYPE_TOKEN_STRING) {
+                if (isTokenCharacter(temp)) {
+                    out.write(temp);
+                }
+            } else {
+                if (isText(temp)) {
+                    out.write(temp);
+                }
+            }
+
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+        }
+
+        if (out.size() > 0) {
+            return out.toByteArray();
+        }
+
+        return null;
+    }
+
+    /**
+     * Extract a byte value from the input stream.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0xFF;
+    }
+
+    /**
+     * Parse Short-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Short-integer = OCTET
+         * Integers in range 0-127 shall be encoded as a one
+         * octet value with the most significant bit set to one (1xxx xxxx)
+         * and with the value in the remaining least significant bits.
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0x7F;
+    }
+
+    /**
+     * Parse Long-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Long-integer = Short-length Multi-octet-integer
+         * The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * The content octets shall be an unsigned integer value
+         * with the most significant octet encoded first (big-endian representation).
+         * The minimum number of octets must be used to encode the value.
+         * Short-length = <Any octet 0-30>
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int count = temp & 0xFF;
+
+        if (count > LONG_INTEGER_LENGTH_MAX) {
+            throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
+        }
+
+        long result = 0;
+
+        for (int i = 0 ; i < count ; i++) {
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            result <<= 8;
+            result += (temp & 0xFF);
+        }
+
+        return result;
+    }
+
+    /**
+     * Parse Integer-Value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Integer-Value = Short-integer | Long-integer
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+        if (temp > SHORT_INTEGER_MAX) {
+            return parseShortInteger(pduDataStream);
+        } else {
+            return parseLongInteger(pduDataStream);
+        }
+    }
+
+    /**
+     * To skip length of the wap value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param length area size
+     * @return the values in this area
+     */
+    protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
+        assert(null != pduDataStream);
+        byte[] area = new byte[length];
+        int readLen = pduDataStream.read(area, 0, length);
+        if (readLen < length) { //The actually read length is lower than the length
+            return -1;
+        } else {
+            return readLen;
+        }
+    }
+
+    /**
+     * Parse content type parameters. For now we just support
+     * four parameters used in mms: "type", "start", "name", "charset".
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters of Content-Type field
+     * @param length length of all the parameters
+     */
+    protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map, Integer length) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Parameter = Typed-parameter | Untyped-parameter
+         * Typed-parameter = Well-known-parameter-token Typed-value
+         * the actual expected type of the value is implied by the well-known parameter
+         * Well-known-parameter-token = Integer-value
+         * the code values used for parameters are specified in the Assigned Numbers appendix
+         * Typed-value = Compact-value | Text-value
+         * In addition to the expected type, there may be no value.
+         * If the value cannot be encoded using the expected type, it shall be encoded as text.
+         * Compact-value = Integer-value |
+         * Date-value | Delta-seconds-value | Q-value | Version-value |
+         * Uri-value
+         * Untyped-parameter = Token-text Untyped-value
+         * the type of the value is unknown, but it shall be encoded as an integer,
+         * if that is possible.
+         * Untyped-value = Integer-value | Text-value
+         */
+        assert(null != pduDataStream);
+        assert(length > 0);
+
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int param = pduDataStream.read();
+            assert(-1 != param);
+            lastLen--;
+
+            switch (param) {
+                /**
+                 * From rfc2387, chapter 3.1
+                 * The type parameter must be specified and its value is the MIME media
+                 * type of the "root" body part. It permits a MIME user agent to
+                 * determine the content-type without reference to the enclosed body
+                 * part. If the value of the type parameter and the root body part's
+                 * content-type differ then the User Agent's behavior is undefined.
+                 *
+                 * From wap-230-wsp-20010705-a.pdf
+                 * type = Constrained-encoding
+                 * Constrained-encoding = Extension-Media | Short-integer
+                 * Extension-media = *TEXT End-of-string
+                 */
+                case PduPart.P_TYPE:
+                case PduPart.P_CT_MR_TYPE:
+                    pduDataStream.mark(1);
+                    int first = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    if (first > TEXT_MAX) {
+                        // Short-integer (well-known type)
+                        int index = parseShortInteger(pduDataStream);
+
+                        if (index < PduContentTypes.contentTypes.length) {
+                            byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
+                            map.put(PduPart.P_TYPE, type);
+                        } else {
+                            //not support this type, ignore it.
+                        }
+                    } else {
+                        // Text-String (extension-media)
+                        byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if ((null != type) && (null != map)) {
+                            map.put(PduPart.P_TYPE, type);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
+                     * Start Parameter Referring to Presentation
+                     *
+                     * From rfc2387, chapter 3.2
+                     * The start parameter, if given, is the content-ID of the compound
+                     * object's "root". If not present the "root" is the first body part in
+                     * the Multipart/Related entity. The "root" is the element the
+                     * applications processes first.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * start = Text-String
+                     */
+                case PduPart.P_START:
+                case PduPart.P_DEP_START:
+                    byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != start) && (null != map)) {
+                        map.put(PduPart.P_START, start);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * In creation, the character set SHALL be either us-ascii
+                     * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
+                     * In retrieval, both us-ascii and utf-8 SHALL be supported.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * charset = Well-known-charset|Text-String
+                     * Well-known-charset = Any-charset | Integer-value
+                     * Both are encoded using values from Character Set
+                     * Assignments table in Assigned Numbers
+                     * Any-charset = <Octet 128>
+                     * Equivalent to the special RFC2616 charset value "*"
+                     */
+                case PduPart.P_CHARSET:
+                    pduDataStream.mark(1);
+                    int firstValue = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    //Check first char
+                    if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
+                            (END_STRING_FLAG == firstValue)) {
+                        //Text-String (extension-charset)
+                        byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        try {
+                            int charsetInt = CharacterSets.getMibEnumValue(
+                                    new String(charsetStr));
+                            map.put(PduPart.P_CHARSET, charsetInt);
+                        } catch (UnsupportedEncodingException e) {
+                            // Not a well-known charset, use "*".
+                            Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
+                            map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
+                        }
+                    } else {
+                        //Well-known-charset
+                        int charset = (int) parseIntegerValue(pduDataStream);
+                        if (map != null) {
+                            map.put(PduPart.P_CHARSET, charset);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * A name for multipart object SHALL be encoded using name-parameter
+                     * for Content-Type header in WSP multipart headers.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * name = Text-String
+                     */
+                case PduPart.P_DEP_NAME:
+                case PduPart.P_NAME:
+                    byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != name) && (null != map)) {
+                        map.put(PduPart.P_NAME, name);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+                default:
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "Not supported Content-Type parameter");
+                    }
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Content-Type");
+                } else {
+                    lastLen = 0;
+                }
+                break;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Content-Type");
+        }
+    }
+
+    /**
+     * Parse content type.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters in Content-Type header field
+     * @return Content-Type value
+     */
+    protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Content-type-value = Constrained-media | Content-general-form
+         * Content-general-form = Value-length Media-type
+         * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+         */
+        assert(null != pduDataStream);
+
+        byte[] contentType = null;
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+
+        int cur = (temp & 0xFF);
+
+        if (cur < TEXT_MIN) {
+            int length = parseValueLength(pduDataStream);
+            int startPos = pduDataStream.available();
+            pduDataStream.mark(1);
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            pduDataStream.reset();
+            int first = (temp & 0xFF);
+
+            if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
+                contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+            } else if (first > TEXT_MAX) {
+                int index = parseShortInteger(pduDataStream);
+
+                if (index < PduContentTypes.contentTypes.length) { //well-known type
+                    contentType = (PduContentTypes.contentTypes[index]).getBytes();
+                } else {
+                    pduDataStream.reset();
+                    contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                }
+            } else {
+                Log.e(LOG_TAG, "Corrupt content-type");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+
+            int endPos = pduDataStream.available();
+            int parameterLen = length - (startPos - endPos);
+            if (parameterLen > 0) {//have parameters
+                parseContentTypeParams(pduDataStream, map, parameterLen);
+            }
+
+            if (parameterLen < 0) {
+                Log.e(LOG_TAG, "Corrupt MMS message");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+        } else if (cur <= TEXT_MAX) {
+            contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+        } else {
+            contentType =
+                (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
+        }
+
+        return contentType;
+    }
+
+    /**
+     * Parse part's headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param part to store the header informations of the part
+     * @param length length of the headers
+     * @return true if parse successfully, false otherwise
+     */
+    protected static boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
+            PduPart part, int length) {
+        assert(null != pduDataStream);
+        assert(null != part);
+        assert(length > 0);
+
+        /**
+         * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
+         * A name for multipart object SHALL be encoded using name-parameter
+         * for Content-Type header in WSP multipart headers.
+         * In decoding, name-parameter of Content-Type SHALL be used if available.
+         * If name-parameter of Content-Type is not available,
+         * filename parameter of Content-Disposition header SHALL be used if available.
+         * If neither name-parameter of Content-Type header nor filename parameter
+         * of Content-Disposition header is available,
+         * Content-Location header SHALL be used if available.
+         *
+         * Within SMIL part the reference to the media object parts SHALL use
+         * either Content-ID or Content-Location mechanism [RFC2557]
+         * and the corresponding WSP part headers in media object parts
+         * contain the corresponding definitions.
+         */
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int header = pduDataStream.read();
+            assert(-1 != header);
+            lastLen--;
+
+            if (header > TEXT_MAX) {
+                // Number assigned headers.
+                switch (header) {
+                    case PduPart.P_CONTENT_LOCATION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-location-value = Uri-value
+                         */
+                        byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != contentLocation) {
+                            part.setContentLocation(contentLocation);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_CONTENT_ID:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-ID-value = Quoted-string
+                         */
+                        byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
+                        if (null != contentId) {
+                            part.setContentId(contentId);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_DEP_CONTENT_DISPOSITION:
+                    case PduPart.P_CONTENT_DISPOSITION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-disposition-value = Value-length Disposition *(Parameter)
+                         * Disposition = Form-data | Attachment | Inline | Token-text
+                         * Form-data = <Octet 128>
+                         * Attachment = <Octet 129>
+                         * Inline = <Octet 130>
+                         */
+                        int len = parseValueLength(pduDataStream);
+                        pduDataStream.mark(1);
+                        int thisStartPos = pduDataStream.available();
+                        int thisEndPos = 0;
+                        int value = pduDataStream.read();
+
+                        if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
+                            part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
+                        } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
+                            part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
+                        } else if (value == PduPart.P_DISPOSITION_INLINE) {
+                            part.setContentDisposition(PduPart.DISPOSITION_INLINE);
+                        } else {
+                            pduDataStream.reset();
+                            /* Token-text */
+                            part.setContentDisposition(parseWapString(pduDataStream, TYPE_TEXT_STRING));
+                        }
+
+                        /* get filename parameter and skip other parameters */
+                        thisEndPos = pduDataStream.available();
+                        if (thisStartPos - thisEndPos < len) {
+                            value = pduDataStream.read();
+                            if (value == PduPart.P_FILENAME) { //filename is text-string
+                                part.setFilename(parseWapString(pduDataStream, TYPE_TEXT_STRING));
+                            }
+
+                            /* skip other parameters */
+                            thisEndPos = pduDataStream.available();
+                            if (thisStartPos - thisEndPos < len) {
+                                int last = len - (thisStartPos - thisEndPos);
+                                byte[] temp = new byte[last];
+                                pduDataStream.read(temp, 0, last);
+                            }
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    default:
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                        }
+                    if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                        Log.e(LOG_TAG, "Corrupt Part headers");
+                        return false;
+                    }
+                    lastLen = 0;
+                    break;
+                }
+            } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
+                // Not assigned header.
+                byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+                // Check the header whether it is "Content-Transfer-Encoding".
+                if (true ==
+                    PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
+                    part.setContentTransferEncoding(tempValue);
+                }
+
+                tempPos = pduDataStream.available();
+                lastLen = length - (startPos - tempPos);
+            } else {
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                }
+                // Skip all headers of this part.
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Part headers");
+                    return false;
+                }
+                lastLen = 0;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Part headers");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check the position of a specified part.
+     *
+     * @param part the part to be checked
+     * @return part position, THE_FIRST_PART when it's the
+     * first one, THE_LAST_PART when it's the last one.
+     */
+    private static int checkPartPosition(PduPart part) {
+        assert(null != part);
+        if ((null == mTypeParam) &&
+                (null == mStartParam)) {
+            return THE_LAST_PART;
+        }
+
+        /* check part's content-id */
+        if (null != mStartParam) {
+            byte[] contentId = part.getContentId();
+            if (null != contentId) {
+                if (true == Arrays.equals(mStartParam, contentId)) {
+                    return THE_FIRST_PART;
+                }
+            }
+        }
+
+        /* check part's content-type */
+        if (null != mTypeParam) {
+            byte[] contentType = part.getContentType();
+            if (null != contentType) {
+                if (true == Arrays.equals(mTypeParam, contentType)) {
+                    return THE_FIRST_PART;
+                }
+            }
+        }
+
+        return THE_LAST_PART;
+    }
+
+    /**
+     * Check mandatory headers of a pdu.
+     *
+     * @param headers pdu headers
+     * @return true if the pdu has all of the mandatory headers, false otherwise.
+     */
+    protected static boolean checkMandatoryHeader(PduHeaders headers) {
+        if (null == headers) {
+            return false;
+        }
+
+        /* get message type */
+        int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check Mms-Version field */
+        int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
+        if (0 == mmsVersion) {
+            // Every message should have Mms-Version field.
+            return false;
+        }
+
+        /* check mandatory header fields */
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                // Content-Type field.
+                byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == srContentType) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == srFrom) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == srTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                // Response-Status field.
+                int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
+                if (0 == scResponseStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == scTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                // Content-Location field.
+                byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
+                if (null == niContentLocation) {
+                    return false;
+                }
+
+                // Expiry field.
+                long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
+                if (-1 == niExpiry) {
+                    return false;
+                }
+
+                // Message-Class field.
+                byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
+                if (null == niMessageClass) {
+                    return false;
+                }
+
+                // Message-Size field.
+                long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
+                if (-1 == niMessageSize) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == niTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                // Status field.
+                int nriStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == nriStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == nriTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                // Content-Type field.
+                byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == rcContentType) {
+                    return false;
+                }
+
+                // Date field.
+                long rcDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == rcDate) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                // Date field.
+                long diDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == diDate) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == diMessageId) {
+                    return false;
+                }
+
+                // Status field.
+                int diStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == diStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == diTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                // Transaction-Id field.
+                byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == aiTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                // Date field.
+                long roDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == roDate) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == roFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == roMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == roReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == roTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                // From field.
+                EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == rrFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == rrMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == rrReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == rrTo) {
+                    return false;
+                }
+
+                break;
+            default:
+                // Parser doesn't support this message type in this version.
+                return false;
+        }
+
+        return true;
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/PduPart.java b/mms-common/java/com/android/mmscommon/mms/pdu/PduPart.java
new file mode 100644
index 0000000..7d51b86
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/PduPart.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import android.net.Uri;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The pdu part.
+ */
+public class PduPart {
+    /**
+     * Well-Known Parameters.
+     */
+    public static final int P_Q                  = 0x80;
+    public static final int P_CHARSET            = 0x81;
+    public static final int P_LEVEL              = 0x82;
+    public static final int P_TYPE               = 0x83;
+    public static final int P_DEP_NAME           = 0x85;
+    public static final int P_DEP_FILENAME       = 0x86;
+    public static final int P_DIFFERENCES        = 0x87;
+    public static final int P_PADDING            = 0x88;
+    // This value of "TYPE" s used with Content-Type: multipart/related
+    public static final int P_CT_MR_TYPE         = 0x89;
+    public static final int P_DEP_START          = 0x8A;
+    public static final int P_DEP_START_INFO     = 0x8B;
+    public static final int P_DEP_COMMENT        = 0x8C;
+    public static final int P_DEP_DOMAIN         = 0x8D;
+    public static final int P_MAX_AGE            = 0x8E;
+    public static final int P_DEP_PATH           = 0x8F;
+    public static final int P_SECURE             = 0x90;
+    public static final int P_SEC                = 0x91;
+    public static final int P_MAC                = 0x92;
+    public static final int P_CREATION_DATE      = 0x93;
+    public static final int P_MODIFICATION_DATE  = 0x94;
+    public static final int P_READ_DATE          = 0x95;
+    public static final int P_SIZE               = 0x96;
+    public static final int P_NAME               = 0x97;
+    public static final int P_FILENAME           = 0x98;
+    public static final int P_START              = 0x99;
+    public static final int P_START_INFO         = 0x9A;
+    public static final int P_COMMENT            = 0x9B;
+    public static final int P_DOMAIN             = 0x9C;
+    public static final int P_PATH               = 0x9D;
+
+    /**
+     *  Header field names.
+     */
+     public static final int P_CONTENT_TYPE       = 0x91;
+     public static final int P_CONTENT_LOCATION   = 0x8E;
+     public static final int P_CONTENT_ID         = 0xC0;
+     public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
+     public static final int P_CONTENT_DISPOSITION = 0xC5;
+    // The next header is unassigned header, use reserved header(0x48) value.
+     public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
+
+     /**
+      * Content=Transfer-Encoding string.
+      */
+     public static final String CONTENT_TRANSFER_ENCODING =
+             "Content-Transfer-Encoding";
+
+     /**
+      * Value of Content-Transfer-Encoding.
+      */
+     public static final String P_BINARY = "binary";
+     public static final String P_7BIT = "7bit";
+     public static final String P_8BIT = "8bit";
+     public static final String P_BASE64 = "base64";
+     public static final String P_QUOTED_PRINTABLE = "quoted-printable";
+
+     /**
+      * Value of disposition can be set to PduPart when the value is octet in
+      * the PDU.
+      * "from-data" instead of Form-data<Octet 128>.
+      * "attachment" instead of Attachment<Octet 129>.
+      * "inline" instead of Inline<Octet 130>.
+      */
+     static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
+     static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
+     static final byte[] DISPOSITION_INLINE = "inline".getBytes();
+
+     /**
+      * Content-Disposition value.
+      */
+     public static final int P_DISPOSITION_FROM_DATA  = 0x80;
+     public static final int P_DISPOSITION_ATTACHMENT = 0x81;
+     public static final int P_DISPOSITION_INLINE     = 0x82;
+
+     /**
+      * Header of part.
+      */
+     private Map<Integer, Object> mPartHeader = null;
+
+     /**
+      * Data uri.
+      */
+     private Uri mUri = null;
+
+     /**
+      * Part data.
+      */
+     private byte[] mPartData = null;
+
+     private static final String TAG = "PduPart";
+
+     /**
+      * Empty Constructor.
+      */
+     public PduPart() {
+         mPartHeader = new HashMap<Integer, Object>();
+     }
+
+     /**
+      * Set part data. The data are stored as byte array.
+      *
+      * @param data the data
+      */
+     public void setData(byte[] data) {
+         if(data == null) {
+            return;
+        }
+
+         mPartData = new byte[data.length];
+         System.arraycopy(data, 0, mPartData, 0, data.length);
+     }
+
+     /**
+      * @return A copy of the part data or null if the data wasn't set or
+      *         the data is stored as Uri.
+      * @see #getDataUri
+      */
+     public byte[] getData() {
+         if(mPartData == null) {
+            return null;
+         }
+
+         byte[] byteArray = new byte[mPartData.length];
+         System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length);
+         return byteArray;
+     }
+
+     /**
+      * Set data uri. The data are stored as Uri.
+      *
+      * @param uri the uri
+      */
+     public void setDataUri(Uri uri) {
+         mUri = uri;
+     }
+
+     /**
+      * @return The Uri of the part data or null if the data wasn't set or
+      *         the data is stored as byte array.
+      * @see #getData
+      */
+     public Uri getDataUri() {
+         return mUri;
+     }
+
+     /**
+      * Set Content-id value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentId(byte[] contentId) {
+         if((contentId == null) || (contentId.length == 0)) {
+             throw new IllegalArgumentException(
+                     "Content-Id may not be null or empty.");
+         }
+
+         if ((contentId.length > 1)
+                 && ((char) contentId[0] == '<')
+                 && ((char) contentId[contentId.length - 1] == '>')) {
+             mPartHeader.put(P_CONTENT_ID, contentId);
+             return;
+         }
+
+         // Insert beginning '<' and trailing '>' for Content-Id.
+         byte[] buffer = new byte[contentId.length + 2];
+         buffer[0] = (byte) (0xff & '<');
+         buffer[buffer.length - 1] = (byte) (0xff & '>');
+         System.arraycopy(contentId, 0, buffer, 1, contentId.length);
+         mPartHeader.put(P_CONTENT_ID, buffer);
+     }
+
+     /**
+      * Get Content-id value.
+      *
+      * @return the value
+      */
+     public byte[] getContentId() {
+         return (byte[]) mPartHeader.get(P_CONTENT_ID);
+     }
+
+     /**
+      * Set Char-set value.
+      *
+      * @param charset the value
+      */
+     public void setCharset(int charset) {
+         mPartHeader.put(P_CHARSET, charset);
+     }
+
+     /**
+      * Get Char-set value
+      *
+      * @return the charset value. Return 0 if charset was not set.
+      */
+     public int getCharset() {
+         Integer charset = (Integer) mPartHeader.get(P_CHARSET);
+         if(charset == null) {
+             return 0;
+         } else {
+             return charset.intValue();
+         }
+     }
+
+     /**
+      * Set Content-Location value.
+      *
+      * @param contentLocation the value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentLocation(byte[] contentLocation) {
+         if(contentLocation == null) {
+             throw new NullPointerException("null content-location");
+         }
+
+         mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
+     }
+
+     /**
+      * Get Content-Location value.
+      *
+      * @return the value
+      *     return PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      *     return PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      *     return PduPart.disposition[2] instead of <Octet 130> (Inline).
+      */
+     public byte[] getContentLocation() {
+         return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+     }
+
+     /**
+      * Set Content-Disposition value.
+      * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
+      *
+      * @param contentDisposition the value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentDisposition(byte[] contentDisposition) {
+         if(contentDisposition == null) {
+             throw new NullPointerException("null content-disposition");
+         }
+
+         mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
+     }
+
+     /**
+      * Get Content-Disposition value.
+      *
+      * @return the value
+      */
+     public byte[] getContentDisposition() {
+         return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
+     }
+
+     /**
+      *  Set Content-Type value.
+      *
+      *  @param value the value
+      *  @throws NullPointerException if the value is null.
+      */
+     public void setContentType(byte[] contentType) {
+         if(contentType == null) {
+             throw new NullPointerException("null content-type");
+         }
+
+         mPartHeader.put(P_CONTENT_TYPE, contentType);
+     }
+
+     /**
+      * Get Content-Type value of part.
+      *
+      * @return the value
+      */
+     public byte[] getContentType() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
+     }
+
+     /**
+      * Set Content-Transfer-Encoding value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setContentTransferEncoding(byte[] contentTransferEncoding) {
+         if(contentTransferEncoding == null) {
+             throw new NullPointerException("null content-transfer-encoding");
+         }
+
+         mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
+     }
+
+     /**
+      * Get Content-Transfer-Encoding value.
+      *
+      * @return the value
+      */
+     public byte[] getContentTransferEncoding() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
+     }
+
+     /**
+      * Set Content-type parameter: name.
+      *
+      * @param name the name value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setName(byte[] name) {
+         if(null == name) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_NAME, name);
+     }
+
+     /**
+      *  Get content-type parameter: name.
+      *
+      *  @return the name
+      */
+     public byte[] getName() {
+         return (byte[]) mPartHeader.get(P_NAME);
+     }
+
+     /**
+      * Get Content-disposition parameter: filename
+      *
+      * @param fileName the filename value
+      * @throws NullPointerException if the value is null.
+      */
+     public void setFilename(byte[] fileName) {
+         if(null == fileName) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_FILENAME, fileName);
+     }
+
+     /**
+      * Set Content-disposition parameter: filename
+      *
+      * @return the filename
+      */
+     public byte[] getFilename() {
+         return (byte[]) mPartHeader.get(P_FILENAME);
+     }
+
+    public String generateLocation() {
+        // Assumption: At least one of the content-location / name / filename
+        // or content-id should be set. This is guaranteed by the PduParser
+        // for incoming messages and by MM composer for outgoing messages.
+        byte[] location = (byte[]) mPartHeader.get(P_NAME);
+        if(null == location) {
+            location = (byte[]) mPartHeader.get(P_FILENAME);
+
+            if (null == location) {
+                location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+            }
+        }
+
+        if (null == location) {
+            byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
+            return "cid:" + new String(contentId);
+        } else {
+            return new String(location);
+        }
+    }
+}
+
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/PduPersister.java b/mms-common/java/com/android/mmscommon/mms/pdu/PduPersister.java
new file mode 100644
index 0000000..46f28c7f
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/PduPersister.java
@@ -0,0 +1,1266 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.mms.pdu.PduPersister;
+
+import com.android.mmscommon.ContentType;
+import com.android.mmscommon.CharacterSets;
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.MmsException;
+import com.android.mmscommon.PduHeaders;
+import com.android.mmscommon.mms.util.PduCache;
+import com.android.mmscommon.mms.util.PduCacheEntry;
+import android.database.sqlite.SqliteWrapper;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import com.android.mmscommon.telephony.TelephonyProvider;
+import com.android.mmscommon.telephony.TelephonyProvider.Mms;
+import com.android.mmscommon.telephony.TelephonyProvider.MmsSms;
+import com.android.mmscommon.telephony.TelephonyProvider.Threads;
+import com.android.mmscommon.telephony.TelephonyProvider.Mms.Addr;
+import com.android.mmscommon.telephony.TelephonyProvider.Mms.Part;
+import com.android.mmscommon.telephony.TelephonyProvider.MmsSms.PendingMessages;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+
+/**
+ * This class is the high-level manager of PDU storage.
+ */
+public class PduPersister {
+    private static final String TAG = "PduPersister";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
+
+    /**
+     * The uri of temporary drm objects.
+     */
+    public static final String TEMPORARY_DRM_OBJECT_URI =
+        "content://mms/" + Long.MAX_VALUE + "/part";
+    /**
+     * Indicate that we transiently failed to process a MM.
+     */
+    public static final int PROC_STATUS_TRANSIENT_FAILURE   = 1;
+    /**
+     * Indicate that we permanently failed to process a MM.
+     */
+    public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
+    /**
+     * Indicate that we have successfully processed a MM.
+     */
+    public static final int PROC_STATUS_COMPLETED           = 3;
+
+    private static PduPersister sPersister;
+    private static final PduCache PDU_CACHE_INSTANCE;
+
+    private static final int[] ADDRESS_FIELDS = new int[] {
+            PduHeaders.BCC,
+            PduHeaders.CC,
+            PduHeaders.FROM,
+            PduHeaders.TO
+    };
+
+    private static final String[] PDU_PROJECTION = new String[] {
+        Mms._ID,
+        Mms.MESSAGE_BOX,
+        Mms.THREAD_ID,
+        Mms.RETRIEVE_TEXT,
+        Mms.SUBJECT,
+        Mms.CONTENT_LOCATION,
+        Mms.CONTENT_TYPE,
+        Mms.MESSAGE_CLASS,
+        Mms.MESSAGE_ID,
+        Mms.RESPONSE_TEXT,
+        Mms.TRANSACTION_ID,
+        Mms.CONTENT_CLASS,
+        Mms.DELIVERY_REPORT,
+        Mms.MESSAGE_TYPE,
+        Mms.MMS_VERSION,
+        Mms.PRIORITY,
+        Mms.READ_REPORT,
+        Mms.READ_STATUS,
+        Mms.REPORT_ALLOWED,
+        Mms.RETRIEVE_STATUS,
+        Mms.STATUS,
+        Mms.DATE,
+        Mms.DELIVERY_TIME,
+        Mms.EXPIRY,
+        Mms.MESSAGE_SIZE,
+        Mms.SUBJECT_CHARSET,
+        Mms.RETRIEVE_TEXT_CHARSET,
+    };
+
+    private static final int PDU_COLUMN_ID                    = 0;
+    private static final int PDU_COLUMN_MESSAGE_BOX           = 1;
+    private static final int PDU_COLUMN_THREAD_ID             = 2;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT         = 3;
+    private static final int PDU_COLUMN_SUBJECT               = 4;
+    private static final int PDU_COLUMN_CONTENT_LOCATION      = 5;
+    private static final int PDU_COLUMN_CONTENT_TYPE          = 6;
+    private static final int PDU_COLUMN_MESSAGE_CLASS         = 7;
+    private static final int PDU_COLUMN_MESSAGE_ID            = 8;
+    private static final int PDU_COLUMN_RESPONSE_TEXT         = 9;
+    private static final int PDU_COLUMN_TRANSACTION_ID        = 10;
+    private static final int PDU_COLUMN_CONTENT_CLASS         = 11;
+    private static final int PDU_COLUMN_DELIVERY_REPORT       = 12;
+    private static final int PDU_COLUMN_MESSAGE_TYPE          = 13;
+    private static final int PDU_COLUMN_MMS_VERSION           = 14;
+    private static final int PDU_COLUMN_PRIORITY              = 15;
+    private static final int PDU_COLUMN_READ_REPORT           = 16;
+    private static final int PDU_COLUMN_READ_STATUS           = 17;
+    private static final int PDU_COLUMN_REPORT_ALLOWED        = 18;
+    private static final int PDU_COLUMN_RETRIEVE_STATUS       = 19;
+    private static final int PDU_COLUMN_STATUS                = 20;
+    private static final int PDU_COLUMN_DATE                  = 21;
+    private static final int PDU_COLUMN_DELIVERY_TIME         = 22;
+    private static final int PDU_COLUMN_EXPIRY                = 23;
+    private static final int PDU_COLUMN_MESSAGE_SIZE          = 24;
+    private static final int PDU_COLUMN_SUBJECT_CHARSET       = 25;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
+
+    private static final String[] PART_PROJECTION = new String[] {
+        Part._ID,
+        Part.CHARSET,
+        Part.CONTENT_DISPOSITION,
+        Part.CONTENT_ID,
+        Part.CONTENT_LOCATION,
+        Part.CONTENT_TYPE,
+        Part.FILENAME,
+        Part.NAME,
+        Part.TEXT
+    };
+
+    private static final int PART_COLUMN_ID                  = 0;
+    private static final int PART_COLUMN_CHARSET             = 1;
+    private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
+    private static final int PART_COLUMN_CONTENT_ID          = 3;
+    private static final int PART_COLUMN_CONTENT_LOCATION    = 4;
+    private static final int PART_COLUMN_CONTENT_TYPE        = 5;
+    private static final int PART_COLUMN_FILENAME            = 6;
+    private static final int PART_COLUMN_NAME                = 7;
+    private static final int PART_COLUMN_TEXT                = 8;
+
+    private static final HashMap<Uri, Integer> MESSAGE_BOX_MAP;
+    // These map are used for convenience in persist() and load().
+    private static final HashMap<Integer, Integer> CHARSET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> ENCODED_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> TEXT_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> OCTET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> LONG_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, String> CHARSET_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> ENCODED_STRING_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> TEXT_STRING_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> OCTET_COLUMN_NAME_MAP;
+    private static final HashMap<Integer, String> LONG_COLUMN_NAME_MAP;
+
+    static {
+        MESSAGE_BOX_MAP = new HashMap<Uri, Integer>();
+        MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI,  Mms.MESSAGE_BOX_INBOX);
+        MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI,   Mms.MESSAGE_BOX_SENT);
+        MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI,  Mms.MESSAGE_BOX_DRAFTS);
+        MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
+
+        CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
+
+        CHARSET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
+
+        // Encoded string field code -> column index/name map.
+        ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
+
+        ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
+
+        // Text string field code -> column index/name map.
+        TEXT_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
+
+        TEXT_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
+
+        // Octet field code -> column index/name map.
+        OCTET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
+
+        OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
+
+        // Long field code -> column index/name map.
+        LONG_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
+
+        LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
+
+        PDU_CACHE_INSTANCE = PduCache.getInstance();
+     }
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+
+    private PduPersister(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+     }
+
+    /** Get(or create if not exist) an instance of PduPersister */
+    public static PduPersister getPduPersister(Context context) {
+        if ((sPersister == null) || !context.equals(sPersister.mContext)) {
+            sPersister = new PduPersister(context);
+        }
+
+        return sPersister;
+    }
+
+    private void setEncodedStringValueToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if ((s != null) && (s.length() > 0)) {
+            int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
+            int charset = c.getInt(charsetColumnIndex);
+            EncodedStringValue value = new EncodedStringValue(
+                    charset, getBytes(s));
+            headers.setEncodedStringValue(value, mapColumn);
+        }
+    }
+
+    private void setTextStringToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if (s != null) {
+            headers.setTextString(getBytes(s), mapColumn);
+        }
+    }
+
+    private void setOctetToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) throws InvalidHeaderValueException {
+        if (!c.isNull(columnIndex)) {
+            int b = c.getInt(columnIndex);
+            headers.setOctet(b, mapColumn);
+        }
+    }
+
+    private void setLongToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        if (!c.isNull(columnIndex)) {
+            long l = c.getLong(columnIndex);
+            headers.setLongInteger(l, mapColumn);
+        }
+    }
+
+    private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return c.getInt(columnIndex);
+        }
+        return null;
+    }
+
+    private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return getBytes(c.getString(columnIndex));
+        }
+        return null;
+    }
+
+    private PduPart[] loadParts(long msgId) throws MmsException {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/part"),
+                PART_PROJECTION, null, null, null);
+
+        PduPart[] parts = null;
+
+        try {
+            if ((c == null) || (c.getCount() == 0)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "loadParts(" + msgId + "): no part to load.");
+                }
+                return null;
+            }
+
+            int partCount = c.getCount();
+            int partIdx = 0;
+            parts = new PduPart[partCount];
+            while (c.moveToNext()) {
+                PduPart part = new PduPart();
+                Integer charset = getIntegerFromPartColumn(
+                        c, PART_COLUMN_CHARSET);
+                if (charset != null) {
+                    part.setCharset(charset);
+                }
+
+                byte[] contentDisposition = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_DISPOSITION);
+                if (contentDisposition != null) {
+                    part.setContentDisposition(contentDisposition);
+                }
+
+                byte[] contentId = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_ID);
+                if (contentId != null) {
+                    part.setContentId(contentId);
+                }
+
+                byte[] contentLocation = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_LOCATION);
+                if (contentLocation != null) {
+                    part.setContentLocation(contentLocation);
+                }
+
+                byte[] contentType = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_TYPE);
+                if (contentType != null) {
+                    part.setContentType(contentType);
+                } else {
+                    throw new MmsException("Content-Type must be set.");
+                }
+
+                byte[] fileName = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_FILENAME);
+                if (fileName != null) {
+                    part.setFilename(fileName);
+                }
+
+                byte[] name = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_NAME);
+                if (name != null) {
+                    part.setName(name);
+                }
+
+                // Construct a Uri for this part.
+                long partId = c.getLong(PART_COLUMN_ID);
+                Uri partURI = Uri.parse("content://mms/part/" + partId);
+                part.setDataUri(partURI);
+
+                // For images/audio/video, we won't keep their data in Part
+                // because their renderer accept Uri as source.
+                String type = toIsoString(contentType);
+                if (!ContentType.isImageType(type)
+                        && !ContentType.isAudioType(type)
+                        && !ContentType.isVideoType(type)) {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    InputStream is = null;
+
+                    // Store simple string values directly in the database instead of an
+                    // external file.  This makes the text searchable and retrieval slightly
+                    // faster.
+                    if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
+                            || ContentType.TEXT_HTML.equals(type)) {
+                        String text = c.getString(PART_COLUMN_TEXT);
+                        if (text == null) {
+                            text = "";
+                        }
+                        byte [] blob = new EncodedStringValue(text).getTextString();
+                        baos.write(blob, 0, blob.length);
+                    } else {
+
+                        try {
+                            is = mContentResolver.openInputStream(partURI);
+
+                            byte[] buffer = new byte[256];
+                            int len = is.read(buffer);
+                            while (len >= 0) {
+                                baos.write(buffer, 0, len);
+                                len = is.read(buffer);
+                            }
+                        } catch (IOException e) {
+                            Log.e(TAG, "Failed to load part data", e);
+                            c.close();
+                            throw new MmsException(e);
+                        } finally {
+                            if (is != null) {
+                                try {
+                                    is.close();
+                                } catch (IOException e) {
+                                    Log.e(TAG, "Failed to close stream", e);
+                                } // Ignore
+                            }
+                        }
+                    }
+                    part.setData(baos.toByteArray());
+                }
+                parts[partIdx++] = part;
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        return parts;
+    }
+
+    private void loadAddress(long msgId, PduHeaders headers) {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE },
+                null, null, null);
+
+        if (c != null) {
+            try {
+                while (c.moveToNext()) {
+                    String addr = c.getString(0);
+                    if (!TextUtils.isEmpty(addr)) {
+                        int addrType = c.getInt(2);
+                        switch (addrType) {
+                            case PduHeaders.FROM:
+                                headers.setEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            case PduHeaders.TO:
+                            case PduHeaders.CC:
+                            case PduHeaders.BCC:
+                                headers.appendEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            default:
+                                Log.e(TAG, "Unknown address type: " + addrType);
+                                break;
+                        }
+                    }
+                }
+            } finally {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Load a PDU from storage by given Uri.
+     *
+     * @param uri The Uri of the PDU to be loaded.
+     * @return A generic PDU object, it may be cast to dedicated PDU.
+     * @throws MmsException Failed to load some fields of a PDU.
+     */
+    public GenericPdu load(Uri uri) throws MmsException {
+        PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+        if (cacheEntry != null) {
+            return cacheEntry.getPdu();
+        }
+
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
+                        PDU_PROJECTION, null, null, null);
+        PduHeaders headers = new PduHeaders();
+        Set<Entry<Integer, Integer>> set;
+        long msgId = ContentUris.parseId(uri);
+        int msgBox;
+        long threadId;
+
+        try {
+            if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
+                throw new MmsException("Bad uri: " + uri);
+            }
+
+            msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
+            threadId = c.getLong(PDU_COLUMN_THREAD_ID);
+
+            set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setEncodedStringValueToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+
+            set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setTextStringToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+
+            set = OCTET_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setOctetToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+
+            set = LONG_COLUMN_INDEX_MAP.entrySet();
+            for (Entry<Integer, Integer> e : set) {
+                setLongToHeaders(
+                        c, e.getValue(), headers, e.getKey());
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        // Check whether 'msgId' has been assigned a valid value.
+        if (msgId == -1L) {
+            throw new MmsException("Error! ID of the message: -1.");
+        }
+
+        // Load address information of the MM.
+        loadAddress(msgId, headers);
+
+        int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+        PduBody body = new PduBody();
+
+        // For PDU which type is M_retrieve.conf or Send.req, we should
+        // load multiparts and put them into the body of the PDU.
+        if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+            PduPart[] parts = loadParts(msgId);
+            if (parts != null) {
+                int partsNum = parts.length;
+                for (int i = 0; i < partsNum; i++) {
+                    body.addPart(parts[i]);
+                }
+            }
+        }
+
+        GenericPdu pdu = null;
+        switch (msgType) {
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                pdu = new NotificationInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                pdu = new DeliveryInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                pdu = new ReadOrigInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                pdu = new RetrieveConf(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                pdu = new SendReq(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                pdu = new AcknowledgeInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                pdu = new NotifyRespInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                pdu = new ReadRecInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+            case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                throw new MmsException(
+                        "Unsupported PDU type: " + Integer.toHexString(msgType));
+
+            default:
+                throw new MmsException(
+                        "Unrecognized PDU type: " + Integer.toHexString(msgType));
+        }
+
+        cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
+        PDU_CACHE_INSTANCE.put(uri, cacheEntry);
+        return pdu;
+    }
+
+    private void persistAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        ContentValues values = new ContentValues(3);
+
+        for (EncodedStringValue addr : array) {
+            values.clear(); // Clear all values first.
+            values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
+            values.put(Addr.CHARSET, addr.getCharacterSet());
+            values.put(Addr.TYPE, type);
+
+            Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
+            SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        }
+    }
+
+    public Uri persistPart(PduPart part, long msgId)
+            throws MmsException {
+        Uri uri = Uri.parse("content://mms/" + msgId + "/part");
+        ContentValues values = new ContentValues(8);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = null;
+        if (part.getContentType() != null) {
+            contentType = toIsoString(part.getContentType());
+            values.put(Part.CONTENT_TYPE, contentType);
+            // To ensure the SMIL part is always the first part.
+            if (ContentType.APP_SMIL.equals(contentType)) {
+                values.put(Part.SEQ, -1);
+            }
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        if (res == null) {
+            throw new MmsException("Failed to persist part, return null.");
+        }
+
+        persistData(part, res, contentType);
+        // After successfully store the data, we should update
+        // the dataUri of the part.
+        part.setDataUri(res);
+
+        return res;
+    }
+
+    /**
+     * Save data of the part into storage. The source data may be given
+     * by a byte[] or a Uri. If it's a byte[], directly save it
+     * into storage, otherwise load source data from the dataUri and then
+     * save it. If the data is an image, we may scale down it according
+     * to user preference.
+     *
+     * @param part The PDU part which contains data to be saved.
+     * @param uri The URI of the part.
+     * @param contentType The MIME type of the part.
+     * @throws MmsException Cannot find source data or error occurred
+     *         while saving the data.
+     */
+    private void persistData(PduPart part, Uri uri,
+            String contentType)
+            throws MmsException {
+        OutputStream os = null;
+        InputStream is = null;
+
+        try {
+            byte[] data = part.getData();
+            if (ContentType.TEXT_PLAIN.equals(contentType)
+                    || ContentType.APP_SMIL.equals(contentType)
+                    || ContentType.TEXT_HTML.equals(contentType)) {
+                ContentValues cv = new ContentValues();
+                cv.put(TelephonyProvider.Mms.Part.TEXT, new EncodedStringValue(data).getString());
+                if (mContentResolver.update(uri, cv, null, null) != 1) {
+                    throw new MmsException("unable to update " + uri.toString());
+                }
+            } else {
+                os = mContentResolver.openOutputStream(uri);
+                if (data == null) {
+                    Uri dataUri = part.getDataUri();
+                    if ((dataUri == null) || (dataUri == uri)) {
+                        Log.w(TAG, "Can't find data for this part.");
+                        return;
+                    }
+                    is = mContentResolver.openInputStream(dataUri);
+
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Saving data to: " + uri);
+                    }
+
+                    byte[] buffer = new byte[256];
+                    for (int len = 0; (len = is.read(buffer)) != -1; ) {
+                        os.write(buffer, 0, len);
+                    }
+                } else {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Saving data to: " + uri);
+                    }
+                    os.write(data);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to open Input/Output stream.", e);
+            throw new MmsException(e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read/write data.", e);
+            throw new MmsException(e);
+        } finally {
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + os, e);
+                } // Ignore
+            }
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + is, e);
+                } // Ignore
+            }
+        }
+    }
+
+    private void updateAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        // Delete old address information and then insert new ones.
+        SqliteWrapper.delete(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                Addr.TYPE + "=" + type, null);
+
+        persistAddress(msgId, type, array);
+    }
+
+    /**
+     * Update headers of a SendReq.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param pdu New headers.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    public void updateHeaders(Uri uri, SendReq sendReq) {
+        PDU_CACHE_INSTANCE.purge(uri);
+
+        ContentValues values = new ContentValues(10);
+        byte[] contentType = sendReq.getContentType();
+        if (contentType != null) {
+            values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
+        }
+
+        long date = sendReq.getDate();
+        if (date != -1) {
+            values.put(Mms.DATE, date);
+        }
+
+        int deliveryReport = sendReq.getDeliveryReport();
+        if (deliveryReport != 0) {
+            values.put(Mms.DELIVERY_REPORT, deliveryReport);
+        }
+
+        long expiry = sendReq.getExpiry();
+        if (expiry != -1) {
+            values.put(Mms.EXPIRY, expiry);
+        }
+
+        byte[] msgClass = sendReq.getMessageClass();
+        if (msgClass != null) {
+            values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
+        }
+
+        int priority = sendReq.getPriority();
+        if (priority != 0) {
+            values.put(Mms.PRIORITY, priority);
+        }
+
+        int readReport = sendReq.getReadReport();
+        if (readReport != 0) {
+            values.put(Mms.READ_REPORT, readReport);
+        }
+
+        byte[] transId = sendReq.getTransactionId();
+        if (transId != null) {
+            values.put(Mms.TRANSACTION_ID, toIsoString(transId));
+        }
+
+        EncodedStringValue subject = sendReq.getSubject();
+        if (subject != null) {
+            values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
+            values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
+        } else {
+            values.put(Mms.SUBJECT, "");
+        }
+
+        long messageSize = sendReq.getMessageSize();
+        if (messageSize > 0) {
+            values.put(Mms.MESSAGE_SIZE, messageSize);
+        }
+
+        PduHeaders headers = sendReq.getPduHeaders();
+        HashSet<String> recipients = new HashSet<String>();
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = headers.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = headers.getEncodedStringValues(addrType);
+            }
+
+            if (array != null) {
+                long msgId = ContentUris.parseId(uri);
+                updateAddress(msgId, addrType, array);
+                if (addrType == PduHeaders.TO) {
+                    for (EncodedStringValue v : array) {
+                        if (v != null) {
+                            recipients.add(v.getString());
+                        }
+                    }
+                }
+            }
+        }
+
+        long threadId = Threads.getOrCreateThreadId(mContext, recipients);
+        values.put(Mms.THREAD_ID, threadId);
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+    }
+
+    private void updatePart(Uri uri, PduPart part) throws MmsException {
+        ContentValues values = new ContentValues(7);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = null;
+        if (part.getContentType() != null) {
+            contentType = toIsoString(part.getContentType());
+            values.put(Part.CONTENT_TYPE, contentType);
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+
+        // Only update the data when:
+        // 1. New binary data supplied or
+        // 2. The Uri of the part is different from the current one.
+        if ((part.getData() != null)
+                || (uri != part.getDataUri())) {
+            persistData(part, uri, contentType);
+        }
+    }
+
+    /**
+     * Update all parts of a PDU.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param body New message body of the PDU.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    public void updateParts(Uri uri, PduBody body)
+            throws MmsException {
+        PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+        if (cacheEntry != null) {
+            ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
+        }
+
+        ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
+        HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>();
+
+        int partsNum = body.getPartsNum();
+        StringBuilder filter = new StringBuilder().append('(');
+        for (int i = 0; i < partsNum; i++) {
+            PduPart part = body.getPart(i);
+            Uri partUri = part.getDataUri();
+            if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) {
+                toBeCreated.add(part);
+            } else {
+                toBeUpdated.put(partUri, part);
+
+                // Don't use 'i > 0' to determine whether we should append
+                // 'AND' since 'i = 0' may be skipped in another branch.
+                if (filter.length() > 1) {
+                    filter.append(" AND ");
+                }
+
+                filter.append(Part._ID);
+                filter.append("!=");
+                DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
+            }
+        }
+        filter.append(')');
+
+        long msgId = ContentUris.parseId(uri);
+
+        // Remove the parts which doesn't exist anymore.
+        SqliteWrapper.delete(mContext, mContentResolver,
+                Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
+                filter.length() > 2 ? filter.toString() : null, null);
+
+        // Create new parts which didn't exist before.
+        for (PduPart part : toBeCreated) {
+            persistPart(part, msgId);
+        }
+
+        // Update the modified parts.
+        for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
+            updatePart(e.getKey(), e.getValue());
+        }
+    }
+
+    /**
+     * Persist a PDU object to specific location in the storage.
+     *
+     * @param pdu The PDU object to be stored.
+     * @param uri Where to store the given PDU object.
+     * @return A Uri which can be used to access the stored PDU.
+     */
+    public Uri persist(GenericPdu pdu, Uri uri) throws MmsException {
+        if (uri == null) {
+            throw new MmsException("Uri may not be null.");
+        }
+
+        Integer msgBox = MESSAGE_BOX_MAP.get(uri);
+        if (msgBox == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+        PDU_CACHE_INSTANCE.purge(uri);
+
+        PduHeaders header = pdu.getPduHeaders();
+        PduBody body = null;
+        ContentValues values = new ContentValues();
+        Set<Entry<Integer, String>> set;
+
+        set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set) {
+            int field = e.getKey();
+            EncodedStringValue encodedString = header.getEncodedStringValue(field);
+            if (encodedString != null) {
+                String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
+                values.put(e.getValue(), toIsoString(encodedString.getTextString()));
+                values.put(charsetColumn, encodedString.getCharacterSet());
+            }
+        }
+
+        set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            byte[] text = header.getTextString(e.getKey());
+            if (text != null) {
+                values.put(e.getValue(), toIsoString(text));
+            }
+        }
+
+        set = OCTET_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            int b = header.getOctet(e.getKey());
+            if (b != 0) {
+                values.put(e.getValue(), b);
+            }
+        }
+
+        set = LONG_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            long l = header.getLongInteger(e.getKey());
+            if (l != -1L) {
+                values.put(e.getValue(), l);
+            }
+        }
+
+        HashMap<Integer, EncodedStringValue[]> addressMap =
+                new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = header.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = header.getEncodedStringValues(addrType);
+            }
+            addressMap.put(addrType, array);
+        }
+
+        HashSet<String> recipients = new HashSet<String>();
+        long threadId = DUMMY_THREAD_ID;
+        int msgType = pdu.getMessageType();
+        // Here we only allocate thread ID for M-Notification.ind,
+        // M-Retrieve.conf and M-Send.req.
+        // Some of other PDU types may be allocated a thread ID outside
+        // this scope.
+        if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
+                || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+            EncodedStringValue[] array = null;
+            switch (msgType) {
+                case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                    array = addressMap.get(PduHeaders.FROM);
+                    break;
+                case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                    array = addressMap.get(PduHeaders.TO);
+                    break;
+            }
+
+            if (array != null) {
+                for (EncodedStringValue v : array) {
+                    if (v != null) {
+                        recipients.add(v.getString());
+                    }
+                }
+            }
+            threadId = Threads.getOrCreateThreadId(mContext, recipients);
+        }
+        values.put(Mms.THREAD_ID, threadId);
+
+        // Save parts first to avoid inconsistent message is loaded
+        // while saving the parts.
+        long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
+        // Get body if the PDU is a RetrieveConf or SendReq.
+        if (pdu instanceof MultimediaMessagePdu) {
+            body = ((MultimediaMessagePdu) pdu).getBody();
+            // Start saving parts if necessary.
+            if (body != null) {
+                int partsNum = body.getPartsNum();
+                for (int i = 0; i < partsNum; i++) {
+                    PduPart part = body.getPart(i);
+                    persistPart(part, dummyId);
+                }
+            }
+        }
+
+        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        if (res == null) {
+            throw new MmsException("persist() failed: return null.");
+        }
+
+        // Get the real ID of the PDU and update all parts which were
+        // saved with the dummy ID.
+        long msgId = ContentUris.parseId(res);
+        values = new ContentValues(1);
+        values.put(Part.MSG_ID, msgId);
+        SqliteWrapper.update(mContext, mContentResolver,
+                             Uri.parse("content://mms/" + dummyId + "/part"),
+                             values, null, null);
+        // We should return the longest URI of the persisted PDU, for
+        // example, if input URI is "content://mms/inbox" and the _ID of
+        // persisted PDU is '8', we should return "content://mms/inbox/8"
+        // instead of "content://mms/8".
+        // FIXME: Should the MmsProvider be responsible for this???
+        res = Uri.parse(uri + "/" + msgId);
+
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = addressMap.get(addrType);
+            if (array != null) {
+                persistAddress(msgId, addrType, array);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * Move a PDU object from one location to another.
+     *
+     * @param from Specify the PDU object to be moved.
+     * @param to The destination location, should be one of the following:
+     *        "content://mms/inbox", "content://mms/sent",
+     *        "content://mms/drafts", "content://mms/outbox",
+     *        "content://mms/trash".
+     * @return New Uri of the moved PDU.
+     * @throws MmsException Error occurred while moving the message.
+     */
+    public Uri move(Uri from, Uri to) throws MmsException {
+        // Check whether the 'msgId' has been assigned a valid value.
+        long msgId = ContentUris.parseId(from);
+        if (msgId == -1L) {
+            throw new MmsException("Error! ID of the message: -1.");
+        }
+
+        // Get corresponding int value of destination box.
+        Integer msgBox = MESSAGE_BOX_MAP.get(to);
+        if (msgBox == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+
+        ContentValues values = new ContentValues(1);
+        values.put(Mms.MESSAGE_BOX, msgBox);
+        SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
+        return ContentUris.withAppendedId(to, msgId);
+    }
+
+    /**
+     * Wrap a byte[] into a String.
+     */
+    public static String toIsoString(byte[] bytes) {
+        try {
+            return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return "";
+        }
+    }
+
+    /**
+     * Unpack a given String into a byte[].
+     */
+    public static byte[] getBytes(String data) {
+        try {
+            return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return new byte[0];
+        }
+    }
+
+    /**
+     * Remove all objects in the temporary path.
+     */
+    public void release() {
+        Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
+        SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
+    }
+
+    /**
+     * Find all messages to be sent or downloaded before certain time.
+     */
+    public Cursor getPendingMessages(long dueTime) {
+        Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
+        uriBuilder.appendQueryParameter("protocol", "mms");
+
+        String selection = PendingMessages.ERROR_TYPE + " < ?"
+                + " AND " + PendingMessages.DUE_TIME + " <= ?";
+
+        String[] selectionArgs = new String[] {
+                String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
+                String.valueOf(dueTime)
+        };
+
+        return SqliteWrapper.query(mContext, mContentResolver,
+                uriBuilder.build(), null, selection, selectionArgs,
+                PendingMessages.DUE_TIME);
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/QuotedPrintable.java b/mms-common/java/com/android/mmscommon/mms/pdu/QuotedPrintable.java
new file mode 100644
index 0000000..e9da7df
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/QuotedPrintable.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import java.io.ByteArrayOutputStream;
+
+public class QuotedPrintable {
+    private static byte ESCAPE_CHAR = '=';
+
+    /**
+     * Decodes an array quoted-printable characters into an array of original bytes.
+     * Escaped characters are converted back to their original representation.
+     *
+     * <p>
+     * This function implements a subset of
+     * quoted-printable encoding specification (rule #1 and rule #2)
+     * as defined in RFC 1521.
+     * </p>
+     *
+     * @param bytes array of quoted-printable characters
+     * @return array of original bytes,
+     *         null if quoted-printable decoding is unsuccessful.
+     */
+    public static final byte[] decodeQuotedPrintable(byte[] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        for (int i = 0; i < bytes.length; i++) {
+            int b = bytes[i];
+            if (b == ESCAPE_CHAR) {
+                try {
+                    if('\r' == (char)bytes[i + 1] &&
+                            '\n' == (char)bytes[i + 2]) {
+                        i += 2;
+                        continue;
+                    }
+                    int u = Character.digit((char) bytes[++i], 16);
+                    int l = Character.digit((char) bytes[++i], 16);
+                    if (u == -1 || l == -1) {
+                        return null;
+                    }
+                    buffer.write((char) ((u << 4) + l));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    return null;
+                }
+            } else {
+                buffer.write(b);
+            }
+        }
+        return buffer.toByteArray();
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/ReadOrigInd.java b/mms-common/java/com/android/mmscommon/mms/pdu/ReadOrigInd.java
new file mode 100644
index 0000000..9678784
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/ReadOrigInd.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+public class ReadOrigInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public ReadOrigInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    ReadOrigInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/ReadRecInd.java b/mms-common/java/com/android/mmscommon/mms/pdu/ReadRecInd.java
new file mode 100644
index 0000000..c1efbbc
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/ReadRecInd.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+public class ReadRecInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-ReadRec.ind pdu.
+     *
+     * @param from the from value
+     * @param messageId the message ID value
+     * @param mmsVersion current viersion of mms
+     * @param readStatus the read status value
+     * @param to the to value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if messageId or to is null.
+     */
+    public ReadRecInd(EncodedStringValue from,
+                      byte[] messageId,
+                      int mmsVersion,
+                      int readStatus,
+                      EncodedStringValue[] to) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+        setFrom(from);
+        setMessageId(messageId);
+        setMmsVersion(mmsVersion);
+        setTo(to);
+        setReadStatus(readStatus);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    ReadRecInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/RetrieveConf.java b/mms-common/java/com/android/mmscommon/mms/pdu/RetrieveConf.java
new file mode 100644
index 0000000..442949e
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/RetrieveConf.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+/**
+ * M-Retrive.conf Pdu.
+ */
+public class RetrieveConf extends MultimediaMessagePdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public RetrieveConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    RetrieveConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    RetrieveConf(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Status value.
+     *
+     * @return the value
+     */
+    public int getRetrieveStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Text value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue getRetrieveText() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Text value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setRetrieveText(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public PreviouslySentByValue getPreviouslySentBy() {return null;}
+     *     public void setPreviouslySentBy(PreviouslySentByValue value) {}
+     *
+     *     public PreviouslySentDateValue getPreviouslySentDate() {}
+     *     public void setPreviouslySentDate(PreviouslySentDateValue value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     */
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/SendConf.java b/mms-common/java/com/android/mmscommon/mms/pdu/SendConf.java
new file mode 100644
index 0000000..0a57b6b
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/SendConf.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+public class SendConf extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public SendConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    SendConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Response-Status.
+     *
+     * @return the value
+     */
+    public int getResponseStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Response-Status.
+     *
+     * @param value the values
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setResponseStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *    public byte[] getContentLocation() {return null;}
+     *    public void setContentLocation(byte[] value) {}
+     *
+     *    public EncodedStringValue getResponseText() {return null;}
+     *    public void setResponseText(EncodedStringValue value) {}
+     *
+     *    public byte getStoreStatus() {return 0x00;}
+     *    public void setStoreStatus(byte value) {}
+     *
+     *    public byte[] getStoreStatusText() {return null;}
+     *    public void setStoreStatusText(byte[] value) {}
+     */
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/pdu/SendReq.java b/mms-common/java/com/android/mmscommon/mms/pdu/SendReq.java
new file mode 100644
index 0000000..5da4719
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/pdu/SendReq.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.pdu;
+
+import android.util.Log;
+
+import com.android.mmscommon.EncodedStringValue;
+import com.android.mmscommon.InvalidHeaderValueException;
+import com.android.mmscommon.PduHeaders;
+
+public class SendReq extends MultimediaMessagePdu {
+    private static final String TAG = "SendReq";
+
+    public SendReq() {
+        super();
+
+        try {
+            setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+            setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
+            // FIXME: Content-type must be decided according to whether
+            // SMIL part present.
+            setContentType("application/vnd.wap.multipart.related".getBytes());
+            setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
+            setTransactionId(generateTransactionId());
+        } catch (InvalidHeaderValueException e) {
+            // Impossible to reach here since all headers we set above are valid.
+            Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private byte[] generateTransactionId() {
+        String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
+        return transactionId.getBytes();
+    }
+
+    /**
+     * Constructor, used when composing a M-Send.req pdu.
+     *
+     * @param contentType the content type value
+     * @param from the from value
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if contentType, form or transactionId is null.
+     */
+    public SendReq(byte[] contentType,
+                   EncodedStringValue from,
+                   int mmsVersion,
+                   byte[] transactionId) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+        setContentType(contentType);
+        setFrom(from);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    SendReq(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    SendReq(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get Bcc value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getBcc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
+    }
+
+    /**
+     * Add a "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addBcc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Set "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setBcc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Set "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setCc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     */
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get X-Mms-MessageSize value.
+     *
+     * Expiry-value = size of message
+     *
+     * @return the value
+     */
+    public long getMessageSize() {
+        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Set X-Mms-MessageSize value.
+     *
+     * @param value the value
+     */
+    public void setMessageSize(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte getAdaptationAllowed() {return 0};
+     *     public void setAdaptationAllowed(btye value) {};
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public long getDeliveryTime() {return 0};
+     *     public void setDeliveryTime(long value) {};
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getStore() {return 0x00;}
+     *     public void setStore(byte value) {}
+     */
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/util/AbstractCache.java b/mms-common/java/com/android/mmscommon/mms/util/AbstractCache.java
new file mode 100644
index 0000000..10a6fce
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/util/AbstractCache.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.util;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.util.HashMap;
+
+public abstract class AbstractCache<K, V> {
+    private static final String TAG = "AbstractCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final int MAX_CACHED_ITEMS  = 500;
+
+    private final HashMap<K, CacheEntry<V>> mCacheMap;
+
+    protected AbstractCache() {
+        mCacheMap = new HashMap<K, CacheEntry<V>>();
+    }
+
+    public boolean put(K key, V value) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to put " + key + " into cache.");
+        }
+
+        if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
+            // TODO Should remove the oldest or least hit cached entry
+            // and then cache the new one.
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Failed! size limitation reached.");
+            }
+            return false;
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = new CacheEntry<V>();
+            cacheEntry.value = value;
+            mCacheMap.put(key, cacheEntry);
+
+            if (LOCAL_LOGV) {
+                Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public V get(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to get " + key + " from cache.");
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = mCacheMap.get(key);
+            if (cacheEntry != null) {
+                cacheEntry.hit++;
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
+                }
+                return cacheEntry.value;
+            }
+        }
+        return null;
+    }
+
+    public V purge(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to purge " + key);
+        }
+
+        CacheEntry<V> v = mCacheMap.remove(key);
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, mCacheMap.size() + " items cached.");
+        }
+
+        return v != null ? v.value : null;
+    }
+
+    public void purgeAll() {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purging cache, " + mCacheMap.size()
+                    + " items dropped.");
+        }
+        mCacheMap.clear();
+    }
+
+    public int size() {
+        return mCacheMap.size();
+    }
+
+    private static class CacheEntry<V> {
+        int hit;
+        V value;
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/util/PduCache.java b/mms-common/java/com/android/mmscommon/mms/util/PduCache.java
new file mode 100644
index 0000000..ca5432f
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/util/PduCache.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.util;
+
+import android.content.ContentUris;
+import android.content.UriMatcher;
+import android.net.Uri;
+import com.android.mmscommon.telephony.TelephonyProvider.Mms;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
+    private static final String TAG = "PduCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+    private static final int MMS_ALL             = 0;
+    private static final int MMS_ALL_ID          = 1;
+    private static final int MMS_INBOX           = 2;
+    private static final int MMS_INBOX_ID        = 3;
+    private static final int MMS_SENT            = 4;
+    private static final int MMS_SENT_ID         = 5;
+    private static final int MMS_DRAFTS          = 6;
+    private static final int MMS_DRAFTS_ID       = 7;
+    private static final int MMS_OUTBOX          = 8;
+    private static final int MMS_OUTBOX_ID       = 9;
+    private static final int MMS_CONVERSATION    = 10;
+    private static final int MMS_CONVERSATION_ID = 11;
+
+    private static final UriMatcher URI_MATCHER;
+    private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;
+
+    private static PduCache sInstance;
+
+    static {
+        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+        URI_MATCHER.addURI("mms", null,         MMS_ALL);
+        URI_MATCHER.addURI("mms", "#",          MMS_ALL_ID);
+        URI_MATCHER.addURI("mms", "inbox",      MMS_INBOX);
+        URI_MATCHER.addURI("mms", "inbox/#",    MMS_INBOX_ID);
+        URI_MATCHER.addURI("mms", "sent",       MMS_SENT);
+        URI_MATCHER.addURI("mms", "sent/#",     MMS_SENT_ID);
+        URI_MATCHER.addURI("mms", "drafts",     MMS_DRAFTS);
+        URI_MATCHER.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
+        URI_MATCHER.addURI("mms", "outbox",     MMS_OUTBOX);
+        URI_MATCHER.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
+        URI_MATCHER.addURI("mms-sms", "conversations",   MMS_CONVERSATION);
+        URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
+
+        MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX,  Mms.MESSAGE_BOX_INBOX);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT,   Mms.MESSAGE_BOX_SENT);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
+    }
+
+    private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
+    private final HashMap<Long, HashSet<Uri>> mThreads;
+
+    private PduCache() {
+        mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
+        mThreads = new HashMap<Long, HashSet<Uri>>();
+    }
+
+    synchronized public static final PduCache getInstance() {
+        if (sInstance == null) {
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Constructing new PduCache instance.");
+            }
+            sInstance = new PduCache();
+        }
+        return sInstance;
+    }
+
+    @Override
+    synchronized public boolean put(Uri uri, PduCacheEntry entry) {
+        int msgBoxId = entry.getMessageBox();
+        HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
+        if (msgBox == null) {
+            msgBox = new HashSet<Uri>();
+            mMessageBoxes.put(msgBoxId, msgBox);
+        }
+
+        long threadId = entry.getThreadId();
+        HashSet<Uri> thread = mThreads.get(threadId);
+        if (thread == null) {
+            thread = new HashSet<Uri>();
+            mThreads.put(threadId, thread);
+        }
+
+        Uri finalKey = normalizeKey(uri);
+        boolean result = super.put(finalKey, entry);
+        if (result) {
+            msgBox.add(finalKey);
+            thread.add(finalKey);
+        }
+        return result;
+    }
+
+    @Override
+    synchronized public PduCacheEntry purge(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        switch (match) {
+            case MMS_ALL_ID:
+                return purgeSingleEntry(uri);
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
+            // Implicit batch of purge, return null.
+            case MMS_ALL:
+            case MMS_CONVERSATION:
+                purgeAll();
+                return null;
+            case MMS_INBOX:
+            case MMS_SENT:
+            case MMS_DRAFTS:
+            case MMS_OUTBOX:
+                purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
+                return null;
+            case MMS_CONVERSATION_ID:
+                purgeByThreadId(ContentUris.parseId(uri));
+                return null;
+            default:
+                return null;
+        }
+    }
+
+    private PduCacheEntry purgeSingleEntry(Uri key) {
+        PduCacheEntry entry = super.purge(key);
+        if (entry != null) {
+            removeFromThreads(key, entry);
+            removeFromMessageBoxes(key, entry);
+            return entry;
+        }
+        return null;
+    }
+
+    @Override
+    synchronized public void purgeAll() {
+        super.purgeAll();
+
+        mMessageBoxes.clear();
+        mThreads.clear();
+    }
+
+    /**
+     * @param uri The Uri to be normalized.
+     * @return Uri The normalized key of cached entry.
+     */
+    private Uri normalizeKey(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        Uri normalizedKey = null;
+
+        switch (match) {
+            case MMS_ALL_ID:
+                normalizedKey = uri;
+                break;
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
+                break;
+            default:
+                return null;
+        }
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, uri + " -> " + normalizedKey);
+        }
+        return normalizedKey;
+    }
+
+    private void purgeByMessageBox(Integer msgBoxId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in message box: " + msgBoxId);
+        }
+
+        if (msgBoxId != null) {
+            HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
+            if (msgBox != null) {
+                for (Uri key : msgBox) {
+                    PduCacheEntry entry = super.purge(key);
+                    if (entry != null) {
+                        removeFromThreads(key, entry);
+                    }
+                }
+            }
+        }
+    }
+
+    private void removeFromThreads(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> thread = mThreads.get(entry.getThreadId());
+        if (thread != null) {
+            thread.remove(key);
+        }
+    }
+
+    private void purgeByThreadId(long threadId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in thread: " + threadId);
+        }
+
+        HashSet<Uri> thread = mThreads.remove(threadId);
+        if (thread != null) {
+            for (Uri key : thread) {
+                PduCacheEntry entry = super.purge(key);
+                if (entry != null) {
+                    removeFromMessageBoxes(key, entry);
+                }
+            }
+        }
+    }
+
+    private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> msgBox = mThreads.get(entry.getMessageBox());
+        if (msgBox != null) {
+            msgBox.remove(key);
+        }
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/mms/util/PduCacheEntry.java b/mms-common/java/com/android/mmscommon/mms/util/PduCacheEntry.java
new file mode 100644
index 0000000..aed741d
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/mms/util/PduCacheEntry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.mms.util;
+
+import com.android.mmscommon.mms.pdu.GenericPdu;
+
+public final class PduCacheEntry {
+    private final GenericPdu mPdu;
+    private final int mMessageBox;
+    private final long mThreadId;
+
+    public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
+        mPdu = pdu;
+        mMessageBox = msgBox;
+        mThreadId = threadId;
+    }
+
+    public GenericPdu getPdu() {
+        return mPdu;
+    }
+
+    public int getMessageBox() {
+        return mMessageBox;
+    }
+
+    public long getThreadId() {
+        return mThreadId;
+    }
+}
diff --git a/mms-common/java/com/android/mmscommon/telephony/TelephonyProvider.java b/mms-common/java/com/android/mmscommon/telephony/TelephonyProvider.java
new file mode 100644
index 0000000..0237bc2
--- /dev/null
+++ b/mms-common/java/com/android/mmscommon/telephony/TelephonyProvider.java
@@ -0,0 +1,1790 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mmscommon.telephony;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.telephony.SmsMessage;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import com.android.common.Patterns;
+import android.database.sqlite.SqliteWrapper;
+
+/**
+ * The Telephony provider contains data related to phone operation.
+ *
+ * @hide
+ */
+
+// This is a copy of the private TelephoneProvider.java file found in:
+//      com.android.providers.telephony
+// TODO: keep these files in sync.
+
+public final class TelephonyProvider {
+    private static final String TAG = "Telephony";
+    private static final boolean DEBUG = true;
+    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+//    public static final Pattern EMAIL_ADDRESS
+//        = Pattern.compile(
+//            "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
+//            "\\@" +
+//            "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
+//            "(" +
+//                "\\." +
+//                "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
+//            ")+"
+//        );
+//
+//    /**
+//     * This pattern is intended for searching for things that look like they
+//     * might be phone numbers in arbitrary text, not for validating whether
+//     * something is in fact a phone number.  It will miss many things that
+//     * are legitimate phone numbers.
+//     *
+//     * <p> The pattern matches the following:
+//     * <ul>
+//     * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
+//     * may follow.
+//     * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
+//     * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
+//     * </ul>
+//     */
+//    public static final Pattern PHONE
+//        = Pattern.compile(                                  // sdd = space, dot, or dash
+//                "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
+//                + "(\\([0-9]+\\)[\\- \\.]*)?"               // (<digits>)<sdd>*
+//                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
+
+    // Constructor
+    public TelephonyProvider() {
+    }
+
+    /**
+     * Base columns for tables that contain text based SMSs.
+     */
+    public interface TextBasedSmsColumns {
+        /**
+         * The type of the message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TYPE = "type";
+
+        public static final int MESSAGE_TYPE_ALL    = 0;
+        public static final int MESSAGE_TYPE_INBOX  = 1;
+        public static final int MESSAGE_TYPE_SENT   = 2;
+        public static final int MESSAGE_TYPE_DRAFT  = 3;
+        public static final int MESSAGE_TYPE_OUTBOX = 4;
+        public static final int MESSAGE_TYPE_FAILED = 5; // for failed outgoing messages
+        public static final int MESSAGE_TYPE_QUEUED = 6; // for messages to send later
+
+
+        /**
+         * The thread ID of the message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THREAD_ID = "thread_id";
+
+        /**
+         * The address of the other party
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADDRESS = "address";
+
+        /**
+         * The person ID of the sender
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String PERSON_ID = "person";
+
+        /**
+         * The date the message was sent
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * Has the message been read
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String READ = "read";
+
+        /**
+         * The TP-Status value for the message, or -1 if no status has
+         * been received
+         */
+        public static final String STATUS = "status";
+
+        public static final int STATUS_NONE = -1;
+        public static final int STATUS_COMPLETE = 0;
+        public static final int STATUS_PENDING = 64;
+        public static final int STATUS_FAILED = 128;
+
+        /**
+         * The subject of the message, if present
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBJECT = "subject";
+
+        /**
+         * The body of the message
+         * <P>Type: TEXT</P>
+         */
+        public static final String BODY = "body";
+
+        /**
+         * The id of the sender of the conversation, if present
+         * <P>Type: INTEGER (reference to item in content://contacts/people)</P>
+         */
+        public static final String PERSON = "person";
+
+        /**
+         * The protocol identifier code
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PROTOCOL = "protocol";
+
+        /**
+         * Whether the <code>TP-Reply-Path</code> bit was set on this message
+         * <P>Type: BOOLEAN</P>
+         */
+        public static final String REPLY_PATH_PRESENT = "reply_path_present";
+
+        /**
+         * The service center (SC) through which to send the message, if present
+         * <P>Type: TEXT</P>
+         */
+        public static final String SERVICE_CENTER = "service_center";
+
+        /**
+         * Has the message been locked?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String LOCKED = "locked";
+
+        /**
+         * Error code associated with sending or receiving this message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ERROR_CODE = "error_code";
+}
+
+    /**
+     * Contains all text based SMS messages.
+     */
+    public static final class Sms implements BaseColumns, TextBasedSmsColumns {
+        public static final Cursor query(ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public static final Cursor query(ContentResolver cr, String[] projection,
+                String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection, where,
+                                         null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://sms");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+        /**
+         * Add an SMS to the given URI.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @return the URI for the new message
+         */
+        public static Uri addMessageToUri(ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport) {
+            return addMessageToUri(resolver, uri, address, body, subject,
+                    date, read, deliveryReport, -1L);
+        }
+
+        /**
+         * Add an SMS to the given URI with thread_id specified.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @param threadId the thread_id of the message
+         * @return the URI for the new message
+         */
+        public static Uri addMessageToUri(ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport, long threadId) {
+            ContentValues values = new ContentValues(7);
+
+            values.put(ADDRESS, address);
+            if (date != null) {
+                values.put(DATE, date);
+            }
+            values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
+            values.put(SUBJECT, subject);
+            values.put(BODY, body);
+            if (deliveryReport) {
+                values.put(STATUS, STATUS_PENDING);
+            }
+            if (threadId != -1L) {
+                values.put(THREAD_ID, threadId);
+            }
+            return resolver.insert(uri, values);
+        }
+
+        /**
+         * Move a message to the given folder.
+         *
+         * @param context the context to use
+         * @param uri the message to move
+         * @param folder the folder to move to
+         * @return true if the operation succeeded
+         */
+        public static boolean moveMessageToFolder(Context context,
+                Uri uri, int folder, int error) {
+            if (uri == null) {
+                return false;
+            }
+
+            boolean markAsUnread = false;
+            boolean markAsRead = false;
+            switch(folder) {
+            case MESSAGE_TYPE_INBOX:
+            case MESSAGE_TYPE_DRAFT:
+                break;
+            case MESSAGE_TYPE_OUTBOX:
+            case MESSAGE_TYPE_SENT:
+                markAsRead = true;
+                break;
+            case MESSAGE_TYPE_FAILED:
+            case MESSAGE_TYPE_QUEUED:
+                markAsUnread = true;
+                break;
+            default:
+                return false;
+            }
+
+            ContentValues values = new ContentValues(3);
+
+            values.put(TYPE, folder);
+            if (markAsUnread) {
+                values.put(READ, Integer.valueOf(0));
+            } else if (markAsRead) {
+                values.put(READ, Integer.valueOf(1));
+            }
+            values.put(ERROR_CODE, error);
+
+            return 1 == SqliteWrapper.update(context, context.getContentResolver(),
+                            uri, values, null, null);
+        }
+
+        /**
+         * Returns true iff the folder (message type) identifies an
+         * outgoing message.
+         */
+        public static boolean isOutgoingFolder(int messageType) {
+            return  (messageType == MESSAGE_TYPE_FAILED)
+                    || (messageType == MESSAGE_TYPE_OUTBOX)
+                    || (messageType == MESSAGE_TYPE_SENT)
+                    || (messageType == MESSAGE_TYPE_QUEUED);
+        }
+
+        /**
+         * Contains all text based SMS messages in the SMS app's inbox.
+         */
+        public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                Uri.parse("content://sms/inbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param read true if the message has been read, false if not
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean read) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, read, false);
+            }
+        }
+
+        /**
+         * Contains all sent text based SMS messages in the SMS app's.
+         */
+        public static final class Sent implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                    Uri.parse("content://sms/sent");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, true, false);
+            }
+        }
+
+        /**
+         * Contains all sent text based SMS messages in the SMS app's.
+         */
+        public static final class Draft implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                    Uri.parse("content://sms/draft");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, true, false);
+            }
+
+            /**
+             * Save over an existing draft message.
+             *
+             * @param resolver the content resolver to use
+             * @param uri of existing message
+             * @param body the new body for the draft message
+             * @return true is successful, false otherwise
+             */
+            public static boolean saveMessage(ContentResolver resolver,
+                    Uri uri, String body) {
+                ContentValues values = new ContentValues(2);
+                values.put(BODY, body);
+                values.put(DATE, System.currentTimeMillis());
+                return resolver.update(uri, values, null, null) == 1;
+            }
+        }
+
+        /**
+         * Contains all pending outgoing text based SMS messages.
+         */
+        public static final class Outbox implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                Uri.parse("content://sms/outbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Out box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param deliveryReport whether a delivery report was requested for the message
+             * @return the URI for the new message
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean deliveryReport, long threadId) {
+                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                        subject, date, true, deliveryReport, threadId);
+            }
+        }
+
+        /**
+         * Contains all sent text-based SMS messages in the SMS app's.
+         */
+        public static final class Conversations
+                implements BaseColumns, TextBasedSmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI =
+                Uri.parse("content://sms/conversations");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * The first 45 characters of the body of the message
+             * <P>Type: TEXT</P>
+             */
+            public static final String SNIPPET = "snippet";
+
+            /**
+             * The number of messages in the conversation
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MESSAGE_COUNT = "msg_count";
+        }
+
+        /**
+         * Contains info about SMS related Intents that are broadcast.
+         */
+        public static final class Intents {
+            /**
+             * Set by BroadcastReceiver. Indicates the message was handled
+             * successfully.
+             */
+            public static final int RESULT_SMS_HANDLED = 1;
+
+            /**
+             * Set by BroadcastReceiver. Indicates a generic error while
+             * processing the message.
+             */
+            public static final int RESULT_SMS_GENERIC_ERROR = 2;
+
+            /**
+             * Set by BroadcastReceiver. Indicates insufficient memory to store
+             * the message.
+             */
+            public static final int RESULT_SMS_OUT_OF_MEMORY = 3;
+
+            /**
+             * Set by BroadcastReceiver. Indicates the message, while
+             * possibly valid, is of a format or encoding that is not
+             * supported.
+             */
+            public static final int RESULT_SMS_UNSUPPORTED = 4;
+
+            /**
+             * Broadcast Action: A new text based SMS message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             */
+            public static final String SMS_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_RECEIVED";
+
+            /**
+             * Broadcast Action: A new data based SMS message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             */
+            public static final String DATA_SMS_RECEIVED_ACTION =
+                    "android.intent.action.DATA_SMS_RECEIVED";
+
+            /**
+             * Broadcast Action: A new WAP PUSH message has been received by the
+             * device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>transactionId (Integer)</em> - The WAP transaction
+             *    ID</li>
+             *   <li><em>pduType (Integer)</em> - The WAP PDU type</li>
+             *   <li><em>header (byte[])</em> - The header of the message</li>
+             *   <li><em>data (byte[])</em> - The data payload of the message</li>
+             * </ul>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             */
+            public static final String WAP_PUSH_RECEIVED_ACTION =
+                    "android.provider.Telephony.WAP_PUSH_RECEIVED";
+
+            /**
+             * Broadcast Action: The SIM storage for SMS messages is full.  If
+             * space is not freed, messages targeted for the SIM (class 2) may
+             * not be saved.
+             */
+            public static final String SIM_FULL_ACTION =
+                "android.provider.Telephony.SIM_FULL";
+
+            /**
+             * Broadcast Action: An incoming SMS has been rejected by the
+             * telephony framework.  This intent is sent in lieu of any
+             * of the RECEIVED_ACTION intents.  The intent will have the
+             * following extra value:</p>
+             *
+             * <ul>
+             *   <li><em>result</em> - An int result code, eg,
+             *   <code>{@link #RESULT_SMS_OUT_OF_MEMORY}</code>,
+             *   indicating the error returned to the network.</li>
+             * </ul>
+
+             */
+            public static final String SMS_REJECTED_ACTION =
+                "android.provider.Telephony.SMS_REJECTED";
+
+            /**
+             * Broadcast Action: The phone service state has changed. The intent will have the following
+             * extra values:</p>
+             * <ul>
+             *   <li><em>state</em> - An int with one of the following values:
+             *          {@link android.telephony.ServiceState#STATE_IN_SERVICE},
+             *          {@link android.telephony.ServiceState#STATE_OUT_OF_SERVICE},
+             *          {@link android.telephony.ServiceState#STATE_EMERGENCY_ONLY}
+             *          or {@link android.telephony.ServiceState#STATE_POWER_OFF}
+             *   <li><em>roaming</em> - A boolean value indicating whether the phone is roaming.</li>
+             *   <li><em>operator-alpha-long</em> - The carrier name as a string.</li>
+             *   <li><em>operator-alpha-short</em> - A potentially shortened version of the carrier name,
+             *          as a string.</li>
+             *   <li><em>operator-numeric</em> - A number representing the carrier, as a string. This is
+             *          a five or six digit number consisting of the MCC (Mobile Country Code, 3 digits)
+             *          and MNC (Mobile Network code, 2-3 digits).</li>
+             *   <li><em>manual</em> - A boolean, where true indicates that the user has chosen to select
+             *          the network manually, and false indicates that network selection is handled by the
+             *          phone.</li>
+             * </ul>
+             *
+             * <p class="note">
+             * Requires the READ_PHONE_STATE permission.
+             *
+             * <p class="note">This is a protected intent that can only be sent
+             * by the system.
+             */
+            public static final String ACTION_SERVICE_STATE_CHANGED =
+                "android.intent.action.SERVICE_STATE";
+
+            /**
+             * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a
+             * {@link #DATA_SMS_RECEIVED_ACTION} intent.
+             *
+             * @param intent the intent to read from
+             * @return an array of SmsMessages for the PDUs
+             */
+            public static final SmsMessage[] getMessagesFromIntent(
+                    Intent intent) {
+                Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
+                byte[][] pduObjs = new byte[messages.length][];
+
+                for (int i = 0; i < messages.length; i++) {
+                    pduObjs[i] = (byte[]) messages[i];
+                }
+                byte[][] pdus = new byte[pduObjs.length][];
+                int pduCount = pdus.length;
+                SmsMessage[] msgs = new SmsMessage[pduCount];
+                for (int i = 0; i < pduCount; i++) {
+                    pdus[i] = pduObjs[i];
+                    msgs[i] = SmsMessage.createFromPdu(pdus[i]);
+                }
+                return msgs;
+            }
+        }
+    }
+
+    /**
+     * Base columns for tables that contain MMSs.
+     */
+    public interface BaseMmsColumns extends BaseColumns {
+
+        public static final int MESSAGE_BOX_ALL    = 0;
+        public static final int MESSAGE_BOX_INBOX  = 1;
+        public static final int MESSAGE_BOX_SENT   = 2;
+        public static final int MESSAGE_BOX_DRAFTS = 3;
+        public static final int MESSAGE_BOX_OUTBOX = 4;
+
+        /**
+         * The date the message was sent.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * The box which the message belong to, for example, MESSAGE_BOX_INBOX.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_BOX = "msg_box";
+
+        /**
+         * Has the message been read.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String READ = "read";
+
+        /**
+         * The Message-ID of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESSAGE_ID = "m_id";
+
+        /**
+         * The subject of the message, if present.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBJECT = "sub";
+
+        /**
+         * The character set of the subject, if present.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SUBJECT_CHARSET = "sub_cs";
+
+        /**
+         * The Content-Type of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CONTENT_TYPE = "ct_t";
+
+        /**
+         * The Content-Location of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CONTENT_LOCATION = "ct_l";
+
+        /**
+         * The address of the sender.
+         * <P>Type: TEXT</P>
+         */
+        public static final String FROM = "from";
+
+        /**
+         * The address of the recipients.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TO = "to";
+
+        /**
+         * The address of the cc. recipients.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CC = "cc";
+
+        /**
+         * The address of the bcc. recipients.
+         * <P>Type: TEXT</P>
+         */
+        public static final String BCC = "bcc";
+
+        /**
+         * The expiry time of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String EXPIRY = "exp";
+
+        /**
+         * The class of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESSAGE_CLASS = "m_cls";
+
+        /**
+         * The type of the message defined by MMS spec.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_TYPE = "m_type";
+
+        /**
+         * The version of specification that this message conform.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MMS_VERSION = "v";
+
+        /**
+         * The size of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_SIZE = "m_size";
+
+        /**
+         * The priority of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PRIORITY = "pri";
+
+        /**
+         * The read-report of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String READ_REPORT = "rr";
+
+        /**
+         * Whether the report is allowed.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPORT_ALLOWED = "rpt_a";
+
+        /**
+         * The response-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RESPONSE_STATUS = "resp_st";
+
+        /**
+         * The status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String STATUS = "st";
+
+        /**
+         * The transaction-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TRANSACTION_ID = "tr_id";
+
+        /**
+         * The retrieve-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RETRIEVE_STATUS = "retr_st";
+
+        /**
+         * The retrieve-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RETRIEVE_TEXT = "retr_txt";
+
+        /**
+         * The character set of the retrieve-text.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RETRIEVE_TEXT_CHARSET = "retr_txt_cs";
+
+        /**
+         * The read-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String READ_STATUS = "read_status";
+
+        /**
+         * The content-class of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CONTENT_CLASS = "ct_cls";
+
+        /**
+         * The delivery-report of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_REPORT = "d_rpt";
+
+        /**
+         * The delivery-time-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_TIME_TOKEN = "d_tm_tok";
+
+        /**
+         * The delivery-time of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_TIME = "d_tm";
+
+        /**
+         * The response-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RESPONSE_TEXT = "resp_txt";
+
+        /**
+         * The sender-visibility of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SENDER_VISIBILITY = "s_vis";
+
+        /**
+         * The reply-charging of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING = "r_chg";
+
+        /**
+         * The reply-charging-deadline-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING_DEADLINE_TOKEN = "r_chg_dl_tok";
+
+        /**
+         * The reply-charging-deadline of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING_DEADLINE = "r_chg_dl";
+
+        /**
+         * The reply-charging-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPLY_CHARGING_ID = "r_chg_id";
+
+        /**
+         * The reply-charging-size of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String REPLY_CHARGING_SIZE = "r_chg_sz";
+
+        /**
+         * The previously-sent-by of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PREVIOUSLY_SENT_BY = "p_s_by";
+
+        /**
+         * The previously-sent-date of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PREVIOUSLY_SENT_DATE = "p_s_d";
+
+        /**
+         * The store of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORE = "store";
+
+        /**
+         * The mm-state of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MM_STATE = "mm_st";
+
+        /**
+         * The mm-flags-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MM_FLAGS_TOKEN = "mm_flg_tok";
+
+        /**
+         * The mm-flags of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MM_FLAGS = "mm_flg";
+
+        /**
+         * The store-status of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORE_STATUS = "store_st";
+
+        /**
+         * The store-status-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORE_STATUS_TEXT = "store_st_txt";
+
+        /**
+         * The stored of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STORED = "stored";
+
+        /**
+         * The totals of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TOTALS = "totals";
+
+        /**
+         * The mbox-totals of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MBOX_TOTALS = "mb_t";
+
+        /**
+         * The mbox-totals-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MBOX_TOTALS_TOKEN = "mb_t_tok";
+
+        /**
+         * The quotas of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String QUOTAS = "qt";
+
+        /**
+         * The mbox-quotas of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MBOX_QUOTAS = "mb_qt";
+
+        /**
+         * The mbox-quotas-token of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MBOX_QUOTAS_TOKEN = "mb_qt_tok";
+
+        /**
+         * The message-count of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_COUNT = "m_cnt";
+
+        /**
+         * The start of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String START = "start";
+
+        /**
+         * The distribution-indicator of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String DISTRIBUTION_INDICATOR = "d_ind";
+
+        /**
+         * The element-descriptor of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ELEMENT_DESCRIPTOR = "e_des";
+
+        /**
+         * The limit of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LIMIT = "limit";
+
+        /**
+         * The recommended-retrieval-mode of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RECOMMENDED_RETRIEVAL_MODE = "r_r_mod";
+
+        /**
+         * The recommended-retrieval-mode-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RECOMMENDED_RETRIEVAL_MODE_TEXT = "r_r_mod_txt";
+
+        /**
+         * The status-text of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String STATUS_TEXT = "st_txt";
+
+        /**
+         * The applic-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String APPLIC_ID = "apl_id";
+
+        /**
+         * The reply-applic-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPLY_APPLIC_ID = "r_apl_id";
+
+        /**
+         * The aux-applic-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String AUX_APPLIC_ID = "aux_apl_id";
+
+        /**
+         * The drm-content of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String DRM_CONTENT = "drm_c";
+
+        /**
+         * The adaptation-allowed of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADAPTATION_ALLOWED = "adp_a";
+
+        /**
+         * The replace-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String REPLACE_ID = "repl_id";
+
+        /**
+         * The cancel-id of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CANCEL_ID = "cl_id";
+
+        /**
+         * The cancel-status of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CANCEL_STATUS = "cl_st";
+
+        /**
+         * The thread ID of the message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THREAD_ID = "thread_id";
+
+        /**
+         * Has the message been locked?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String LOCKED = "locked";
+    }
+
+    /**
+     * Columns for the "canonical_addresses" table used by MMS and
+     * SMS."
+     */
+    public interface CanonicalAddressesColumns extends BaseColumns {
+        /**
+         * An address used in MMS or SMS.  Email addresses are
+         * converted to lower case and are compared by string
+         * equality.  Other addresses are compared using
+         * PHONE_NUMBERS_EQUAL.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADDRESS = "address";
+    }
+
+    /**
+     * Columns for the "threads" table used by MMS and SMS.
+     */
+    public interface ThreadsColumns extends BaseColumns {
+        /**
+         * The date at which the thread was created.
+         *
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * A string encoding of the recipient IDs of the recipients of
+         * the message, in numerical order and separated by spaces.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RECIPIENT_IDS = "recipient_ids";
+
+        /**
+         * The message count of the thread.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_COUNT = "message_count";
+        /**
+         * Indicates whether all messages of the thread have been read.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String READ = "read";
+        /**
+         * The snippet of the latest message in the thread.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SNIPPET = "snippet";
+        /**
+         * The charset of the snippet.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SNIPPET_CHARSET = "snippet_cs";
+        /**
+         * Type of the thread, either Threads.COMMON_THREAD or
+         * Threads.BROADCAST_THREAD.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TYPE = "type";
+        /**
+         * Indicates whether there is a transmission error in the thread.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ERROR = "error";
+        /**
+         * Indicates whether this thread contains any attachments.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String HAS_ATTACHMENT = "has_attachment";
+    }
+
+    /**
+     * Helper functions for the "threads" table used by MMS and SMS.
+     */
+    public static final class Threads implements ThreadsColumns {
+        private static final String[] ID_PROJECTION = { BaseColumns._ID };
+        private static final String STANDARD_ENCODING = "UTF-8";
+        private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
+                "content://mms-sms/threadID");
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                MmsSms.CONTENT_URI, "conversations");
+        public static final Uri OBSOLETE_THREADS_URI = Uri.withAppendedPath(
+                CONTENT_URI, "obsolete");
+
+        public static final int COMMON_THREAD    = 0;
+        public static final int BROADCAST_THREAD = 1;
+
+        // No one should construct an instance of this class.
+        private Threads() {
+        }
+
+        /**
+         * This is a single-recipient version of
+         * getOrCreateThreadId.  It's convenient for use with SMS
+         * messages.
+         */
+        public static long getOrCreateThreadId(Context context, String recipient) {
+            Set<String> recipients = new HashSet<String>();
+
+            recipients.add(recipient);
+            return getOrCreateThreadId(context, recipients);
+        }
+
+        /**
+         * Given the recipients list and subject of an unsaved message,
+         * return its thread ID.  If the message starts a new thread,
+         * allocate a new thread ID.  Otherwise, use the appropriate
+         * existing thread ID.
+         *
+         * Find the thread ID of the same set of recipients (in
+         * any order, without any additions). If one
+         * is found, return it.  Otherwise, return a unique thread ID.
+         */
+        public static long getOrCreateThreadId(
+                Context context, Set<String> recipients) {
+            Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
+
+            for (String recipient : recipients) {
+                if (Mms.isEmailAddress(recipient)) {
+                    recipient = Mms.extractAddrSpec(recipient);
+                }
+
+                uriBuilder.appendQueryParameter("recipient", recipient);
+            }
+
+            Uri uri = uriBuilder.build();
+            if (DEBUG) {
+                Log.v(TAG, "getOrCreateThreadId uri: " + uri);
+            }
+            Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+                    uri, ID_PROJECTION, null, null, null);
+            if (DEBUG) {
+                Log.v(TAG, "getOrCreateThreadId cursor cnt: " + cursor.getCount());
+            }
+            if (cursor != null) {
+                try {
+                    if (cursor.moveToFirst()) {
+                        return cursor.getLong(0);
+                    } else {
+                        Log.e(TAG, "getOrCreateThreadId returned no rows!");
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+
+            Log.e(TAG, "getOrCreateThreadId failed with uri " + uri.toString());
+            throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+        }
+    }
+
+    /**
+     * Contains all MMS messages.
+     */
+    public static final class Mms implements BaseMmsColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://mms");
+
+        public static final Uri REPORT_REQUEST_URI = Uri.withAppendedPath(
+                                            CONTENT_URI, "report-request");
+
+        public static final Uri REPORT_STATUS_URI = Uri.withAppendedPath(
+                                            CONTENT_URI, "report-status");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+        /**
+         * mailbox         =       name-addr
+         * name-addr       =       [display-name] angle-addr
+         * angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS]
+         */
+        public static final Pattern NAME_ADDR_EMAIL_PATTERN =
+                Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+        /**
+         * quoted-string   =       [CFWS]
+         *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+         *                         [CFWS]
+         */
+        public static final Pattern QUOTED_STRING_PATTERN =
+                Pattern.compile("\\s*\"([^\"]*)\"\\s*");
+
+        public static final Cursor query(
+                ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        public static final Cursor query(
+                ContentResolver cr, String[] projection,
+                String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection,
+                    where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        public static final String getMessageBoxName(int msgBox) {
+            switch (msgBox) {
+                case MESSAGE_BOX_ALL:
+                    return "all";
+                case MESSAGE_BOX_INBOX:
+                    return "inbox";
+                case MESSAGE_BOX_SENT:
+                    return "sent";
+                case MESSAGE_BOX_DRAFTS:
+                    return "drafts";
+                case MESSAGE_BOX_OUTBOX:
+                    return "outbox";
+                default:
+                    throw new IllegalArgumentException("Invalid message box: " + msgBox);
+            }
+        }
+
+        public static String extractAddrSpec(String address) {
+            Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
+
+            if (match.matches()) {
+                return match.group(2);
+            }
+            return address;
+        }
+
+        /**
+         * Returns true if the address is an email address
+         *
+         * @param address the input address to be tested
+         * @return true if address is an email address
+         */
+        public static boolean isEmailAddress(String address) {
+            if (TextUtils.isEmpty(address)) {
+                return false;
+            }
+
+            String s = extractAddrSpec(address);
+            Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
+            return match.matches();
+        }
+
+        /**
+         * Returns true if the number is a Phone number
+         *
+         * @param number the input number to be tested
+         * @return true if number is a Phone number
+         */
+        public static boolean isPhoneNumber(String number) {
+            if (TextUtils.isEmpty(number)) {
+                return false;
+            }
+
+            Matcher match = Patterns.PHONE.matcher(number);
+            return match.matches();
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's inbox.
+         */
+        public static final class Inbox implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/inbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's sent box.
+         */
+        public static final class Sent implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/sent");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's drafts box.
+         */
+        public static final class Draft implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/drafts");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app's outbox.
+         */
+        public static final class Outbox implements BaseMmsColumns {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/outbox");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        public static final class Addr implements BaseColumns {
+            /**
+             * The ID of MM which this address entry belongs to.
+             */
+            public static final String MSG_ID = "msg_id";
+
+            /**
+             * The ID of contact entry in Phone Book.
+             */
+            public static final String CONTACT_ID = "contact_id";
+
+            /**
+             * The address text.
+             */
+            public static final String ADDRESS = "address";
+
+            /**
+             * Type of address, must be one of PduHeaders.BCC,
+             * PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO.
+             */
+            public static final String TYPE = "type";
+
+            /**
+             * Character set of this entry.
+             */
+            public static final String CHARSET = "charset";
+        }
+
+        public static final class Part implements BaseColumns {
+            /**
+             * The identifier of the message which this part belongs to.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MSG_ID = "mid";
+
+            /**
+             * The order of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String SEQ = "seq";
+
+            /**
+             * The content type of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CONTENT_TYPE = "ct";
+
+            /**
+             * The name of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String NAME = "name";
+
+            /**
+             * The charset of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CHARSET = "chset";
+
+            /**
+             * The file name of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String FILENAME = "fn";
+
+            /**
+             * The content disposition of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CONTENT_DISPOSITION = "cd";
+
+            /**
+             * The content ID of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CONTENT_ID = "cid";
+
+            /**
+             * The content location of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CONTENT_LOCATION = "cl";
+
+            /**
+             * The start of content-type of the message.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CT_START = "ctt_s";
+
+            /**
+             * The type of content-type of the message.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CT_TYPE = "ctt_t";
+
+            /**
+             * The location(on filesystem) of the binary data of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String _DATA = "_data";
+
+            public static final String TEXT = "text";
+
+        }
+
+        public static final class Rate {
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                    Mms.CONTENT_URI, "rate");
+            /**
+             * When a message was successfully sent.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String SENT_TIME = "sent_time";
+        }
+
+        public static final class ScrapSpace {
+            /**
+             * The content:// style URL for this table
+             */
+            public static final Uri CONTENT_URI = Uri.parse("content://mms/scrapSpace");
+
+            /**
+             * This is the scrap file we use to store the media attachment when the user
+             * chooses to capture a photo to be attached . We pass {#link@Uri} to the Camera app,
+             * which streams the captured image to the uri. Internally we write the media content
+             * to this file. It's named '.temp.jpg' so Gallery won't pick it up.
+             */
+            public static final String SCRAP_FILE_PATH = "/sdcard/mms/scrapSpace/.temp.jpg";
+        }
+
+        public static final class Intents {
+            private Intents() {
+                // Non-instantiatable.
+            }
+
+            /**
+             * The extra field to store the contents of the Intent,
+             * which should be an array of Uri.
+             */
+            public static final String EXTRA_CONTENTS = "contents";
+            /**
+             * The extra field to store the type of the contents,
+             * which should be an array of String.
+             */
+            public static final String EXTRA_TYPES    = "types";
+            /**
+             * The extra field to store the 'Cc' addresses.
+             */
+            public static final String EXTRA_CC       = "cc";
+            /**
+             * The extra field to store the 'Bcc' addresses;
+             */
+            public static final String EXTRA_BCC      = "bcc";
+            /**
+             * The extra field to store the 'Subject'.
+             */
+            public static final String EXTRA_SUBJECT  = "subject";
+            /**
+             * Indicates that the contents of specified URIs were changed.
+             * The application which is showing or caching these contents
+             * should be updated.
+             */
+            public static final String
+            CONTENT_CHANGED_ACTION = "android.intent.action.CONTENT_CHANGED";
+            /**
+             * An extra field which stores the URI of deleted contents.
+             */
+            public static final String DELETED_CONTENTS = "deleted_contents";
+        }
+    }
+
+    /**
+     * Contains all MMS and SMS messages.
+     */
+    public static final class MmsSms implements BaseColumns {
+        /**
+         * The column to distinguish SMS &amp; MMS messages in query results.
+         */
+        public static final String TYPE_DISCRIMINATOR_COLUMN =
+                "transport_type";
+
+        public static final Uri CONTENT_URI = Uri.parse("content://mms-sms/");
+
+        public static final Uri CONTENT_CONVERSATIONS_URI = Uri.parse(
+                "content://mms-sms/conversations");
+
+        public static final Uri CONTENT_FILTER_BYPHONE_URI = Uri.parse(
+                "content://mms-sms/messages/byphone");
+
+        public static final Uri CONTENT_UNDELIVERED_URI = Uri.parse(
+                "content://mms-sms/undelivered");
+
+        public static final Uri CONTENT_DRAFT_URI = Uri.parse(
+                "content://mms-sms/draft");
+
+        public static final Uri CONTENT_LOCKED_URI = Uri.parse(
+                "content://mms-sms/locked");
+
+        /***
+         * Pass in a query parameter called "pattern" which is the text
+         * to search for.
+         * The sort order is fixed to be thread_id ASC,date DESC.
+         */
+        public static final Uri SEARCH_URI = Uri.parse(
+                "content://mms-sms/search");
+
+        // Constants for message protocol types.
+        public static final int SMS_PROTO = 0;
+        public static final int MMS_PROTO = 1;
+
+        // Constants for error types of pending messages.
+        public static final int NO_ERROR                      = 0;
+        public static final int ERR_TYPE_GENERIC              = 1;
+        public static final int ERR_TYPE_SMS_PROTO_TRANSIENT  = 2;
+        public static final int ERR_TYPE_MMS_PROTO_TRANSIENT  = 3;
+        public static final int ERR_TYPE_TRANSPORT_FAILURE    = 4;
+        public static final int ERR_TYPE_GENERIC_PERMANENT    = 10;
+        public static final int ERR_TYPE_SMS_PROTO_PERMANENT  = 11;
+        public static final int ERR_TYPE_MMS_PROTO_PERMANENT  = 12;
+
+        public static final class PendingMessages implements BaseColumns {
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                    MmsSms.CONTENT_URI, "pending");
+            /**
+             * The type of transport protocol(MMS or SMS).
+             * <P>Type: INTEGER</P>
+             */
+            public static final String PROTO_TYPE = "proto_type";
+            /**
+             * The ID of the message to be sent or downloaded.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MSG_ID = "msg_id";
+            /**
+             * The type of the message to be sent or downloaded.
+             * This field is only valid for MM. For SM, its value is always
+             * set to 0.
+             */
+            public static final String MSG_TYPE = "msg_type";
+            /**
+             * The type of the error code.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String ERROR_TYPE = "err_type";
+            /**
+             * The error code of sending/retrieving process.
+             * <P>Type:  INTEGER</P>
+             */
+            public static final String ERROR_CODE = "err_code";
+            /**
+             * How many times we tried to send or download the message.
+             * <P>Type:  INTEGER</P>
+             */
+            public static final String RETRY_INDEX = "retry_index";
+            /**
+             * The time to do next retry.
+             */
+            public static final String DUE_TIME = "due_time";
+            /**
+             * The time we last tried to send or download the message.
+             */
+            public static final String LAST_TRY = "last_try";
+        }
+    }
+
+    public static final class Carriers implements BaseColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI =
+            Uri.parse("content://telephony/carriers");
+
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+        public static final String NAME = "name";
+
+        public static final String APN = "apn";
+
+        public static final String PROXY = "proxy";
+
+        public static final String PORT = "port";
+
+        public static final String MMSPROXY = "mmsproxy";
+
+        public static final String MMSPORT = "mmsport";
+
+        public static final String SERVER = "server";
+
+        public static final String USER = "user";
+
+        public static final String PASSWORD = "password";
+
+        public static final String MMSC = "mmsc";
+
+        public static final String MCC = "mcc";
+
+        public static final String MNC = "mnc";
+
+        public static final String NUMERIC = "numeric";
+
+        public static final String AUTH_TYPE = "authtype";
+
+        public static final String TYPE = "type";
+
+        public static final String CURRENT = "current";
+    }
+
+    public static final class Intents {
+        private Intents() {
+            // Not instantiable
+        }
+
+        /**
+         * Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are
+         * of the form *#*#<code>#*#*. The intent will have the data URI:</p>
+         *
+         * <p><code>android_secret_code://&lt;code&gt;</code></p>
+         */
+        public static final String SECRET_CODE_ACTION =
+                "android.provider.Telephony.SECRET_CODE";
+
+        /**
+         * Broadcast Action: The Service Provider string(s) have been updated.  Activities or
+         * services that use these strings should update their display.
+         * The intent will have the following extra values:</p>
+         * <ul>
+         *   <li><em>showPlmn</em> - Boolean that indicates whether the PLMN should be shown.</li>
+         *   <li><em>plmn</em> - The operator name of the registered network, as a string.</li>
+         *   <li><em>showSpn</em> - Boolean that indicates whether the SPN should be shown.</li>
+         *   <li><em>spn</em> - The service provider name, as a string.</li>
+         * </ul>
+         * Note that <em>showPlmn</em> may indicate that <em>plmn</em> should be displayed, even
+         * though the value for <em>plmn</em> is null.  This can happen, for example, if the phone
+         * has not registered to a network yet.  In this case the receiver may substitute an
+         * appropriate placeholder string (eg, "No service").
+         *
+         * It is recommended to display <em>plmn</em> before / above <em>spn</em> if
+         * both are displayed.
+         *
+         * <p>Note this is a protected intent that can only be sent
+         * by the system.
+         */
+        public static final String SPN_STRINGS_UPDATED_ACTION =
+                "android.provider.Telephony.SPN_STRINGS_UPDATED";
+
+        public static final String EXTRA_SHOW_PLMN  = "showPlmn";
+        public static final String EXTRA_PLMN       = "plmn";
+        public static final String EXTRA_SHOW_SPN   = "showSpn";
+        public static final String EXTRA_SPN        = "spn";
+    }
+}
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/Android.mk b/opengl/libagl/Android.mk
index 9837845..6cb146c 100644
--- a/opengl/libagl/Android.mk
+++ b/opengl/libagl/Android.mk
@@ -29,7 +29,7 @@
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 LOCAL_CFLAGS += -fvisibility=hidden
 
-LOCAL_SHARED_LIBRARIES := libcutils libhardware libutils libpixelflinger
+LOCAL_SHARED_LIBRARIES := libcutils libhardware libutils libpixelflinger libETC1
 LOCAL_LDLIBS := -lpthread -ldl
 
 ifeq ($(TARGET_ARCH),arm)
@@ -39,6 +39,11 @@
 
 ifneq ($(TARGET_SIMULATOR),true)
     # we need to access the private Bionic header <bionic_tls.h>
+    # on ARM platforms, we need to mirror the ARCH_ARM_HAVE_TLS_REGISTER
+    # behavior from the bionic Android.mk file
+    ifeq ($(TARGET_ARCH)-$(ARCH_ARM_HAVE_TLS_REGISTER),arm-true)
+        LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
+    endif
     LOCAL_C_INCLUDES += bionic/libc/private
 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/libagl/state.cpp b/opengl/libagl/state.cpp
index 0f1f27d..1224a96 100644
--- a/opengl/libagl/state.cpp
+++ b/opengl/libagl/state.cpp
@@ -51,6 +51,9 @@
     //        "GL_OES_point_size_array "              // TODO
     //        "GL_OES_point_sprite "                  // TODO
     "GL_OES_EGL_image "                     // OK
+#ifdef GL_OES_compressed_ETC1_RGB8_texture
+    "GL_OES_compressed_ETC1_RGB8_texture "  // OK
+#endif
     "GL_ARB_texture_compression "           // OK
     "GL_ARB_texture_non_power_of_two "      // OK
     "GL_ANDROID_user_clip_plane "           // OK
@@ -386,6 +389,7 @@
 
 void glGetIntegerv(GLenum pname, GLint *params)
 {
+    int i;
     ogles_context_t* c = ogles_context_t::get();
     switch (pname) {
     case GL_ALIASED_POINT_SIZE_RANGE:
@@ -431,6 +435,10 @@
         params[ 7] = GL_PALETTE8_R5_G6_B5_OES;
         params[ 8] = GL_PALETTE8_RGBA4_OES;
         params[ 9] = GL_PALETTE8_RGB5_A1_OES;
+        i = 10;
+#ifdef GL_OES_compressed_ETC1_RGB8_texture
+        params[i++] = GL_ETC1_RGB8_OES;
+#endif
         break;
     case GL_DEPTH_BITS:
         params[0] = c->rasterizer.state.buffers.depth.format ? 0 : 16;
diff --git a/opengl/libagl/texture.cpp b/opengl/libagl/texture.cpp
index 13d078e..a1a776f 100644
--- a/opengl/libagl/texture.cpp
+++ b/opengl/libagl/texture.cpp
@@ -24,6 +24,7 @@
 #include "TextureObjectManager.h"
 
 #include <private/ui/android_natives_priv.h>
+#include <ETC1/etc1.h>
 
 #ifdef LIBAGL_USE_GRALLOC_COPYBITS
 #include "copybit.h"
@@ -583,7 +584,7 @@
 
 
 static __attribute__((noinline))
-void set_depth_and_fog(ogles_context_t* c, GLint z)
+void set_depth_and_fog(ogles_context_t* c, GGLfixed z)
 {
     const uint32_t enables = c->rasterizer.state.enables;
     // we need to compute Zw
@@ -592,8 +593,8 @@
     GGLfixed Zw;
     GGLfixed n = gglFloatToFixed(c->transforms.vpt.zNear);
     GGLfixed f = gglFloatToFixed(c->transforms.vpt.zFar);
-    if (z<=0)       Zw = n;
-    else if (z>=1)  Zw = f;
+    if (z<=0)               Zw = n;
+    else if (z>=0x10000)    Zw = f;
     else            Zw = gglMulAddx(z, (f-n), n);
     if (enables & GGL_ENABLE_FOG) {
         // set up fog if needed...
@@ -836,7 +837,7 @@
             c->rasterizer.procs.texCoord2i(c, s0, t0);
             const uint32_t enables = c->rasterizer.state.enables;
             if (ggl_unlikely(enables & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_FOG)))
-                set_depth_and_fog(c, z);
+                set_depth_and_fog(c, gglIntToFixed(z));
 
             c->rasterizer.procs.color4xv(c, c->currentColorClamped.v);
             c->rasterizer.procs.disable(c, GGL_W_LERP);
@@ -1081,11 +1082,6 @@
         ogles_error(c, GL_INVALID_ENUM);
         return;
     }
-    if ((internalformat < GL_PALETTE4_RGB8_OES ||
-         internalformat > GL_PALETTE8_RGB5_A1_OES)) {
-        ogles_error(c, GL_INVALID_ENUM);
-        return;
-    }
     if (width<0 || height<0 || border!=0) {
         ogles_error(c, GL_INVALID_VALUE);
         return;
@@ -1121,6 +1117,12 @@
         format      = GL_RGBA;
         type        = GL_UNSIGNED_SHORT_5_5_5_1;
         break;
+#ifdef GL_OES_compressed_ETC1_RGB8_texture
+    case GL_ETC1_RGB8_OES:
+        format      = GL_RGB;
+        type        = GL_UNSIGNED_BYTE;
+        break;
+#endif
     default:
         ogles_error(c, GL_INVALID_ENUM);
         return;
@@ -1133,6 +1135,30 @@
 
     int32_t size;
     GGLSurface* surface;
+
+#ifdef GL_OES_compressed_ETC1_RGB8_texture
+    if (internalformat == GL_ETC1_RGB8_OES) {
+        GLsizei compressedSize = etc1_get_encoded_data_size(width, height);
+        if (compressedSize > imageSize) {
+            ogles_error(c, GL_INVALID_VALUE);
+            return;
+        }
+        int error = createTextureSurface(c, &surface, &size,
+                level, format, type, width, height);
+        if (error) {
+            ogles_error(c, error);
+            return;
+        }
+        if (etc1_decode_image(
+                (const etc1_byte*)data,
+                (etc1_byte*)surface->data,
+                width, height, 3, surface->stride*3) != 0) {
+            ogles_error(c, GL_INVALID_OPERATION);
+        }
+        return;
+    }
+#endif
+
     // all mipmap levels are specified at once.
     const int numLevels = level<0 ? -level : 1;
 
@@ -1389,9 +1415,20 @@
     // (x,y) is the lower-left corner of colorBuffer
     y = cbSurface.height - (y + height);
 
+    /* The GLES spec says:
+     * If any of the pixels within the specified rectangle are outside
+     * the framebuffer associated with the current rendering context,
+     * then the values obtained for those pixels are undefined.
+     */
+    if (x+width > GLint(cbSurface.width))
+        width = cbSurface.width - x;
+
+    if (y+height > GLint(cbSurface.height))
+        height = cbSurface.height - y;
+
     int err = copyPixels(c,
             txSurface, 0, 0,
-            cbSurface, x, y, cbSurface.width, cbSurface.height);
+            cbSurface, x, y, width, height);
     if (err) {
         ogles_error(c, err);
     }
@@ -1439,6 +1476,17 @@
     const GGLSurface& cbSurface = c->rasterizer.state.buffers.color.s;
     y = cbSurface.height - (y + height);
 
+    /* The GLES spec says:
+     * If any of the pixels within the specified rectangle are outside
+     * the framebuffer associated with the current rendering context,
+     * then the values obtained for those pixels are undefined.
+     */
+    if (x+width > GLint(cbSurface.width))
+        width = cbSurface.width - x;
+
+    if (y+height > GLint(cbSurface.height))
+        height = cbSurface.height - y;
+
     int err = copyPixels(c,
             surface, xoffset, yoffset,
             cbSurface, x, y, width, height);
diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk
index 6d20e80..6b7020f 100644
--- a/opengl/libs/Android.mk
+++ b/opengl/libs/Android.mk
@@ -20,6 +20,11 @@
 ifeq ($(TARGET_SIMULATOR),true)
 else
     LOCAL_SHARED_LIBRARIES += libdl
+    # Bionic's private TLS header relies on the ARCH_ARM_HAVE_TLS_REGISTER to
+    # select the appropriate TLS codepath
+    ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
+        LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
+    endif
     # we need to access the private Bionic header <bionic_tls.h>
     LOCAL_C_INCLUDES += bionic/libc/private
 endif
@@ -75,6 +80,9 @@
 else
     LOCAL_SHARED_LIBRARIES += libdl
     # we need to access the private Bionic header <bionic_tls.h>
+    ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
+        LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
+    endif
     LOCAL_C_INCLUDES += bionic/libc/private
 endif
 
@@ -108,6 +116,9 @@
 else
     LOCAL_SHARED_LIBRARIES += libdl
     # we need to access the private Bionic header <bionic_tls.h>
+    ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
+        LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
+    endif
     LOCAL_C_INCLUDES += bionic/libc/private
 endif
 
@@ -120,3 +131,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/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index c22c21b..d2f8ced 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -1111,10 +1111,10 @@
         if (cur_c == NULL) {
             // no current context
             if (draw != EGL_NO_SURFACE || read != EGL_NO_SURFACE) {
-                // calling eglMakeCurrent( ..., EGL_NO_CONTEXT, !=0, !=0);
-                return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+                // calling eglMakeCurrent( ..., !=0, !=0, EGL_NO_CONTEXT);
+                return setError(EGL_BAD_MATCH, EGL_FALSE);
             }
-            // not an error, there is just not current context.
+            // not an error, there is just no current context.
             return EGL_TRUE;
         }
     }
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/opengl/tools/glgen/specs/jsr239/glspec-1.1extpack b/opengl/tools/glgen/specs/jsr239/glspec-1.1extpack
index ca9e6d2..d6012d9 100644
--- a/opengl/tools/glgen/specs/jsr239/glspec-1.1extpack
+++ b/opengl/tools/glgen/specs/jsr239/glspec-1.1extpack
@@ -7,14 +7,14 @@
 GLint glCheckFramebufferStatusOES ( GLint target )
 void glCompressedTexImage2D ( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data )
 void glCopyTexImage2D ( GLint target, GLint level, GLint internalformat, GLint x, GLint y, GLint width, GLint height, GLint border )
-void glDeleteFramebuffersOES ( GLint n, GLint *framebuffers )
-void glDeleteRenderbuffersOES ( GLint n, GLint *renderbuffers )
+void glDeleteFramebuffersOES ( GLint n, GLuint *framebuffers )
+void glDeleteRenderbuffersOES ( GLint n, GLuint *renderbuffers )
 void glEnable ( GLint cap )
 void glFramebufferRenderbufferOES ( GLint target, GLint attachment, GLint renderbuffertarget, GLint renderbuffer )
 void glFramebufferTexture2DOES ( GLint target, GLint attachment, GLint textarget, GLint texture, GLint level )
 void glGenerateMipmapOES ( GLint target )
-void glGenFramebuffersOES ( GLint n, GLint *framebuffers )
-void glGenRenderbuffersOES ( GLint n, GLint *renderbuffers )
+void glGenFramebuffersOES ( GLint n, GLuint *framebuffers )
+void glGenRenderbuffersOES ( GLint n, GLuint *renderbuffers )
 void glGetFramebufferAttachmentParameterivOES ( GLint target, GLint attachment, GLint pname, GLint *params )
 void glGetIntegerv ( GLint pname, GLint *params )
 void glGetRenderbufferParameterivOES ( GLint target, GLint pname, GLint *params )
diff --git a/opengl/tools/glgen/specs/jsr239/glspec-checks b/opengl/tools/glgen/specs/jsr239/glspec-checks
index c28e403..9f8a793 100644
--- a/opengl/tools/glgen/specs/jsr239/glspec-checks
+++ b/opengl/tools/glgen/specs/jsr239/glspec-checks
@@ -29,28 +29,28 @@
 glDrawTexivOES check coords 5
 glDrawTexsvOES check coords 5
 glDrawTexxvOES check coords 5
-glBindFramebufferOES unsupported
-glBindRenderbufferOES unsupported
-glBlendEquation unsupported
-glBlendEquationSeparate unsupported
-glBlendFuncSeparate unsupported
-glCheckFramebufferStatusOES unsupported return 0
-glDeleteFramebuffersOES unsupported
-glDeleteRenderbuffersOES unsupported
-glFramebufferRenderbufferOES unsupported
-glFramebufferStorageOES unsupported
-glFramebufferTexture2DOES unsupported
-glGenFramebuffersOES unsupported
-glGenRenderbuffersOES unsupported
-glGenerateMipmapOES unsupported
+glBindFramebufferOES requires OES_framebuffer_object
+glBindRenderbufferOES requires OES_framebuffer_object
+glBlendEquation requires OES_blend_subtract
+glBlendEquationSeparate requires OES_blend_equation_separate
+glBlendFuncSeparate requires OES_blend_equation_separate
+glCheckFramebufferStatusOES requires OES_framebuffer_object return 0
+glDeleteFramebuffersOES requires OES_framebuffer_object check framebuffers n
+glDeleteRenderbuffersOES requires OES_framebuffer_object check renderbuffers n
+glFramebufferRenderbufferOES requires OES_framebuffer_object
+glFramebufferStorageOES requires OES_framebuffer_object
+glFramebufferTexture2DOES requires OES_framebuffer_object
+glGenFramebuffersOES requires OES_framebuffer_object check framebuffers n
+glGenRenderbuffersOES requires OES_framebuffer_object check renderbuffers n
+glGenerateMipmapOES requires OES_framebuffer_object
+glGetFramebufferAttachmentParameterivOES requires OES_framebuffer_object
+glGetRenderbufferParameterivOES requires OES_framebuffer_object
+glIsFramebufferOES requires OES_framebuffer_object return JNI_FALSE
+glIsRenderbufferOES requires OES_framebuffer_object return JNI_FALSE
+glRenderbufferStorageOES requires OES_framebuffer_object
+glGetTexGen requires OES_texture_cube_map
+glTexGen requires OES_texture_cube_map
+glTexGenf requires OES_texture_cube_map
+glTexGeni requires OES_texture_cube_map
+glTexGenx requires OES_texture_cube_map
 glGetBufferParameter unsupported
-glGetFramebufferAttachmentParameterivOES unsupported
-glGetRenderbufferParameterivOES unsupported
-glGetTexGen unsupported
-glIsFramebufferOES unsupported return JNI_FALSE
-glIsRenderbufferOES unsupported return JNI_FALSE
-glRenderbufferStorageOES unsupported return false
-glTexGen unsupported
-glTexGenf unsupported
-glTexGeni unsupported
-glTexGenx unsupported
diff --git a/opengl/tools/glgen/src/JniCodeEmitter.java b/opengl/tools/glgen/src/JniCodeEmitter.java
index e79170a..2db4e8d 100644
--- a/opengl/tools/glgen/src/JniCodeEmitter.java
+++ b/opengl/tools/glgen/src/JniCodeEmitter.java
@@ -211,6 +211,8 @@
                     index += 5;
                 } else if (checks[index].equals("unsupported")) {
                     index += 1;
+                } else if (checks[index].equals("requires")) {
+                    index += 2;
                 } else if (checks[index].equals("nullAllowed")) {
                     return true;
                 } else {
@@ -243,6 +245,8 @@
                     index += 5;
                 } else if (checks[index].equals("unsupported")) {
                     index += 1;
+                } else if (checks[index].equals("requires")) {
+                    index += 2;
                 } else if (checks[index].equals("nullAllowed")) {
                     index += 1;
                 } else {
@@ -263,6 +267,8 @@
             while (index < checks.length) {
                 if (checks[index].equals("unsupported")) {
                     return true;
+                } else if (checks[index].equals("requires")) {
+                    index += 2;
                 } else if (checks[index].equals("return")) {
                     index += 2;
                 } else if (checks[index].startsWith("check")) {
@@ -280,7 +286,34 @@
         }
         return false;
     }
-
+    
+    String isRequiresFunc(CFunc cfunc) {
+        String[] checks = mChecker.getChecks(cfunc.getName());
+        int index = 1;
+        if (checks != null) {
+            while (index < checks.length) {
+                if (checks[index].equals("unsupported")) {
+                    index += 1;
+                } else if (checks[index].equals("requires")) {
+                    return checks[index+1];
+                } else if (checks[index].equals("return")) {
+                    index += 2;
+                } else if (checks[index].startsWith("check")) {
+                    index += 3;
+                } else if (checks[index].equals("ifcheck")) {
+                    index += 5;
+                } else if (checks[index].equals("nullAllowed")) {
+                    index += 1;
+                } else {
+                    System.out.println("Error: unknown keyword \"" +
+                                       checks[index] + "\"");
+                    System.exit(0);
+                }
+            }
+        }
+        return null;
+    }
+    
     void emitNativeBoundsChecks(CFunc cfunc, String cname, PrintStream out,
             boolean isBuffer, boolean emitExceptionCheck, String offset, String remaining, String iii) {
 
@@ -365,6 +398,9 @@
                         } else if (checks[index].equals("unsupported")) {
                             // ignore
                             index += 1;
+                        } else if (checks[index].equals("requires")) {
+                            // ignore
+                            index += 2;
                         } else if (checks[index].equals("nullAllowed")) {
                             // ignore
                             index += 1;
@@ -776,7 +812,23 @@
             out.println();
             return;
         }
-
+        
+        String requiresExtension = isRequiresFunc(cfunc);
+        if (requiresExtension != null) {
+            out.println(indent +
+                        "if (! supportsExtension(_env, _this, have_" + requiresExtension + "ID)) {");
+            out.println(indent + indent +
+                        "_env->ThrowNew(UOEClass,");
+            out.println(indent + indent +
+                        "    \"" + cfunc.getName() + "\");");
+            if (isVoid) {
+                out.println(indent + indent + "    return;");
+            } else {
+                String retval = getErrorReturnValue(cfunc);
+                out.println(indent + indent + "    return " + retval + ";");
+            }
+            out.println(indent + "}");
+        }
         if (mUseContextPointer) {
             out.println(indent +
                 "android::gl::ogles_context_t *ctx = getContext(_env, _this);");
diff --git a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
index b3d1c6c..c2464b0 100644
--- a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
+++ b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
@@ -23,6 +23,23 @@
 #include <GLES/gl.h>
 #include <GLES/glext.h>
 
+// Work around differences between the generated name and the actual name.
+
+#define glBlendEquation glBlendEquationOES
+#define glBlendEquationSeparate glBlendEquationSeparateOES
+#define glBlendFuncSeparate glBlendFuncSeparateOES
+#define glGetTexGenfv glGetTexGenfvOES
+#define glGetTexGeniv glGetTexGenivOES
+#define glGetTexGenxv glGetTexGenxvOES
+#define glTexGenf glTexGenfOES
+#define glTexGenfv glTexGenfvOES
+#define glTexGeni glTexGeniOES
+#define glTexGeniv glTexGenivOES
+#define glTexGenx glTexGenxOES
+#define glTexGenxv glTexGenxvOES
+
+
+
 /* special calls implemented in Android's GLES wrapper used to more
  * efficiently bound-check passed arrays */
 extern "C" {
@@ -58,6 +75,11 @@
 static jfieldID positionID;
 static jfieldID limitID;
 static jfieldID elementSizeShiftID;
+static jfieldID haveCheckedExtensionsID;
+static jfieldID have_OES_blend_equation_separateID;
+static jfieldID have_OES_blend_subtractID;
+static jfieldID have_OES_framebuffer_objectID;
+static jfieldID have_OES_texture_cube_mapID;
 
 /* Cache method IDs each time the class is loaded. */
 
@@ -72,6 +94,11 @@
 
     jclass g11impClassLocal = _env->FindClass("com/google/android/gles_jni/GLImpl");
     G11ImplClass = (jclass) _env->NewGlobalRef(g11impClassLocal);
+    haveCheckedExtensionsID =  _env->GetFieldID(G11ImplClass, "haveCheckedExtensions", "Z");
+    have_OES_blend_equation_separateID =  _env->GetFieldID(G11ImplClass, "have_OES_blend_equation_separate", "Z");
+    have_OES_blend_subtractID =  _env->GetFieldID(G11ImplClass, "have_OES_blend_subtract", "Z");
+    have_OES_framebuffer_objectID =  _env->GetFieldID(G11ImplClass, "have_OES_framebuffer_object", "Z");
+    have_OES_texture_cube_mapID =  _env->GetFieldID(G11ImplClass, "have_OES_texture_cube_map", "Z");
 
     getBasePointerID = _env->GetStaticMethodID(nioAccessClass,
             "getBasePointer", "(Ljava/nio/Buffer;)J");
@@ -193,5 +220,63 @@
     return numCompressedTextureFormats;
 }
 
+// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is
+// terminated by either 0 or space, while pExtension is terminated by 0.
+
+static bool
+extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) {
+    while (true) {
+        char a = *pExtensions++;
+        char b = *pExtension++;
+        bool aEnd = a == '\0' || a == ' ';
+        bool bEnd = b == '\0';
+        if ( aEnd || bEnd) {
+            return aEnd == bEnd;
+        }
+        if ( a != b ) {
+            return false;
+        }
+    }
+}
+
+static const GLubyte*
+nextExtension(const GLubyte* pExtensions) {
+    while (true) {
+        char a = *pExtensions++;
+        if ( a == '\0') {
+            return pExtensions-1;
+        } else if ( a == ' ') {
+            return pExtensions;
+        }
+    }
+}
+    
+static bool
+checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) {
+    for (;*pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) {
+        if (extensionEqual(pExtensions, pExtension)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+supportsExtension(JNIEnv *_env, jobject impl, jfieldID fieldId) {
+    if (!_env->GetBooleanField(impl, haveCheckedExtensionsID)) {
+        _env->SetBooleanField(impl, haveCheckedExtensionsID, true);
+        const GLubyte* sExtensions = glGetString(GL_EXTENSIONS);
+        _env->SetBooleanField(impl, have_OES_blend_equation_separateID,
+            checkForExtension(sExtensions, (const GLubyte*) "GL_OES_blend_equation_separate"));
+        _env->SetBooleanField(impl, have_OES_blend_subtractID,
+            checkForExtension(sExtensions, (const GLubyte*) "GL_OES_blend_subtract"));
+        _env->SetBooleanField(impl, have_OES_framebuffer_objectID,
+            checkForExtension(sExtensions, (const GLubyte*) "GL_OES_framebuffer_object"));
+        _env->SetBooleanField(impl, have_OES_texture_cube_mapID,
+            checkForExtension(sExtensions, (const GLubyte*) "GL_OES_texture_cube_map"));
+    }
+    return _env->GetBooleanField(impl, fieldId);
+}
+
 // --------------------------------------------------------------------------
 
diff --git a/opengl/tools/glgen/stubs/jsr239/GLImplHeader.java-impl b/opengl/tools/glgen/stubs/jsr239/GLImplHeader.java-impl
index 76fea3f..3727106 100644
--- a/opengl/tools/glgen/stubs/jsr239/GLImplHeader.java-impl
+++ b/opengl/tools/glgen/stubs/jsr239/GLImplHeader.java-impl
@@ -47,6 +47,12 @@
     Buffer _pointSizePointerOES = null;
     Buffer _matrixIndexPointerOES = null;
     Buffer _weightPointerOES = null;
+    
+    private boolean haveCheckedExtensions;
+    private boolean have_OES_blend_equation_separate;
+    private boolean have_OES_blend_subtract;
+    private boolean have_OES_framebuffer_object;
+    private boolean have_OES_texture_cube_map;
 
     public GLImpl() {
     }
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.
diff --git a/vpn/tests/vpntests/Android.mk b/vpn/tests/vpntests/Android.mk
new file mode 100644
index 0000000..a19fb56
--- /dev/null
+++ b/vpn/tests/vpntests/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := FrameworksVpnTests
+
+include $(BUILD_PACKAGE)
+
diff --git a/vpn/tests/vpntests/AndroidManifest.xml b/vpn/tests/vpntests/AndroidManifest.xml
new file mode 100644
index 0000000..d8405f6
--- /dev/null
+++ b/vpn/tests/vpntests/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.frameworks.vpntests">
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+    	android:name="android.test.InstrumentationTestRunner"
+    	android:targetPackage="com.android.frameworks.vpntests"
+    	android:label="Frameworks VPN Tests" />
+</manifest>
diff --git a/vpn/tests/vpntests/src/android/net/vpn/VpnTest.java b/vpn/tests/vpntests/src/android/net/vpn/VpnTest.java
new file mode 100755
index 0000000..46a57d3
--- /dev/null
+++ b/vpn/tests/vpntests/src/android/net/vpn/VpnTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vpn;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.vpn.L2tpProfile;
+import android.net.vpn.L2tpIpsecProfile;
+import android.net.vpn.L2tpIpsecPskProfile;
+import android.net.vpn.PptpProfile;
+import android.net.vpn.VpnManager;
+import android.net.vpn.VpnProfile;
+import android.net.vpn.VpnState;
+import android.net.vpn.VpnType;
+import android.os.ConditionVariable;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+/**
+ * Unit test class to test VPN api
+ * Use the below command to run the vpn unit test only
+ * runtest vpntest or
+ * adb shell am instrument -e class 'com.android.unit_tests.VpnTest'
+ *   -w com.android.unit_tests/android.test.InstrumentationTestRunner
+ */
+public class VpnTest extends AndroidTestCase {
+    private static final String NAME = "a name";
+    private static final String SERVER_NAME = "a server name";
+    private static final String ID = "some id";
+    private static final String SUFFICES = "some suffices";
+    private static final String ROUTES = "some routes";
+    private static final String SAVED_NAME = "some name";
+
+    @Override
+    public void setUp() {
+    }
+
+    @Override
+    public void tearDown() {
+    }
+
+    @SmallTest
+    public void testVpnType() {
+        testVpnType(VpnType.L2TP);
+        testVpnType(VpnType.L2TP_IPSEC);
+        testVpnType(VpnType.L2TP_IPSEC_PSK);
+        testVpnType(VpnType.PPTP);
+    }
+
+    @SmallTest
+    public void testVpnProfile() {
+        VpnState state = VpnState.CONNECTING;
+        testVpnProfile(createTestProfile(state), state);
+    }
+
+    @SmallTest
+    public void testGetType() {
+        assertEquals(VpnType.L2TP, new L2tpProfile().getType());
+        assertEquals(VpnType.L2TP_IPSEC, new L2tpIpsecProfile().getType());
+        assertEquals(VpnType.L2TP_IPSEC_PSK, 
+                new L2tpIpsecPskProfile().getType());
+        assertEquals(VpnType.PPTP, new PptpProfile().getType());
+    }
+
+    @SmallTest
+    public void testVpnTypes() {
+        assertTrue(VpnManager.getSupportedVpnTypes().length > 0);
+    }
+
+    @SmallTest
+    public void testGetTypeFromManager() {
+        VpnManager m = new VpnManager(getContext());
+        VpnType[] types = VpnManager.getSupportedVpnTypes();
+        for (VpnType t : types) {
+            assertEquals(t, m.createVpnProfile(t).getType());
+        }
+    }
+
+    @SmallTest
+    public void testParcelable() {
+        VpnProfile p = createTestProfile(VpnState.CONNECTED);
+        Parcel parcel = Parcel.obtain();
+        p.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        // VpnState is transient and not saved in the parcel
+        testVpnProfile(VpnProfile.CREATOR.createFromParcel(parcel), null);
+    }
+
+    @SmallTest
+    public void testReceiver() {
+        final String profileName = "whatever";
+        final VpnState state = VpnState.DISCONNECTING;
+        final ConditionVariable cv = new ConditionVariable();
+        cv.close();
+        BroadcastReceiver r = new BroadcastReceiver() {
+            public void onReceive(Context c, Intent i) {
+                assertEquals(profileName,
+                        i.getStringExtra(VpnManager.BROADCAST_PROFILE_NAME));
+                assertEquals(state, i.getSerializableExtra(
+                        VpnManager.BROADCAST_CONNECTION_STATE));
+                cv.open();
+            }
+        };
+
+        VpnManager m = new VpnManager(getContext());
+        m.registerConnectivityReceiver(r);
+        m.broadcastConnectivity(profileName, state);
+
+        // fail it if onReceive() doesn't get executed in 5 sec
+        assertTrue(cv.block(5000));
+    }
+
+    private void testVpnType(VpnType type) {
+        assertFalse(TextUtils.isEmpty(type.getDisplayName()));
+        assertNotNull(type.getProfileClass());
+    }
+
+    private VpnProfile createTestProfile(VpnState state) {
+        VpnProfile p = new L2tpProfile();
+        p.setName(NAME);
+        p.setServerName(SERVER_NAME);
+        p.setId(ID);
+        p.setDomainSuffices(SUFFICES);
+        p.setRouteList(ROUTES);
+        p.setSavedUsername(SAVED_NAME);
+        p.setState(state);
+        return p;
+    }
+
+    private void testVpnProfile(VpnProfile p, VpnState state) {
+        assertEquals(NAME, p.getName());
+        assertEquals(SERVER_NAME, p.getServerName());
+        assertEquals(ID, p.getId());
+        assertEquals(SUFFICES, p.getDomainSuffices());
+        assertEquals(ROUTES, p.getRouteList());
+        assertEquals(SAVED_NAME, p.getSavedUsername());
+        if (state != null) assertEquals(state, p.getState());
+    }
+}