Merge "Fixed comment for selectAllOnFocus"
diff --git a/api/current.txt b/api/current.txt
index d034b89..0a83fe0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14884,7 +14884,7 @@
 
   public class Looper {
     method public void dump(android.util.Printer, java.lang.String);
-    method public static synchronized android.os.Looper getMainLooper();
+    method public static android.os.Looper getMainLooper();
     method public java.lang.Thread getThread();
     method public static void loop();
     method public static android.os.Looper myLooper();
diff --git a/cmds/content/Android.mk b/cmds/content/Android.mk
index a3d83cf..88c46f2 100644
--- a/cmds/content/Android.mk
+++ b/cmds/content/Android.mk
@@ -7,8 +7,6 @@
 
 LOCAL_MODULE := content
 
-LOCAL_MODULE_TAGS := optional
-
 include $(BUILD_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 11e94e8..f26747b 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -147,4 +147,28 @@
 
 include $(BUILD_EXECUTABLE)
 
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=               \
+        codec.cpp               \
+        SimplePlayer.cpp        \
+
+LOCAL_SHARED_LIBRARIES := \
+	libstagefright liblog libutils libbinder libstagefright_foundation \
+        libmedia libgui libcutils libui
+
+LOCAL_C_INCLUDES:= \
+	$(JNI_H_INCLUDE) \
+	frameworks/base/media/libstagefright \
+	$(TOP)/frameworks/base/include/media/stagefright/openmax
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_MODULE:= codec
+
+include $(BUILD_EXECUTABLE)
 
diff --git a/cmds/stagefright/SimplePlayer.cpp b/cmds/stagefright/SimplePlayer.cpp
new file mode 100644
index 0000000..d5b9223
--- /dev/null
+++ b/cmds/stagefright/SimplePlayer.cpp
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SimplePlayer"
+#include <utils/Log.h>
+
+#include "SimplePlayer.h"
+
+#include <gui/SurfaceTextureClient.h>
+#include <media/AudioTrack.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/NativeWindowWrapper.h>
+#include <media/stagefright/NuMediaExtractor.h>
+
+namespace android {
+
+SimplePlayer::SimplePlayer()
+    : mState(UNINITIALIZED),
+      mDoMoreStuffGeneration(0),
+      mStartTimeRealUs(-1ll) {
+}
+
+SimplePlayer::~SimplePlayer() {
+}
+
+// static
+status_t PostAndAwaitResponse(
+        const sp<AMessage> &msg, sp<AMessage> *response) {
+    status_t err = msg->postAndAwaitResponse(response);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (!(*response)->findInt32("err", &err)) {
+        err = OK;
+    }
+
+    return err;
+}
+status_t SimplePlayer::setDataSource(const char *path) {
+    sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
+    msg->setString("path", path);
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t SimplePlayer::setSurface(const sp<ISurfaceTexture> &surfaceTexture) {
+    sp<AMessage> msg = new AMessage(kWhatSetSurface, id());
+
+    sp<SurfaceTextureClient> surfaceTextureClient;
+    if (surfaceTexture != NULL) {
+        surfaceTextureClient = new SurfaceTextureClient(surfaceTexture);
+    }
+
+    msg->setObject(
+            "native-window", new NativeWindowWrapper(surfaceTextureClient));
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t SimplePlayer::prepare() {
+    sp<AMessage> msg = new AMessage(kWhatPrepare, id());
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t SimplePlayer::start() {
+    sp<AMessage> msg = new AMessage(kWhatStart, id());
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t SimplePlayer::stop() {
+    sp<AMessage> msg = new AMessage(kWhatStop, id());
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t SimplePlayer::reset() {
+    sp<AMessage> msg = new AMessage(kWhatReset, id());
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+void SimplePlayer::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatSetDataSource:
+        {
+            status_t err;
+            if (mState != UNINITIALIZED) {
+                err = INVALID_OPERATION;
+            } else {
+                CHECK(msg->findString("path", &mPath));
+                mState = UNPREPARED;
+            }
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatSetSurface:
+        {
+            status_t err;
+            if (mState != UNPREPARED) {
+                err = INVALID_OPERATION;
+            } else {
+                sp<RefBase> obj;
+                CHECK(msg->findObject("native-window", &obj));
+
+                mNativeWindow = static_cast<NativeWindowWrapper *>(obj.get());
+
+                err = OK;
+            }
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatPrepare:
+        {
+            status_t err;
+            if (mState != UNPREPARED) {
+                err = INVALID_OPERATION;
+            } else {
+                err = onPrepare();
+
+                if (err == OK) {
+                    mState = STOPPED;
+                }
+            }
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatStart:
+        {
+            status_t err = OK;
+
+            if (mState == UNPREPARED) {
+                err = onPrepare();
+
+                if (err == OK) {
+                    mState = STOPPED;
+                }
+            }
+
+            if (err == OK) {
+                if (mState != STOPPED) {
+                    err = INVALID_OPERATION;
+                } else {
+                    err = onStart();
+
+                    if (err == OK) {
+                        mState = STARTED;
+                    }
+                }
+            }
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatStop:
+        {
+            status_t err;
+
+            if (mState != STARTED) {
+                err = INVALID_OPERATION;
+            } else {
+                err = onStop();
+
+                if (err == OK) {
+                    mState = STOPPED;
+                }
+            }
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatReset:
+        {
+            status_t err = OK;
+
+            if (mState == STARTED) {
+                CHECK_EQ(onStop(), (status_t)OK);
+                mState = STOPPED;
+            }
+
+            if (mState == STOPPED) {
+                err = onReset();
+                mState = UNINITIALIZED;
+            }
+
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatDoMoreStuff:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mDoMoreStuffGeneration) {
+                break;
+            }
+
+            status_t err = onDoMoreStuff();
+
+            if (err == OK) {
+                msg->post(5000ll);
+            }
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+status_t SimplePlayer::onPrepare() {
+    CHECK_EQ(mState, UNPREPARED);
+
+    mExtractor = new NuMediaExtractor;
+
+    status_t err = mExtractor->setDataSource(mPath.c_str());
+
+    if (err != OK) {
+        mExtractor.clear();
+        return err;
+    }
+
+    if (mCodecLooper == NULL) {
+        mCodecLooper = new ALooper;
+        mCodecLooper->start();
+    }
+
+    bool haveAudio = false;
+    bool haveVideo = false;
+    for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
+        sp<AMessage> format;
+        status_t err = mExtractor->getTrackFormat(i, &format);
+        CHECK_EQ(err, (status_t)OK);
+
+        AString mime;
+        CHECK(format->findString("mime", &mime));
+
+        if (!haveAudio && !strncasecmp(mime.c_str(), "audio/", 6)) {
+            haveAudio = true;
+        } else if (!haveVideo && !strncasecmp(mime.c_str(), "video/", 6)) {
+            haveVideo = true;
+        } else {
+            continue;
+        }
+
+        err = mExtractor->selectTrack(i);
+        CHECK_EQ(err, (status_t)OK);
+
+        CodecState *state =
+            &mStateByTrackIndex.editValueAt(
+                    mStateByTrackIndex.add(i, CodecState()));
+
+        state->mNumFramesWritten = 0;
+        state->mCodec = MediaCodec::CreateByType(
+                mCodecLooper, mime.c_str(), false /* encoder */);
+
+        CHECK(state->mCodec != NULL);
+
+        err = state->mCodec->configure(
+                format, mNativeWindow->getSurfaceTextureClient(),
+                0 /* flags */);
+
+        CHECK_EQ(err, (status_t)OK);
+
+        size_t j = 0;
+        sp<RefBase> obj;
+        while (format->findObject(StringPrintf("csd-%d", j).c_str(), &obj)) {
+            sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+            state->mCSD.push_back(buffer);
+
+            ++j;
+        }
+    }
+
+    for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) {
+        CodecState *state = &mStateByTrackIndex.editValueAt(i);
+
+        status_t err = state->mCodec->start();
+        CHECK_EQ(err, (status_t)OK);
+
+        err = state->mCodec->getInputBuffers(&state->mBuffers[0]);
+        CHECK_EQ(err, (status_t)OK);
+
+        err = state->mCodec->getOutputBuffers(&state->mBuffers[1]);
+        CHECK_EQ(err, (status_t)OK);
+
+        for (size_t j = 0; j < state->mCSD.size(); ++j) {
+            const sp<ABuffer> &srcBuffer = state->mCSD.itemAt(j);
+
+            size_t index;
+            err = state->mCodec->dequeueInputBuffer(&index, -1ll);
+            CHECK_EQ(err, (status_t)OK);
+
+            const sp<ABuffer> &dstBuffer = state->mBuffers[0].itemAt(index);
+
+            CHECK_LE(srcBuffer->size(), dstBuffer->capacity());
+            dstBuffer->setRange(0, srcBuffer->size());
+            memcpy(dstBuffer->data(), srcBuffer->data(), srcBuffer->size());
+
+            err = state->mCodec->queueInputBuffer(
+                    index,
+                    0,
+                    dstBuffer->size(),
+                    0ll,
+                    MediaCodec::BUFFER_FLAG_CODECCONFIG);
+            CHECK_EQ(err, (status_t)OK);
+        }
+    }
+
+    return OK;
+}
+
+status_t SimplePlayer::onStart() {
+    CHECK_EQ(mState, STOPPED);
+
+    mStartTimeRealUs = -1ll;
+
+    sp<AMessage> msg = new AMessage(kWhatDoMoreStuff, id());
+    msg->setInt32("generation", ++mDoMoreStuffGeneration);
+    msg->post();
+
+    return OK;
+}
+
+status_t SimplePlayer::onStop() {
+    CHECK_EQ(mState, STARTED);
+
+    ++mDoMoreStuffGeneration;
+
+    return OK;
+}
+
+status_t SimplePlayer::onReset() {
+    CHECK_EQ(mState, STOPPED);
+
+    for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) {
+        CodecState *state = &mStateByTrackIndex.editValueAt(i);
+
+        CHECK_EQ(state->mCodec->stop(), (status_t)OK);
+    }
+
+    mStartTimeRealUs = -1ll;
+
+    mStateByTrackIndex.clear();
+    mCodecLooper.clear();
+    mExtractor.clear();
+    mNativeWindow.clear();
+    mPath.clear();
+
+    return OK;
+}
+
+status_t SimplePlayer::onDoMoreStuff() {
+    for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) {
+        CodecState *state = &mStateByTrackIndex.editValueAt(i);
+
+        size_t index;
+        status_t err = state->mCodec->dequeueInputBuffer(&index);
+
+        if (err == OK) {
+            state->mAvailInputBufferIndices.push_back(index);
+        }
+
+        BufferInfo info;
+        err = state->mCodec->dequeueOutputBuffer(
+                &info.mIndex,
+                &info.mOffset,
+                &info.mSize,
+                &info.mPresentationTimeUs,
+                &info.mFlags);
+
+        if (err == OK) {
+            state->mAvailOutputBufferInfos.push_back(info);
+        } else if (err == INFO_FORMAT_CHANGED) {
+            err = onOutputFormatChanged(mStateByTrackIndex.keyAt(i), state);
+            CHECK_EQ(err, (status_t)OK);
+        } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
+            err = state->mCodec->getOutputBuffers(&state->mBuffers[1]);
+            CHECK_EQ(err, (status_t)OK);
+        }
+    }
+
+    for (;;) {
+        size_t trackIndex;
+        status_t err = mExtractor->getSampleTrackIndex(&trackIndex);
+
+        if (err != OK) {
+            ALOGI("encountered input EOS.");
+            break;
+        } else {
+            CodecState *state = &mStateByTrackIndex.editValueFor(trackIndex);
+
+            if (state->mAvailInputBufferIndices.empty()) {
+                break;
+            }
+
+            size_t index = *state->mAvailInputBufferIndices.begin();
+            state->mAvailInputBufferIndices.erase(
+                    state->mAvailInputBufferIndices.begin());
+
+            const sp<ABuffer> &dstBuffer =
+                state->mBuffers[0].itemAt(index);
+
+            err = mExtractor->readSampleData(dstBuffer);
+            CHECK_EQ(err, (status_t)OK);
+
+            int64_t timeUs;
+            CHECK_EQ(mExtractor->getSampleTime(&timeUs), (status_t)OK);
+
+            err = state->mCodec->queueInputBuffer(
+                    index,
+                    dstBuffer->offset(),
+                    dstBuffer->size(),
+                    timeUs,
+                    0);
+            CHECK_EQ(err, (status_t)OK);
+
+            err = mExtractor->advance();
+            CHECK_EQ(err, (status_t)OK);
+        }
+    }
+
+    int64_t nowUs = ALooper::GetNowUs();
+
+    if (mStartTimeRealUs < 0ll) {
+        mStartTimeRealUs = nowUs + 1000000ll;
+    }
+
+    for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) {
+        CodecState *state = &mStateByTrackIndex.editValueAt(i);
+
+        while (!state->mAvailOutputBufferInfos.empty()) {
+            BufferInfo *info = &*state->mAvailOutputBufferInfos.begin();
+
+            int64_t whenRealUs = info->mPresentationTimeUs + mStartTimeRealUs;
+            int64_t lateByUs = nowUs - whenRealUs;
+
+            if (lateByUs > -10000ll) {
+                bool release = true;
+
+                if (lateByUs > 30000ll) {
+                    ALOGI("track %d buffer late by %lld us, dropping.",
+                          i, lateByUs);
+                    state->mCodec->releaseOutputBuffer(info->mIndex);
+                } else {
+                    if (state->mAudioTrack != NULL) {
+                        const sp<ABuffer> &srcBuffer =
+                            state->mBuffers[1].itemAt(info->mIndex);
+
+                        renderAudio(state, info, srcBuffer);
+
+                        if (info->mSize > 0) {
+                            release = false;
+                        }
+                    }
+
+                    if (release) {
+                        state->mCodec->renderOutputBufferAndRelease(
+                                info->mIndex);
+                    }
+                }
+
+                if (release) {
+                    state->mAvailOutputBufferInfos.erase(
+                            state->mAvailOutputBufferInfos.begin());
+
+                    info = NULL;
+                } else {
+                    break;
+                }
+            } else {
+                ALOGV("track %d buffer early by %lld us.", i, -lateByUs);
+                break;
+            }
+        }
+    }
+
+    return OK;
+}
+
+status_t SimplePlayer::onOutputFormatChanged(
+        size_t trackIndex, CodecState *state) {
+    sp<AMessage> format;
+    status_t err = state->mCodec->getOutputFormat(&format);
+
+    if (err != OK) {
+        return err;
+    }
+
+    AString mime;
+    CHECK(format->findString("mime", &mime));
+
+    if (!strncasecmp(mime.c_str(), "audio/", 6)) {
+        int32_t channelCount;
+        int32_t sampleRate;
+        CHECK(format->findInt32("channel-count", &channelCount));
+        CHECK(format->findInt32("sample-rate", &sampleRate));
+
+        state->mAudioTrack = new AudioTrack(
+                AUDIO_STREAM_MUSIC,
+                sampleRate,
+                AUDIO_FORMAT_PCM_16_BIT,
+                (channelCount == 1)
+                    ? AUDIO_CHANNEL_OUT_MONO : AUDIO_CHANNEL_OUT_STEREO,
+                0);
+
+        state->mNumFramesWritten = 0;
+    }
+
+    return OK;
+}
+
+void SimplePlayer::renderAudio(
+        CodecState *state, BufferInfo *info, const sp<ABuffer> &buffer) {
+    CHECK(state->mAudioTrack != NULL);
+
+    if (state->mAudioTrack->stopped()) {
+        state->mAudioTrack->start();
+    }
+
+    uint32_t numFramesPlayed;
+    CHECK_EQ(state->mAudioTrack->getPosition(&numFramesPlayed), (status_t)OK);
+
+    uint32_t numFramesAvailableToWrite =
+        state->mAudioTrack->frameCount()
+            - (state->mNumFramesWritten - numFramesPlayed);
+
+    size_t numBytesAvailableToWrite =
+        numFramesAvailableToWrite * state->mAudioTrack->frameSize();
+
+    size_t copy = info->mSize;
+    if (copy > numBytesAvailableToWrite) {
+        copy = numBytesAvailableToWrite;
+    }
+
+    if (copy == 0) {
+        return;
+    }
+
+    int64_t startTimeUs = ALooper::GetNowUs();
+
+    ssize_t nbytes = state->mAudioTrack->write(
+            buffer->base() + info->mOffset, copy);
+
+    CHECK_EQ(nbytes, (ssize_t)copy);
+
+    int64_t delayUs = ALooper::GetNowUs() - startTimeUs;
+
+    uint32_t numFramesWritten = nbytes / state->mAudioTrack->frameSize();
+
+    if (delayUs > 2000ll) {
+        ALOGW("AudioTrack::write took %lld us, numFramesAvailableToWrite=%u, "
+              "numFramesWritten=%u",
+              delayUs, numFramesAvailableToWrite, numFramesWritten);
+    }
+
+    info->mOffset += nbytes;
+    info->mSize -= nbytes;
+
+    state->mNumFramesWritten += numFramesWritten;
+}
+
+}  // namespace android
diff --git a/cmds/stagefright/SimplePlayer.h b/cmds/stagefright/SimplePlayer.h
new file mode 100644
index 0000000..2548252
--- /dev/null
+++ b/cmds/stagefright/SimplePlayer.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/AString.h>
+#include <utils/KeyedVector.h>
+
+namespace android {
+
+struct ABuffer;
+struct ALooper;
+struct AudioTrack;
+struct ISurfaceTexture;
+struct MediaCodec;
+struct NativeWindowWrapper;
+struct NuMediaExtractor;
+
+struct SimplePlayer : public AHandler {
+    SimplePlayer();
+
+    status_t setDataSource(const char *path);
+    status_t setSurface(const sp<ISurfaceTexture> &surfaceTexture);
+    status_t prepare();
+    status_t start();
+    status_t stop();
+    status_t reset();
+
+protected:
+    virtual ~SimplePlayer();
+
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    enum State {
+        UNINITIALIZED,
+        UNPREPARED,
+        STOPPED,
+        STARTED
+    };
+
+    enum {
+        kWhatSetDataSource,
+        kWhatSetSurface,
+        kWhatPrepare,
+        kWhatStart,
+        kWhatStop,
+        kWhatReset,
+        kWhatDoMoreStuff,
+    };
+
+    struct BufferInfo {
+        size_t mIndex;
+        size_t mOffset;
+        size_t mSize;
+        int64_t mPresentationTimeUs;
+        uint32_t mFlags;
+    };
+
+    struct CodecState
+    {
+        sp<MediaCodec> mCodec;
+        Vector<sp<ABuffer> > mCSD;
+        Vector<sp<ABuffer> > mBuffers[2];
+
+        List<size_t> mAvailInputBufferIndices;
+        List<BufferInfo> mAvailOutputBufferInfos;
+
+        sp<AudioTrack> mAudioTrack;
+        uint32_t mNumFramesWritten;
+    };
+
+    State mState;
+    AString mPath;
+    sp<NativeWindowWrapper> mNativeWindow;
+
+    sp<NuMediaExtractor> mExtractor;
+    sp<ALooper> mCodecLooper;
+    KeyedVector<size_t, CodecState> mStateByTrackIndex;
+    int32_t mDoMoreStuffGeneration;
+
+    int64_t mStartTimeRealUs;
+
+    status_t onPrepare();
+    status_t onStart();
+    status_t onStop();
+    status_t onReset();
+    status_t onDoMoreStuff();
+    status_t onOutputFormatChanged(size_t trackIndex, CodecState *state);
+
+    void renderAudio(
+            CodecState *state, BufferInfo *info, const sp<ABuffer> &buffer);
+
+    DISALLOW_EVIL_CONSTRUCTORS(SimplePlayer);
+};
+
+}  // namespace android
diff --git a/cmds/stagefright/codec.cpp b/cmds/stagefright/codec.cpp
new file mode 100644
index 0000000..02dc8dd
--- /dev/null
+++ b/cmds/stagefright/codec.cpp
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "codec"
+#include <utils/Log.h>
+
+#include "SimplePlayer.h"
+
+#include <binder/ProcessState.h>
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
+
+static void usage(const char *me) {
+    fprintf(stderr, "usage: %s [-a] use audio\n"
+                    "\t\t[-v] use video\n"
+                    "\t\t[-p] playback\n", me);
+
+    exit(1);
+}
+
+namespace android {
+
+struct CodecState {
+    sp<MediaCodec> mCodec;
+    Vector<sp<ABuffer> > mCSD;
+    size_t mCSDIndex;
+    Vector<sp<ABuffer> > mInBuffers;
+    Vector<sp<ABuffer> > mOutBuffers;
+    bool mSawOutputEOS;
+};
+
+}  // namespace android
+
+static int decode(
+        const android::sp<android::ALooper> &looper,
+        const char *path,
+        bool useAudio,
+        bool useVideo) {
+    using namespace android;
+
+    sp<NuMediaExtractor> extractor = new NuMediaExtractor;
+    if (extractor->setDataSource(path) != OK) {
+        fprintf(stderr, "unable to instantiate extractor.\n");
+        return 1;
+    }
+
+    KeyedVector<size_t, CodecState> stateByTrack;
+
+    bool haveAudio = false;
+    bool haveVideo = false;
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<AMessage> format;
+        status_t err = extractor->getTrackFormat(i, &format);
+        CHECK_EQ(err, (status_t)OK);
+
+        AString mime;
+        CHECK(format->findString("mime", &mime));
+
+        if (useAudio && !haveAudio
+                && !strncasecmp(mime.c_str(), "audio/", 6)) {
+            haveAudio = true;
+        } else if (useVideo && !haveVideo
+                && !strncasecmp(mime.c_str(), "video/", 6)) {
+            haveVideo = true;
+        } else {
+            continue;
+        }
+
+        ALOGV("selecting track %d", i);
+
+        err = extractor->selectTrack(i);
+        CHECK_EQ(err, (status_t)OK);
+
+        CodecState *state =
+            &stateByTrack.editValueAt(stateByTrack.add(i, CodecState()));
+
+        state->mCodec = MediaCodec::CreateByType(
+                looper, mime.c_str(), false /* encoder */);
+
+        CHECK(state->mCodec != NULL);
+
+        err = state->mCodec->configure(
+                format, NULL /* surfaceTexture */, 0 /* flags */);
+
+        CHECK_EQ(err, (status_t)OK);
+
+        size_t j = 0;
+        sp<RefBase> obj;
+        while (format->findObject(StringPrintf("csd-%d", j).c_str(), &obj)) {
+            sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+            state->mCSD.push_back(buffer);
+
+            ++j;
+        }
+
+        state->mCSDIndex = 0;
+        state->mSawOutputEOS = false;
+
+        ALOGV("got %d pieces of codec specific data.", state->mCSD.size());
+    }
+
+    CHECK(!stateByTrack.isEmpty());
+
+    for (size_t i = 0; i < stateByTrack.size(); ++i) {
+        CodecState *state = &stateByTrack.editValueAt(i);
+
+        sp<MediaCodec> codec = state->mCodec;
+
+        CHECK_EQ((status_t)OK, codec->start());
+
+        CHECK_EQ((status_t)OK, codec->getInputBuffers(&state->mInBuffers));
+        CHECK_EQ((status_t)OK, codec->getOutputBuffers(&state->mOutBuffers));
+
+        ALOGV("got %d input and %d output buffers",
+              state->mInBuffers.size(), state->mOutBuffers.size());
+
+        while (state->mCSDIndex < state->mCSD.size()) {
+            size_t index;
+            status_t err = codec->dequeueInputBuffer(&index);
+
+            if (err == -EAGAIN) {
+                usleep(10000);
+                continue;
+            }
+
+            CHECK_EQ(err, (status_t)OK);
+
+            const sp<ABuffer> &srcBuffer =
+                state->mCSD.itemAt(state->mCSDIndex++);
+
+            const sp<ABuffer> &buffer = state->mInBuffers.itemAt(index);
+
+            memcpy(buffer->data(), srcBuffer->data(), srcBuffer->size());
+
+            err = codec->queueInputBuffer(
+                    index,
+                    0 /* offset */,
+                    srcBuffer->size(),
+                    0ll /* timeUs */,
+                    MediaCodec::BUFFER_FLAG_CODECCONFIG);
+
+            CHECK_EQ(err, (status_t)OK);
+        }
+    }
+
+    bool sawInputEOS = false;
+
+    for (;;) {
+        if (!sawInputEOS) {
+            size_t trackIndex;
+            status_t err = extractor->getSampleTrackIndex(&trackIndex);
+
+            if (err != OK) {
+                ALOGV("signalling EOS.");
+
+                for (size_t i = 0; i < stateByTrack.size(); ++i) {
+                    CodecState *state = &stateByTrack.editValueAt(i);
+
+                    for (;;) {
+                        size_t index;
+                        err = state->mCodec->dequeueInputBuffer(&index);
+
+                        if (err == -EAGAIN) {
+                            continue;
+                        }
+
+                        CHECK_EQ(err, (status_t)OK);
+
+                        err = state->mCodec->queueInputBuffer(
+                                index,
+                                0 /* offset */,
+                                0 /* size */,
+                                0ll /* timeUs */,
+                                MediaCodec::BUFFER_FLAG_EOS);
+
+                        CHECK_EQ(err, (status_t)OK);
+                        break;
+                    }
+                }
+
+                sawInputEOS = true;
+            } else {
+                CodecState *state = &stateByTrack.editValueFor(trackIndex);
+
+                size_t index;
+                err = state->mCodec->dequeueInputBuffer(&index);
+
+                if (err == OK) {
+                    ALOGV("filling input buffer %d", index);
+
+                    const sp<ABuffer> &buffer = state->mInBuffers.itemAt(index);
+
+                    err = extractor->readSampleData(buffer);
+                    CHECK_EQ(err, (status_t)OK);
+
+                    int64_t timeUs;
+                    err = extractor->getSampleTime(&timeUs);
+                    CHECK_EQ(err, (status_t)OK);
+
+                    err = state->mCodec->queueInputBuffer(
+                            index,
+                            0 /* offset */,
+                            buffer->size(),
+                            timeUs,
+                            0 /* flags */);
+
+                    CHECK_EQ(err, (status_t)OK);
+
+                    extractor->advance();
+                } else {
+                    CHECK_EQ(err, -EAGAIN);
+                }
+            }
+        }
+
+        bool sawOutputEOSOnAllTracks = true;
+        for (size_t i = 0; i < stateByTrack.size(); ++i) {
+            CodecState *state = &stateByTrack.editValueAt(i);
+            if (!state->mSawOutputEOS) {
+                sawOutputEOSOnAllTracks = false;
+                break;
+            }
+        }
+
+        if (sawOutputEOSOnAllTracks) {
+            break;
+        }
+
+        for (size_t i = 0; i < stateByTrack.size(); ++i) {
+            CodecState *state = &stateByTrack.editValueAt(i);
+
+            if (state->mSawOutputEOS) {
+                continue;
+            }
+
+            size_t index;
+            size_t offset;
+            size_t size;
+            int64_t presentationTimeUs;
+            uint32_t flags;
+            status_t err = state->mCodec->dequeueOutputBuffer(
+                    &index, &offset, &size, &presentationTimeUs, &flags,
+                    10000ll);
+
+            if (err == OK) {
+                ALOGV("draining output buffer %d, time = %lld us",
+                      index, presentationTimeUs);
+
+                err = state->mCodec->releaseOutputBuffer(index);
+                CHECK_EQ(err, (status_t)OK);
+
+                if (flags & MediaCodec::BUFFER_FLAG_EOS) {
+                    ALOGV("reached EOS on output.");
+
+                    state->mSawOutputEOS = true;
+                }
+            } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
+                ALOGV("INFO_OUTPUT_BUFFERS_CHANGED");
+                CHECK_EQ((status_t)OK,
+                         state->mCodec->getOutputBuffers(&state->mOutBuffers));
+
+                ALOGV("got %d output buffers", state->mOutBuffers.size());
+            } else if (err == INFO_FORMAT_CHANGED) {
+                sp<AMessage> format;
+                CHECK_EQ((status_t)OK, state->mCodec->getOutputFormat(&format));
+
+                ALOGV("INFO_FORMAT_CHANGED: %s", format->debugString().c_str());
+            } else {
+                CHECK_EQ(err, -EAGAIN);
+            }
+        }
+    }
+
+    for (size_t i = 0; i < stateByTrack.size(); ++i) {
+        CodecState *state = &stateByTrack.editValueAt(i);
+
+        CHECK_EQ((status_t)OK, state->mCodec->stop());
+    }
+
+    return 0;
+}
+
+int main(int argc, char **argv) {
+    using namespace android;
+
+    const char *me = argv[0];
+
+    bool useAudio = false;
+    bool useVideo = false;
+    bool playback = false;
+
+    int res;
+    while ((res = getopt(argc, argv, "havp")) >= 0) {
+        switch (res) {
+            case 'a':
+            {
+                useAudio = true;
+                break;
+            }
+
+            case 'v':
+            {
+                useVideo = true;
+                break;
+            }
+
+            case 'p':
+            {
+                playback = true;
+                break;
+            }
+
+            case '?':
+            case 'h':
+            default:
+            {
+                usage(me);
+            }
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc != 1) {
+        usage(me);
+    }
+
+    if (!useAudio && !useVideo) {
+        useAudio = useVideo = true;
+    }
+
+    ProcessState::self()->startThreadPool();
+
+    DataSource::RegisterDefaultSniffers();
+
+    sp<ALooper> looper = new ALooper;
+    looper->start();
+
+    if (playback) {
+        sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient;
+        CHECK_EQ(composerClient->initCheck(), (status_t)OK);
+
+        ssize_t displayWidth = composerClient->getDisplayWidth(0);
+        ssize_t displayHeight = composerClient->getDisplayHeight(0);
+
+        ALOGV("display is %ld x %ld\n", displayWidth, displayHeight);
+
+        sp<SurfaceControl> control =
+            composerClient->createSurface(
+                    String8("A Surface"),
+                    0,
+                    displayWidth,
+                    displayHeight,
+                    PIXEL_FORMAT_RGB_565,
+                    0);
+
+        CHECK(control != NULL);
+        CHECK(control->isValid());
+
+        SurfaceComposerClient::openGlobalTransaction();
+        CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK);
+        CHECK_EQ(control->show(), (status_t)OK);
+        SurfaceComposerClient::closeGlobalTransaction();
+
+        sp<Surface> surface = control->getSurface();
+        CHECK(surface != NULL);
+
+        sp<SimplePlayer> player = new SimplePlayer;
+        looper->registerHandler(player);
+
+        player->setDataSource(argv[0]);
+        player->setSurface(surface->getSurfaceTexture());
+        player->start();
+        sleep(10);
+        player->stop();
+        player->reset();
+
+        composerClient->dispose();
+    } else {
+        decode(looper, argv[0], useAudio, useVideo);
+    }
+
+    looper->stop();
+
+    return 0;
+}
diff --git a/cmds/stagefright/sf2.cpp b/cmds/stagefright/sf2.cpp
index ae80f88..18e2532 100644
--- a/cmds/stagefright/sf2.cpp
+++ b/cmds/stagefright/sf2.cpp
@@ -198,9 +198,7 @@
 
                     (new AMessage(kWhatSeek, id()))->post(5000000ll);
                 } else if (what == ACodec::kWhatOutputFormatChanged) {
-                } else {
-                    CHECK_EQ(what, (int32_t)ACodec::kWhatShutdownCompleted);
-
+                } else if (what == ACodec::kWhatShutdownCompleted) {
                     mDecodeLooper->unregisterHandler(mCodec->id());
 
                     if (mDecodeLooper != looper()) {
diff --git a/core/java/android/pim/ContactsAsyncHelper.java b/core/java/android/pim/ContactsAsyncHelper.java
deleted file mode 100644
index 21fc594..0000000
--- a/core/java/android/pim/ContactsAsyncHelper.java
+++ /dev/null
@@ -1,342 +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 android.pim;
-
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.Connection;
-
-import android.content.ContentUris;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.ContactsContract.Contacts;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-
-import java.io.InputStream;
-
-/**
- * Helper class for async access of images.
- */
-public class ContactsAsyncHelper extends Handler {
-
-    private static final boolean DBG = false;
-    private static final String LOG_TAG = "ContactsAsyncHelper";
-
-    /**
-     * Interface for a WorkerHandler result return.
-     */
-    public interface OnImageLoadCompleteListener {
-        /**
-         * Called when the image load is complete.
-         *
-         * @param imagePresent true if an image was found
-         */
-        public void onImageLoadComplete(int token, Object cookie, ImageView iView,
-                boolean imagePresent);
-    }
-
-    // constants
-    private static final int EVENT_LOAD_IMAGE = 1;
-    private static final int DEFAULT_TOKEN = -1;
-
-    // static objects
-    private static Handler sThreadHandler;
-    private static ContactsAsyncHelper sInstance;
-
-    static {
-        sInstance = new ContactsAsyncHelper();
-    }
-
-    private static final class WorkerArgs {
-        public Context context;
-        public ImageView view;
-        public Uri uri;
-        public int defaultResource;
-        public Object result;
-        public Object cookie;
-        public OnImageLoadCompleteListener listener;
-        public CallerInfo info;
-    }
-
-    /**
-     * public inner class to help out the ContactsAsyncHelper callers
-     * with tracking the state of the CallerInfo Queries and image
-     * loading.
-     *
-     * Logic contained herein is used to remove the race conditions
-     * that exist as the CallerInfo queries run and mix with the image
-     * loads, which then mix with the Phone state changes.
-     */
-    public static class ImageTracker {
-
-        // Image display states
-        public static final int DISPLAY_UNDEFINED = 0;
-        public static final int DISPLAY_IMAGE = -1;
-        public static final int DISPLAY_DEFAULT = -2;
-
-        // State of the image on the imageview.
-        private CallerInfo mCurrentCallerInfo;
-        private int displayMode;
-
-        public ImageTracker() {
-            mCurrentCallerInfo = null;
-            displayMode = DISPLAY_UNDEFINED;
-        }
-
-        /**
-         * Used to see if the requested call / connection has a
-         * different caller attached to it than the one we currently
-         * have in the CallCard.
-         */
-        public boolean isDifferentImageRequest(CallerInfo ci) {
-            // note, since the connections are around for the lifetime of the
-            // call, and the CallerInfo-related items as well, we can
-            // definitely use a simple != comparison.
-            return (mCurrentCallerInfo != ci);
-        }
-
-        public boolean isDifferentImageRequest(Connection connection) {
-            // if the connection does not exist, see if the
-            // mCurrentCallerInfo is also null to match.
-            if (connection == null) {
-                if (DBG) Log.d(LOG_TAG, "isDifferentImageRequest: connection is null");
-                return (mCurrentCallerInfo != null);
-            }
-            Object o = connection.getUserData();
-
-            // if the call does NOT have a callerInfo attached
-            // then it is ok to query.
-            boolean runQuery = true;
-            if (o instanceof CallerInfo) {
-                runQuery = isDifferentImageRequest((CallerInfo) o);
-            }
-            return runQuery;
-        }
-
-        /**
-         * Simple setter for the CallerInfo object.
-         */
-        public void setPhotoRequest(CallerInfo ci) {
-            mCurrentCallerInfo = ci;
-        }
-
-        /**
-         * Convenience method used to retrieve the URI
-         * representing the Photo file recorded in the attached
-         * CallerInfo Object.
-         */
-        public Uri getPhotoUri() {
-            if (mCurrentCallerInfo != null) {
-                return ContentUris.withAppendedId(Contacts.CONTENT_URI,
-                        mCurrentCallerInfo.person_id);
-            }
-            return null;
-        }
-
-        /**
-         * Simple setter for the Photo state.
-         */
-        public void setPhotoState(int state) {
-            displayMode = state;
-        }
-
-        /**
-         * Simple getter for the Photo state.
-         */
-        public int getPhotoState() {
-            return displayMode;
-        }
-    }
-
-    /**
-     * Thread worker class that handles the task of opening the stream and loading
-     * the images.
-     */
-    private class WorkerHandler extends Handler {
-        public WorkerHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            WorkerArgs args = (WorkerArgs) msg.obj;
-
-            switch (msg.arg1) {
-                case EVENT_LOAD_IMAGE:
-                    InputStream inputStream = null;
-                    try {
-                        inputStream = Contacts.openContactPhotoInputStream(
-                                args.context.getContentResolver(), args.uri, true);
-                    } catch (Exception e) {
-                        Log.e(LOG_TAG, "Error opening photo input stream", e);
-                    }
-
-                    if (inputStream != null) {
-                        args.result = Drawable.createFromStream(inputStream, args.uri.toString());
-
-                        if (DBG) Log.d(LOG_TAG, "Loading image: " + msg.arg1 +
-                                " token: " + msg.what + " image URI: " + args.uri);
-                    } else {
-                        args.result = null;
-                        if (DBG) Log.d(LOG_TAG, "Problem with image: " + msg.arg1 +
-                                " token: " + msg.what + " image URI: " + args.uri +
-                                ", using default image.");
-                    }
-                    break;
-                default:
-            }
-
-            // send the reply to the enclosing class.
-            Message reply = ContactsAsyncHelper.this.obtainMessage(msg.what);
-            reply.arg1 = msg.arg1;
-            reply.obj = msg.obj;
-            reply.sendToTarget();
-        }
-    }
-
-    /**
-     * Private constructor for static class
-     */
-    private ContactsAsyncHelper() {
-        HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
-        thread.start();
-        sThreadHandler = new WorkerHandler(thread.getLooper());
-    }
-
-    /**
-     * Convenience method for calls that do not want to deal with listeners and tokens.
-     */
-    public static final void updateImageViewWithContactPhotoAsync(Context context,
-            ImageView imageView, Uri person, int placeholderImageResource) {
-        // Added additional Cookie field in the callee.
-        updateImageViewWithContactPhotoAsync (null, DEFAULT_TOKEN, null, null, context,
-                imageView, person, placeholderImageResource);
-    }
-
-    /**
-     * Convenience method for calls that do not want to deal with listeners and tokens, but have
-     * a CallerInfo object to cache the image to.
-     */
-    public static final void updateImageViewWithContactPhotoAsync(CallerInfo info, Context context,
-            ImageView imageView, Uri person, int placeholderImageResource) {
-        // Added additional Cookie field in the callee.
-        updateImageViewWithContactPhotoAsync (info, DEFAULT_TOKEN, null, null, context,
-                imageView, person, placeholderImageResource);
-    }
-
-
-    /**
-     * Start an image load, attach the result to the specified CallerInfo object.
-     * Note, when the query is started, we make the ImageView INVISIBLE if the
-     * placeholderImageResource value is -1.  When we're given a valid (!= -1)
-     * placeholderImageResource value, we make sure the image is visible.
-     */
-    public static final void updateImageViewWithContactPhotoAsync(CallerInfo info, int token,
-            OnImageLoadCompleteListener listener, Object cookie, Context context,
-            ImageView imageView, Uri person, int placeholderImageResource) {
-
-        // in case the source caller info is null, the URI will be null as well.
-        // just update using the placeholder image in this case.
-        if (person == null) {
-            if (DBG) Log.d(LOG_TAG, "target image is null, just display placeholder.");
-            imageView.setVisibility(View.VISIBLE);
-            imageView.setImageResource(placeholderImageResource);
-            return;
-        }
-
-        // Added additional Cookie field in the callee to handle arguments
-        // sent to the callback function.
-
-        // setup arguments
-        WorkerArgs args = new WorkerArgs();
-        args.cookie = cookie;
-        args.context = context;
-        args.view = imageView;
-        args.uri = person;
-        args.defaultResource = placeholderImageResource;
-        args.listener = listener;
-        args.info = info;
-
-        // setup message arguments
-        Message msg = sThreadHandler.obtainMessage(token);
-        msg.arg1 = EVENT_LOAD_IMAGE;
-        msg.obj = args;
-
-        if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri +
-                ", displaying default image for now.");
-
-        // set the default image first, when the query is complete, we will
-        // replace the image with the correct one.
-        if (placeholderImageResource != -1) {
-            imageView.setVisibility(View.VISIBLE);
-            imageView.setImageResource(placeholderImageResource);
-        } else {
-            imageView.setVisibility(View.INVISIBLE);
-        }
-
-        // notify the thread to begin working
-        sThreadHandler.sendMessage(msg);
-    }
-
-    /**
-     * Called when loading is done.
-     */
-    @Override
-    public void handleMessage(Message msg) {
-        WorkerArgs args = (WorkerArgs) msg.obj;
-        switch (msg.arg1) {
-            case EVENT_LOAD_IMAGE:
-                boolean imagePresent = false;
-
-                // if the image has been loaded then display it, otherwise set default.
-                // in either case, make sure the image is visible.
-                if (args.result != null) {
-                    args.view.setVisibility(View.VISIBLE);
-                    args.view.setImageDrawable((Drawable) args.result);
-                    // make sure the cached photo data is updated.
-                    if (args.info != null) {
-                        args.info.cachedPhoto = (Drawable) args.result;
-                    }
-                    imagePresent = true;
-                } else if (args.defaultResource != -1) {
-                    args.view.setVisibility(View.VISIBLE);
-                    args.view.setImageResource(args.defaultResource);
-                }
-
-                // Note that the data is cached.
-                if (args.info != null) {
-                    args.info.isCachedPhotoCurrent = true;
-                }
-
-                // notify the listener if it is there.
-                if (args.listener != null) {
-                    if (DBG) Log.d(LOG_TAG, "Notifying listener: " + args.listener.toString() +
-                            " image: " + args.uri + " completed");
-                    args.listener.onImageLoadComplete(msg.what, args.cookie, args.view,
-                            imagePresent);
-                }
-                break;
-            default:
-        }
-    }
-}
diff --git a/core/java/android/pim/package.html b/core/java/android/pim/package.html
deleted file mode 100644
index 75237c9..0000000
--- a/core/java/android/pim/package.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<HTML>
-<BODY>
-{@hide}
-Provides helpers for working with PIM (Personal Information Manager) data used
-by contact lists and calendars.
-</BODY>
-</HTML>
\ No newline at end of file
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 384e39a..0675a74 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13055,11 +13055,6 @@
             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
         }
 
-        if (getAccessibilityNodeProvider() != null) {
-            throw new IllegalStateException("Views with AccessibilityNodeProvider"
-                    + " can't have children.");
-        }
-
         mPrivateFlags |= FORCE_LAYOUT;
         mPrivateFlags |= INVALIDATED;
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c68d77d..2848e88 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2669,6 +2669,15 @@
         return child.draw(canvas, this, drawingTime);
     }
 
+    @Override
+    public void requestLayout() {
+        if (mChildrenCount > 0 && getAccessibilityNodeProvider() != null) {
+            throw new IllegalStateException("Views with AccessibilityNodeProvider"
+                    + " can't have children.");
+        }
+        super.requestLayout();
+    }
+
     /**
      * 
      * @param enabled True if children should be drawn with layers, false otherwise.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6dc3be5..de78210 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -382,21 +382,24 @@
 
         @Override
         public boolean sendKeyEvent(KeyEvent event) {
-            // Latin IME occasionally sends delete codes directly using
-            // sendKeyEvents. WebViewInputConnection should treat this
-            // as a deleteSurroundingText.
-            if (!mIsKeySentByMe
-                    && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
-                Editable editable = getEditable();
-                int selectionStart = Selection.getSelectionStart(editable);
-                int selectionEnd = Selection.getSelectionEnd(editable);
-                if (selectionEnd > 0 && (selectionStart == selectionEnd)) {
-                    int action = event.getAction();
-                    if (action == KeyEvent.ACTION_UP) {
+            // Some IMEs send key events directly using sendKeyEvents.
+            // WebViewInputConnection should treat these as text changes.
+            if (!mIsKeySentByMe) {
+                if (event.getAction() == KeyEvent.ACTION_UP) {
+                    if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
                         return deleteSurroundingText(1, 0);
-                    } else if (action == KeyEvent.ACTION_DOWN) {
-                        return true; // the delete will happen in ACTION_UP
+                    } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
+                        return deleteSurroundingText(0, 1);
+                    } else if (event.getUnicodeChar() != 0){
+                        String newComposingText =
+                                Character.toString((char)event.getUnicodeChar());
+                        return commitText(newComposingText, 1);
                     }
+                } else if (event.getAction() == KeyEvent.ACTION_DOWN &&
+                        (event.getKeyCode() == KeyEvent.KEYCODE_DEL
+                        || event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL
+                        || event.getUnicodeChar() != 0)) {
+                    return true; // only act on action_down
                 }
             }
             return super.sendKeyEvent(event);
@@ -552,13 +555,17 @@
                         && TextUtils.regionMatches(text, 0, original, 0,
                                 textLength);
             }
+            boolean sendChange = false;
             if (isCharacterAdd) {
-                sendCharacter(text.charAt(textLength - 1));
+                sendChange = !sendCharacter(text.charAt(textLength - 1));
             } else if (isCharacterDelete) {
                 sendDeleteKey();
-            } else if (textLength != originalLength ||
-                    !TextUtils.regionMatches(text, 0, original, 0,
-                            textLength)) {
+            } else {
+                sendChange = (textLength != originalLength) ||
+                        !TextUtils.regionMatches(text, 0, original, 0,
+                                textLength);
+            }
+            if (sendChange) {
                 // Send a message so that key strokes and text replacement
                 // do not come out of order.
                 Message replaceMessage = mPrivateHandler.obtainMessage(
@@ -572,18 +579,20 @@
          * Send a single character to the WebView as a key down and up event.
          * @param c The character to be sent.
          */
-        private void sendCharacter(char c) {
+        private boolean sendCharacter(char c) {
             if (mKeyCharacterMap == null) {
                 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
             }
             char[] chars = new char[1];
             chars[0] = c;
             KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
-            if (events != null) {
+            boolean mapsToKeyEvent = (events != null);
+            if (mapsToKeyEvent) {
                 for (KeyEvent event : events) {
                     sendKeyEvent(event);
                 }
             }
+            return mapsToKeyEvent;
         }
 
         /**
@@ -908,14 +917,22 @@
     // know to handle Shift and arrows natively first
     private boolean mAccessibilityScriptInjected;
 
+
+    /**
+     * How long the caret handle will last without being touched.
+     */
+    private static final long CARET_HANDLE_STAMINA_MS = 3000;
+
     private Drawable mSelectHandleLeft;
     private Drawable mSelectHandleRight;
+    private Drawable mSelectHandleCenter;
     private Rect mSelectCursorBase = new Rect();
     private int mSelectCursorBaseLayerId;
     private Rect mSelectCursorExtent = new Rect();
     private int mSelectCursorExtentLayerId;
     private Rect mSelectDraggingCursor;
     private Point mSelectDraggingOffset = new Point();
+    private boolean mIsCaretSelection;
     static final int HANDLE_ID_START = 0;
     static final int HANDLE_ID_END = 1;
     static final int HANDLE_ID_BASE = 2;
@@ -1012,6 +1029,7 @@
     static final int COPY_TO_CLIPBOARD                  = 141;
     static final int INIT_EDIT_FIELD                    = 142;
     static final int REPLACE_TEXT                       = 143;
+    static final int CLEAR_CARET_HANDLE                 = 144;
 
     private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
     private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1418,7 +1436,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(KeyChain.ACTION_STORAGE_CHANGED);
         sTrustStorageListener = new TrustStorageListener();
-        Intent current = 
+        Intent current =
             context.getApplicationContext().registerReceiver(sTrustStorageListener, filter);
         if (current != null) {
             handleCertTrustChanged();
@@ -5108,31 +5126,45 @@
     }
 
     private void drawTextSelectionHandles(Canvas canvas) {
-        if (mSelectHandleLeft == null) {
-            mSelectHandleLeft = mContext.getResources().getDrawable(
-                    com.android.internal.R.drawable.text_select_handle_left);
-        }
         int[] handles = new int[4];
         getSelectionHandles(handles);
         int start_x = contentToViewDimension(handles[0]);
         int start_y = contentToViewDimension(handles[1]);
         int end_x = contentToViewDimension(handles[2]);
         int end_y = contentToViewDimension(handles[3]);
-        // Magic formula copied from TextView
-        start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
-        mSelectHandleLeft.setBounds(start_x, start_y,
-                start_x + mSelectHandleLeft.getIntrinsicWidth(),
-                start_y + mSelectHandleLeft.getIntrinsicHeight());
-        if (mSelectHandleRight == null) {
-            mSelectHandleRight = mContext.getResources().getDrawable(
-                    com.android.internal.R.drawable.text_select_handle_right);
+
+        if (mIsCaretSelection) {
+            if (mSelectHandleCenter == null) {
+                mSelectHandleCenter = mContext.getResources().getDrawable(
+                        com.android.internal.R.drawable.text_select_handle_middle);
+            }
+            // Caret handle is centered
+            start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2);
+            mSelectHandleCenter.setBounds(start_x, start_y,
+                    start_x + mSelectHandleCenter.getIntrinsicWidth(),
+                    start_y + mSelectHandleCenter.getIntrinsicHeight());
+            mSelectHandleCenter.draw(canvas);
+        } else {
+            if (mSelectHandleLeft == null) {
+                mSelectHandleLeft = mContext.getResources().getDrawable(
+                        com.android.internal.R.drawable.text_select_handle_left);
+            }
+            // Magic formula copied from TextView
+            start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
+            mSelectHandleLeft.setBounds(start_x, start_y,
+                    start_x + mSelectHandleLeft.getIntrinsicWidth(),
+                    start_y + mSelectHandleLeft.getIntrinsicHeight());
+            if (mSelectHandleRight == null) {
+                mSelectHandleRight = mContext.getResources().getDrawable(
+                        com.android.internal.R.drawable.text_select_handle_right);
+            }
+            end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
+            mSelectHandleRight.setBounds(end_x, end_y,
+                    end_x + mSelectHandleRight.getIntrinsicWidth(),
+                    end_y + mSelectHandleRight.getIntrinsicHeight());
+            mSelectHandleLeft.draw(canvas);
+            mSelectHandleRight.draw(canvas);
         }
-        end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
-        mSelectHandleRight.setBounds(end_x, end_y,
-                end_x + mSelectHandleRight.getIntrinsicWidth(),
-                end_y + mSelectHandleRight.getIntrinsicHeight());
-        mSelectHandleLeft.draw(canvas);
-        mSelectHandleRight.draw(canvas);
     }
 
     /**
@@ -5554,6 +5586,9 @@
                     + "keyCode=" + keyCode
                     + ", " + event + ", unicode=" + event.getUnicodeChar());
         }
+        if (mIsCaretSelection) {
+            selectionDone();
+        }
         if (mBlockWebkitViewMessages) {
             return false;
         }
@@ -5866,6 +5901,7 @@
 
     private boolean startSelectActionMode() {
         mSelectCallback = new SelectActionModeCallback();
+        mSelectCallback.setTextSelected(!mIsCaretSelection);
         mSelectCallback.setWebView(this);
         if (startActionMode(mSelectCallback) == null) {
             // There is no ActionMode, so do not allow the user to modify a
@@ -5886,9 +5922,13 @@
 
     private boolean setupWebkitSelect() {
         syncSelectionCursors();
-        if (!startSelectActionMode()) {
-            selectionDone();
-            return false;
+        ClipboardManager cm = (ClipboardManager)(mContext
+                .getSystemService(Context.CLIPBOARD_SERVICE));
+        if (!mIsCaretSelection || cm.hasPrimaryClip()) {
+            if (!startSelectActionMode()) {
+                selectionDone();
+                return false;
+            }
         }
         mSelectingText = true;
         mTouchMode = TOUCH_DRAG_MODE;
@@ -5897,6 +5937,9 @@
 
     private void updateWebkitSelection() {
         int[] handles = null;
+        if (mIsCaretSelection) {
+            mSelectCursorExtent.set(mSelectCursorBase);
+        }
         if (mSelectingText) {
             handles = new int[4];
             handles[0] = mSelectCursorBase.centerX();
@@ -5910,6 +5953,14 @@
         mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles);
     }
 
+    private void resetCaretTimer() {
+        mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
+        if (!mSelectionStarted) {
+            mPrivateHandler.sendEmptyMessageDelayed(CLEAR_CARET_HANDLE,
+                    CARET_HANDLE_STAMINA_MS);
+        }
+    }
+
     /**
      * Use this method to put the WebView into text selection mode.
      * Do not rely on this functionality; it will be deprecated in the future.
@@ -5937,9 +5988,14 @@
             mSelectingText = false;
             // finish is idempotent, so this is fine even if selectionDone was
             // called by mSelectCallback.onDestroyActionMode
-            mSelectCallback.finish();
-            mSelectCallback = null;
-            updateWebkitSelection();
+            if (mSelectCallback != null) {
+                mSelectCallback.finish();
+                mSelectCallback = null;
+            }
+            if (!mIsCaretSelection) {
+                updateWebkitSelection();
+            }
+            mIsCaretSelection = false;
             invalidate(); // redraw without selection
             mAutoScrollX = 0;
             mAutoScrollY = 0;
@@ -6553,18 +6609,26 @@
                                 (eventTime - mLastTouchUpTime), eventTime);
                     }
                     mSelectionStarted = false;
-                    if (mSelectingText && mSelectHandleLeft != null
-                            && mSelectHandleRight != null) {
+                    if (mSelectingText) {
                         int shiftedY = y - getTitleHeight() + mScrollY;
                         int shiftedX = x + mScrollX;
-                        if (mSelectHandleLeft.getBounds()
+                        if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds()
                                 .contains(shiftedX, shiftedY)) {
                             mSelectionStarted = true;
                             mSelectDraggingCursor = mSelectCursorBase;
-                        } else if (mSelectHandleRight.getBounds()
+                            mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
+                        } else if (mSelectHandleLeft != null
+                                && mSelectHandleLeft.getBounds()
+                                    .contains(shiftedX, shiftedY)) {
+                                mSelectionStarted = true;
+                                mSelectDraggingCursor = mSelectCursorBase;
+                        } else if (mSelectHandleRight != null
+                                && mSelectHandleRight.getBounds()
                                 .contains(shiftedX, shiftedY)) {
                             mSelectionStarted = true;
                             mSelectDraggingCursor = mSelectCursorExtent;
+                        } else if (mIsCaretSelection) {
+                            selectionDone();
                         }
                         if (mSelectDraggingCursor != null) {
                             mSelectDraggingOffset.set(
@@ -7215,6 +7279,9 @@
 
         if (mSelectingText) {
             mSelectionStarted = false;
+            if (mIsCaretSelection) {
+                resetCaretTimer();
+            }
             syncSelectionCursors();
             invalidate();
         }
@@ -9173,6 +9240,9 @@
                     }
                     break;
                 }
+                case CLEAR_CARET_HANDLE:
+                    selectionDone();
+                    break;
 
                 default:
                     super.handleMessage(msg);
@@ -9188,7 +9258,7 @@
         if (mTouchHighlightRegion.isEmpty()) {
             return false;
         }
-        if (mFocusedNode.mHasFocus) {
+        if (mFocusedNode.mHasFocus && !isInTouchMode()) {
             return !mFocusedNode.mEditable;
         }
         if (mInitialHitTestResult.mType == HitTestResult.UNKNOWN_TYPE) {
@@ -9423,14 +9493,19 @@
                 mInputConnection.setSelection(data.mStart, data.mEnd);
             }
         }
-
         nativeSetTextSelection(mNativeClass, data.mSelectTextPtr);
         if (data.mSelectTextPtr != 0) {
+            mIsCaretSelection = (mFieldPointer == nodePointer)
+                    && (mFieldPointer != 0)
+                    && (data.mStart == data.mEnd);
             if (!mSelectingText) {
                 setupWebkitSelect();
             } else if (!mSelectionStarted) {
                 syncSelectionCursors();
             }
+            if (mIsCaretSelection) {
+                resetCaretTimer();
+            }
         } else {
             selectionDone();
         }
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 84f0b9c..e7d3238 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2834,7 +2834,7 @@
     // called by JNI
     private void initEditField(int pointer, String text, int inputType,
             boolean isSpellCheckEnabled, boolean nextFieldIsText,
-            String label, int start, int end) {
+            String label, int start, int end, int selectionPtr) {
         if (mWebView == null) {
             return;
         }
@@ -2844,7 +2844,7 @@
                 WebView.INIT_EDIT_FIELD, initData).sendToTarget();
         Message.obtain(mWebView.mPrivateHandler,
                 WebView.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, pointer,
-                0, new TextSelectionData(start, end, 0))
+                0, new TextSelectionData(start, end, selectionPtr))
                 .sendToTarget();
     }
 
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index e7bc1e1..2602523 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2035,13 +2035,6 @@
             }
 
             child = mAdapter.getView(position, scrapView, this);
-            if (mAdapterHasStableIds) {
-                LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (lp == null) {
-                    lp = (LayoutParams) generateDefaultLayoutParams();
-                }
-                lp.itemId = mAdapter.getItemId(position);
-            }
 
             if (ViewDebug.TRACE_RECYCLER) {
                 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
@@ -2072,6 +2065,20 @@
             }
         }
 
+        if (mAdapterHasStableIds) {
+            final ViewGroup.LayoutParams vlp = child.getLayoutParams();
+            LayoutParams lp;
+            if (vlp == null) {
+                lp = (LayoutParams) generateDefaultLayoutParams();
+            } else if (!checkLayoutParams(vlp)) {
+                lp = (LayoutParams) generateLayoutParams(vlp);
+            } else {
+                lp = (LayoutParams) vlp;
+            }
+            lp.itemId = mAdapter.getItemId(position);
+            child.setLayoutParams(lp);
+        }
+
         return child;
     }
 
@@ -5383,6 +5390,12 @@
     }
 
     @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+    }
+
+    @Override
     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
         return new LayoutParams(p);
     }
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 6bc5a15..0dedf8b 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1029,10 +1029,9 @@
         if (count > 0) {
             final View child = obtainView(0, mIsScrap);
 
-            AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+            AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
             if (p == null) {
-                p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+                p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
                 child.setLayoutParams(p);
             }
             p.viewType = mAdapter.getItemViewType(0);
@@ -1362,10 +1361,9 @@
 
         // Respect layout params that are already in the view. Otherwise make
         // some up...
-        AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+        AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
         if (p == null) {
-            p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+            p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
         }
         p.viewType = mAdapter.getItemViewType(position);
 
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 46c2c07..71700b3 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1163,8 +1163,7 @@
     private void measureScrapChild(View child, int position, int widthMeasureSpec) {
         LayoutParams p = (LayoutParams) child.getLayoutParams();
         if (p == null) {
-            p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+            p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
             child.setLayoutParams(p);
         }
         p.viewType = mAdapter.getItemViewType(position);
@@ -1808,8 +1807,7 @@
         // noinspection unchecked
         AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
         if (p == null) {
-            p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+            p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
         }
         p.viewType = mAdapter.getItemViewType(position);
 
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index f5d3746..b870cee 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -735,8 +735,7 @@
          * @param heightAttr the height attribute to fetch
          */
         @Override
-        protected void setBaseAttributes(TypedArray a,
-                int widthAttr, int heightAttr) {
+        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
             this.width = MATCH_PARENT;
             if (a.hasValue(heightAttr)) {
                 this.height = a.getLayoutDimension(heightAttr, "layout_height");
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f66da29..a4087d5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -232,34 +232,6 @@
     static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
 
-    private static final int PRIORITY = 100;
-    private int mCurrentAlpha = 255;
-
-    final int[] mTempCoords = new int[2];
-    Rect mTempRect;
-
-    private ColorStateList mTextColor;
-    private int mCurTextColor;
-    private ColorStateList mHintTextColor;
-    private ColorStateList mLinkTextColor;
-    private int mCurHintTextColor;
-    private boolean mFreezesText;
-    private boolean mFrozenWithFocus;
-    private boolean mTemporaryDetach;
-    private boolean mDispatchTemporaryDetach;
-
-    private boolean mDiscardNextActionUp = false;
-    private boolean mIgnoreActionUpEvent = false;
-
-    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
-    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
-
-    private float mShadowRadius, mShadowDx, mShadowDy;
-
-    private boolean mPreDrawRegistered;
-
-    private TextUtils.TruncateAt mEllipsize = null;
-
     // Enum for the "typeface" XML parameter.
     // TODO: How can we get this from the XML instead of hardcoding it here?
     private static final int SANS = 1;
@@ -271,122 +243,10 @@
     private static final int SIGNED = 2;
     private static final int DECIMAL = 4;
 
-    static class Drawables {
-        final Rect mCompoundRect = new Rect();
-        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
-                mDrawableStart, mDrawableEnd;
-        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
-                mDrawableSizeStart, mDrawableSizeEnd;
-        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
-                mDrawableHeightStart, mDrawableHeightEnd;
-        int mDrawablePadding;
-    }
-    private Drawables mDrawables;
-
-    private DisplayList mTextDisplayList;
-    private boolean mTextDisplayListIsValid;
-
-    private CharSequence mError;
-    private boolean mErrorWasChanged;
-    private ErrorPopup mPopup;
-    /**
-     * This flag is set if the TextView tries to display an error before it
-     * is attached to the window (so its position is still unknown).
-     * It causes the error to be shown later, when onAttachedToWindow()
-     * is called.
-     */
-    private boolean mShowErrorAfterAttach;
-
-    private CharWrapper mCharWrapper = null;
-
-    private boolean mSelectionMoved = false;
-    private boolean mTouchFocusSelected = false;
-
-    private Marquee mMarquee;
-    private boolean mRestartMarquee;
-
-    private int mMarqueeRepeatLimit = 3;
-
-    static class InputContentType {
-        int imeOptions = EditorInfo.IME_NULL;
-        String privateImeOptions;
-        CharSequence imeActionLabel;
-        int imeActionId;
-        Bundle extras;
-        OnEditorActionListener onEditorActionListener;
-        boolean enterDown;
-    }
-    InputContentType mInputContentType;
-
-    static class InputMethodState {
-        Rect mCursorRectInWindow = new Rect();
-        RectF mTmpRectF = new RectF();
-        float[] mTmpOffset = new float[2];
-        ExtractedTextRequest mExtracting;
-        final ExtractedText mTmpExtracted = new ExtractedText();
-        int mBatchEditNesting;
-        boolean mCursorChanged;
-        boolean mSelectionModeChanged;
-        boolean mContentChanged;
-        int mChangedStart, mChangedEnd, mChangedDelta;
-    }
-    InputMethodState mInputMethodState;
-
-    private int mTextSelectHandleLeftRes;
-    private int mTextSelectHandleRightRes;
-    private int mTextSelectHandleRes;
-
-    private int mTextEditSuggestionItemLayout;
-    private SuggestionsPopupWindow mSuggestionsPopupWindow;
-    private SuggestionRangeSpan mSuggestionRangeSpan;
-    private Runnable mShowSuggestionRunnable;
-
-    private int mCursorDrawableRes;
-    private final Drawable[] mCursorDrawable = new Drawable[2];
-    private int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2 (split)
-
-    private Drawable mSelectHandleLeft;
-    private Drawable mSelectHandleRight;
-    private Drawable mSelectHandleCenter;
-
-    // Global listener that detects changes in the global position of the TextView
-    private PositionListener mPositionListener;
-
-    private float mLastDownPositionX, mLastDownPositionY;
-    private Callback mCustomSelectionActionModeCallback;
-
-    // Set when this TextView gained focus with some text selected. Will start selection mode.
-    private boolean mCreatedWithASelection = false;
-
-    private WordIterator mWordIterator;
-
-    private SpellChecker mSpellChecker;
-
-    // The alignment to pass to Layout, or null if not resolved.
-    private Layout.Alignment mLayoutAlignment;
-
-    // The default value for mTextAlign.
-    private TextAlign mTextAlign = TextAlign.INHERIT;
-
-    private static enum TextAlign {
+    private static enum TEXT_ALIGN {
         INHERIT, GRAVITY, TEXT_START, TEXT_END, CENTER, VIEW_START, VIEW_END;
     }
 
-    private boolean mResolvedDrawables = false;
-
-    /**
-     * On some devices the fading edges add a performance penalty if used
-     * extensively in the same layout. This mode indicates how the marquee
-     * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
-     */
-    private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
-
-    /**
-     * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
-     * the layout that should be used when the mode switches.
-     */
-    private Layout mSavedMarqueeModeLayout;
-
     /**
      * Draw marquee text with fading edges as usual
      */
@@ -403,6 +263,169 @@
      */
     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
 
+    private static final int LINES = 1;
+    private static final int EMS = LINES;
+    private static final int PIXELS = 2;
+
+    private static final RectF TEMP_RECTF = new RectF();
+    private static final float[] TEMP_POSITION = new float[2];
+
+    // XXX should be much larger
+    private static final int VERY_WIDE = 1024*1024;
+    private static final int BLINK = 500;
+    private static final int ANIMATED_SCROLL_GAP = 250;
+
+    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+    private static final Spanned EMPTY_SPANNED = new SpannedString("");
+
+    private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
+    private static final int CHANGE_WATCHER_PRIORITY = 100;
+
+    // New state used to change background based on whether this TextView is multiline.
+    private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
+
+    // System wide time for last cut or copy action.
+    private static long LAST_CUT_OR_COPY_TIME;
+
+    private int mCurrentAlpha = 255;
+
+    private ColorStateList mTextColor;
+    private ColorStateList mHintTextColor;
+    private ColorStateList mLinkTextColor;
+    private int mCurTextColor;
+    private int mCurHintTextColor;
+    private boolean mFreezesText;
+    private boolean mTemporaryDetach;
+    private boolean mDispatchTemporaryDetach;
+
+    private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
+    private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
+
+    private float mShadowRadius, mShadowDx, mShadowDy;
+
+    private boolean mPreDrawRegistered;
+
+    private TextUtils.TruncateAt mEllipsize;
+
+    static class Drawables {
+        final Rect mCompoundRect = new Rect();
+        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
+                mDrawableStart, mDrawableEnd;
+        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
+                mDrawableSizeStart, mDrawableSizeEnd;
+        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
+                mDrawableHeightStart, mDrawableHeightEnd;
+        int mDrawablePadding;
+    }
+    private Drawables mDrawables;
+
+    private CharWrapper mCharWrapper;
+
+    private Marquee mMarquee;
+    private boolean mRestartMarquee;
+
+    private int mMarqueeRepeatLimit = 3;
+
+    // The alignment to pass to Layout, or null if not resolved.
+    private Layout.Alignment mLayoutAlignment;
+
+    // The default value for mTextAlign.
+    private TEXT_ALIGN mTextAlign = TEXT_ALIGN.INHERIT;
+
+    private boolean mResolvedDrawables;
+
+    /**
+     * On some devices the fading edges add a performance penalty if used
+     * extensively in the same layout. This mode indicates how the marquee
+     * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
+     */
+    private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
+
+    /**
+     * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
+     * the layout that should be used when the mode switches.
+     */
+    private Layout mSavedMarqueeModeLayout;
+
+    @ViewDebug.ExportedProperty(category = "text")
+    private CharSequence mText;
+    private CharSequence mTransformed;
+    private BufferType mBufferType = BufferType.NORMAL;
+
+    private CharSequence mHint;
+    private Layout mHintLayout;
+
+    private MovementMethod mMovement;
+
+    private TransformationMethod mTransformation;
+    private boolean mAllowTransformationLengthChange;
+    private ChangeWatcher mChangeWatcher;
+
+    private ArrayList<TextWatcher> mListeners;
+
+    // display attributes
+    private final TextPaint mTextPaint;
+    private boolean mUserSetTextScaleX;
+    private Layout mLayout;
+
+    private int mGravity = Gravity.TOP | Gravity.START;
+    private boolean mHorizontallyScrolling;
+
+    private int mAutoLinkMask;
+    private boolean mLinksClickable = true;
+
+    private float mSpacingMult = 1.0f;
+    private float mSpacingAdd = 0.0f;
+
+    private int mMaximum = Integer.MAX_VALUE;
+    private int mMaxMode = LINES;
+    private int mMinimum = 0;
+    private int mMinMode = LINES;
+
+    private int mOldMaximum = mMaximum;
+    private int mOldMaxMode = mMaxMode;
+
+    private int mMaxWidth = Integer.MAX_VALUE;
+    private int mMaxWidthMode = PIXELS;
+    private int mMinWidth = 0;
+    private int mMinWidthMode = PIXELS;
+
+    private boolean mSingleLine;
+    private int mDesiredHeightAtMeasure = -1;
+    private boolean mIncludePad = true;
+
+    // tmp primitives, so we don't alloc them on each draw
+    private Rect mTempRect;
+    private long mLastScroll;
+    private Scroller mScroller;
+
+    private BoringLayout.Metrics mBoring, mHintBoring;
+    private BoringLayout mSavedLayout, mSavedHintLayout;
+
+    private TextDirectionHeuristic mTextDir;
+
+    private InputFilter[] mFilters = NO_FILTERS;
+
+    // Although these fields are specific to editable text, they are not added to Editor because
+    // they are defined by the TextView's style and are theme-dependent.
+    private int mHighlightColor = 0x6633B5E5;
+    private int mCursorDrawableRes;
+    // These four fields, could be moved to Editor, since we know their default values and we
+    // could condition the creation of the Editor to a non standard value. This is however
+    // brittle since the hardcoded values here (such as
+    // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
+    // default style is modified.
+    private int mTextSelectHandleLeftRes;
+    private int mTextSelectHandleRightRes;
+    private int mTextSelectHandleRes;
+    private int mTextEditSuggestionItemLayout;
+
+    /**
+     * EditText specific data, created on demand when one of the Editor fields is used.
+     * See {@link #createEditorIfNeeded(String)}.
+     */
+    private Editor mEditor;
+
     /*
      * Kick-start the font cache for the zygote process (to pay the cost of
      * initializing freetype for our default font only once).
@@ -454,14 +477,8 @@
         mTextPaint.density = res.getDisplayMetrics().density;
         mTextPaint.setCompatibilityScaling(compat.applicationScale);
 
-        // If we get the paint from the skin, we should set it to left, since
-        // the layout always wants it to be left.
-        // mTextPaint.setTextAlign(Paint.Align.LEFT);
-
-        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
-
         mMovement = getDefaultMovementMethod();
+
         mTransformation = null;
 
         int textColorHighlight = 0;
@@ -608,12 +625,6 @@
                 mLinksClickable = a.getBoolean(attr, true);
                 break;
 
-//            TODO uncomment when this attribute is made public in the next release
-//                 also add TextView_showSoftInputOnFocus to the list of attributes above
-//            case com.android.internal.R.styleable.TextView_showSoftInputOnFocus:
-//                setShowSoftInputOnFocus(a.getBoolean(attr, true));
-//                break;
-
             case com.android.internal.R.styleable.TextView_drawableLeft:
                 drawableLeft = a.getDrawable(attr);
                 break;
@@ -805,30 +816,33 @@
                 break;
 
             case com.android.internal.R.styleable.TextView_inputType:
-                inputType = a.getInt(attr, mInputType);
+                inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
                 break;
 
             case com.android.internal.R.styleable.TextView_imeOptions:
-                if (mInputContentType == null) {
-                    mInputContentType = new InputContentType();
+                createEditorIfNeeded("IME options specified in constructor");
+                if (getEditor().mInputContentType == null) {
+                    getEditor().mInputContentType = new InputContentType();
                 }
-                mInputContentType.imeOptions = a.getInt(attr,
-                        mInputContentType.imeOptions);
+                getEditor().mInputContentType.imeOptions = a.getInt(attr,
+                        getEditor().mInputContentType.imeOptions);
                 break;
 
             case com.android.internal.R.styleable.TextView_imeActionLabel:
-                if (mInputContentType == null) {
-                    mInputContentType = new InputContentType();
+                createEditorIfNeeded("IME action label specified in constructor");
+                if (getEditor().mInputContentType == null) {
+                    getEditor().mInputContentType = new InputContentType();
                 }
-                mInputContentType.imeActionLabel = a.getText(attr);
+                getEditor().mInputContentType.imeActionLabel = a.getText(attr);
                 break;
 
             case com.android.internal.R.styleable.TextView_imeActionId:
-                if (mInputContentType == null) {
-                    mInputContentType = new InputContentType();
+                createEditorIfNeeded("IME action id specified in constructor");
+                if (getEditor().mInputContentType == null) {
+                    getEditor().mInputContentType = new InputContentType();
                 }
-                mInputContentType.imeActionId = a.getInt(attr,
-                        mInputContentType.imeActionId);
+                getEditor().mInputContentType.imeActionId = a.getInt(attr,
+                        getEditor().mInputContentType.imeActionId);
                 break;
 
             case com.android.internal.R.styleable.TextView_privateImeOptions:
@@ -866,7 +880,7 @@
                 break;
 
             case com.android.internal.R.styleable.TextView_textIsSelectable:
-                mTextIsSelectable = a.getBoolean(attr, false);
+                setTextIsSelectable(a.getBoolean(attr, false));
                 break;
 
             case com.android.internal.R.styleable.TextView_textAllCaps:
@@ -897,35 +911,39 @@
             }
 
             try {
-                mInput = (KeyListener) c.newInstance();
+                createEditorIfNeeded("inputMethod in ctor");
+                getEditor().mKeyListener = (KeyListener) c.newInstance();
             } catch (InstantiationException ex) {
                 throw new RuntimeException(ex);
             } catch (IllegalAccessException ex) {
                 throw new RuntimeException(ex);
             }
             try {
-                mInputType = inputType != EditorInfo.TYPE_NULL
+                getEditor().mInputType = inputType != EditorInfo.TYPE_NULL
                         ? inputType
-                        : mInput.getInputType();
+                        : getEditor().mKeyListener.getInputType();
             } catch (IncompatibleClassChangeError e) {
-                mInputType = EditorInfo.TYPE_CLASS_TEXT;
+                getEditor().mInputType = EditorInfo.TYPE_CLASS_TEXT;
             }
         } else if (digits != null) {
-            mInput = DigitsKeyListener.getInstance(digits.toString());
+            createEditorIfNeeded("digits in ctor");
+            getEditor().mKeyListener = DigitsKeyListener.getInstance(digits.toString());
             // If no input type was specified, we will default to generic
             // text, since we can't tell the IME about the set of digits
             // that was selected.
-            mInputType = inputType != EditorInfo.TYPE_NULL
+            getEditor().mInputType = inputType != EditorInfo.TYPE_NULL
                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
         } else if (inputType != EditorInfo.TYPE_NULL) {
             setInputType(inputType, true);
             // If set, the input type overrides what was set using the deprecated singleLine flag.
             singleLine = !isMultilineInputType(inputType);
         } else if (phone) {
-            mInput = DialerKeyListener.getInstance();
-            mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
+            createEditorIfNeeded("dialer in ctor");
+            getEditor().mKeyListener = DialerKeyListener.getInstance();
+            getEditor().mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
         } else if (numeric != 0) {
-            mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
+            createEditorIfNeeded("numeric in ctor");
+            getEditor().mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
                                                    (numeric & DECIMAL) != 0);
             inputType = EditorInfo.TYPE_CLASS_NUMBER;
             if ((numeric & SIGNED) != 0) {
@@ -934,7 +952,7 @@
             if ((numeric & DECIMAL) != 0) {
                 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
             }
-            mInputType = inputType;
+            getEditor().mInputType = inputType;
         } else if (autotext || autocap != -1) {
             TextKeyListener.Capitalize cap;
 
@@ -961,22 +979,24 @@
                 break;
             }
 
-            mInput = TextKeyListener.getInstance(autotext, cap);
-            mInputType = inputType;
-        } else if (mTextIsSelectable) {
+            createEditorIfNeeded("text input in ctor");
+            getEditor().mKeyListener = TextKeyListener.getInstance(autotext, cap);
+            getEditor().mInputType = inputType;
+        } else if (isTextSelectable()) {
             // Prevent text changes from keyboard.
-            mInputType = EditorInfo.TYPE_NULL;
-            mInput = null;
+            if (mEditor != null) {
+                getEditor().mKeyListener = null;
+                getEditor().mInputType = EditorInfo.TYPE_NULL;
+            }
             bufferType = BufferType.SPANNABLE;
-            // Required to request focus while in touch mode.
-            setFocusableInTouchMode(true);
             // So that selection can be changed using arrow keys and touch is handled.
             setMovementMethod(ArrowKeyMovementMethod.getInstance());
         } else if (editable) {
-            mInput = TextKeyListener.getInstance();
-            mInputType = EditorInfo.TYPE_CLASS_TEXT;
+            createEditorIfNeeded("editable input in ctor");
+            getEditor().mKeyListener = TextKeyListener.getInstance();
+            getEditor().mInputType = EditorInfo.TYPE_CLASS_TEXT;
         } else {
-            mInput = null;
+            if (mEditor != null) getEditor().mKeyListener = null;
 
             switch (buffertype) {
                 case 0:
@@ -991,27 +1011,12 @@
             }
         }
 
-        // mInputType has been set from inputType, possibly modified by mInputMethod.
-        // Specialize mInputType to [web]password if we have a text class and the original input
-        // type was a password.
-        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
-            if (password || passwordInputType) {
-                mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
-                        | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
-            }
-            if (webPasswordInputType) {
-                mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
-                        | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
-            }
-        } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
-            if (numberPasswordInputType) {
-                mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
-                        | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
-            }
-        }
+        if (mEditor != null) getEditor().adjustInputType(password, passwordInputType, webPasswordInputType,
+                numberPasswordInputType);
 
         if (selectallonfocus) {
-            mSelectAllOnFocus = true;
+            createEditorIfNeeded("selectallonfocus in constructor");
+            getEditor().mSelectAllOnFocus = true;
 
             if (bufferType == BufferType.NORMAL)
                 bufferType = BufferType.SPANNABLE;
@@ -1027,7 +1032,7 @@
         setInputTypeSingleLine(singleLine);
         applySingleLine(singleLine, singleLine, singleLine);
 
-        if (singleLine && mInput == null && ellipsize < 0) {
+        if (singleLine && getKeyListener() == null && ellipsize < 0) {
                 ellipsize = 3; // END
         }
 
@@ -1068,7 +1073,7 @@
         if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
             setTransformationMethod(PasswordTransformationMethod.getInstance());
             typefaceIndex = MONOSPACE;
-        } else if ((mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
+        } else if (mEditor != null && (getEditor().mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
             typefaceIndex = MONOSPACE;
         }
@@ -1097,7 +1102,7 @@
                                            com.android.internal.R.styleable.View,
                                            defStyle, 0);
 
-        boolean focusable = mMovement != null || mInput != null;
+        boolean focusable = mMovement != null || getKeyListener() != null;
         boolean clickable = focusable;
         boolean longClickable = focusable;
 
@@ -1205,7 +1210,7 @@
             if (imm != null) imm.restartInput(this);
         }
 
-        mTextDisplayListIsValid = false;
+        if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
         prepareCursorControllers();
 
         // start or stop the cursor blinking as appropriate
@@ -1310,7 +1315,7 @@
      * This will frequently be null for non-EditText TextViews.
      */
     public final KeyListener getKeyListener() {
-        return mInput;
+        return mEditor == null ? null : getEditor().mKeyListener;
     }
 
     /**
@@ -1340,16 +1345,17 @@
         fixFocusableAndClickableSettings();
 
         if (input != null) {
+            createEditorIfNeeded("input is not null");
             try {
-                mInputType = mInput.getInputType();
+                getEditor().mInputType = getEditor().mKeyListener.getInputType();
             } catch (IncompatibleClassChangeError e) {
-                mInputType = EditorInfo.TYPE_CLASS_TEXT;
+                getEditor().mInputType = EditorInfo.TYPE_CLASS_TEXT;
             }
             // Change inputType, without affecting transformation.
             // No need to applySingleLine since mSingleLine is unchanged.
             setInputTypeSingleLine(mSingleLine);
         } else {
-            mInputType = EditorInfo.TYPE_NULL;
+            if (mEditor != null) getEditor().mInputType = EditorInfo.TYPE_NULL;
         }
 
         InputMethodManager imm = InputMethodManager.peekInstance();
@@ -1357,11 +1363,17 @@
     }
 
     private void setKeyListenerOnly(KeyListener input) {
-        mInput = input;
-        if (mInput != null && !(mText instanceof Editable))
-            setText(mText);
+        if (mEditor == null && input == null) return; // null is the default value
 
-        setFilters((Editable) mText, mFilters);
+        createEditorIfNeeded("setKeyListenerOnly");
+        if (getEditor().mKeyListener != input) {
+            getEditor().mKeyListener = input;
+            if (input != null && !(mText instanceof Editable)) {
+                setText(mText);
+            }
+
+            setFilters((Editable) mText, mFilters);
+        }
     }
 
     /**
@@ -1384,19 +1396,22 @@
      * back the way you want it.
      */
     public final void setMovementMethod(MovementMethod movement) {
-        mMovement = movement;
+        if (mMovement != movement) {
+            mMovement = movement;
 
-        if (mMovement != null && !(mText instanceof Spannable))
-            setText(mText);
+            if (movement != null && !(mText instanceof Spannable)) {
+                setText(mText);
+            }
 
-        fixFocusableAndClickableSettings();
+            fixFocusableAndClickableSettings();
 
-        // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
-        prepareCursorControllers();
+            // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
+            prepareCursorControllers();
+        }
     }
 
     private void fixFocusableAndClickableSettings() {
-        if ((mMovement != null) || mInput != null) {
+        if (mMovement != null || (mEditor != null && getEditor().mKeyListener != null)) {
             setFocusable(true);
             setClickable(true);
             setLongClickable(true);
@@ -1439,7 +1454,7 @@
 
         if (method instanceof TransformationMethod2) {
             TransformationMethod2 method2 = (TransformationMethod2) method;
-            mAllowTransformationLengthChange = !mTextIsSelectable && !(mText instanceof Editable);
+            mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
         } else {
             mAllowTransformationLengthChange = false;
@@ -2311,7 +2326,7 @@
     public void setHighlightColor(int color) {
         if (mHighlightColor != color) {
             mHighlightColor = color;
-            mTextDisplayListIsValid = false;
+            if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
             invalidate();
         }
     }
@@ -2332,7 +2347,7 @@
         mShadowDx = dx;
         mShadowDy = dy;
 
-        mTextDisplayListIsValid = false;
+        if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
         invalidate();
     }
 
@@ -2824,7 +2839,7 @@
             }
         }
         if (inval) {
-            mTextDisplayListIsValid = false;
+            if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
             invalidate();
         }
     }
@@ -2862,73 +2877,6 @@
         }
     }
 
-    /**
-     * User interface state that is stored by TextView for implementing
-     * {@link View#onSaveInstanceState}.
-     */
-    public static class SavedState extends BaseSavedState {
-        int selStart;
-        int selEnd;
-        CharSequence text;
-        boolean frozenWithFocus;
-        CharSequence error;
-
-        SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeInt(selStart);
-            out.writeInt(selEnd);
-            out.writeInt(frozenWithFocus ? 1 : 0);
-            TextUtils.writeToParcel(text, out, flags);
-
-            if (error == null) {
-                out.writeInt(0);
-            } else {
-                out.writeInt(1);
-                TextUtils.writeToParcel(error, out, flags);
-            }
-        }
-
-        @Override
-        public String toString() {
-            String str = "TextView.SavedState{"
-                    + Integer.toHexString(System.identityHashCode(this))
-                    + " start=" + selStart + " end=" + selEnd;
-            if (text != null) {
-                str += " text=" + text;
-            }
-            return str + "}";
-        }
-
-        @SuppressWarnings("hiding")
-        public static final Parcelable.Creator<SavedState> CREATOR
-                = new Parcelable.Creator<SavedState>() {
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-
-        private SavedState(Parcel in) {
-            super(in);
-            selStart = in.readInt();
-            selEnd = in.readInt();
-            frozenWithFocus = (in.readInt() != 0);
-            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-
-            if (in.readInt() != 0) {
-                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-            }
-        }
-    }
-
     @Override
     public Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
@@ -2968,8 +2916,10 @@
                     sp.removeSpan(cw);
                 }
 
-                removeMisspelledSpans(sp);
-                sp.removeSpan(mSuggestionRangeSpan);
+                if (mEditor != null) {
+                    removeMisspelledSpans(sp);
+                    sp.removeSpan(getEditor().mSuggestionRangeSpan);
+                }
 
                 ss.text = sp;
             } else {
@@ -2980,7 +2930,7 @@
                 ss.frozenWithFocus = true;
             }
 
-            ss.error = mError;
+            ss.error = getError();
 
             return ss;
         }
@@ -3034,7 +2984,8 @@
                                            ss.selEnd);
 
                     if (ss.frozenWithFocus) {
-                        mFrozenWithFocus = true;
+                        createEditorIfNeeded("restore instance with focus");
+                        getEditor().mFrozenWithFocus = true;
                     }
                 }
             }
@@ -3192,7 +3143,8 @@
             needEditableForNotification = true;
         }
 
-        if (type == BufferType.EDITABLE || mInput != null || needEditableForNotification) {
+        if (type == BufferType.EDITABLE || getKeyListener() != null || needEditableForNotification) {
+            createEditorIfNeeded("setText with BufferType.EDITABLE or non null mInput");
             Editable t = mEditableFactory.newEditable(text);
             text = t;
             setFilters(t, mFilters);
@@ -3257,10 +3209,10 @@
                 mChangeWatcher = new ChangeWatcher();
 
             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
-                       (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
+                       (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
 
-            if (mInput != null) {
-                sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            if (mEditor != null && getEditor().mKeyListener != null) {
+                sp.setSpan(getEditor().mKeyListener, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             }
 
             if (mTransformation != null) {
@@ -3275,7 +3227,7 @@
                  * selection, so reset mSelectionMoved to keep that from
                  * interfering with the normal on-focus selection-setting.
                  */
-                mSelectionMoved = false;
+                if (mEditor != null) getEditor().mSelectionMoved = false;
             }
         }
 
@@ -3329,100 +3281,6 @@
         setText(mCharWrapper, mBufferType, false, oldlen);
     }
 
-    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
-        private char[] mChars;
-        private int mStart, mLength;
-
-        public CharWrapper(char[] chars, int start, int len) {
-            mChars = chars;
-            mStart = start;
-            mLength = len;
-        }
-
-        /* package */ void set(char[] chars, int start, int len) {
-            mChars = chars;
-            mStart = start;
-            mLength = len;
-        }
-
-        public int length() {
-            return mLength;
-        }
-
-        public char charAt(int off) {
-            return mChars[off + mStart];
-        }
-
-        @Override
-        public String toString() {
-            return new String(mChars, mStart, mLength);
-        }
-
-        public CharSequence subSequence(int start, int end) {
-            if (start < 0 || end < 0 || start > mLength || end > mLength) {
-                throw new IndexOutOfBoundsException(start + ", " + end);
-            }
-
-            return new String(mChars, start + mStart, end - start);
-        }
-
-        public void getChars(int start, int end, char[] buf, int off) {
-            if (start < 0 || end < 0 || start > mLength || end > mLength) {
-                throw new IndexOutOfBoundsException(start + ", " + end);
-            }
-
-            System.arraycopy(mChars, start + mStart, buf, off, end - start);
-        }
-
-        public void drawText(Canvas c, int start, int end,
-                             float x, float y, Paint p) {
-            c.drawText(mChars, start + mStart, end - start, x, y, p);
-        }
-
-        public void drawTextRun(Canvas c, int start, int end,
-                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
-            int count = end - start;
-            int contextCount = contextEnd - contextStart;
-            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
-                    contextCount, x, y, flags, p);
-        }
-
-        public float measureText(int start, int end, Paint p) {
-            return p.measureText(mChars, start + mStart, end - start);
-        }
-
-        public int getTextWidths(int start, int end, float[] widths, Paint p) {
-            return p.getTextWidths(mChars, start + mStart, end - start, widths);
-        }
-
-        public float getTextRunAdvances(int start, int end, int contextStart,
-                int contextEnd, int flags, float[] advances, int advancesIndex,
-                Paint p) {
-            int count = end - start;
-            int contextCount = contextEnd - contextStart;
-            return p.getTextRunAdvances(mChars, start + mStart, count,
-                    contextStart + mStart, contextCount, flags, advances,
-                    advancesIndex);
-        }
-
-        public float getTextRunAdvances(int start, int end, int contextStart,
-                int contextEnd, int flags, float[] advances, int advancesIndex,
-                Paint p, int reserved) {
-            int count = end - start;
-            int contextCount = contextEnd - contextStart;
-            return p.getTextRunAdvances(mChars, start + mStart, count,
-                    contextStart + mStart, contextCount, flags, advances,
-                    advancesIndex, reserved);
-        }
-
-        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
-                int offset, int cursorOpt, Paint p) {
-            int contextCount = contextEnd - contextStart;
-            return p.getTextRunCursor(mChars, contextStart + mStart,
-                    contextCount, flags, offset + mStart, cursorOpt);
-        }
-    }
-
     /**
      * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
      * except that the cursor position (if any) is retained in the new text.
@@ -3474,7 +3332,9 @@
         }
 
         // Invalidate display list if hint will be used
-        if (mText.length() == 0 && mHint != null) mTextDisplayListIsValid = false;
+        if (mEditor != null && mText.length() == 0 && mHint != null) {
+            getEditor().mTextDisplayListIsValid = false;
+        }
     }
 
     /**
@@ -3520,8 +3380,8 @@
      * @attr ref android.R.styleable#TextView_inputType
      */
     public void setInputType(int type) {
-        final boolean wasPassword = isPasswordInputType(mInputType);
-        final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
+        final boolean wasPassword = isPasswordInputType(getInputType());
+        final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
         setInputType(type, false);
         final boolean isPassword = isPasswordInputType(type);
         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
@@ -3605,7 +3465,9 @@
      * @attr ref android.R.styleable#TextView_inputType
      */
     public void setRawInputType(int type) {
-        mInputType = type;
+        if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
+        createEditorIfNeeded("non null input type");
+        getEditor().mInputType = type;
     }
 
     private void setInputType(int type, boolean direct) {
@@ -3646,20 +3508,22 @@
             input = TextKeyListener.getInstance();
         }
         setRawInputType(type);
-        if (direct) mInput = input;
-        else {
+        if (direct) {
+            createEditorIfNeeded("setInputType");
+            getEditor().mKeyListener = input;
+        } else {
             setKeyListenerOnly(input);
         }
     }
 
     /**
-     * Get the type of the content.
+     * Get the type of the editable content.
      *
      * @see #setInputType(int)
      * @see android.text.InputType
      */
     public int getInputType() {
-        return mInputType;
+        return mEditor == null ? EditorInfo.TYPE_NULL : getEditor().mInputType;
     }
 
     /**
@@ -3671,10 +3535,11 @@
      * @attr ref android.R.styleable#TextView_imeOptions
      */
     public void setImeOptions(int imeOptions) {
-        if (mInputContentType == null) {
-            mInputContentType = new InputContentType();
+        createEditorIfNeeded("IME options specified");
+        if (getEditor().mInputContentType == null) {
+            getEditor().mInputContentType = new InputContentType();
         }
-        mInputContentType.imeOptions = imeOptions;
+        getEditor().mInputContentType.imeOptions = imeOptions;
     }
 
     /**
@@ -3684,8 +3549,8 @@
      * @see android.view.inputmethod.EditorInfo
      */
     public int getImeOptions() {
-        return mInputContentType != null
-                ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
+        return mEditor != null && getEditor().mInputContentType != null
+                ? getEditor().mInputContentType.imeOptions : EditorInfo.IME_NULL;
     }
 
     /**
@@ -3699,11 +3564,12 @@
      * @attr ref android.R.styleable#TextView_imeActionId
      */
     public void setImeActionLabel(CharSequence label, int actionId) {
-        if (mInputContentType == null) {
-            mInputContentType = new InputContentType();
+        createEditorIfNeeded("IME action label specified");
+        if (getEditor().mInputContentType == null) {
+            getEditor().mInputContentType = new InputContentType();
         }
-        mInputContentType.imeActionLabel = label;
-        mInputContentType.imeActionId = actionId;
+        getEditor().mInputContentType.imeActionLabel = label;
+        getEditor().mInputContentType.imeActionId = actionId;
     }
 
     /**
@@ -3713,8 +3579,8 @@
      * @see android.view.inputmethod.EditorInfo
      */
     public CharSequence getImeActionLabel() {
-        return mInputContentType != null
-                ? mInputContentType.imeActionLabel : null;
+        return mEditor != null && getEditor().mInputContentType != null
+                ? getEditor().mInputContentType.imeActionLabel : null;
     }
 
     /**
@@ -3724,8 +3590,8 @@
      * @see android.view.inputmethod.EditorInfo
      */
     public int getImeActionId() {
-        return mInputContentType != null
-                ? mInputContentType.imeActionId : 0;
+        return mEditor != null && getEditor().mInputContentType != null
+                ? getEditor().mInputContentType.imeActionId : 0;
     }
 
     /**
@@ -3737,12 +3603,13 @@
      * modifier will, however, allow the user to insert a newline character.
      */
     public void setOnEditorActionListener(OnEditorActionListener l) {
-        if (mInputContentType == null) {
-            mInputContentType = new InputContentType();
+        createEditorIfNeeded("Editor action listener set");
+        if (getEditor().mInputContentType == null) {
+            getEditor().mInputContentType = new InputContentType();
         }
-        mInputContentType.onEditorActionListener = l;
+        getEditor().mInputContentType.onEditorActionListener = l;
     }
-    
+
     /**
      * Called when an attached input method calls
      * {@link InputConnection#performEditorAction(int)
@@ -3764,7 +3631,7 @@
      * @see #setOnEditorActionListener
      */
     public void onEditorAction(int actionCode) {
-        final InputContentType ict = mInputContentType;
+        final InputContentType ict = mEditor == null ? null : getEditor().mInputContentType;
         if (ict != null) {
             if (ict.onEditorActionListener != null) {
                 if (ict.onEditorActionListener.onEditorAction(this,
@@ -3835,8 +3702,10 @@
      * @attr ref android.R.styleable#TextView_privateImeOptions
      */
     public void setPrivateImeOptions(String type) {
-        if (mInputContentType == null) mInputContentType = new InputContentType();
-        mInputContentType.privateImeOptions = type;
+        createEditorIfNeeded("Private IME option set");
+        if (getEditor().mInputContentType == null)
+            getEditor().mInputContentType = new InputContentType();
+        getEditor().mInputContentType.privateImeOptions = type;
     }
 
     /**
@@ -3846,8 +3715,8 @@
      * @see EditorInfo#privateImeOptions
      */
     public String getPrivateImeOptions() {
-        return mInputContentType != null
-                ? mInputContentType.privateImeOptions : null;
+        return mEditor != null && getEditor().mInputContentType != null
+                ? getEditor().mInputContentType.privateImeOptions : null;
     }
 
     /**
@@ -3861,12 +3730,13 @@
      * @see EditorInfo#extras
      * @attr ref android.R.styleable#TextView_editorExtras
      */
-    public void setInputExtras(int xmlResId)
-            throws XmlPullParserException, IOException {
+    public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
+        createEditorIfNeeded("Input extra set");
         XmlResourceParser parser = getResources().getXml(xmlResId);
-        if (mInputContentType == null) mInputContentType = new InputContentType();
-        mInputContentType.extras = new Bundle();
-        getResources().parseBundleExtras(parser, mInputContentType.extras);
+        if (getEditor().mInputContentType == null)
+            getEditor().mInputContentType = new InputContentType();
+        getEditor().mInputContentType.extras = new Bundle();
+        getResources().parseBundleExtras(parser, getEditor().mInputContentType.extras);
     }
 
     /**
@@ -3880,15 +3750,17 @@
      * @attr ref android.R.styleable#TextView_editorExtras
      */
     public Bundle getInputExtras(boolean create) {
-        if (mInputContentType == null) {
+        if (mEditor == null && !create) return null;
+        createEditorIfNeeded("get Input extra");
+        if (getEditor().mInputContentType == null) {
             if (!create) return null;
-            mInputContentType = new InputContentType();
+            getEditor().mInputContentType = new InputContentType();
         }
-        if (mInputContentType.extras == null) {
+        if (getEditor().mInputContentType.extras == null) {
             if (!create) return null;
-            mInputContentType.extras = new Bundle();
+            getEditor().mInputContentType.extras = new Bundle();
         }
-        return mInputContentType.extras;
+        return getEditor().mInputContentType.extras;
     }
 
     /**
@@ -3897,7 +3769,7 @@
      * or if it the error was cleared by the widget after user input.
      */
     public CharSequence getError() {
-        return mError;
+        return mEditor == null ? null : getEditor().mError;
     }
 
     /**
@@ -3931,10 +3803,11 @@
      * be cleared (and you should provide a <code>null</code> icon as well).
      */
     public void setError(CharSequence error, Drawable icon) {
+        createEditorIfNeeded("setError");
         error = TextUtils.stringOrSpannedString(error);
 
-        mError = error;
-        mErrorWasChanged = true;
+        getEditor().mError = error;
+        getEditor().mErrorWasChanged = true;
         final Drawables dr = mDrawables;
         if (dr != null) {
             switch (getResolvedLayoutDirection()) {
@@ -3953,12 +3826,12 @@
         }
 
         if (error == null) {
-            if (mPopup != null) {
-                if (mPopup.isShowing()) {
-                    mPopup.dismiss();
+            if (getEditor().mErrorPopup != null) {
+                if (getEditor().mErrorPopup.isShowing()) {
+                    getEditor().mErrorPopup.dismiss();
                 }
 
-                mPopup = null;
+                getEditor().mErrorPopup = null;
             }
         } else {
             if (isFocused()) {
@@ -3969,83 +3842,29 @@
 
     private void showError() {
         if (getWindowToken() == null) {
-            mShowErrorAfterAttach = true;
+            getEditor().mShowErrorAfterAttach = true;
             return;
         }
 
-        if (mPopup == null) {
+        if (getEditor().mErrorPopup == null) {
             LayoutInflater inflater = LayoutInflater.from(getContext());
             final TextView err = (TextView) inflater.inflate(
                     com.android.internal.R.layout.textview_hint, null);
 
             final float scale = getResources().getDisplayMetrics().density;
-            mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
-            mPopup.setFocusable(false);
+            getEditor().mErrorPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
+            getEditor().mErrorPopup.setFocusable(false);
             // The user is entering text, so the input method is needed.  We
             // don't want the popup to be displayed on top of it.
-            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+            getEditor().mErrorPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
         }
 
-        TextView tv = (TextView) mPopup.getContentView();
-        chooseSize(mPopup, mError, tv);
-        tv.setText(mError);
+        TextView tv = (TextView) getEditor().mErrorPopup.getContentView();
+        chooseSize(getEditor().mErrorPopup, getEditor().mError, tv);
+        tv.setText(getEditor().mError);
 
-        mPopup.showAsDropDown(this, getErrorX(), getErrorY());
-        mPopup.fixDirection(mPopup.isAboveAnchor());
-    }
-
-    private static class ErrorPopup extends PopupWindow {
-        private boolean mAbove = false;
-        private final TextView mView;
-        private int mPopupInlineErrorBackgroundId = 0;
-        private int mPopupInlineErrorAboveBackgroundId = 0;
-
-        ErrorPopup(TextView v, int width, int height) {
-            super(v, width, height);
-            mView = v;
-            // Make sure the TextView has a background set as it will be used the first time it is
-            // shown and positionned. Initialized with below background, which should have
-            // dimensions identical to the above version for this to work (and is more likely).
-            mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
-                    com.android.internal.R.styleable.Theme_errorMessageBackground);
-            mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
-        }
-
-        void fixDirection(boolean above) {
-            mAbove = above;
-
-            if (above) {
-                mPopupInlineErrorAboveBackgroundId =
-                    getResourceId(mPopupInlineErrorAboveBackgroundId,
-                            com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
-            } else {
-                mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
-                        com.android.internal.R.styleable.Theme_errorMessageBackground);
-            }
-
-            mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
-                mPopupInlineErrorBackgroundId);
-        }
-
-        private int getResourceId(int currentId, int index) {
-            if (currentId == 0) {
-                TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
-                        R.styleable.Theme);
-                currentId = styledAttributes.getResourceId(index, 0);
-                styledAttributes.recycle();
-            }
-            return currentId;
-        }
-
-        @Override
-        public void update(int x, int y, int w, int h, boolean force) {
-            super.update(x, y, w, h, force);
-
-            boolean above = isAboveAnchor();
-            if (above != mAbove) {
-                fixDirection(above);
-            }
-        }
+        getEditor().mErrorPopup.showAsDropDown(this, getErrorX(), getErrorY());
+        getEditor().mErrorPopup.fixDirection(getEditor().mErrorPopup.isAboveAnchor());
     }
 
     /**
@@ -4060,7 +3879,7 @@
         final float scale = getResources().getDisplayMetrics().density;
 
         final Drawables dr = mDrawables;
-        return getWidth() - mPopup.getWidth() - getPaddingRight() -
+        return getWidth() - getEditor().mErrorPopup.getWidth() - getPaddingRight() -
                 (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
     }
 
@@ -4090,13 +3909,13 @@
     }
 
     private void hideError() {
-        if (mPopup != null) {
-            if (mPopup.isShowing()) {
-                mPopup.dismiss();
+        if (getEditor().mErrorPopup != null) {
+            if (getEditor().mErrorPopup.isShowing()) {
+                getEditor().mErrorPopup.dismiss();
             }
         }
 
-        mShowErrorAfterAttach = false;
+        getEditor().mShowErrorAfterAttach = false;
     }
 
     private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
@@ -4125,12 +3944,7 @@
     protected boolean setFrame(int l, int t, int r, int b) {
         boolean result = super.setFrame(l, t, r, b);
 
-        if (mPopup != null) {
-            TextView tv = (TextView) mPopup.getContentView();
-            chooseSize(mPopup, mError, tv);
-            mPopup.update(this, getErrorX(), getErrorY(),
-                          mPopup.getWidth(), mPopup.getHeight());
-        }
+        if (mEditor != null) getEditor().setFrame();
 
         restartMarqueeIfNeeded();
 
@@ -4146,7 +3960,7 @@
 
     /**
      * Sets the list of input filters that will be used if the buffer is
-     * Editable.  Has no effect otherwise.
+     * Editable. Has no effect otherwise.
      *
      * @attr ref android.R.styleable#TextView_maxLength
      */
@@ -4167,11 +3981,11 @@
      * and includes mInput in the list if it is an InputFilter.
      */
     private void setFilters(Editable e, InputFilter[] filters) {
-        if (mInput instanceof InputFilter) {
+        if (mEditor != null && getEditor().mKeyListener instanceof InputFilter) {
             InputFilter[] nf = new InputFilter[filters.length + 1];
 
             System.arraycopy(filters, 0, nf, 0, filters.length);
-            nf[filters.length] = (InputFilter) mInput;
+            nf[filters.length] = (InputFilter) getEditor().mKeyListener;
 
             e.setFilters(nf);
         } else {
@@ -4251,14 +4065,14 @@
     }
 
     private void invalidateCursorPath() {
-        if (mHighlightPathBogus) {
+        if (getEditor().mHighlightPathBogus) {
             invalidateCursor();
         } else {
             final int horizontalPadding = getCompoundPaddingLeft();
             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
 
-            if (mCursorCount == 0) {
-                synchronized (sTempRect) {
+            if (getEditor().mCursorCount == 0) {
+                synchronized (TEMP_RECTF) {
                     /*
                      * The reason for this concern about the thickness of the
                      * cursor and doing the floor/ceil on the coordinates is that
@@ -4275,16 +4089,16 @@
 
                     thick /= 2.0f;
 
-                    mHighlightPath.computeBounds(sTempRect, false);
+                    getEditor().mHighlightPath.computeBounds(TEMP_RECTF, false);
 
-                    invalidate((int) FloatMath.floor(horizontalPadding + sTempRect.left - thick),
-                            (int) FloatMath.floor(verticalPadding + sTempRect.top - thick),
-                            (int) FloatMath.ceil(horizontalPadding + sTempRect.right + thick),
-                            (int) FloatMath.ceil(verticalPadding + sTempRect.bottom + thick));
+                    invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
+                            (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
+                            (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
+                            (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
                 }
             } else {
-                for (int i = 0; i < mCursorCount; i++) {
-                    Rect bounds = mCursorDrawable[i].getBounds();
+                for (int i = 0; i < getEditor().mCursorCount; i++) {
+                    Rect bounds = getEditor().mCursorDrawable[i].getBounds();
                     invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
                             bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
                 }
@@ -4338,8 +4152,8 @@
                 int bottom = mLayout.getLineBottom(lineEnd);
 
                 if (invalidateCursor) {
-                    for (int i = 0; i < mCursorCount; i++) {
-                        Rect bounds = mCursorDrawable[i].getBounds();
+                    for (int i = 0; i < getEditor().mCursorCount; i++) {
+                        Rect bounds = getEditor().mCursorDrawable[i].getBounds();
                         top = Math.min(top, bounds.top);
                         bottom = Math.max(bottom, bounds.bottom);
                     }
@@ -4389,8 +4203,8 @@
              */
             int curs = getSelectionEnd();
             // Do not create the controller if it is not already created.
-            if (mSelectionModifierCursorController != null &&
-                    mSelectionModifierCursorController.isSelectionStartDragged()) {
+            if (mEditor != null && getEditor().mSelectionModifierCursorController != null &&
+                    getEditor().mSelectionModifierCursorController.isSelectionStartDragged()) {
                 curs = getSelectionStart();
             }
 
@@ -4399,8 +4213,7 @@
              * it already was before the text changed.  I'm not sure
              * of a good way to tell from here if it was.
              */
-            if (curs < 0 &&
-                  (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
+            if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
                 curs = mText.length();
             }
 
@@ -4414,9 +4227,9 @@
         // This has to be checked here since:
         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
         //   a screen rotation) since layout is not yet initialized at that point.
-        if (mCreatedWithASelection) {
+        if (mEditor != null && getEditor().mCreatedWithASelection) {
             startSelectionActionMode();
-            mCreatedWithASelection = false;
+            getEditor().mCreatedWithASelection = false;
         }
 
         // Phone specific code (there is no ExtractEditText on tablets).
@@ -4438,25 +4251,15 @@
 
         mTemporaryDetach = false;
         
-        if (mShowErrorAfterAttach) {
+        if (mEditor != null && getEditor().mShowErrorAfterAttach) {
             showError();
-            mShowErrorAfterAttach = false;
-        }
-
-        final ViewTreeObserver observer = getViewTreeObserver();
-        // No need to create the controller.
-        // The get method will add the listener on controller creation.
-        if (mInsertionPointCursorController != null) {
-            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
-        }
-        if (mSelectionModifierCursorController != null) {
-            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+            getEditor().mShowErrorAfterAttach = false;
         }
 
         // Resolve drawables as the layout direction has been resolved
         resolveDrawables();
 
-        updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */);
+        if (mEditor != null) getEditor().onAttachedToWindow();
     }
 
     @Override
@@ -4468,40 +4271,9 @@
             mPreDrawRegistered = false;
         }
 
-        if (mError != null) {
-            hideError();
-        }
-
-        if (mBlink != null) {
-            mBlink.removeCallbacks(mBlink);
-        }
-
-        if (mInsertionPointCursorController != null) {
-            mInsertionPointCursorController.onDetached();
-        }
-
-        if (mSelectionModifierCursorController != null) {
-            mSelectionModifierCursorController.onDetached();
-        }
-
-        if (mShowSuggestionRunnable != null) {
-            removeCallbacks(mShowSuggestionRunnable);
-        }
-
-        hideControllers();
-
         resetResolvedDrawables();
 
-        if (mTextDisplayList != null) {
-            mTextDisplayList.invalidate();
-        }
-
-        if (mSpellChecker != null) {
-            mSpellChecker.closeSession();
-            // Forces the creation of a new SpellChecker next time this window is created.
-            // Will handle the cases where the settings has been changed in the meantime.
-            mSpellChecker = null;
-        }
+        if (mEditor != null) getEditor().onDetachedFromWindow();
     }
 
     @Override
@@ -4648,13 +4420,13 @@
                     if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha);
                     if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha);
                 }
-                mTextDisplayListIsValid = false;
+                if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
             }
             return true;
         }
 
         if (mCurrentAlpha != 255) {
-            mTextDisplayListIsValid = false;
+            if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
         }
         mCurrentAlpha = 255;
         return false;
@@ -4678,12 +4450,12 @@
      * @attr ref android.R.styleable#TextView_textIsSelectable
      */
     public boolean isTextSelectable() {
-        return mTextIsSelectable;
+        return mEditor == null ? false : getEditor().mTextIsSelectable;
     }
 
     /**
      * Sets whether or not (default) the content of this view is selectable by the user.
-     * 
+     *
      * Note that this methods affect the {@link #setFocusable(boolean)},
      * {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
      * {@link #setLongClickable(boolean)} states and you may want to restore these if they were
@@ -4694,16 +4466,18 @@
      * @param selectable Whether or not the content of this TextView should be selectable.
      */
     public void setTextIsSelectable(boolean selectable) {
-        if (mTextIsSelectable == selectable) return;
+        if (!selectable && mEditor == null) return; // false is default value with no edit data
 
-        mTextIsSelectable = selectable;
+        createEditorIfNeeded("setTextIsSelectable");
+        if (getEditor().mTextIsSelectable == selectable) return;
 
+        getEditor().mTextIsSelectable = selectable;
         setFocusableInTouchMode(selectable);
         setFocusable(selectable);
         setClickable(selectable);
         setLongClickable(selectable);
 
-        // mInputType is already EditorInfo.TYPE_NULL and mInput is null;
+        // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
 
         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
         setText(getText(), selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
@@ -4723,7 +4497,7 @@
             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
         }
 
-        if (mTextIsSelectable) {
+        if (isTextSelectable()) {
             // Disable pressed state, which was introduced when TextView was made clickable.
             // Prevents text color change.
             // setClickable(false) would have a similar effect, but it also disables focus changes
@@ -4822,7 +4596,6 @@
         }
 
         Layout layout = mLayout;
-        int cursorcolor = color;
 
         if (mHint != null && mText.length() == 0) {
             if (mHintTextColor != null) {
@@ -4870,14 +4643,12 @@
         int voffsetCursor = 0;
 
         // translate in by our padding
-        {
-            /* shortcircuit calling getVerticaOffset() */
-            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
-                voffsetText = getVerticalOffset(false);
-                voffsetCursor = getVerticalOffset(true);
-            }
-            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
+        /* shortcircuit calling getVerticaOffset() */
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+            voffsetText = getVerticalOffset(false);
+            voffsetCursor = getVerticalOffset(true);
         }
+        canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
 
         final int layoutDirection = getResolvedLayoutDirection();
         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
@@ -4894,154 +4665,17 @@
             }
         }
 
-        Path highlight = null;
-        int selStart = -1, selEnd = -1;
-        boolean drawCursor = false;
-
-        //  If there is no movement method, then there can be no selection.
-        //  Check that first and attempt to skip everything having to do with
-        //  the cursor.
-        //  XXX This is not strictly true -- a program could set the
-        //  selection manually if it really wanted to.
-        if (mMovement != null && (isFocused() || isPressed())) {
-            selStart = getSelectionStart();
-            selEnd = getSelectionEnd();
-
-            if (selStart >= 0) {
-                if (mHighlightPath == null) mHighlightPath = new Path();
-
-                if (selStart == selEnd) {
-                    if (isCursorVisible() &&
-                            (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
-                        if (mHighlightPathBogus) {
-                            mHighlightPath.reset();
-                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
-                            updateCursorsPositions();
-                            mHighlightPathBogus = false;
-                        }
-
-                        // XXX should pass to skin instead of drawing directly
-                        mHighlightPaint.setColor(cursorcolor);
-                        if (mCurrentAlpha != 255) {
-                            mHighlightPaint.setAlpha(
-                                    (mCurrentAlpha * Color.alpha(cursorcolor)) / 255);
-                        }
-                        mHighlightPaint.setStyle(Paint.Style.STROKE);
-                        highlight = mHighlightPath;
-                        drawCursor = mCursorCount > 0;
-                    }
-                } else if (textCanBeSelected()) {
-                    if (mHighlightPathBogus) {
-                        mHighlightPath.reset();
-                        mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
-                        mHighlightPathBogus = false;
-                    }
-
-                    // XXX should pass to skin instead of drawing directly
-                    mHighlightPaint.setColor(mHighlightColor);
-                    if (mCurrentAlpha != 255) {
-                        mHighlightPaint.setAlpha(
-                                (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
-                    }
-                    mHighlightPaint.setStyle(Paint.Style.FILL);
-
-                    highlight = mHighlightPath;
-                }
-            }
-        }
-
-        final InputMethodState ims = mInputMethodState;
         final int cursorOffsetVertical = voffsetCursor - voffsetText;
-        if (ims != null && ims.mBatchEditNesting == 0) {
-            InputMethodManager imm = InputMethodManager.peekInstance();
-            if (imm != null) {
-                if (imm.isActive(this)) {
-                    boolean reported = false;
-                    if (ims.mContentChanged || ims.mSelectionModeChanged) {
-                        // We are in extract mode and the content has changed
-                        // in some way... just report complete new text to the
-                        // input method.
-                        reported = reportExtractedText();
-                    }
-                    if (!reported && highlight != null) {
-                        int candStart = -1;
-                        int candEnd = -1;
-                        if (mText instanceof Spannable) {
-                            Spannable sp = (Spannable)mText;
-                            candStart = EditableInputConnection.getComposingSpanStart(sp);
-                            candEnd = EditableInputConnection.getComposingSpanEnd(sp);
-                        }
-                        imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
-                    }
-                }
-                
-                if (imm.isWatchingCursor(this) && highlight != null) {
-                    highlight.computeBounds(ims.mTmpRectF, true);
-                    ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
-    
-                    canvas.getMatrix().mapPoints(ims.mTmpOffset);
-                    ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
-    
-                    ims.mTmpRectF.offset(0, cursorOffsetVertical);
-    
-                    ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
-                            (int)(ims.mTmpRectF.top + 0.5),
-                            (int)(ims.mTmpRectF.right + 0.5),
-                            (int)(ims.mTmpRectF.bottom + 0.5));
-    
-                    imm.updateCursor(this,
-                            ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
-                            ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
-                }
-            }
-        }
 
-        if (mCorrectionHighlighter != null) {
-            mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
-        }
-
-        if (drawCursor) {
-            drawCursor(canvas, cursorOffsetVertical);
-            // Rely on the drawable entirely, do not draw the cursor line.
-            // Has to be done after the IMM related code above which relies on the highlight.
-            highlight = null;
-        }
-
-        if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
-            final int width = mRight - mLeft;
-            final int height = mBottom - mTop;
-
-            if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
-                    !mTextDisplayListIsValid) {
-                if (mTextDisplayList == null) {
-                    mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
-                }
-
-                final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
-                try {
-                    hardwareCanvas.setViewport(width, height);
-                    // The dirty rect should always be null for a display list
-                    hardwareCanvas.onPreDraw(null);
-                    hardwareCanvas.translate(-mScrollX, -mScrollY);
-                    layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
-                    hardwareCanvas.translate(mScrollX, mScrollY);
-                } finally {
-                    hardwareCanvas.onPostDraw();
-                    mTextDisplayList.end();
-                    mTextDisplayListIsValid = true;
-                }
-            }
-            canvas.translate(mScrollX, mScrollY);
-            ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
-                    DisplayList.FLAG_CLIP_CHILDREN);
-            canvas.translate(-mScrollX, -mScrollY);
+        if (mEditor != null) {
+            getEditor().onDraw(canvas, layout, cursorOffsetVertical);
         } else {
-            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
-        }
+            layout.draw(canvas, null, null, cursorOffsetVertical);
 
-        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
-            canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
-            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
+            if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+                canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
+                layout.draw(canvas, null, null, cursorOffsetVertical);
+            }
         }
 
         canvas.restore();
@@ -5049,7 +4683,7 @@
 
     private void updateCursorsPositions() {
         if (mCursorDrawableRes == 0) {
-            mCursorCount = 0;
+            getEditor().mCursorCount = 0;
             return; 
         }
 
@@ -5058,40 +4692,39 @@
         final int top = mLayout.getLineTop(line);
         final int bottom = mLayout.getLineTop(line + 1);
 
-        mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
+        getEditor().mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
 
         int middle = bottom;
-        if (mCursorCount == 2) {
+        if (getEditor().mCursorCount == 2) {
             // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
             middle = (top + bottom) >> 1;
         }
 
         updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
 
-        if (mCursorCount == 2) {
+        if (getEditor().mCursorCount == 2) {
             updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
         }
     }
 
     private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
-        if (mCursorDrawable[cursorIndex] == null)
-            mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
+        if (getEditor().mCursorDrawable[cursorIndex] == null)
+            getEditor().mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
 
         if (mTempRect == null) mTempRect = new Rect();
-
-        mCursorDrawable[cursorIndex].getPadding(mTempRect);
-        final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
+        getEditor().mCursorDrawable[cursorIndex].getPadding(mTempRect);
+        final int width = getEditor().mCursorDrawable[cursorIndex].getIntrinsicWidth();
         horizontal = Math.max(0.5f, horizontal - 0.5f);
         final int left = (int) (horizontal) - mTempRect.left;
-        mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
+        getEditor().mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
                 bottom + mTempRect.bottom);
     }
 
     private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
         final boolean translate = cursorOffsetVertical != 0;
         if (translate) canvas.translate(0, cursorOffsetVertical);
-        for (int i = 0; i < mCursorCount; i++) {
-            mCursorDrawable[i].draw(canvas);
+        for (int i = 0; i < getEditor().mCursorCount; i++) {
+            getEditor().mCursorDrawable[i].draw(canvas);
         }
         if (translate) canvas.translate(0, -cursorOffsetVertical);
     }
@@ -5125,18 +4758,23 @@
                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
             } else {
-                // Selection extends across multiple lines -- the focused
-                // rect covers the entire width.
-                if (mHighlightPath == null) mHighlightPath = new Path();
-                if (mHighlightPathBogus) {
-                    mHighlightPath.reset();
-                    mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
-                    mHighlightPathBogus = false;
-                }
-                synchronized (sTempRect) {
-                    mHighlightPath.computeBounds(sTempRect, true);
-                    r.left = (int)sTempRect.left-1;
-                    r.right = (int)sTempRect.right+1;
+                // Selection extends across multiple lines -- make the focused
+                // rect cover the entire width.
+                if (mEditor != null) {
+                    if (getEditor().mHighlightPath == null) getEditor().mHighlightPath = new Path();
+                    if (getEditor().mHighlightPathBogus) {
+                        getEditor().mHighlightPath.reset();
+                        mLayout.getSelectionPath(selStart, selEnd, getEditor().mHighlightPath);
+                        getEditor().mHighlightPathBogus = false;
+                    }
+                    synchronized (TEMP_RECTF) {
+                        getEditor().mHighlightPath.computeBounds(TEMP_RECTF, true);
+                        r.left = (int)TEMP_RECTF.left-1;
+                        r.right = (int)TEMP_RECTF.right+1;
+                    }
+                } else {
+                    r.left = 0;
+                    r.right = getMeasuredWidth();
                 }
             }
         }
@@ -5148,6 +4786,8 @@
             paddingTop += getVerticalOffset(false);
         }
         r.offset(paddingLeft, paddingTop);
+        int paddingBottom = getExtendedPaddingBottom();
+        r.bottom += paddingBottom;
     }
 
     /**
@@ -5232,7 +4872,7 @@
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
-            boolean isInSelectionMode = mSelectionActionMode != null;
+            boolean isInSelectionMode = mEditor != null && getEditor().mSelectionActionMode != null;
 
             if (isInSelectionMode) {
                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
@@ -5290,14 +4930,16 @@
         // but adding that is a more complicated change.
         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
         if (which == 1) {
-            mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+            // mEditor and getEditor().mInput are not null from doKeyDown
+            getEditor().mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
             while (--repeatCount > 0) {
-                mInput.onKeyDown(this, (Editable)mText, keyCode, down);
-                mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+                getEditor().mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
+                getEditor().mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
             }
             hideErrorIfUnchanged();
 
         } else if (which == 2) {
+            // mMovement is not null from doKeyDown
             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
             while (--repeatCount > 0) {
                 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
@@ -5315,7 +4957,7 @@
      * lines but where it doesn't make sense to insert newlines.
      */
     private boolean shouldAdvanceFocusOnEnter() {
-        if (mInput == null) {
+        if (getKeyListener() == null) {
             return false;
         }
 
@@ -5323,8 +4965,8 @@
             return true;
         }
 
-        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
-            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
+        if (mEditor != null && (getEditor().mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+            int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
                 return true;
@@ -5339,9 +4981,9 @@
      * of inserting the character.  Insert tabs only in multi-line editors.
      */
     private boolean shouldAdvanceFocusOnTab() {
-        if (mInput != null && !mSingleLine) {
-            if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
-                int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
+        if (getKeyListener() != null && !mSingleLine) {
+            if (mEditor != null && (getEditor().mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+                int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
                 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
                         || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
                     return false;
@@ -5363,13 +5005,13 @@
                     // running in a "modern" cupcake environment, so don't need
                     // to worry about the application trying to capture
                     // enter key events.
-                    if (mInputContentType != null) {
+                    if (mEditor != null && getEditor().mInputContentType != null) {
                         // If there is an action listener, given them a
                         // chance to consume the event.
-                        if (mInputContentType.onEditorActionListener != null &&
-                                mInputContentType.onEditorActionListener.onEditorAction(
+                        if (getEditor().mInputContentType.onEditorActionListener != null &&
+                                getEditor().mInputContentType.onEditorActionListener.onEditorAction(
                                 this, EditorInfo.IME_NULL, event)) {
-                            mInputContentType.enterDown = true;
+                            getEditor().mInputContentType.enterDown = true;
                             // We are consuming the enter key for them.
                             return -1;
                         }
@@ -5406,21 +5048,21 @@
 
                 // Has to be done on key down (and not on key up) to correctly be intercepted.
             case KeyEvent.KEYCODE_BACK:
-                if (mSelectionActionMode != null) {
+                if (mEditor != null && getEditor().mSelectionActionMode != null) {
                     stopSelectionActionMode();
                     return -1;
                 }
                 break;
         }
 
-        if (mInput != null) {
+        if (mEditor != null && getEditor().mKeyListener != null) {
             resetErrorChangedFlag();
 
             boolean doDown = true;
             if (otherEvent != null) {
                 try {
                     beginBatchEdit();
-                    final boolean handled = mInput.onKeyOther(this, (Editable) mText, otherEvent);
+                    final boolean handled = getEditor().mKeyListener.onKeyOther(this, (Editable) mText, otherEvent);
                     hideErrorIfUnchanged();
                     doDown = false;
                     if (handled) {
@@ -5436,7 +5078,7 @@
             
             if (doDown) {
                 beginBatchEdit();
-                final boolean handled = mInput.onKeyDown(this, (Editable) mText, keyCode, event);
+                final boolean handled = getEditor().mKeyListener.onKeyDown(this, (Editable) mText, keyCode, event);
                 endBatchEdit();
                 hideErrorIfUnchanged();
                 if (handled) return 1;
@@ -5482,14 +5124,14 @@
          * that error showing.  Otherwise, we take down whatever
          * error was showing when the user types something.
          */
-        mErrorWasChanged = false;
+        if (mEditor != null) getEditor().mErrorWasChanged = false;
     }
 
     /**
      * @hide
      */
     public void hideErrorIfUnchanged() {
-        if (mError != null && !mErrorWasChanged) {
+        if (mEditor != null && getEditor().mError != null && !getEditor().mErrorWasChanged) {
             setError(null, null);
         }
     }
@@ -5527,11 +5169,11 @@
 
             case KeyEvent.KEYCODE_ENTER:
                 if (event.hasNoModifiers()) {
-                    if (mInputContentType != null
-                            && mInputContentType.onEditorActionListener != null
-                            && mInputContentType.enterDown) {
-                        mInputContentType.enterDown = false;
-                        if (mInputContentType.onEditorActionListener.onEditorAction(
+                    if (mEditor != null && getEditor().mInputContentType != null
+                            && getEditor().mInputContentType.onEditorActionListener != null
+                            && getEditor().mInputContentType.enterDown) {
+                        getEditor().mInputContentType.enterDown = false;
+                        if (getEditor().mInputContentType.onEditorActionListener.onEditorAction(
                                 this, EditorInfo.IME_NULL, event)) {
                             return true;
                         }
@@ -5582,8 +5224,8 @@
                 break;
         }
 
-        if (mInput != null)
-            if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
+        if (mEditor != null && getEditor().mKeyListener != null)
+            if (getEditor().mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
                 return true;
 
         if (mMovement != null && mLayout != null)
@@ -5595,22 +5237,23 @@
 
     @Override
     public boolean onCheckIsTextEditor() {
-        return mInputType != EditorInfo.TYPE_NULL;
+        return mEditor != null && getEditor().mInputType != EditorInfo.TYPE_NULL;
     }
 
     @Override
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        createEditorIfNeeded("onCreateInputConnection");
         if (onCheckIsTextEditor() && isEnabled()) {
-            if (mInputMethodState == null) {
-                mInputMethodState = new InputMethodState();
+            if (getEditor().mInputMethodState == null) {
+                getEditor().mInputMethodState = new InputMethodState();
             }
-            outAttrs.inputType = mInputType;
-            if (mInputContentType != null) {
-                outAttrs.imeOptions = mInputContentType.imeOptions;
-                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
-                outAttrs.actionLabel = mInputContentType.imeActionLabel;
-                outAttrs.actionId = mInputContentType.imeActionId;
-                outAttrs.extras = mInputContentType.extras;
+            outAttrs.inputType = getInputType();
+            if (getEditor().mInputContentType != null) {
+                outAttrs.imeOptions = getEditor().mInputContentType.imeOptions;
+                outAttrs.privateImeOptions = getEditor().mInputContentType.privateImeOptions;
+                outAttrs.actionLabel = getEditor().mInputContentType.imeActionLabel;
+                outAttrs.actionId = getEditor().mInputContentType.imeActionId;
+                outAttrs.extras = getEditor().mInputContentType.extras;
             } else {
                 outAttrs.imeOptions = EditorInfo.IME_NULL;
             }
@@ -5644,7 +5287,7 @@
                 InputConnection ic = new EditableInputConnection(this);
                 outAttrs.initialSelStart = getSelectionStart();
                 outAttrs.initialSelEnd = getSelectionEnd();
-                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
+                outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
                 return ic;
             }
         }
@@ -5736,13 +5379,13 @@
     }
     
     boolean reportExtractedText() {
-        final InputMethodState ims = mInputMethodState;
+        final InputMethodState ims = getEditor().mInputMethodState;
         if (ims != null) {
             final boolean contentChanged = ims.mContentChanged;
             if (contentChanged || ims.mSelectionModeChanged) {
                 ims.mContentChanged = false;
                 ims.mSelectionModeChanged = false;
-                final ExtractedTextRequest req = mInputMethodState.mExtracting;
+                final ExtractedTextRequest req = ims.mExtracting;
                 if (req != null) {
                     InputMethodManager imm = InputMethodManager.peekInstance();
                     if (imm != null) {
@@ -5758,8 +5401,7 @@
                                     + ims.mTmpExtracted.partialStartOffset
                                     + " end=" + ims.mTmpExtracted.partialEndOffset
                                     + ": " + ims.mTmpExtracted.text);
-                            imm.updateExtractedText(this, req.token,
-                                    mInputMethodState.mTmpExtracted);
+                            imm.updateExtractedText(this, req.token, ims.mTmpExtracted);
                             ims.mChangedStart = EXTRACT_UNKNOWN;
                             ims.mChangedEnd = EXTRACT_UNKNOWN;
                             ims.mChangedDelta = 0;
@@ -5836,8 +5478,8 @@
      * @hide
      */
     public void setExtracting(ExtractedTextRequest req) {
-        if (mInputMethodState != null) {
-            mInputMethodState.mExtracting = req;
+        if (getEditor().mInputMethodState != null) {
+            getEditor().mInputMethodState.mExtracting = req;
         }
         // This would stop a possible selection mode, but no such mode is started in case
         // extracted mode will start. Some text is selected though, and will trigger an action mode
@@ -5868,109 +5510,20 @@
      * @param info The auto correct info about the text that was corrected.
      */
     public void onCommitCorrection(CorrectionInfo info) {
-        if (mCorrectionHighlighter == null) {
-            mCorrectionHighlighter = new CorrectionHighlighter();
+        if (mEditor == null) return;
+        if (getEditor().mCorrectionHighlighter == null) {
+            getEditor().mCorrectionHighlighter = new CorrectionHighlighter();
         } else {
-            mCorrectionHighlighter.invalidate(false);
+            getEditor().mCorrectionHighlighter.invalidate(false);
         }
 
-        mCorrectionHighlighter.highlight(info);
-    }
-
-    private class CorrectionHighlighter {
-        private final Path mPath = new Path();
-        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        private int mStart, mEnd;
-        private long mFadingStartTime;
-        private final static int FADE_OUT_DURATION = 400;
-
-        public CorrectionHighlighter() {
-            mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
-            mPaint.setStyle(Paint.Style.FILL);
-        }
-
-        public void highlight(CorrectionInfo info) {
-            mStart = info.getOffset();
-            mEnd = mStart + info.getNewText().length();
-            mFadingStartTime = SystemClock.uptimeMillis();
-
-            if (mStart < 0 || mEnd < 0) {
-                stopAnimation();
-            }
-        }
-
-        public void draw(Canvas canvas, int cursorOffsetVertical) {
-            if (updatePath() && updatePaint()) {
-                if (cursorOffsetVertical != 0) {
-                    canvas.translate(0, cursorOffsetVertical);
-                }
-
-                canvas.drawPath(mPath, mPaint);
-
-                if (cursorOffsetVertical != 0) {
-                    canvas.translate(0, -cursorOffsetVertical);
-                }
-                invalidate(true); // TODO invalidate cursor region only
-            } else {
-                stopAnimation();
-                invalidate(false); // TODO invalidate cursor region only
-            }
-        }
-
-        private boolean updatePaint() {
-            final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
-            if (duration > FADE_OUT_DURATION) return false;
-
-            final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
-            final int highlightColorAlpha = Color.alpha(mHighlightColor);
-            final int color = (mHighlightColor & 0x00FFFFFF) +
-                    ((int) (highlightColorAlpha * coef) << 24);
-            mPaint.setColor(color);
-            return true;
-        }
-
-        private boolean updatePath() {
-            final Layout layout = TextView.this.mLayout;
-            if (layout == null) return false;
-
-            // Update in case text is edited while the animation is run
-            final int length = mText.length();
-            int start = Math.min(length, mStart);
-            int end = Math.min(length, mEnd);
-
-            mPath.reset();
-            TextView.this.mLayout.getSelectionPath(start, end, mPath);
-            return true;
-        }
-
-        private void invalidate(boolean delayed) {
-            if (TextView.this.mLayout == null) return;
-
-            synchronized (sTempRect) {
-                mPath.computeBounds(sTempRect, false);
-
-                int left = getCompoundPaddingLeft();
-                int top = getExtendedPaddingTop() + getVerticalOffset(true);
-
-                if (delayed) {
-                    TextView.this.postInvalidateDelayed(16, // 60 Hz update
-                            left + (int) sTempRect.left, top + (int) sTempRect.top,
-                            left + (int) sTempRect.right, top + (int) sTempRect.bottom);
-                } else {
-                    TextView.this.postInvalidate((int) sTempRect.left, (int) sTempRect.top,
-                            (int) sTempRect.right, (int) sTempRect.bottom);
-                }
-            }
-        }
-
-        private void stopAnimation() {
-            TextView.this.mCorrectionHighlighter = null;
-        }
+        getEditor().mCorrectionHighlighter.highlight(info);
     }
 
     public void beginBatchEdit() {
-        mInBatchEditControllers = true;
-        final InputMethodState ims = mInputMethodState;
+        if (mEditor == null) return;
+        getEditor().mInBatchEditControllers = true;
+        final InputMethodState ims = getEditor().mInputMethodState;
         if (ims != null) {
             int nesting = ++ims.mBatchEditNesting;
             if (nesting == 1) {
@@ -5992,8 +5545,9 @@
     }
     
     public void endBatchEdit() {
-        mInBatchEditControllers = false;
-        final InputMethodState ims = mInputMethodState;
+        if (mEditor == null) return;
+        getEditor().mInBatchEditControllers = false;
+        final InputMethodState ims = getEditor().mInputMethodState;
         if (ims != null) {
             int nesting = --ims.mBatchEditNesting;
             if (nesting == 0) {
@@ -6003,7 +5557,7 @@
     }
     
     void ensureEndedBatchEdit() {
-        final InputMethodState ims = mInputMethodState;
+        final InputMethodState ims = getEditor().mInputMethodState;
         if (ims != null && ims.mBatchEditNesting != 0) {
             ims.mBatchEditNesting = 0;
             finishBatchEdit(ims);
@@ -6031,7 +5585,7 @@
         }
 
         if (curs >= 0) {
-            mHighlightPathBogus = true;
+            getEditor().mHighlightPathBogus = true;
             makeBlink();
             bringPointIntoView(curs);
         }
@@ -6046,7 +5600,7 @@
     public void onBeginBatchEdit() {
         // intentionally empty
     }
-    
+
     /**
      * Called by the framework in response to a request to end a batch
      * of edit operations through a call to link {@link #endBatchEdit}.
@@ -6111,8 +5665,8 @@
         super.resetResolvedLayoutDirection();
 
         if (mLayoutAlignment != null &&
-                (mTextAlign == TextAlign.VIEW_START ||
-                mTextAlign == TextAlign.VIEW_END)) {
+                (mTextAlign == TEXT_ALIGN.VIEW_START ||
+                mTextAlign == TEXT_ALIGN.VIEW_END)) {
             mLayoutAlignment = null;
         }
     }
@@ -6120,7 +5674,7 @@
     private Layout.Alignment getLayoutAlignment() {
         if (mLayoutAlignment == null) {
             Layout.Alignment alignment;
-            TextAlign textAlign = mTextAlign;
+            TEXT_ALIGN textAlign = mTextAlign;
             switch (textAlign) {
                 case INHERIT:
                     // fall through to gravity temporarily
@@ -6188,7 +5742,7 @@
         mOldMaximum = mMaximum;
         mOldMaxMode = mMaxMode;
 
-        mHighlightPathBogus = true;
+        if (mEditor != null) getEditor().mHighlightPathBogus = true;
 
         if (wantWidth < 0) {
             wantWidth = 0;
@@ -6198,7 +5752,7 @@
         }
 
         Layout.Alignment alignment = getLayoutAlignment();
-        boolean shouldEllipsize = mEllipsize != null && mInput == null;
+        boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
                 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
         TruncateAt effectiveEllipsize = mEllipsize;
@@ -6315,7 +5869,7 @@
         if (mText instanceof Spannable) {
             result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
                     alignment, mTextDir, mSpacingMult,
-                    mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
+                    mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
                             ellipsisWidth);
         } else {
             if (boring == UNKNOWN_BORING) {
@@ -6776,7 +6330,7 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        if (changed) mTextDisplayListIsValid = false;
+        if (changed && mEditor != null) getEditor().mTextDisplayListIsValid = false;
     }
 
     /**
@@ -7002,11 +6556,11 @@
             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
             // requestRectangleOnScreen() is in terms of content coordinates.
 
-            if (mTempRect == null) mTempRect = new Rect();
             // The offsets here are to ensure the rectangle we are using is
             // within our view bounds, in case the cursor is on the far left
             // or right.  If it isn't withing the bounds, then this request
             // will be ignored.
+            if (mTempRect == null) mTempRect = new Rect();
             mTempRect.set(x - 2, top, x + 2, bottom);
             getInterestingRect(mTempRect, line);
             mTempRect.offset(mScrollX, mScrollY);
@@ -7226,11 +6780,11 @@
      * @param singleLine
      */
     private void setInputTypeSingleLine(boolean singleLine) {
-        if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+        if (mEditor != null && (getEditor().mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
             if (singleLine) {
-                mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+                getEditor().mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
             } else {
-                mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+                getEditor().mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
             }
         }
     }
@@ -7309,7 +6863,8 @@
      */
     @android.view.RemotableViewMethod
     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
-        mSelectAllOnFocus = selectAllOnFocus;
+        createEditorIfNeeded("setSelectAllOnFocus");
+        getEditor().mSelectAllOnFocus = selectAllOnFocus;
 
         if (selectAllOnFocus && !(mText instanceof Spannable)) {
             setText(mText, BufferType.SPANNABLE);
@@ -7323,8 +6878,10 @@
      */
     @android.view.RemotableViewMethod
     public void setCursorVisible(boolean visible) {
-        if (mCursorVisible != visible) {
-            mCursorVisible = visible;
+        if (visible && mEditor == null) return; // visible is the default value with no edit data
+        createEditorIfNeeded("setCursorVisible");
+        if (getEditor().mCursorVisible != visible) {
+            getEditor().mCursorVisible = visible;
             invalidate();
 
             makeBlink();
@@ -7335,7 +6892,8 @@
     }
 
     private boolean isCursorVisible() {
-        return mCursorVisible && isTextEditable();
+        // The default value is true, even when there is no associated Editor
+        return mEditor == null ? true : (getEditor().mCursorVisible && isTextEditable());
     }
 
     private boolean canMarquee() {
@@ -7347,7 +6905,7 @@
 
     private void startMarquee() {
         // Do not ellipsize EditText
-        if (mInput != null) return;
+        if (getKeyListener() != null) return;
 
         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
             return;
@@ -7397,142 +6955,6 @@
         }
     }
 
-    private static final class Marquee extends Handler {
-        // TODO: Add an option to configure this
-        private static final float MARQUEE_DELTA_MAX = 0.07f;
-        private static final int MARQUEE_DELAY = 1200;
-        private static final int MARQUEE_RESTART_DELAY = 1200;
-        private static final int MARQUEE_RESOLUTION = 1000 / 30;
-        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
-
-        private static final byte MARQUEE_STOPPED = 0x0;
-        private static final byte MARQUEE_STARTING = 0x1;
-        private static final byte MARQUEE_RUNNING = 0x2;
-
-        private static final int MESSAGE_START = 0x1;
-        private static final int MESSAGE_TICK = 0x2;
-        private static final int MESSAGE_RESTART = 0x3;
-
-        private final WeakReference<TextView> mView;
-
-        private byte mStatus = MARQUEE_STOPPED;
-        private final float mScrollUnit;
-        private float mMaxScroll;
-        float mMaxFadeScroll;
-        private float mGhostStart;
-        private float mGhostOffset;
-        private float mFadeStop;
-        private int mRepeatLimit;
-
-        float mScroll;
-
-        Marquee(TextView v) {
-            final float density = v.getContext().getResources().getDisplayMetrics().density;
-            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
-            mView = new WeakReference<TextView>(v);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MESSAGE_START:
-                    mStatus = MARQUEE_RUNNING;
-                    tick();
-                    break;
-                case MESSAGE_TICK:
-                    tick();
-                    break;
-                case MESSAGE_RESTART:
-                    if (mStatus == MARQUEE_RUNNING) {
-                        if (mRepeatLimit >= 0) {
-                            mRepeatLimit--;
-                        }
-                        start(mRepeatLimit);
-                    }
-                    break;
-            }
-        }
-
-        void tick() {
-            if (mStatus != MARQUEE_RUNNING) {
-                return;
-            }
-
-            removeMessages(MESSAGE_TICK);
-
-            final TextView textView = mView.get();
-            if (textView != null && (textView.isFocused() || textView.isSelected())) {
-                mScroll += mScrollUnit;
-                if (mScroll > mMaxScroll) {
-                    mScroll = mMaxScroll;
-                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
-                } else {
-                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
-                }
-                textView.invalidate();
-            }
-        }
-
-        void stop() {
-            mStatus = MARQUEE_STOPPED;
-            removeMessages(MESSAGE_START);
-            removeMessages(MESSAGE_RESTART);
-            removeMessages(MESSAGE_TICK);
-            resetScroll();
-        }
-
-        private void resetScroll() {
-            mScroll = 0.0f;
-            final TextView textView = mView.get();
-            if (textView != null) textView.invalidate();
-        }
-
-        void start(int repeatLimit) {
-            if (repeatLimit == 0) {
-                stop();
-                return;
-            }
-            mRepeatLimit = repeatLimit;
-            final TextView textView = mView.get();
-            if (textView != null && textView.mLayout != null) {
-                mStatus = MARQUEE_STARTING;
-                mScroll = 0.0f;
-                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
-                        textView.getCompoundPaddingRight();
-                final float lineWidth = textView.mLayout.getLineWidth(0);
-                final float gap = textWidth / 3.0f;
-                mGhostStart = lineWidth - textWidth + gap;
-                mMaxScroll = mGhostStart + textWidth;
-                mGhostOffset = lineWidth + gap;
-                mFadeStop = lineWidth + textWidth / 6.0f;
-                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
-
-                textView.invalidate();
-                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
-            }
-        }
-
-        float getGhostOffset() {
-            return mGhostOffset;
-        }
-
-        boolean shouldDrawLeftFade() {
-            return mScroll <= mFadeStop;
-        }
-
-        boolean shouldDrawGhost() {
-            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
-        }
-
-        boolean isRunning() {
-            return mStatus == MARQUEE_RUNNING;
-        }
-
-        boolean isStopped() {
-            return mStatus == MARQUEE_STOPPED;
-        }
-    }
-
     /**
      * This method is called when the text is changed, in case any subclasses
      * would like to know.
@@ -7561,7 +6983,7 @@
      */
     protected void onSelectionChanged(int selStart, int selEnd) {
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
-        mTextDisplayListIsValid = false;
+        getEditor().mTextDisplayListIsValid = false;
     }
 
     /**
@@ -7640,13 +7062,7 @@
             }
         }
 
-        updateSpellCheckSpans(start, start + after, false);
-        mTextDisplayListIsValid = false;
-
-        // Hide the controllers as soon as text is modified (typing, procedural...)
-        // We do not hide the span controllers, since they can be added when a new text is
-        // inserted into the text view (voice IME).
-        hideCursorControllers();
+        if (mEditor != null) getEditor().sendOnTextChanged(start, after);
     }
 
     /**
@@ -7668,7 +7084,7 @@
      * through a thunk.
      */
     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
-        final InputMethodState ims = mInputMethodState;
+        final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState;
         if (ims == null || ims.mBatchEditNesting == 0) {
             updateAfterEdit();
         }
@@ -7687,7 +7103,7 @@
         sendOnTextChanged(buffer, start, before, after);
         onTextChanged(buffer, start, before, after);
     }
-    
+
     /**
      * Not private so it can be called from an inner class without going
      * through a thunk.
@@ -7698,18 +7114,13 @@
 
         boolean selChanged = false;
         int newSelStart=-1, newSelEnd=-1;
-        
-        final InputMethodState ims = mInputMethodState;
-        
+
+        final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState;
+
         if (what == Selection.SELECTION_END) {
-            mHighlightPathBogus = true;
             selChanged = true;
             newSelEnd = newStart;
 
-            if (!isFocused()) {
-                mSelectionMoved = true;
-            }
-
             if (oldStart >= 0 || newStart >= 0) {
                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
                 registerForPreDraw();
@@ -7718,14 +7129,9 @@
         }
 
         if (what == Selection.SELECTION_START) {
-            mHighlightPathBogus = true;
             selChanged = true;
             newSelStart = newStart;
 
-            if (!isFocused()) {
-                mSelectionMoved = true;
-            }
-
             if (oldStart >= 0 || newStart >= 0) {
                 int end = Selection.getSelectionEnd(buf);
                 invalidateCursor(end, oldStart, newStart);
@@ -7733,6 +7139,11 @@
         }
 
         if (selChanged) {
+            if (mEditor != null) {
+                getEditor().mHighlightPathBogus = true;
+                if (!isFocused()) getEditor().mSelectionMoved = true;
+            }
+
             if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
                 if (newSelStart < 0) {
                     newSelStart = Selection.getSelectionStart(buf);
@@ -7748,16 +7159,16 @@
                 what instanceof CharacterStyle) {
             if (ims == null || ims.mBatchEditNesting == 0) {
                 invalidate();
-                mHighlightPathBogus = true;
+                if (mEditor != null) getEditor().mHighlightPathBogus = true;
                 checkForResize();
             } else {
                 ims.mContentChanged = true;
             }
-            mTextDisplayListIsValid = false;
+            if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
         }
 
         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
-            mHighlightPathBogus = true;
+            if (mEditor != null) getEditor().mHighlightPathBogus = true;
             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
                 ims.mSelectionModeChanged = true;
             }
@@ -7801,8 +7212,8 @@
             }
         }
 
-        if (mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
-            mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
+        if (mEditor != null && getEditor().mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
+            getEditor().mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
         }
     }
 
@@ -7811,289 +7222,16 @@
      */
     private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) {
         if (isTextEditable() && isSuggestionsEnabled() && !(this instanceof ExtractEditText)) {
-            if (mSpellChecker == null && createSpellChecker) {
-                mSpellChecker = new SpellChecker(this);
+            if (getEditor().mSpellChecker == null && createSpellChecker) {
+                getEditor().mSpellChecker = new SpellChecker(this);
             }
-            if (mSpellChecker != null) {
-                mSpellChecker.spellCheck(start, end);
+            if (getEditor().mSpellChecker != null) {
+                getEditor().mSpellChecker.spellCheck(start, end);
             }
         }
     }
 
     /**
-     * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
-     * pop-up should be displayed.
-     */
-    private class EasyEditSpanController {
-
-        private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
-
-        private EasyEditPopupWindow mPopupWindow;
-
-        private EasyEditSpan mEasyEditSpan;
-
-        private Runnable mHidePopup;
-
-        private void hide() {
-            if (mPopupWindow != null) {
-                mPopupWindow.hide();
-                TextView.this.removeCallbacks(mHidePopup);
-            }
-            removeSpans(mText);
-            mEasyEditSpan = null;
-        }
-
-        /**
-         * Monitors the changes in the text.
-         *
-         * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
-         * as the notifications are not sent when a spannable (with spans) is inserted.
-         */
-        public void onTextChange(CharSequence buffer) {
-            adjustSpans(mText);
-
-            if (getWindowVisibility() != View.VISIBLE) {
-                // The window is not visible yet, ignore the text change.
-                return;
-            }
-
-            if (mLayout == null) {
-                // The view has not been layout yet, ignore the text change
-                return;
-            }
-
-            InputMethodManager imm = InputMethodManager.peekInstance();
-            if (!(TextView.this instanceof ExtractEditText)
-                    && imm != null && imm.isFullscreenMode()) {
-                // The input is in extract mode. We do not have to handle the easy edit in the
-                // original TextView, as the ExtractEditText will do
-                return;
-            }
-
-            // Remove the current easy edit span, as the text changed, and remove the pop-up
-            // (if any)
-            if (mEasyEditSpan != null) {
-                if (mText instanceof Spannable) {
-                    ((Spannable) mText).removeSpan(mEasyEditSpan);
-                }
-                mEasyEditSpan = null;
-            }
-            if (mPopupWindow != null && mPopupWindow.isShowing()) {
-                mPopupWindow.hide();
-            }
-
-            // Display the new easy edit span (if any).
-            if (buffer instanceof Spanned) {
-                mEasyEditSpan = getSpan((Spanned) buffer);
-                if (mEasyEditSpan != null) {
-                    if (mPopupWindow == null) {
-                        mPopupWindow = new EasyEditPopupWindow();
-                        mHidePopup = new Runnable() {
-                            @Override
-                            public void run() {
-                                hide();
-                            }
-                        };
-                    }
-                    mPopupWindow.show(mEasyEditSpan);
-                    TextView.this.removeCallbacks(mHidePopup);
-                    TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
-                }
-            }
-        }
-
-        /**
-         * Adjusts the spans by removing all of them except the last one.
-         */
-        private void adjustSpans(CharSequence buffer) {
-            // This method enforces that only one easy edit span is attached to the text.
-            // A better way to enforce this would be to listen for onSpanAdded, but this method
-            // cannot be used in this scenario as no notification is triggered when a text with
-            // spans is inserted into a text.
-            if (buffer instanceof Spannable) {
-                Spannable spannable = (Spannable) buffer;
-                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
-                        EasyEditSpan.class);
-                for (int i = 0; i < spans.length - 1; i++) {
-                    spannable.removeSpan(spans[i]);
-                }
-            }
-        }
-
-        /**
-         * Removes all the {@link EasyEditSpan} currently attached.
-         */
-        private void removeSpans(CharSequence buffer) {
-            if (buffer instanceof Spannable) {
-                Spannable spannable = (Spannable) buffer;
-                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
-                        EasyEditSpan.class);
-                for (int i = 0; i < spans.length; i++) {
-                    spannable.removeSpan(spans[i]);
-                }
-            }
-        }
-
-        private EasyEditSpan getSpan(Spanned spanned) {
-            EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
-                    EasyEditSpan.class);
-            if (easyEditSpans.length == 0) {
-                return null;
-            } else {
-                return easyEditSpans[0];
-            }
-        }
-    }
-
-    /**
-     * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
-     * by {@link EasyEditSpanController}.
-     */
-    private class EasyEditPopupWindow extends PinnedPopupWindow
-            implements OnClickListener {
-        private static final int POPUP_TEXT_LAYOUT =
-                com.android.internal.R.layout.text_edit_action_popup_text;
-        private TextView mDeleteTextView;
-        private EasyEditSpan mEasyEditSpan;
-
-        @Override
-        protected void createPopupWindow() {
-            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
-                    com.android.internal.R.attr.textSelectHandleWindowStyle);
-            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
-            mPopupWindow.setClippingEnabled(true);
-        }
-
-        @Override
-        protected void initContentView() {
-            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
-            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
-            mContentView = linearLayout;
-            mContentView.setBackgroundResource(
-                    com.android.internal.R.drawable.text_edit_side_paste_window);
-
-            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
-                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-            LayoutParams wrapContent = new LayoutParams(
-                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
-            mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
-            mDeleteTextView.setLayoutParams(wrapContent);
-            mDeleteTextView.setText(com.android.internal.R.string.delete);
-            mDeleteTextView.setOnClickListener(this);
-            mContentView.addView(mDeleteTextView);
-        }
-
-        public void show(EasyEditSpan easyEditSpan) {
-            mEasyEditSpan = easyEditSpan;
-            super.show();
-        }
-
-        @Override
-        public void onClick(View view) {
-            if (view == mDeleteTextView) {
-                Editable editable = (Editable) mText;
-                int start = editable.getSpanStart(mEasyEditSpan);
-                int end = editable.getSpanEnd(mEasyEditSpan);
-                if (start >= 0 && end >= 0) {
-                    deleteText_internal(start, end);
-                }
-            }
-        }
-
-        @Override
-        protected int getTextOffset() {
-            // Place the pop-up at the end of the span
-            Editable editable = (Editable) mText;
-            return editable.getSpanEnd(mEasyEditSpan);
-        }
-
-        @Override
-        protected int getVerticalLocalPosition(int line) {
-            return mLayout.getLineBottom(line);
-        }
-
-        @Override
-        protected int clipVertically(int positionY) {
-            // As we display the pop-up below the span, no vertical clipping is required.
-            return positionY;
-        }
-    }
-
-    private class ChangeWatcher implements TextWatcher, SpanWatcher {
-
-        private CharSequence mBeforeText;
-
-        private EasyEditSpanController mEasyEditSpanController;
-
-        private ChangeWatcher() {
-            mEasyEditSpanController = new EasyEditSpanController();
-        }
-
-        public void beforeTextChanged(CharSequence buffer, int start,
-                                      int before, int after) {
-            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
-                    + " before=" + before + " after=" + after + ": " + buffer);
-
-            if (AccessibilityManager.getInstance(mContext).isEnabled()
-                    && !isPasswordInputType(mInputType)
-                    && !hasPasswordTransformationMethod()) {
-                mBeforeText = buffer.toString();
-            }
-
-            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
-        }
-
-        public void onTextChanged(CharSequence buffer, int start,
-                                  int before, int after) {
-            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
-                    + " before=" + before + " after=" + after + ": " + buffer);
-            TextView.this.handleTextChanged(buffer, start, before, after);
-
-            mEasyEditSpanController.onTextChange(buffer);
-
-            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
-                    (isFocused() || isSelected() && isShown())) {
-                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
-                mBeforeText = null;
-            }
-        }
-
-        public void afterTextChanged(Editable buffer) {
-            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
-            TextView.this.sendAfterTextChanged(buffer);
-
-            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
-                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
-            }
-        }
-
-        public void onSpanChanged(Spannable buf,
-                                  Object what, int s, int e, int st, int en) {
-            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
-                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
-            TextView.this.spanChange(buf, what, s, st, e, en);
-        }
-
-        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
-            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
-                    + " what=" + what + ": " + buf);
-            TextView.this.spanChange(buf, what, -1, s, -1, e);
-        }
-
-        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
-            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
-                    + " what=" + what + ": " + buf);
-            TextView.this.spanChange(buf, what, s, -1, e, -1);
-        }
-
-        private void hideControllers() {
-            mEasyEditSpanController.hide();
-        }
-    }
-
-    /**
      * @hide
      */
     @Override
@@ -8113,7 +7251,7 @@
         // Because of View recycling in ListView, there is no easy way to know when a TextView with
         // selection becomes visible again. Until a better solution is found, stop text selection
         // mode (if any) as soon as this TextView is recycled.
-        hideControllers();
+        if (mEditor != null) hideControllers();
     }
 
     @Override
@@ -8131,95 +7269,14 @@
             super.onFocusChanged(focused, direction, previouslyFocusedRect);
             return;
         }
-        
-        mShowCursor = SystemClock.uptimeMillis();
 
-        ensureEndedBatchEdit();
+        if (mEditor != null) getEditor().onFocusChanged(focused, direction);
 
         if (focused) {
-            int selStart = getSelectionStart();
-            int selEnd = getSelectionEnd();
-
-            // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
-            // mode for these, unless there was a specific selection already started.
-            final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
-                    selEnd == mText.length();
-            mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
-
-            if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
-                // If a tap was used to give focus to that view, move cursor at tap position.
-                // Has to be done before onTakeFocus, which can be overloaded.
-                final int lastTapPosition = getLastTapPosition();
-                if (lastTapPosition >= 0) {
-                    Selection.setSelection((Spannable) mText, lastTapPosition);
-                }
-
-                if (mMovement != null) {
-                    mMovement.onTakeFocus(this, (Spannable) mText, direction);
-                }
-
-                // The DecorView does not have focus when the 'Done' ExtractEditText button is
-                // pressed. Since it is the ViewAncestor's mView, it requests focus before
-                // ExtractEditText clears focus, which gives focus to the ExtractEditText.
-                // This special case ensure that we keep current selection in that case.
-                // It would be better to know why the DecorView does not have focus at that time.
-                if (((this instanceof ExtractEditText) || mSelectionMoved) &&
-                        selStart >= 0 && selEnd >= 0) {
-                    /*
-                     * Someone intentionally set the selection, so let them
-                     * do whatever it is that they wanted to do instead of
-                     * the default on-focus behavior.  We reset the selection
-                     * here instead of just skipping the onTakeFocus() call
-                     * because some movement methods do something other than
-                     * just setting the selection in theirs and we still
-                     * need to go through that path.
-                     */
-                    Selection.setSelection((Spannable) mText, selStart, selEnd);
-                }
-
-                if (mSelectAllOnFocus) {
-                    selectAll();
-                }
-
-                mTouchFocusSelected = true;
-            }
-
-            mFrozenWithFocus = false;
-            mSelectionMoved = false;
-
             if (mText instanceof Spannable) {
                 Spannable sp = (Spannable) mText;
                 MetaKeyKeyListener.resetMetaState(sp);
             }
-
-            makeBlink();
-
-            if (mError != null) {
-                showError();
-            }
-        } else {
-            if (mError != null) {
-                hideError();
-            }
-            // Don't leave us in the middle of a batch edit.
-            onEndBatchEdit();
-
-            if (this instanceof ExtractEditText) {
-                // terminateTextSelectionMode removes selection, which we want to keep when
-                // ExtractEditText goes out of focus.
-                final int selStart = getSelectionStart();
-                final int selEnd = getSelectionEnd();
-                hideControllers();
-                Selection.setSelection((Spannable) mText, selStart, selEnd);
-            } else {
-                hideControllers();
-                downgradeEasyCorrectionSpans();
-            }
-
-            // No need to create the controller
-            if (mSelectionModifierCursorController != null) {
-                mSelectionModifierCursorController.resetTouchOffsets();
-            }
         }
 
         startStopMarquee(focused);
@@ -8231,48 +7288,11 @@
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
     }
 
-    private int getLastTapPosition() {
-        // No need to create the controller at that point, no last tap position saved
-        if (mSelectionModifierCursorController != null) {
-            int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
-            if (lastTapPosition >= 0) {
-                // Safety check, should not be possible.
-                if (lastTapPosition > mText.length()) {
-                    Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
-                            + mText.length() + ")");
-                    lastTapPosition = mText.length();
-                }
-                return lastTapPosition;
-            }
-        }
-
-        return -1;
-    }
-
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
 
-        if (hasWindowFocus) {
-            if (mBlink != null) {
-                mBlink.uncancel();
-                makeBlink();
-            }
-        } else {
-            if (mBlink != null) {
-                mBlink.cancel();
-            }
-            // Don't leave us in the middle of a batch edit.
-            onEndBatchEdit();
-            if (mInputContentType != null) {
-                mInputContentType.enterDown = false;
-            }
-
-            hideControllers();
-            if (mSuggestionsPopupWindow != null) {
-                mSuggestionsPopupWindow.onParentLostFocus();
-            }
-        }
+        if (mEditor != null) getEditor().onWindowFocusChanged(hasWindowFocus);
 
         startStopMarquee(hasWindowFocus);
     }
@@ -8280,7 +7300,7 @@
     @Override
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
-        if (visibility != VISIBLE) {
+        if (mEditor != null && visibility != VISIBLE) {
             hideControllers();
         }
     }
@@ -8315,23 +7335,7 @@
     public boolean onTouchEvent(MotionEvent event) {
         final int action = event.getActionMasked();
 
-        if (hasSelectionController()) {
-            getSelectionController().onTouchEvent(event);
-        }
-
-        if (mShowSuggestionRunnable != null) {
-            removeCallbacks(mShowSuggestionRunnable);
-        }
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            mLastDownPositionX = event.getX();
-            mLastDownPositionY = event.getY();
-
-            // Reset this state; it will be re-set if super.onTouchEvent
-            // causes focus to move to the view.
-            mTouchFocusSelected = false;
-            mIgnoreActionUpEvent = false;
-        }
+        if (mEditor != null) getEditor().onTouchEvent(event);
 
         final boolean superResult = super.onTouchEvent(event);
 
@@ -8340,13 +7344,13 @@
          * move the selection away from whatever the menu action was
          * trying to affect.
          */
-        if (mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
-            mDiscardNextActionUp = false;
+        if (mEditor != null && getEditor().mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
+            getEditor().mDiscardNextActionUp = false;
             return superResult;
         }
 
         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
-                !mIgnoreActionUpEvent && isFocused();
+                (mEditor == null || !getEditor().mIgnoreActionUpEvent) && isFocused();
 
          if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
                 && mText instanceof Spannable && mLayout != null) {
@@ -8356,7 +7360,8 @@
                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
             }
 
-            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable) {
+            final boolean textIsSelectable = isTextSelectable();
+            if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
                 // The LinkMovementMethod which should handle taps on links has not been installed
                 // on non editable text that support text selection.
                 // We reproduce its behavior here to open links for these.
@@ -8369,34 +7374,33 @@
                 }
             }
 
-            if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
+            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
                 // Show the IME, except when selecting in read-only text.
                 final InputMethodManager imm = InputMethodManager.peekInstance();
                 viewClicked(imm);
-                if (!mTextIsSelectable) {
+                if (!textIsSelectable) {
                     handled |= imm != null && imm.showSoftInput(this, 0);
                 }
 
-                boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
+                boolean selectAllGotFocus = getEditor().mSelectAllOnFocus && didTouchFocusSelect();
                 hideControllers();
                 if (!selectAllGotFocus && mText.length() > 0) {
                     // Move cursor
                     final int offset = getOffsetForPosition(event.getX(), event.getY());
                     Selection.setSelection((Spannable) mText, offset);
-                    if (mSpellChecker != null) {
+                    if (getEditor().mSpellChecker != null) {
                         // When the cursor moves, the word that was typed may need spell check
-                        mSpellChecker.onSelectionChanged();
+                        getEditor().mSpellChecker.onSelectionChanged();
                     }
                     if (!extractedTextModeWillBeStarted()) {
                         if (isCursorInsideEasyCorrectionSpan()) {
-                            if (mShowSuggestionRunnable == null) {
-                                mShowSuggestionRunnable = new Runnable() {
-                                    public void run() {
-                                        showSuggestions();
-                                    }
-                                };
-                            }
-                            postDelayed(mShowSuggestionRunnable,
+                            getEditor().mShowSuggestionRunnable = new Runnable() {
+                                public void run() {
+                                    showSuggestions();
+                                }
+                            };
+                            // removeCallbacks is performed on every touch
+                            postDelayed(getEditor().mShowSuggestionRunnable,
                                     ViewConfiguration.getDoubleTapTimeout());
                         } else if (hasInsertionController()) {
                             getInsertionController().show();
@@ -8479,6 +7483,8 @@
     }
 
     private void prepareCursorControllers() {
+        if (mEditor == null) return;
+
         boolean windowSupportsHandles = false;
 
         ViewGroup.LayoutParams params = getRootView().getLayoutParams();
@@ -8488,23 +7494,23 @@
                     || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
         }
 
-        mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
-        mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
+        getEditor().mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
+        getEditor().mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
                 mLayout != null;
 
-        if (!mInsertionControllerEnabled) {
+        if (!getEditor().mInsertionControllerEnabled) {
             hideInsertionPointCursorController();
-            if (mInsertionPointCursorController != null) {
-                mInsertionPointCursorController.onDetached();
-                mInsertionPointCursorController = null;
+            if (getEditor().mInsertionPointCursorController != null) {
+                getEditor().mInsertionPointCursorController.onDetached();
+                getEditor().mInsertionPointCursorController = null;
             }
         }
 
-        if (!mSelectionControllerEnabled) {
+        if (!getEditor().mSelectionControllerEnabled) {
             stopSelectionActionMode();
-            if (mSelectionModifierCursorController != null) {
-                mSelectionModifierCursorController.onDetached();
-                mSelectionModifierCursorController = null;
+            if (getEditor().mSelectionModifierCursorController != null) {
+                getEditor().mSelectionModifierCursorController.onDetached();
+                getEditor().mSelectionModifierCursorController = null;
             }
         }
     }
@@ -8524,19 +7530,18 @@
      * of interest.
      */
     public boolean didTouchFocusSelect() {
-        return mTouchFocusSelected;
+        return mEditor != null && getEditor().mTouchFocusSelected;
     }
     
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
-        mIgnoreActionUpEvent = true;
+        if (mEditor != null) getEditor().mIgnoreActionUpEvent = true;
     }
 
     @Override
     public boolean onTrackballEvent(MotionEvent event) {
-        if (mMovement != null && mText instanceof Spannable &&
-            mLayout != null) {
+        if (mMovement != null && mText instanceof Spannable && mLayout != null) {
             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
                 return true;
             }
@@ -8549,49 +7554,11 @@
         mScroller = s;
     }
 
-    private static class Blink extends Handler implements Runnable {
-        private final WeakReference<TextView> mView;
-        private boolean mCancelled;
-
-        public Blink(TextView v) {
-            mView = new WeakReference<TextView>(v);
-        }
-
-        public void run() {
-            if (mCancelled) {
-                return;
-            }
-
-            removeCallbacks(Blink.this);
-
-            TextView tv = mView.get();
-
-            if (tv != null && tv.shouldBlink()) {
-                if (tv.mLayout != null) {
-                    tv.invalidateCursorPath();
-                }
-
-                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
-            }
-        }
-
-        void cancel() {
-            if (!mCancelled) {
-                removeCallbacks(Blink.this);
-                mCancelled = true;
-            }
-        }
-
-        void uncancel() {
-            mCancelled = false;
-        }
-    }
-
     /**
      * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
      */
     private boolean shouldBlink() {
-        if (!isCursorVisible() || !isFocused()) return false;
+        if (mEditor == null || !isCursorVisible() || !isFocused()) return false;
 
         final int start = getSelectionStart();
         if (start < 0) return false;
@@ -8604,12 +7571,12 @@
 
     private void makeBlink() {
         if (shouldBlink()) {
-            mShowCursor = SystemClock.uptimeMillis();
-            if (mBlink == null) mBlink = new Blink(this);
-            mBlink.removeCallbacks(mBlink);
-            mBlink.postAtTime(mBlink, mShowCursor + BLINK);
+            getEditor().mShowCursor = SystemClock.uptimeMillis();
+            if (getEditor().mBlink == null) getEditor().mBlink = new Blink(this);
+            getEditor().mBlink.removeCallbacks(getEditor().mBlink);
+            getEditor().mBlink.postAtTime(getEditor().mBlink, getEditor().mShowCursor + BLINK);
         } else {
-            if (mBlink != null) mBlink.removeCallbacks(mBlink);
+            if (mEditor != null && getEditor().mBlink != null) getEditor().mBlink.removeCallbacks(getEditor().mBlink);
         }
     }
 
@@ -8808,7 +7775,7 @@
         // If you change this condition, make sure prepareCursorController is called anywhere
         // the value of this condition might be changed.
         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
-        return isTextEditable() || (mTextIsSelectable && mText instanceof Spannable && isEnabled());
+        return isTextEditable() || (isTextSelectable() && mText instanceof Spannable && isEnabled());
     }
 
     private boolean canCut() {
@@ -8816,7 +7783,7 @@
             return false;
         }
 
-        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
+        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && getEditor().mKeyListener != null) {
             return true;
         }
 
@@ -8837,7 +7804,7 @@
 
     private boolean canPaste() {
         return (mText instanceof Editable &&
-                mInput != null &&
+                mEditor != null && getEditor().mKeyListener != null &&
                 getSelectionStart() >= 0 &&
                 getSelectionEnd() >= 0 &&
                 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
@@ -8878,8 +7845,9 @@
             return selectAll();
         }
 
-        int klass = mInputType & InputType.TYPE_MASK_CLASS;
-        int variation = mInputType & InputType.TYPE_MASK_VARIATION;
+        int inputType = getInputType();
+        int klass = inputType & InputType.TYPE_MASK_CLASS;
+        int variation = inputType & InputType.TYPE_MASK_VARIATION;
 
         // Specific text field types: select the entire text for these
         if (klass == InputType.TYPE_CLASS_NUMBER ||
@@ -8949,17 +7917,17 @@
 
     void onLocaleChanged() {
         // Will be re-created on demand in getWordIterator with the proper new locale
-        mWordIterator = null;
+        getEditor().mWordIterator = null;
     }
 
     /**
      * @hide
      */
     public WordIterator getWordIterator() {
-        if (mWordIterator == null) {
-            mWordIterator = new WordIterator(getTextServicesLocale());
+        if (getEditor().mWordIterator == null) {
+            getEditor().mWordIterator = new WordIterator(getTextServicesLocale());
         }
-        return mWordIterator;
+        return getEditor().mWordIterator;
     }
 
     private long getCharRange(int offset) {
@@ -9213,17 +8181,6 @@
         return new DragShadowBuilder(shadowView);
     }
 
-    private static class DragLocalState {
-        public TextView sourceTextView;
-        public int start, end;
-
-        public DragLocalState(TextView sourceTextView, int start, int end) {
-            this.sourceTextView = sourceTextView;
-            this.start = start;
-            this.end = end;
-        }
-    }
-
     @Override
     public boolean performLongClick() {
         boolean handled = false;
@@ -9234,9 +8191,9 @@
         }
 
         // Long press in empty space moves cursor and shows the Paste affordance if available.
-        if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
-                mInsertionControllerEnabled) {
-            final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
+        if (!handled && mEditor != null && !isPositionOnText(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY) &&
+                getEditor().mInsertionControllerEnabled) {
+            final int offset = getOffsetForPosition(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY);
             stopSelectionActionMode();
             Selection.setSelection((Spannable) mText, offset);
             getInsertionController().showWithActionPopup();
@@ -9244,7 +8201,7 @@
             vibrate = false;
         }
 
-        if (!handled && mSelectionActionMode != null) {
+        if (!handled && (mEditor == null || getEditor().mSelectionActionMode != null)) {
             if (touchPositionIsInSelection()) {
                 // Start a drag
                 final int start = getSelectionStart();
@@ -9271,8 +8228,8 @@
             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
         }
 
-        if (handled) {
-            mDiscardNextActionUp = true;
+        if (handled && mEditor != null) {
+            getEditor().mDiscardNextActionUp = true;
         }
 
         return handled;
@@ -9301,10 +8258,10 @@
     }
 
     private PositionListener getPositionListener() {
-        if (mPositionListener == null) {
-            mPositionListener = new PositionListener();
+        if (getEditor().mPositionListener == null) {
+            getEditor().mPositionListener = new PositionListener();
         }
-        return mPositionListener;
+        return getEditor().mPositionListener;
     }
 
     private interface TextViewPositionListener {
@@ -9312,6 +8269,1420 @@
                 boolean parentPositionChanged, boolean parentScrolled);
     }
 
+    private boolean isPositionVisible(int positionX, int positionY) {
+        synchronized (TEMP_POSITION) {
+            final float[] position = TEMP_POSITION;
+            position[0] = positionX;
+            position[1] = positionY;
+            View view = this;
+
+            while (view != null) {
+                if (view != this) {
+                    // Local scroll is already taken into account in positionX/Y
+                    position[0] -= view.getScrollX();
+                    position[1] -= view.getScrollY();
+                }
+
+                if (position[0] < 0 || position[1] < 0 ||
+                        position[0] > view.getWidth() || position[1] > view.getHeight()) {
+                    return false;
+                }
+
+                if (!view.getMatrix().isIdentity()) {
+                    view.getMatrix().mapPoints(position);
+                }
+
+                position[0] += view.getLeft();
+                position[1] += view.getTop();
+
+                final ViewParent parent = view.getParent();
+                if (parent instanceof View) {
+                    view = (View) parent;
+                } else {
+                    // We've reached the ViewRoot, stop iterating
+                    view = null;
+                }
+            }
+        }
+
+        // We've been able to walk up the view hierarchy and the position was never clipped
+        return true;
+    }
+
+    private boolean isOffsetVisible(int offset) {
+        final int line = mLayout.getLineForOffset(offset);
+        final int lineBottom = mLayout.getLineBottom(line);
+        final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
+        return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
+                lineBottom + viewportToContentVerticalOffset());
+    }
+
+    @Override
+    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
+        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
+        if (mEditor != null && getEditor().mPositionListener != null) {
+            getEditor().mPositionListener.onScrollChanged();
+        }
+    }
+
+    /**
+     * Removes the suggestion spans.
+     */
+    CharSequence removeSuggestionSpans(CharSequence text) {
+       if (text instanceof Spanned) {
+           Spannable spannable;
+           if (text instanceof Spannable) {
+               spannable = (Spannable) text;
+           } else {
+               spannable = new SpannableString(text);
+               text = spannable;
+           }
+
+           SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
+           for (int i = 0; i < spans.length; i++) {
+               spannable.removeSpan(spans[i]);
+           }
+       }
+       return text;
+    }
+
+    void showSuggestions() {
+        if (getEditor().mSuggestionsPopupWindow == null) {
+            getEditor().mSuggestionsPopupWindow = new SuggestionsPopupWindow();
+        }
+        hideControllers();
+        getEditor().mSuggestionsPopupWindow.show();
+    }
+
+    boolean areSuggestionsShown() {
+        return getEditor().mSuggestionsPopupWindow != null && getEditor().mSuggestionsPopupWindow.isShowing();
+    }
+
+    /**
+     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
+     * by the IME or by the spell checker as the user types. This is done by adding
+     * {@link SuggestionSpan}s to the text.
+     *
+     * When suggestions are enabled (default), this list of suggestions will be displayed when the
+     * user asks for them on these parts of the text. This value depends on the inputType of this
+     * TextView.
+     *
+     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
+     *
+     * In addition, the type variation must be one of
+     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
+     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
+     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
+     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
+     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
+     *
+     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
+     *
+     * @return true if the suggestions popup window is enabled, based on the inputType.
+     */
+    public boolean isSuggestionsEnabled() {
+        if (mEditor == null) return false;
+        if ((getEditor().mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
+        if ((getEditor().mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
+
+        final int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
+        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
+                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
+                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
+                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
+                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
+    }
+
+    /**
+     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
+     * selection is initiated in this View.
+     *
+     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
+     * Paste actions, depending on what this View supports.
+     *
+     * A custom implementation can add new entries in the default menu in its
+     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
+     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
+     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
+     * or {@link android.R.id#paste} ids as parameters.
+     *
+     * Returning false from
+     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
+     * the action mode from being started.
+     *
+     * Action click events should be handled by the custom implementation of
+     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
+     *
+     * Note that text selection mode is not started when a TextView receives focus and the
+     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
+     * that case, to allow for quick replacement.
+     */
+    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        createEditorIfNeeded("custom selection action mode set");
+        getEditor().mCustomSelectionActionModeCallback = actionModeCallback;
+    }
+
+    /**
+     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
+     *
+     * @return The current custom selection callback.
+     */
+    public ActionMode.Callback getCustomSelectionActionModeCallback() {
+        return mEditor == null ? null : getEditor().mCustomSelectionActionModeCallback;
+    }
+
+    /**
+     *
+     * @return true if the selection mode was actually started.
+     */
+    private boolean startSelectionActionMode() {
+        if (getEditor().mSelectionActionMode != null) {
+            // Selection action mode is already started
+            return false;
+        }
+
+        if (!canSelectText() || !requestFocus()) {
+            Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
+            return false;
+        }
+
+        if (!hasSelection()) {
+            // There may already be a selection on device rotation
+            if (!selectCurrentWord()) {
+                // No word found under cursor or text selection not permitted.
+                return false;
+            }
+        }
+
+        boolean willExtract = extractedTextModeWillBeStarted();
+
+        // Do not start the action mode when extracted text will show up full screen, which would
+        // immediately hide the newly created action bar and would be visually distracting.
+        if (!willExtract) {
+            ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
+            getEditor().mSelectionActionMode = startActionMode(actionModeCallback);
+        }
+
+        final boolean selectionStarted = getEditor().mSelectionActionMode != null || willExtract;
+        if (selectionStarted && !isTextSelectable()) {
+            // Show the IME to be able to replace text, except when selecting non editable text.
+            final InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null) {
+                imm.showSoftInput(this, 0, null);
+            }
+        }
+
+        return selectionStarted;
+    }
+
+    private boolean extractedTextModeWillBeStarted() {
+        if (!(this instanceof ExtractEditText)) {
+            final InputMethodManager imm = InputMethodManager.peekInstance();
+            return  imm != null && imm.isFullscreenMode();
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    protected void stopSelectionActionMode() {
+        if (getEditor().mSelectionActionMode != null) {
+            // This will hide the mSelectionModifierCursorController
+            getEditor().mSelectionActionMode.finish();
+        }
+    }
+
+    /**
+     * Paste clipboard content between min and max positions.
+     */
+    private void paste(int min, int max) {
+        ClipboardManager clipboard =
+            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+        ClipData clip = clipboard.getPrimaryClip();
+        if (clip != null) {
+            boolean didFirst = false;
+            for (int i=0; i<clip.getItemCount(); i++) {
+                CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
+                if (paste != null) {
+                    if (!didFirst) {
+                        long minMax = prepareSpacesAroundPaste(min, max, paste);
+                        min = extractRangeStartFromLong(minMax);
+                        max = extractRangeEndFromLong(minMax);
+                        Selection.setSelection((Spannable) mText, max);
+                        ((Editable) mText).replace(min, max, paste);
+                        didFirst = true;
+                    } else {
+                        ((Editable) mText).insert(getSelectionEnd(), "\n");
+                        ((Editable) mText).insert(getSelectionEnd(), paste);
+                    }
+                }
+            }
+            stopSelectionActionMode();
+            LAST_CUT_OR_COPY_TIME = 0;
+        }
+    }
+
+    private void setPrimaryClip(ClipData clip) {
+        ClipboardManager clipboard = (ClipboardManager) getContext().
+                getSystemService(Context.CLIPBOARD_SERVICE);
+        clipboard.setPrimaryClip(clip);
+        LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
+    }
+
+    private void hideInsertionPointCursorController() {
+        // No need to create the controller to hide it.
+        if (getEditor().mInsertionPointCursorController != null) {
+            getEditor().mInsertionPointCursorController.hide();
+        }
+    }
+
+    /**
+     * Hides the insertion controller and stops text selection mode, hiding the selection controller
+     */
+    private void hideControllers() {
+        hideCursorControllers();
+        hideSpanControllers();
+    }
+
+    private void hideSpanControllers() {
+        if (mChangeWatcher != null) {
+            mChangeWatcher.hideControllers();
+        }
+    }
+
+    private void hideCursorControllers() {
+        if (getEditor().mSuggestionsPopupWindow != null && !getEditor().mSuggestionsPopupWindow.isShowingUp()) {
+            // Should be done before hide insertion point controller since it triggers a show of it
+            getEditor().mSuggestionsPopupWindow.hide();
+        }
+        hideInsertionPointCursorController();
+        stopSelectionActionMode();
+    }
+
+    /**
+     * Get the character offset closest to the specified absolute position. A typical use case is to
+     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
+     *
+     * @param x The horizontal absolute position of a point on screen
+     * @param y The vertical absolute position of a point on screen
+     * @return the character offset for the character whose position is closest to the specified
+     *  position. Returns -1 if there is no layout.
+     */
+    public int getOffsetForPosition(float x, float y) {
+        if (getLayout() == null) return -1;
+        final int line = getLineAtCoordinate(y);
+        final int offset = getOffsetAtCoordinate(line, x);
+        return offset;
+    }
+
+    private float convertToLocalHorizontalCoordinate(float x) {
+        x -= getTotalPaddingLeft();
+        // Clamp the position to inside of the view.
+        x = Math.max(0.0f, x);
+        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
+        x += getScrollX();
+        return x;
+    }
+
+    private int getLineAtCoordinate(float y) {
+        y -= getTotalPaddingTop();
+        // Clamp the position to inside of the view.
+        y = Math.max(0.0f, y);
+        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
+        y += getScrollY();
+        return getLayout().getLineForVertical((int) y);
+    }
+
+    private int getOffsetAtCoordinate(int line, float x) {
+        x = convertToLocalHorizontalCoordinate(x);
+        return getLayout().getOffsetForHorizontal(line, x);
+    }
+
+    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
+     * in the view. Returns false when the position is in the empty space of left/right of text.
+     */
+    private boolean isPositionOnText(float x, float y) {
+        if (getLayout() == null) return false;
+
+        final int line = getLineAtCoordinate(y);
+        x = convertToLocalHorizontalCoordinate(x);
+
+        if (x < getLayout().getLineLeft(line)) return false;
+        if (x > getLayout().getLineRight(line)) return false;
+        return true;
+    }
+
+    @Override
+    public boolean onDragEvent(DragEvent event) {
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                return hasInsertionController();
+
+            case DragEvent.ACTION_DRAG_ENTERED:
+                TextView.this.requestFocus();
+                return true;
+
+            case DragEvent.ACTION_DRAG_LOCATION:
+                final int offset = getOffsetForPosition(event.getX(), event.getY());
+                Selection.setSelection((Spannable)mText, offset);
+                return true;
+
+            case DragEvent.ACTION_DROP:
+                onDrop(event);
+                return true;
+
+            case DragEvent.ACTION_DRAG_ENDED:
+            case DragEvent.ACTION_DRAG_EXITED:
+            default:
+                return true;
+        }
+    }
+
+    private void onDrop(DragEvent event) {
+        StringBuilder content = new StringBuilder("");
+        ClipData clipData = event.getClipData();
+        final int itemCount = clipData.getItemCount();
+        for (int i=0; i < itemCount; i++) {
+            Item item = clipData.getItemAt(i);
+            content.append(item.coerceToText(TextView.this.mContext));
+        }
+
+        final int offset = getOffsetForPosition(event.getX(), event.getY());
+
+        Object localState = event.getLocalState();
+        DragLocalState dragLocalState = null;
+        if (localState instanceof DragLocalState) {
+            dragLocalState = (DragLocalState) localState;
+        }
+        boolean dragDropIntoItself = dragLocalState != null &&
+                dragLocalState.sourceTextView == this;
+
+        if (dragDropIntoItself) {
+            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
+                // A drop inside the original selection discards the drop.
+                return;
+            }
+        }
+
+        final int originalLength = mText.length();
+        long minMax = prepareSpacesAroundPaste(offset, offset, content);
+        int min = extractRangeStartFromLong(minMax);
+        int max = extractRangeEndFromLong(minMax);
+
+        Selection.setSelection((Spannable) mText, max);
+        replaceText_internal(min, max, content);
+
+        if (dragDropIntoItself) {
+            int dragSourceStart = dragLocalState.start;
+            int dragSourceEnd = dragLocalState.end;
+            if (max <= dragSourceStart) {
+                // Inserting text before selection has shifted positions
+                final int shift = mText.length() - originalLength;
+                dragSourceStart += shift;
+                dragSourceEnd += shift;
+            }
+
+            // Delete original selection
+            deleteText_internal(dragSourceStart, dragSourceEnd);
+
+            // Make sure we do not leave two adjacent spaces.
+            if ((dragSourceStart == 0 ||
+                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
+                    (dragSourceStart == mText.length() ||
+                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
+                final int pos = dragSourceStart == mText.length() ?
+                        dragSourceStart - 1 : dragSourceStart;
+                deleteText_internal(pos, pos + 1);
+            }
+        }
+    }
+
+    /**
+     * @return True if this view supports insertion handles.
+     */
+    boolean hasInsertionController() {
+        return getEditor().mInsertionControllerEnabled;
+    }
+
+    /**
+     * @return True if this view supports selection handles.
+     */
+    boolean hasSelectionController() {
+        return getEditor().mSelectionControllerEnabled;
+    }
+
+    InsertionPointCursorController getInsertionController() {
+        if (!getEditor().mInsertionControllerEnabled) {
+            return null;
+        }
+
+        if (getEditor().mInsertionPointCursorController == null) {
+            getEditor().mInsertionPointCursorController = new InsertionPointCursorController();
+
+            final ViewTreeObserver observer = getViewTreeObserver();
+            observer.addOnTouchModeChangeListener(getEditor().mInsertionPointCursorController);
+        }
+
+        return getEditor().mInsertionPointCursorController;
+    }
+
+    SelectionModifierCursorController getSelectionController() {
+        if (!getEditor().mSelectionControllerEnabled) {
+            return null;
+        }
+
+        if (getEditor().mSelectionModifierCursorController == null) {
+            getEditor().mSelectionModifierCursorController = new SelectionModifierCursorController();
+
+            final ViewTreeObserver observer = getViewTreeObserver();
+            observer.addOnTouchModeChangeListener(getEditor().mSelectionModifierCursorController);
+        }
+
+        return getEditor().mSelectionModifierCursorController;
+    }
+
+    boolean isInBatchEditMode() {
+        if (mEditor == null) return false;
+        final InputMethodState ims = getEditor().mInputMethodState;
+        if (ims != null) {
+            return ims.mBatchEditNesting > 0;
+        }
+        return getEditor().mInBatchEditControllers;
+    }
+
+    @Override
+    public void onResolveTextDirection() {
+        if (hasPasswordTransformationMethod()) {
+            mTextDir = TextDirectionHeuristics.LOCALE;
+            return;
+        }
+
+        // Always need to resolve layout direction first
+        final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
+
+        // Now, we can select the heuristic
+        int textDir = getResolvedTextDirection();
+        switch (textDir) {
+            default:
+            case TEXT_DIRECTION_FIRST_STRONG:
+                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
+                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
+                break;
+            case TEXT_DIRECTION_ANY_RTL:
+                mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
+                break;
+            case TEXT_DIRECTION_LTR:
+                mTextDir = TextDirectionHeuristics.LTR;
+                break;
+            case TEXT_DIRECTION_RTL:
+                mTextDir = TextDirectionHeuristics.RTL;
+                break;
+            case TEXT_DIRECTION_LOCALE:
+                mTextDir = TextDirectionHeuristics.LOCALE;
+                break;
+        }
+    }
+
+    /**
+     * Subclasses will need to override this method to implement their own way of resolving
+     * drawables depending on the layout direction.
+     *
+     * A call to the super method will be required from the subclasses implementation.
+     */
+    protected void resolveDrawables() {
+        // No need to resolve twice
+        if (mResolvedDrawables) {
+            return;
+        }
+        // No drawable to resolve
+        if (mDrawables == null) {
+            return;
+        }
+        // No relative drawable to resolve
+        if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
+            mResolvedDrawables = true;
+            return;
+        }
+
+        Drawables dr = mDrawables;
+        switch(getResolvedLayoutDirection()) {
+            case LAYOUT_DIRECTION_RTL:
+                if (dr.mDrawableStart != null) {
+                    dr.mDrawableRight = dr.mDrawableStart;
+
+                    dr.mDrawableSizeRight = dr.mDrawableSizeStart;
+                    dr.mDrawableHeightRight = dr.mDrawableHeightStart;
+                }
+                if (dr.mDrawableEnd != null) {
+                    dr.mDrawableLeft = dr.mDrawableEnd;
+
+                    dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
+                    dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
+                }
+                break;
+
+            case LAYOUT_DIRECTION_LTR:
+            default:
+                if (dr.mDrawableStart != null) {
+                    dr.mDrawableLeft = dr.mDrawableStart;
+
+                    dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
+                    dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
+                }
+                if (dr.mDrawableEnd != null) {
+                    dr.mDrawableRight = dr.mDrawableEnd;
+
+                    dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
+                    dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
+                }
+                break;
+        }
+        mResolvedDrawables = true;
+    }
+
+    protected void resetResolvedDrawables() {
+        mResolvedDrawables = false;
+    }
+
+    /**
+     * @hide
+     */
+    protected void viewClicked(InputMethodManager imm) {
+        if (imm != null) {
+            imm.viewClicked(this);
+        }
+    }
+
+    /**
+     * Deletes the range of text [start, end[.
+     * @hide
+     */
+    protected void deleteText_internal(int start, int end) {
+        ((Editable) mText).delete(start, end);
+    }
+
+    /**
+     * Replaces the range of text [start, end[ by replacement text
+     * @hide
+     */
+    protected void replaceText_internal(int start, int end, CharSequence text) {
+        ((Editable) mText).replace(start, end, text);
+    }
+
+    /**
+     * Sets a span on the specified range of text
+     * @hide
+     */
+    protected void setSpan_internal(Object span, int start, int end, int flags) {
+        ((Editable) mText).setSpan(span, start, end, flags);
+    }
+
+    /**
+     * Moves the cursor to the specified offset position in text
+     * @hide
+     */
+    protected void setCursorPosition_internal(int start, int end) {
+        Selection.setSelection(((Editable) mText), start, end);
+    }
+
+    /**
+     * An Editor should be created as soon as any of the editable-specific fields (grouped
+     * inside the Editor object) is assigned to a non-default value.
+     * This method will create the Editor if needed.
+     *
+     * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
+     * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
+     * Editor for backward compatibility, as soon as one of these fields is assigned.
+     *
+     * Also note that for performance reasons, the mEditor is created when needed, but not
+     * reset when no more edit-specific fields are needed.
+     */
+    private void createEditorIfNeeded(String reason) {
+        if (mEditor == null) {
+            if (!(this instanceof EditText)) {
+                Log.e(LOG_TAG + " EDITOR", "Creating Editor on TextView. " + reason);
+            }
+            mEditor = new Editor();
+        } else {
+            if (!(this instanceof EditText)) {
+                Log.d(LOG_TAG + " EDITOR", "Redundant Editor creation. " + reason);
+            }
+        }
+    }
+
+    private Editor getEditor() {
+        if (mEditor == null) {
+            //createEditorIfNeeded("Problem: mEditor is not initialized!");
+            Log.e(LOG_TAG, "mEditor not initialized. Please send a bug report to debunne@");
+        }
+        return mEditor;
+    }
+
+    /**
+     * User interface state that is stored by TextView for implementing
+     * {@link View#onSaveInstanceState}.
+     */
+    public static class SavedState extends BaseSavedState {
+        int selStart;
+        int selEnd;
+        CharSequence text;
+        boolean frozenWithFocus;
+        CharSequence error;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(selStart);
+            out.writeInt(selEnd);
+            out.writeInt(frozenWithFocus ? 1 : 0);
+            TextUtils.writeToParcel(text, out, flags);
+
+            if (error == null) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                TextUtils.writeToParcel(error, out, flags);
+            }
+        }
+
+        @Override
+        public String toString() {
+            String str = "TextView.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " start=" + selStart + " end=" + selEnd;
+            if (text != null) {
+                str += " text=" + text;
+            }
+            return str + "}";
+        }
+
+        @SuppressWarnings("hiding")
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+
+        private SavedState(Parcel in) {
+            super(in);
+            selStart = in.readInt();
+            selEnd = in.readInt();
+            frozenWithFocus = (in.readInt() != 0);
+            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+
+            if (in.readInt() != 0) {
+                error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            }
+        }
+    }
+
+    private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
+        private char[] mChars;
+        private int mStart, mLength;
+
+        public CharWrapper(char[] chars, int start, int len) {
+            mChars = chars;
+            mStart = start;
+            mLength = len;
+        }
+
+        /* package */ void set(char[] chars, int start, int len) {
+            mChars = chars;
+            mStart = start;
+            mLength = len;
+        }
+
+        public int length() {
+            return mLength;
+        }
+
+        public char charAt(int off) {
+            return mChars[off + mStart];
+        }
+
+        @Override
+        public String toString() {
+            return new String(mChars, mStart, mLength);
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            if (start < 0 || end < 0 || start > mLength || end > mLength) {
+                throw new IndexOutOfBoundsException(start + ", " + end);
+            }
+
+            return new String(mChars, start + mStart, end - start);
+        }
+
+        public void getChars(int start, int end, char[] buf, int off) {
+            if (start < 0 || end < 0 || start > mLength || end > mLength) {
+                throw new IndexOutOfBoundsException(start + ", " + end);
+            }
+
+            System.arraycopy(mChars, start + mStart, buf, off, end - start);
+        }
+
+        public void drawText(Canvas c, int start, int end,
+                             float x, float y, Paint p) {
+            c.drawText(mChars, start + mStart, end - start, x, y, p);
+        }
+
+        public void drawTextRun(Canvas c, int start, int end,
+                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
+                    contextCount, x, y, flags, p);
+        }
+
+        public float measureText(int start, int end, Paint p) {
+            return p.measureText(mChars, start + mStart, end - start);
+        }
+
+        public int getTextWidths(int start, int end, float[] widths, Paint p) {
+            return p.getTextWidths(mChars, start + mStart, end - start, widths);
+        }
+
+        public float getTextRunAdvances(int start, int end, int contextStart,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
+                Paint p) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunAdvances(mChars, start + mStart, count,
+                    contextStart + mStart, contextCount, flags, advances,
+                    advancesIndex);
+        }
+
+        public float getTextRunAdvances(int start, int end, int contextStart,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
+                Paint p, int reserved) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunAdvances(mChars, start + mStart, count,
+                    contextStart + mStart, contextCount, flags, advances,
+                    advancesIndex, reserved);
+        }
+
+        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
+                int offset, int cursorOpt, Paint p) {
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunCursor(mChars, contextStart + mStart,
+                    contextCount, flags, offset + mStart, cursorOpt);
+        }
+    }
+
+    private static class ErrorPopup extends PopupWindow {
+        private boolean mAbove = false;
+        private final TextView mView;
+        private int mPopupInlineErrorBackgroundId = 0;
+        private int mPopupInlineErrorAboveBackgroundId = 0;
+
+        ErrorPopup(TextView v, int width, int height) {
+            super(v, width, height);
+            mView = v;
+            // Make sure the TextView has a background set as it will be used the first time it is
+            // shown and positionned. Initialized with below background, which should have
+            // dimensions identical to the above version for this to work (and is more likely).
+            mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
+                    com.android.internal.R.styleable.Theme_errorMessageBackground);
+            mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
+        }
+
+        void fixDirection(boolean above) {
+            mAbove = above;
+
+            if (above) {
+                mPopupInlineErrorAboveBackgroundId =
+                    getResourceId(mPopupInlineErrorAboveBackgroundId,
+                            com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
+            } else {
+                mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
+                        com.android.internal.R.styleable.Theme_errorMessageBackground);
+            }
+
+            mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
+                mPopupInlineErrorBackgroundId);
+        }
+
+        private int getResourceId(int currentId, int index) {
+            if (currentId == 0) {
+                TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
+                        R.styleable.Theme);
+                currentId = styledAttributes.getResourceId(index, 0);
+                styledAttributes.recycle();
+            }
+            return currentId;
+        }
+
+        @Override
+        public void update(int x, int y, int w, int h, boolean force) {
+            super.update(x, y, w, h, force);
+
+            boolean above = isAboveAnchor();
+            if (above != mAbove) {
+                fixDirection(above);
+            }
+        }
+    }
+
+    private class CorrectionHighlighter {
+        private final Path mPath = new Path();
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        private int mStart, mEnd;
+        private long mFadingStartTime;
+        private final static int FADE_OUT_DURATION = 400;
+
+        public CorrectionHighlighter() {
+            mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
+            mPaint.setStyle(Paint.Style.FILL);
+        }
+
+        public void highlight(CorrectionInfo info) {
+            mStart = info.getOffset();
+            mEnd = mStart + info.getNewText().length();
+            mFadingStartTime = SystemClock.uptimeMillis();
+
+            if (mStart < 0 || mEnd < 0) {
+                stopAnimation();
+            }
+        }
+
+        public void draw(Canvas canvas, int cursorOffsetVertical) {
+            if (updatePath() && updatePaint()) {
+                if (cursorOffsetVertical != 0) {
+                    canvas.translate(0, cursorOffsetVertical);
+                }
+
+                canvas.drawPath(mPath, mPaint);
+
+                if (cursorOffsetVertical != 0) {
+                    canvas.translate(0, -cursorOffsetVertical);
+                }
+                invalidate(true); // TODO invalidate cursor region only
+            } else {
+                stopAnimation();
+                invalidate(false); // TODO invalidate cursor region only
+            }
+        }
+
+        private boolean updatePaint() {
+            final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
+            if (duration > FADE_OUT_DURATION) return false;
+
+            final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
+            final int highlightColorAlpha = Color.alpha(mHighlightColor);
+            final int color = (mHighlightColor & 0x00FFFFFF) +
+                    ((int) (highlightColorAlpha * coef) << 24);
+            mPaint.setColor(color);
+            return true;
+        }
+
+        private boolean updatePath() {
+            final Layout layout = TextView.this.mLayout;
+            if (layout == null) return false;
+
+            // Update in case text is edited while the animation is run
+            final int length = mText.length();
+            int start = Math.min(length, mStart);
+            int end = Math.min(length, mEnd);
+
+            mPath.reset();
+            TextView.this.mLayout.getSelectionPath(start, end, mPath);
+            return true;
+        }
+
+        private void invalidate(boolean delayed) {
+            if (TextView.this.mLayout == null) return;
+
+            synchronized (TEMP_RECTF) {
+                mPath.computeBounds(TEMP_RECTF, false);
+
+                int left = getCompoundPaddingLeft();
+                int top = getExtendedPaddingTop() + getVerticalOffset(true);
+
+                if (delayed) {
+                    TextView.this.postInvalidateDelayed(16, // 60 Hz update
+                            left + (int) TEMP_RECTF.left, top + (int) TEMP_RECTF.top,
+                            left + (int) TEMP_RECTF.right, top + (int) TEMP_RECTF.bottom);
+                } else {
+                    TextView.this.postInvalidate((int) TEMP_RECTF.left, (int) TEMP_RECTF.top,
+                            (int) TEMP_RECTF.right, (int) TEMP_RECTF.bottom);
+                }
+            }
+        }
+
+        private void stopAnimation() {
+            TextView.this.getEditor().mCorrectionHighlighter = null;
+        }
+    }
+
+    private static final class Marquee extends Handler {
+        // TODO: Add an option to configure this
+        private static final float MARQUEE_DELTA_MAX = 0.07f;
+        private static final int MARQUEE_DELAY = 1200;
+        private static final int MARQUEE_RESTART_DELAY = 1200;
+        private static final int MARQUEE_RESOLUTION = 1000 / 30;
+        private static final int MARQUEE_PIXELS_PER_SECOND = 30;
+
+        private static final byte MARQUEE_STOPPED = 0x0;
+        private static final byte MARQUEE_STARTING = 0x1;
+        private static final byte MARQUEE_RUNNING = 0x2;
+
+        private static final int MESSAGE_START = 0x1;
+        private static final int MESSAGE_TICK = 0x2;
+        private static final int MESSAGE_RESTART = 0x3;
+
+        private final WeakReference<TextView> mView;
+
+        private byte mStatus = MARQUEE_STOPPED;
+        private final float mScrollUnit;
+        private float mMaxScroll;
+        float mMaxFadeScroll;
+        private float mGhostStart;
+        private float mGhostOffset;
+        private float mFadeStop;
+        private int mRepeatLimit;
+
+        float mScroll;
+
+        Marquee(TextView v) {
+            final float density = v.getContext().getResources().getDisplayMetrics().density;
+            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
+            mView = new WeakReference<TextView>(v);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_START:
+                    mStatus = MARQUEE_RUNNING;
+                    tick();
+                    break;
+                case MESSAGE_TICK:
+                    tick();
+                    break;
+                case MESSAGE_RESTART:
+                    if (mStatus == MARQUEE_RUNNING) {
+                        if (mRepeatLimit >= 0) {
+                            mRepeatLimit--;
+                        }
+                        start(mRepeatLimit);
+                    }
+                    break;
+            }
+        }
+
+        void tick() {
+            if (mStatus != MARQUEE_RUNNING) {
+                return;
+            }
+
+            removeMessages(MESSAGE_TICK);
+
+            final TextView textView = mView.get();
+            if (textView != null && (textView.isFocused() || textView.isSelected())) {
+                mScroll += mScrollUnit;
+                if (mScroll > mMaxScroll) {
+                    mScroll = mMaxScroll;
+                    sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
+                } else {
+                    sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
+                }
+                textView.invalidate();
+            }
+        }
+
+        void stop() {
+            mStatus = MARQUEE_STOPPED;
+            removeMessages(MESSAGE_START);
+            removeMessages(MESSAGE_RESTART);
+            removeMessages(MESSAGE_TICK);
+            resetScroll();
+        }
+
+        private void resetScroll() {
+            mScroll = 0.0f;
+            final TextView textView = mView.get();
+            if (textView != null) textView.invalidate();
+        }
+
+        void start(int repeatLimit) {
+            if (repeatLimit == 0) {
+                stop();
+                return;
+            }
+            mRepeatLimit = repeatLimit;
+            final TextView textView = mView.get();
+            if (textView != null && textView.mLayout != null) {
+                mStatus = MARQUEE_STARTING;
+                mScroll = 0.0f;
+                final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
+                        textView.getCompoundPaddingRight();
+                final float lineWidth = textView.mLayout.getLineWidth(0);
+                final float gap = textWidth / 3.0f;
+                mGhostStart = lineWidth - textWidth + gap;
+                mMaxScroll = mGhostStart + textWidth;
+                mGhostOffset = lineWidth + gap;
+                mFadeStop = lineWidth + textWidth / 6.0f;
+                mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
+
+                textView.invalidate();
+                sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
+            }
+        }
+
+        float getGhostOffset() {
+            return mGhostOffset;
+        }
+
+        boolean shouldDrawLeftFade() {
+            return mScroll <= mFadeStop;
+        }
+
+        boolean shouldDrawGhost() {
+            return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
+        }
+
+        boolean isRunning() {
+            return mStatus == MARQUEE_RUNNING;
+        }
+
+        boolean isStopped() {
+            return mStatus == MARQUEE_STOPPED;
+        }
+    }
+
+    /**
+     * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
+     * pop-up should be displayed.
+     */
+    private class EasyEditSpanController {
+
+        private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
+
+        private EasyEditPopupWindow mPopupWindow;
+
+        private EasyEditSpan mEasyEditSpan;
+
+        private Runnable mHidePopup;
+
+        private void hide() {
+            if (mPopupWindow != null) {
+                mPopupWindow.hide();
+                TextView.this.removeCallbacks(mHidePopup);
+            }
+            removeSpans(mText);
+            mEasyEditSpan = null;
+        }
+
+        /**
+         * Monitors the changes in the text.
+         *
+         * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
+         * as the notifications are not sent when a spannable (with spans) is inserted.
+         */
+        public void onTextChange(CharSequence buffer) {
+            adjustSpans(mText);
+
+            if (getWindowVisibility() != View.VISIBLE) {
+                // The window is not visible yet, ignore the text change.
+                return;
+            }
+
+            if (mLayout == null) {
+                // The view has not been layout yet, ignore the text change
+                return;
+            }
+
+            InputMethodManager imm = InputMethodManager.peekInstance();
+            if (!(TextView.this instanceof ExtractEditText)
+                    && imm != null && imm.isFullscreenMode()) {
+                // The input is in extract mode. We do not have to handle the easy edit in the
+                // original TextView, as the ExtractEditText will do
+                return;
+            }
+
+            // Remove the current easy edit span, as the text changed, and remove the pop-up
+            // (if any)
+            if (mEasyEditSpan != null) {
+                if (mText instanceof Spannable) {
+                    ((Spannable) mText).removeSpan(mEasyEditSpan);
+                }
+                mEasyEditSpan = null;
+            }
+            if (mPopupWindow != null && mPopupWindow.isShowing()) {
+                mPopupWindow.hide();
+            }
+
+            // Display the new easy edit span (if any).
+            if (buffer instanceof Spanned) {
+                mEasyEditSpan = getSpan((Spanned) buffer);
+                if (mEasyEditSpan != null) {
+                    if (mPopupWindow == null) {
+                        mPopupWindow = new EasyEditPopupWindow();
+                        mHidePopup = new Runnable() {
+                            @Override
+                            public void run() {
+                                hide();
+                            }
+                        };
+                    }
+                    mPopupWindow.show(mEasyEditSpan);
+                    TextView.this.removeCallbacks(mHidePopup);
+                    TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
+                }
+            }
+        }
+
+        /**
+         * Adjusts the spans by removing all of them except the last one.
+         */
+        private void adjustSpans(CharSequence buffer) {
+            // This method enforces that only one easy edit span is attached to the text.
+            // A better way to enforce this would be to listen for onSpanAdded, but this method
+            // cannot be used in this scenario as no notification is triggered when a text with
+            // spans is inserted into a text.
+            if (buffer instanceof Spannable) {
+                Spannable spannable = (Spannable) buffer;
+                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
+                        EasyEditSpan.class);
+                for (int i = 0; i < spans.length - 1; i++) {
+                    spannable.removeSpan(spans[i]);
+                }
+            }
+        }
+
+        /**
+         * Removes all the {@link EasyEditSpan} currently attached.
+         */
+        private void removeSpans(CharSequence buffer) {
+            if (buffer instanceof Spannable) {
+                Spannable spannable = (Spannable) buffer;
+                EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
+                        EasyEditSpan.class);
+                for (int i = 0; i < spans.length; i++) {
+                    spannable.removeSpan(spans[i]);
+                }
+            }
+        }
+
+        private EasyEditSpan getSpan(Spanned spanned) {
+            EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
+                    EasyEditSpan.class);
+            if (easyEditSpans.length == 0) {
+                return null;
+            } else {
+                return easyEditSpans[0];
+            }
+        }
+    }
+
+    /**
+     * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
+     * by {@link EasyEditSpanController}.
+     */
+    private class EasyEditPopupWindow extends PinnedPopupWindow
+            implements OnClickListener {
+        private static final int POPUP_TEXT_LAYOUT =
+                com.android.internal.R.layout.text_edit_action_popup_text;
+        private TextView mDeleteTextView;
+        private EasyEditSpan mEasyEditSpan;
+
+        @Override
+        protected void createPopupWindow() {
+            mPopupWindow = new PopupWindow(TextView.this.mContext, null,
+                    com.android.internal.R.attr.textSelectHandleWindowStyle);
+            mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+            mPopupWindow.setClippingEnabled(true);
+        }
+
+        @Override
+        protected void initContentView() {
+            LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
+            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+            mContentView = linearLayout;
+            mContentView.setBackgroundResource(
+                    com.android.internal.R.drawable.text_edit_side_paste_window);
+
+            LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
+                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+            LayoutParams wrapContent = new LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+            mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
+            mDeleteTextView.setLayoutParams(wrapContent);
+            mDeleteTextView.setText(com.android.internal.R.string.delete);
+            mDeleteTextView.setOnClickListener(this);
+            mContentView.addView(mDeleteTextView);
+        }
+
+        public void show(EasyEditSpan easyEditSpan) {
+            mEasyEditSpan = easyEditSpan;
+            super.show();
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (view == mDeleteTextView) {
+                Editable editable = (Editable) mText;
+                int start = editable.getSpanStart(mEasyEditSpan);
+                int end = editable.getSpanEnd(mEasyEditSpan);
+                if (start >= 0 && end >= 0) {
+                    deleteText_internal(start, end);
+                }
+            }
+        }
+
+        @Override
+        protected int getTextOffset() {
+            // Place the pop-up at the end of the span
+            Editable editable = (Editable) mText;
+            return editable.getSpanEnd(mEasyEditSpan);
+        }
+
+        @Override
+        protected int getVerticalLocalPosition(int line) {
+            return mLayout.getLineBottom(line);
+        }
+
+        @Override
+        protected int clipVertically(int positionY) {
+            // As we display the pop-up below the span, no vertical clipping is required.
+            return positionY;
+        }
+    }
+
+    private class ChangeWatcher implements TextWatcher, SpanWatcher {
+
+        private CharSequence mBeforeText;
+
+        private EasyEditSpanController mEasyEditSpanController;
+
+        private ChangeWatcher() {
+            mEasyEditSpanController = new EasyEditSpanController();
+        }
+
+        public void beforeTextChanged(CharSequence buffer, int start,
+                                      int before, int after) {
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
+                    + " before=" + before + " after=" + after + ": " + buffer);
+
+            if (AccessibilityManager.getInstance(mContext).isEnabled()
+                    && !isPasswordInputType(getInputType())
+                    && !hasPasswordTransformationMethod()) {
+                mBeforeText = buffer.toString();
+            }
+
+            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
+        }
+
+        public void onTextChanged(CharSequence buffer, int start,
+                                  int before, int after) {
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
+                    + " before=" + before + " after=" + after + ": " + buffer);
+            TextView.this.handleTextChanged(buffer, start, before, after);
+
+            mEasyEditSpanController.onTextChange(buffer);
+
+            if (AccessibilityManager.getInstance(mContext).isEnabled() &&
+                    (isFocused() || isSelected() && isShown())) {
+                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
+                mBeforeText = null;
+            }
+        }
+
+        public void afterTextChanged(Editable buffer) {
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
+            TextView.this.sendAfterTextChanged(buffer);
+
+            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
+                MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
+            }
+        }
+
+        public void onSpanChanged(Spannable buf,
+                                  Object what, int s, int e, int st, int en) {
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
+                    + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
+            TextView.this.spanChange(buf, what, s, st, e, en);
+        }
+
+        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
+                    + " what=" + what + ": " + buf);
+            TextView.this.spanChange(buf, what, -1, s, -1, e);
+        }
+
+        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
+                    + " what=" + what + ": " + buf);
+            TextView.this.spanChange(buf, what, s, -1, e, -1);
+        }
+
+        private void hideControllers() {
+            mEasyEditSpanController.hide();
+        }
+    }
+
+    private static class Blink extends Handler implements Runnable {
+        private final WeakReference<TextView> mView;
+        private boolean mCancelled;
+
+        public Blink(TextView v) {
+            mView = new WeakReference<TextView>(v);
+        }
+
+        public void run() {
+            if (mCancelled) {
+                return;
+            }
+
+            removeCallbacks(Blink.this);
+
+            TextView tv = mView.get();
+
+            if (tv != null && tv.shouldBlink()) {
+                if (tv.mLayout != null) {
+                    tv.invalidateCursorPath();
+                }
+
+                postAtTime(this, SystemClock.uptimeMillis() + BLINK);
+            }
+        }
+
+        void cancel() {
+            if (!mCancelled) {
+                removeCallbacks(Blink.this);
+                mCancelled = true;
+            }
+        }
+
+        void uncancel() {
+            mCancelled = false;
+        }
+    }
+
+    private static class DragLocalState {
+        public TextView sourceTextView;
+        public int start, end;
+
+        public DragLocalState(TextView sourceTextView, int start, int end) {
+            this.sourceTextView = sourceTextView;
+            this.start = start;
+            this.end = end;
+        }
+    }
+    
     private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
         // 3 handles
         // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
@@ -9324,6 +9695,7 @@
         private int mPositionX, mPositionY;
         private int mNumberOfListeners;
         private boolean mScrollHasChanged;
+        final int[] mTempCoords = new int[2];
 
         public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
             if (mNumberOfListeners == 0) {
@@ -9402,62 +9774,6 @@
         }
     }
 
-    private boolean isPositionVisible(int positionX, int positionY) {
-        synchronized (sTmpPosition) {
-            final float[] position = sTmpPosition;
-            position[0] = positionX;
-            position[1] = positionY;
-            View view = this;
-
-            while (view != null) {
-                if (view != this) {
-                    // Local scroll is already taken into account in positionX/Y
-                    position[0] -= view.getScrollX();
-                    position[1] -= view.getScrollY();
-                }
-
-                if (position[0] < 0 || position[1] < 0 ||
-                        position[0] > view.getWidth() || position[1] > view.getHeight()) {
-                    return false;
-                }
-
-                if (!view.getMatrix().isIdentity()) {
-                    view.getMatrix().mapPoints(position);
-                }
-
-                position[0] += view.getLeft();
-                position[1] += view.getTop();
-
-                final ViewParent parent = view.getParent();
-                if (parent instanceof View) {
-                    view = (View) parent;
-                } else {
-                    // We've reached the ViewRoot, stop iterating
-                    view = null;
-                }
-            }
-        }
-
-        // We've been able to walk up the view hierarchy and the position was never clipped
-        return true;
-    }
-
-    private boolean isOffsetVisible(int offset) {
-        final int line = mLayout.getLineForOffset(offset);
-        final int lineBottom = mLayout.getLineBottom(line);
-        final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
-        return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
-                lineBottom + viewportToContentVerticalOffset());
-    }
-
-    @Override
-    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
-        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
-        if (mPositionListener != null) {
-            mPositionListener.onScrollChanged();
-        }
-    }
-
     private abstract class PinnedPopupWindow implements TextViewPositionListener {
         protected PopupWindow mPopupWindow;
         protected ViewGroup mContentView;
@@ -9585,7 +9901,7 @@
                 TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
 
                 // Safe cast since show() checks that mText is an Editable
-                ((Spannable) mText).removeSpan(mSuggestionRangeSpan);
+                ((Spannable) mText).removeSpan(getEditor().mSuggestionRangeSpan);
 
                 setCursorVisible(mCursorWasVisibleBeforeSuggestions);
                 if (hasInsertionController()) {
@@ -9595,7 +9911,7 @@
         }
 
         public SuggestionsPopupWindow() {
-            mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+            mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
             mSuggestionSpanComparator = new SuggestionSpanComparator();
             mSpansLengths = new HashMap<SuggestionSpan, Integer>();
         }
@@ -9733,7 +10049,7 @@
             if (!(mText instanceof Editable)) return;
 
             if (updateSuggestions()) {
-                mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+                mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
                 setCursorVisible(false);
                 mIsShowingUp = true;
                 super.show();
@@ -9888,17 +10204,17 @@
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             mNumberOfSuggestions++;
 
-            if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
+            if (getEditor().mSuggestionRangeSpan == null) getEditor().mSuggestionRangeSpan = new SuggestionRangeSpan();
             if (underlineColor == 0) {
                 // Fallback on the default highlight color when the first span does not provide one
-                mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
+                getEditor().mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
             } else {
                 final float BACKGROUND_TRANSPARENCY = 0.4f;
                 final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
-                mSuggestionRangeSpan.setBackgroundColor(
+                getEditor().mSuggestionRangeSpan.setBackgroundColor(
                         (underlineColor & 0x00FFFFFF) + (newAlpha << 24));
             }
-            spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
+            spannable.setSpan(getEditor().mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
             mSuggestionsAdapter.notifyDataSetChanged();
@@ -9931,8 +10247,8 @@
             SuggestionInfo suggestionInfo = mSuggestionInfos[position];
 
             if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
-                final int spanUnionStart = editable.getSpanStart(mSuggestionRangeSpan);
-                int spanUnionEnd = editable.getSpanEnd(mSuggestionRangeSpan);
+                final int spanUnionStart = editable.getSpanStart(getEditor().mSuggestionRangeSpan);
+                int spanUnionEnd = editable.getSpanEnd(getEditor().mSuggestionRangeSpan);
                 if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) {
                     // Do not leave two adjacent spaces after deletion, or one at beginning of text
                     if (spanUnionEnd < editable.length() &&
@@ -10032,209 +10348,6 @@
     }
 
     /**
-     * Removes the suggestion spans.
-     */
-    CharSequence removeSuggestionSpans(CharSequence text) {
-       if (text instanceof Spanned) {
-           Spannable spannable;
-           if (text instanceof Spannable) {
-               spannable = (Spannable) text;
-           } else {
-               spannable = new SpannableString(text);
-               text = spannable;
-           }
-
-           SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
-           for (int i = 0; i < spans.length; i++) {
-               spannable.removeSpan(spans[i]);
-           }
-       }
-       return text;
-    }
-
-    void showSuggestions() {
-        if (mSuggestionsPopupWindow == null) {
-            mSuggestionsPopupWindow = new SuggestionsPopupWindow();
-        }
-        hideControllers();
-        mSuggestionsPopupWindow.show();
-    }
-
-    boolean areSuggestionsShown() {
-        return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
-    }
-
-    /**
-     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
-     * by the IME or by the spell checker as the user types. This is done by adding
-     * {@link SuggestionSpan}s to the text.
-     *
-     * When suggestions are enabled (default), this list of suggestions will be displayed when the
-     * user asks for them on these parts of the text. This value depends on the inputType of this
-     * TextView.
-     *
-     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
-     *
-     * In addition, the type variation must be one of
-     * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
-     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
-     * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
-     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
-     * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
-     *
-     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
-     *
-     * @return true if the suggestions popup window is enabled, based on the inputType.
-     */
-    public boolean isSuggestionsEnabled() {
-        if ((mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
-        if ((mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
-
-        final int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
-        return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
-                variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
-                variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
-                variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
-                variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
-    }
-
-    /**
-     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
-     * selection is initiated in this View.
-     *
-     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
-     * Paste actions, depending on what this View supports.
-     *
-     * A custom implementation can add new entries in the default menu in its
-     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
-     * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
-     * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
-     * or {@link android.R.id#paste} ids as parameters.
-     *
-     * Returning false from 
-     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
-     * the action mode from being started.
-     *
-     * Action click events should be handled by the custom implementation of
-     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
-     *
-     * Note that text selection mode is not started when a TextView receives focus and the
-     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
-     * that case, to allow for quick replacement.
-     */
-    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
-        mCustomSelectionActionModeCallback = actionModeCallback;
-    }
-
-    /**
-     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
-     *
-     * @return The current custom selection callback.
-     */
-    public ActionMode.Callback getCustomSelectionActionModeCallback() {
-        return mCustomSelectionActionModeCallback;
-    }
-
-    /**
-     *
-     * @return true if the selection mode was actually started.
-     */
-    private boolean startSelectionActionMode() {
-        if (mSelectionActionMode != null) {
-            // Selection action mode is already started
-            return false;
-        }
-
-        if (!canSelectText() || !requestFocus()) {
-            Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
-            return false;
-        }
-
-        if (!hasSelection()) {
-            // There may already be a selection on device rotation
-            if (!selectCurrentWord()) {
-                // No word found under cursor or text selection not permitted.
-                return false;
-            }
-        }
-
-        boolean willExtract = extractedTextModeWillBeStarted();
-
-        // Do not start the action mode when extracted text will show up full screen, which would
-        // immediately hide the newly created action bar and would be visually distracting.
-        if (!willExtract) {
-            ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
-            mSelectionActionMode = startActionMode(actionModeCallback);
-        }
-
-        final boolean selectionStarted = mSelectionActionMode != null || willExtract;
-        if (selectionStarted && !mTextIsSelectable) {
-            // Show the IME to be able to replace text, except when selecting non editable text.
-            final InputMethodManager imm = InputMethodManager.peekInstance();
-            if (imm != null) {
-                imm.showSoftInput(this, 0, null);
-            }
-        }
-
-        return selectionStarted;
-    }
-
-    private boolean extractedTextModeWillBeStarted() {
-        if (!(this instanceof ExtractEditText)) {
-            final InputMethodManager imm = InputMethodManager.peekInstance();
-            return  imm != null && imm.isFullscreenMode();
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    protected void stopSelectionActionMode() {
-        if (mSelectionActionMode != null) {
-            // This will hide the mSelectionModifierCursorController
-            mSelectionActionMode.finish();
-        }
-    }
-
-    /**
-     * Paste clipboard content between min and max positions.
-     */
-    private void paste(int min, int max) {
-        ClipboardManager clipboard =
-            (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
-        ClipData clip = clipboard.getPrimaryClip();
-        if (clip != null) {
-            boolean didFirst = false;
-            for (int i=0; i<clip.getItemCount(); i++) {
-                CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
-                if (paste != null) {
-                    if (!didFirst) {
-                        long minMax = prepareSpacesAroundPaste(min, max, paste);
-                        min = extractRangeStartFromLong(minMax);
-                        max = extractRangeEndFromLong(minMax);
-                        Selection.setSelection((Spannable) mText, max);
-                        ((Editable) mText).replace(min, max, paste);
-                        didFirst = true;
-                    } else {
-                        ((Editable) mText).insert(getSelectionEnd(), "\n");
-                        ((Editable) mText).insert(getSelectionEnd(), paste);
-                    }
-                }
-            }
-            stopSelectionActionMode();
-            sLastCutOrCopyTime = 0;
-        }
-    }
-
-    private void setPrimaryClip(ClipData clip) {
-        ClipboardManager clipboard = (ClipboardManager) getContext().
-                getSystemService(Context.CLIPBOARD_SERVICE);
-        clipboard.setPrimaryClip(clip);
-        sLastCutOrCopyTime = SystemClock.uptimeMillis();
-    }
-
-    /**
      * An ActionMode Callback class that is used to provide actions while in text selection mode.
      *
      * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
@@ -10296,8 +10409,8 @@
 
             styledAttributes.recycle();
 
-            if (mCustomSelectionActionModeCallback != null) {
-                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
+            if (getEditor().mCustomSelectionActionModeCallback != null) {
+                if (!getEditor().mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
                     // The custom mode can choose to cancel the action mode
                     return false;
                 }
@@ -10313,16 +10426,16 @@
 
         @Override
         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-            if (mCustomSelectionActionModeCallback != null) {
-                return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
+            if (getEditor().mCustomSelectionActionModeCallback != null) {
+                return getEditor().mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
             }
             return true;
         }
 
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-            if (mCustomSelectionActionModeCallback != null &&
-                 mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
+            if (getEditor().mCustomSelectionActionModeCallback != null &&
+                 getEditor().mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
                 return true;
             }
             return onTextContextMenuItem(item.getItemId());
@@ -10330,16 +10443,16 @@
 
         @Override
         public void onDestroyActionMode(ActionMode mode) {
-            if (mCustomSelectionActionModeCallback != null) {
-                mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
+            if (getEditor().mCustomSelectionActionModeCallback != null) {
+                getEditor().mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
             }
             Selection.setSelection((Spannable) mText, getSelectionEnd());
 
-            if (mSelectionModifierCursorController != null) {
-                mSelectionModifierCursorController.hide();
+            if (getEditor().mSelectionModifierCursorController != null) {
+                getEditor().mSelectionModifierCursorController.hide();
             }
 
-            mSelectionActionMode = null;
+            getEditor().mSelectionActionMode = null;
         }
     }
 
@@ -10353,7 +10466,7 @@
         protected void createPopupWindow() {
             mPopupWindow = new PopupWindow(TextView.this.mContext, null,
                     com.android.internal.R.attr.textSelectHandleWindowStyle);
-            mPopupWindow.setClippingEnabled(true);
+            mPopupWindow.setClippingEnabled(true);   
         }
 
         @Override
@@ -10753,7 +10866,7 @@
         public void show() {
             super.show();
 
-            final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
+            final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - LAST_CUT_OR_COPY_TIME;
             if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
                 showActionPopupWindow(0);
             }
@@ -10767,13 +10880,14 @@
         }
 
         private void hideAfterDelay() {
-            removeHiderCallback();
             if (mHider == null) {
                 mHider = new Runnable() {
                     public void run() {
                         hide();
                     }
                 };
+            } else {
+                removeHiderCallback();
             }
             TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT);
         }
@@ -10994,12 +11108,12 @@
         }
 
         private InsertionHandleView getHandle() {
-            if (mSelectHandleCenter == null) {
-                mSelectHandleCenter = mContext.getResources().getDrawable(
+            if (getEditor().mSelectHandleCenter == null) {
+                getEditor().mSelectHandleCenter = mContext.getResources().getDrawable(
                         mTextSelectHandleRes);
             }
             if (mHandle == null) {
-                mHandle = new InsertionHandleView(mSelectHandleCenter);
+                mHandle = new InsertionHandleView(getEditor().mSelectHandleCenter);
             }
             return mHandle;
         }
@@ -11040,12 +11154,12 @@
         }
 
         private void initDrawables() {
-            if (mSelectHandleLeft == null) {
-                mSelectHandleLeft = mContext.getResources().getDrawable(
+            if (getEditor().mSelectHandleLeft == null) {
+                getEditor().mSelectHandleLeft = mContext.getResources().getDrawable(
                         mTextSelectHandleLeftRes);
             }
-            if (mSelectHandleRight == null) {
-                mSelectHandleRight = mContext.getResources().getDrawable(
+            if (getEditor().mSelectHandleRight == null) {
+                getEditor().mSelectHandleRight = mContext.getResources().getDrawable(
                         mTextSelectHandleRightRes);
             }
         }
@@ -11053,10 +11167,10 @@
         private void initHandles() {
             // Lazy object creation has to be done before updatePosition() is called.
             if (mStartHandle == null) {
-                mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight);
+                mStartHandle = new SelectionStartHandleView(getEditor().mSelectHandleLeft, getEditor().mSelectHandleRight);
             }
             if (mEndHandle == null) {
-                mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft);
+                mEndHandle = new SelectionEndHandleView(getEditor().mSelectHandleRight, getEditor().mSelectHandleLeft);
             }
 
             mStartHandle.show();
@@ -11101,7 +11215,7 @@
 
                             if (stayedInArea && isPositionOnText(x, y)) {
                                 startSelectionActionMode();
-                                mDiscardNextActionUp = true;
+                                getEditor().mDiscardNextActionUp = true;
                             }
                         }
                     }
@@ -11190,461 +11304,501 @@
         }
     }
 
-    private void hideInsertionPointCursorController() {
-        // No need to create the controller to hide it.
-        if (mInsertionPointCursorController != null) {
-            mInsertionPointCursorController.hide();
-        }
+    static class InputContentType {
+        int imeOptions = EditorInfo.IME_NULL;
+        String privateImeOptions;
+        CharSequence imeActionLabel;
+        int imeActionId;
+        Bundle extras;
+        OnEditorActionListener onEditorActionListener;
+        boolean enterDown;
     }
 
-    /**
-     * Hides the insertion controller and stops text selection mode, hiding the selection controller
-     */
-    private void hideControllers() {
-        hideCursorControllers();
-        hideSpanControllers();
+    static class InputMethodState {
+        Rect mCursorRectInWindow = new Rect();
+        RectF mTmpRectF = new RectF();
+        float[] mTmpOffset = new float[2];
+        ExtractedTextRequest mExtracting;
+        final ExtractedText mTmpExtracted = new ExtractedText();
+        int mBatchEditNesting;
+        boolean mCursorChanged;
+        boolean mSelectionModeChanged;
+        boolean mContentChanged;
+        int mChangedStart, mChangedEnd, mChangedDelta;
     }
 
-    private void hideSpanControllers() {
-        if (mChangeWatcher != null) {
-            mChangeWatcher.hideControllers();
-        }
-    }
-
-    private void hideCursorControllers() {
-        if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) {
-            // Should be done before hide insertion point controller since it triggers a show of it
-            mSuggestionsPopupWindow.hide();
-        }
-        hideInsertionPointCursorController();
-        stopSelectionActionMode();
-    }
-
-    /**
-     * Get the character offset closest to the specified absolute position. A typical use case is to
-     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
-     *
-     * @param x The horizontal absolute position of a point on screen
-     * @param y The vertical absolute position of a point on screen
-     * @return the character offset for the character whose position is closest to the specified
-     *  position. Returns -1 if there is no layout.
-     */
-    public int getOffsetForPosition(float x, float y) {
-        if (getLayout() == null) return -1;
-        final int line = getLineAtCoordinate(y);
-        final int offset = getOffsetAtCoordinate(line, x);
-        return offset;
-    }
-
-    private float convertToLocalHorizontalCoordinate(float x) {
-        x -= getTotalPaddingLeft();
-        // Clamp the position to inside of the view.
-        x = Math.max(0.0f, x);
-        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
-        x += getScrollX();
-        return x;
-    }
-
-    private int getLineAtCoordinate(float y) {
-        y -= getTotalPaddingTop();
-        // Clamp the position to inside of the view.
-        y = Math.max(0.0f, y);
-        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
-        y += getScrollY();
-        return getLayout().getLineForVertical((int) y);
-    }
-
-    private int getOffsetAtCoordinate(int line, float x) {
-        x = convertToLocalHorizontalCoordinate(x);
-        return getLayout().getOffsetForHorizontal(line, x);
-    }
-
-    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
-     * in the view. Returns false when the position is in the empty space of left/right of text.
-     */
-    private boolean isPositionOnText(float x, float y) {
-        if (getLayout() == null) return false;
-
-        final int line = getLineAtCoordinate(y);
-        x = convertToLocalHorizontalCoordinate(x);
-
-        if (x < getLayout().getLineLeft(line)) return false;
-        if (x > getLayout().getLineRight(line)) return false;
-        return true;
-    }
-
-    @Override
-    public boolean onDragEvent(DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                return hasInsertionController();
-
-            case DragEvent.ACTION_DRAG_ENTERED:
-                TextView.this.requestFocus();
-                return true;
-
-            case DragEvent.ACTION_DRAG_LOCATION:
-                final int offset = getOffsetForPosition(event.getX(), event.getY());
-                Selection.setSelection((Spannable)mText, offset);
-                return true;
-
-            case DragEvent.ACTION_DROP:
-                onDrop(event);
-                return true;
-
-            case DragEvent.ACTION_DRAG_ENDED:
-            case DragEvent.ACTION_DRAG_EXITED:
-            default:
-                return true;
-        }
-    }
-
-    private void onDrop(DragEvent event) {
-        StringBuilder content = new StringBuilder("");
-        ClipData clipData = event.getClipData();
-        final int itemCount = clipData.getItemCount();
-        for (int i=0; i < itemCount; i++) {
-            Item item = clipData.getItemAt(i);
-            content.append(item.coerceToText(TextView.this.mContext));
+    private class Editor {
+        Editor() {
+            mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            final CompatibilityInfo compat = TextView.this.getResources().getCompatibilityInfo();
+            mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
         }
 
-        final int offset = getOffsetForPosition(event.getX(), event.getY());
+        // Cursor Controllers.
+        InsertionPointCursorController mInsertionPointCursorController;
+        SelectionModifierCursorController mSelectionModifierCursorController;
+        ActionMode mSelectionActionMode;
+        boolean mInsertionControllerEnabled;
+        boolean mSelectionControllerEnabled;
 
-        Object localState = event.getLocalState();
-        DragLocalState dragLocalState = null;
-        if (localState instanceof DragLocalState) {
-            dragLocalState = (DragLocalState) localState;
-        }
-        boolean dragDropIntoItself = dragLocalState != null &&
-                dragLocalState.sourceTextView == this;
+        // Used to highlight a word when it is corrected by the IME
+        CorrectionHighlighter mCorrectionHighlighter;
 
-        if (dragDropIntoItself) {
-            if (offset >= dragLocalState.start && offset < dragLocalState.end) {
-                // A drop inside the original selection discards the drop.
-                return;
-            }
-        }
+        InputContentType mInputContentType;
+        InputMethodState mInputMethodState;
 
-        final int originalLength = mText.length();
-        long minMax = prepareSpacesAroundPaste(offset, offset, content);
-        int min = extractRangeStartFromLong(minMax);
-        int max = extractRangeEndFromLong(minMax);
+        Path mHighlightPath;
+        boolean mHighlightPathBogus = true;
+        final Paint mHighlightPaint;
 
-        Selection.setSelection((Spannable) mText, max);
-        replaceText_internal(min, max, content);
+        DisplayList mTextDisplayList;
+        boolean mTextDisplayListIsValid;
 
-        if (dragDropIntoItself) {
-            int dragSourceStart = dragLocalState.start;
-            int dragSourceEnd = dragLocalState.end;
-            if (max <= dragSourceStart) {
-                // Inserting text before selection has shifted positions
-                final int shift = mText.length() - originalLength;
-                dragSourceStart += shift;
-                dragSourceEnd += shift;
-            }
+        boolean mFrozenWithFocus;
+        boolean mSelectionMoved;
+        boolean mTouchFocusSelected;
 
-            // Delete original selection
-            deleteText_internal(dragSourceStart, dragSourceEnd);
+        KeyListener mKeyListener;
+        int mInputType = EditorInfo.TYPE_NULL;
 
-            // Make sure we do not leave two adjacent spaces.
-            if ((dragSourceStart == 0 ||
-                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
-                    (dragSourceStart == mText.length() ||
-                    Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
-                final int pos = dragSourceStart == mText.length() ?
-                        dragSourceStart - 1 : dragSourceStart;
-                deleteText_internal(pos, pos + 1);
-            }
-        }
-    }
+        boolean mDiscardNextActionUp;
+        boolean mIgnoreActionUpEvent;
 
-    /**
-     * @return True if this view supports insertion handles.
-     */
-    boolean hasInsertionController() {
-        return mInsertionControllerEnabled;
-    }
+        long mShowCursor;
+        Blink mBlink;
 
-    /**
-     * @return True if this view supports selection handles.
-     */
-    boolean hasSelectionController() {
-        return mSelectionControllerEnabled;
-    }
+        boolean mCursorVisible = true;
+        boolean mSelectAllOnFocus;
+        boolean mTextIsSelectable;
 
-    InsertionPointCursorController getInsertionController() {
-        if (!mInsertionControllerEnabled) {
-            return null;
-        }
+        CharSequence mError;
+        boolean mErrorWasChanged;
+        ErrorPopup mErrorPopup;
+        /**
+         * This flag is set if the TextView tries to display an error before it
+         * is attached to the window (so its position is still unknown).
+         * It causes the error to be shown later, when onAttachedToWindow()
+         * is called.
+         */
+        boolean mShowErrorAfterAttach;
 
-        if (mInsertionPointCursorController == null) {
-            mInsertionPointCursorController = new InsertionPointCursorController();
+        boolean mInBatchEditControllers;
 
+        SuggestionsPopupWindow mSuggestionsPopupWindow;
+        SuggestionRangeSpan mSuggestionRangeSpan;
+        Runnable mShowSuggestionRunnable;
+
+        final Drawable[] mCursorDrawable = new Drawable[2];
+        int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2 (split)
+
+        Drawable mSelectHandleLeft;
+        Drawable mSelectHandleRight;
+        Drawable mSelectHandleCenter;
+
+        // Global listener that detects changes in the global position of the TextView
+        PositionListener mPositionListener;
+
+        float mLastDownPositionX, mLastDownPositionY;
+        Callback mCustomSelectionActionModeCallback;
+
+        // Set when this TextView gained focus with some text selected. Will start selection mode.
+        boolean mCreatedWithASelection;
+
+        WordIterator mWordIterator;
+        SpellChecker mSpellChecker;
+
+        void onAttachedToWindow() {
             final ViewTreeObserver observer = getViewTreeObserver();
-            observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+            // No need to create the controller.
+            // The get method will add the listener on controller creation.
+            if (mInsertionPointCursorController != null) {
+                observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+            }
+            if (mSelectionModifierCursorController != null) {
+                observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+            }
+            updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */);
         }
 
-        return mInsertionPointCursorController;
-    }
+        void onDetachedFromWindow() {
+            if (mError != null) {
+                hideError();
+            }
 
-    SelectionModifierCursorController getSelectionController() {
-        if (!mSelectionControllerEnabled) {
-            return null;
+            if (mBlink != null) {
+                mBlink.removeCallbacks(mBlink);
+            }
+
+            if (mInsertionPointCursorController != null) {
+                mInsertionPointCursorController.onDetached();
+            }
+
+            if (mSelectionModifierCursorController != null) {
+                mSelectionModifierCursorController.onDetached();
+            }
+
+            if (mShowSuggestionRunnable != null) {
+                removeCallbacks(mShowSuggestionRunnable);
+            }
+
+            if (mTextDisplayList != null) {
+                mTextDisplayList.invalidate();
+            }
+
+            if (mSpellChecker != null) {
+                mSpellChecker.closeSession();
+                // Forces the creation of a new SpellChecker next time this window is created.
+                // Will handle the cases where the settings has been changed in the meantime.
+                mSpellChecker = null;
+            }
+
+            hideControllers();
         }
 
-        if (mSelectionModifierCursorController == null) {
-            mSelectionModifierCursorController = new SelectionModifierCursorController();
-
-            final ViewTreeObserver observer = getViewTreeObserver();
-            observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
-        }
-
-        return mSelectionModifierCursorController;
-    }
-
-    boolean isInBatchEditMode() {
-        final InputMethodState ims = mInputMethodState;
-        if (ims != null) {
-            return ims.mBatchEditNesting > 0;
-        }
-        return mInBatchEditControllers;
-    }
-
-    @Override
-    public void onResolveTextDirection() {
-        if (hasPasswordTransformationMethod()) {
-            mTextDir = TextDirectionHeuristics.LOCALE;
-            return;
-        }
-
-        // Always need to resolve layout direction first
-        final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
-
-        // Now, we can select the heuristic
-        int textDir = getResolvedTextDirection();
-        switch (textDir) {
-            default:
-            case TEXT_DIRECTION_FIRST_STRONG:
-                mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
-                        TextDirectionHeuristics.FIRSTSTRONG_LTR);
-                break;
-            case TEXT_DIRECTION_ANY_RTL:
-                mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
-                break;
-            case TEXT_DIRECTION_LTR:
-                mTextDir = TextDirectionHeuristics.LTR;
-                break;
-            case TEXT_DIRECTION_RTL:
-                mTextDir = TextDirectionHeuristics.RTL;
-                break;
-            case TEXT_DIRECTION_LOCALE:
-                mTextDir = TextDirectionHeuristics.LOCALE;
-                break;
-        }
-    }
-
-    /**
-     * Subclasses will need to override this method to implement their own way of resolving
-     * drawables depending on the layout direction.
-     *
-     * A call to the super method will be required from the subclasses implementation.
-     */
-    protected void resolveDrawables() {
-        // No need to resolve twice
-        if (mResolvedDrawables) {
-            return;
-        }
-        // No drawable to resolve
-        if (mDrawables == null) {
-            return;
-        }
-        // No relative drawable to resolve
-        if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
-            mResolvedDrawables = true;
-            return;
-        }
-
-        Drawables dr = mDrawables;
-        switch(getResolvedLayoutDirection()) {
-            case LAYOUT_DIRECTION_RTL:
-                if (dr.mDrawableStart != null) {
-                    dr.mDrawableRight = dr.mDrawableStart;
-
-                    dr.mDrawableSizeRight = dr.mDrawableSizeStart;
-                    dr.mDrawableHeightRight = dr.mDrawableHeightStart;
+        void adjustInputType(boolean password, boolean passwordInputType,
+                boolean webPasswordInputType, boolean numberPasswordInputType) {
+            // mInputType has been set from inputType, possibly modified by mInputMethod.
+            // Specialize mInputType to [web]password if we have a text class and the original input
+            // type was a password.
+            if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+                if (password || passwordInputType) {
+                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+                            | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
                 }
-                if (dr.mDrawableEnd != null) {
-                    dr.mDrawableLeft = dr.mDrawableEnd;
-
-                    dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
-                    dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
+                if (webPasswordInputType) {
+                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+                            | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
                 }
-                break;
-
-            case LAYOUT_DIRECTION_LTR:
-            default:
-                if (dr.mDrawableStart != null) {
-                    dr.mDrawableLeft = dr.mDrawableStart;
-
-                    dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
-                    dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
+            } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
+                if (numberPasswordInputType) {
+                    mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+                            | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
                 }
-                if (dr.mDrawableEnd != null) {
-                    dr.mDrawableRight = dr.mDrawableEnd;
-
-                    dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
-                    dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
-                }
-                break;
+            }
         }
-        mResolvedDrawables = true;
-    }
 
-    protected void resetResolvedDrawables() {
-        mResolvedDrawables = false;
-    }
+        void setFrame() {
+            if (mErrorPopup != null) {
+                TextView tv = (TextView) mErrorPopup.getContentView();
+                chooseSize(mErrorPopup, mError, tv);
+                mErrorPopup.update(TextView.this, getErrorX(), getErrorY(),
+                        mErrorPopup.getWidth(), mErrorPopup.getHeight());
+            }
+        }
 
-    /**
-     * @hide
-     */
-    protected void viewClicked(InputMethodManager imm) {
-        if (imm != null) {
-            imm.viewClicked(this);
+        void onFocusChanged(boolean focused, int direction) {
+            mShowCursor = SystemClock.uptimeMillis();
+            ensureEndedBatchEdit();
+
+            if (focused) {
+                int selStart = getSelectionStart();
+                int selEnd = getSelectionEnd();
+
+                // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
+                // mode for these, unless there was a specific selection already started.
+                final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
+                        selEnd == mText.length();
+
+                mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
+
+                if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
+                    // If a tap was used to give focus to that view, move cursor at tap position.
+                    // Has to be done before onTakeFocus, which can be overloaded.
+                    final int lastTapPosition = getLastTapPosition();
+                    if (lastTapPosition >= 0) {
+                        Selection.setSelection((Spannable) mText, lastTapPosition);
+                    }
+
+                    // Note this may have to be moved out of the Editor class
+                    if (mMovement != null) {
+                        mMovement.onTakeFocus(TextView.this, (Spannable) mText, direction);
+                    }
+
+                    // The DecorView does not have focus when the 'Done' ExtractEditText button is
+                    // pressed. Since it is the ViewAncestor's mView, it requests focus before
+                    // ExtractEditText clears focus, which gives focus to the ExtractEditText.
+                    // This special case ensure that we keep current selection in that case.
+                    // It would be better to know why the DecorView does not have focus at that time.
+                    if (((TextView.this instanceof ExtractEditText) || mSelectionMoved) &&
+                            selStart >= 0 && selEnd >= 0) {
+                        /*
+                         * Someone intentionally set the selection, so let them
+                         * do whatever it is that they wanted to do instead of
+                         * the default on-focus behavior.  We reset the selection
+                         * here instead of just skipping the onTakeFocus() call
+                         * because some movement methods do something other than
+                         * just setting the selection in theirs and we still
+                         * need to go through that path.
+                         */
+                        Selection.setSelection((Spannable) mText, selStart, selEnd);
+                    }
+
+                    if (mSelectAllOnFocus) {
+                        selectAll();
+                    }
+
+                    mTouchFocusSelected = true;
+                }
+
+                mFrozenWithFocus = false;
+                mSelectionMoved = false;
+
+                if (mError != null) {
+                    showError();
+                }
+
+                makeBlink();
+            } else {
+                if (mError != null) {
+                    hideError();
+                }
+                // Don't leave us in the middle of a batch edit.
+                onEndBatchEdit();
+
+                if (TextView.this instanceof ExtractEditText) {
+                    // terminateTextSelectionMode removes selection, which we want to keep when
+                    // ExtractEditText goes out of focus.
+                    final int selStart = getSelectionStart();
+                    final int selEnd = getSelectionEnd();
+                    hideControllers();
+                    Selection.setSelection((Spannable) mText, selStart, selEnd);
+                } else {
+                    hideControllers();
+                    downgradeEasyCorrectionSpans();
+                }
+
+                // No need to create the controller
+                if (mSelectionModifierCursorController != null) {
+                    mSelectionModifierCursorController.resetTouchOffsets();
+                }
+            }
+        }
+
+        void sendOnTextChanged(int start, int after) {
+            updateSpellCheckSpans(start, start + after, false);
+            mTextDisplayListIsValid = false;
+
+            // Hide the controllers as soon as text is modified (typing, procedural...)
+            // We do not hide the span controllers, since they can be added when a new text is
+            // inserted into the text view (voice IME).
+            hideCursorControllers();
+        }
+
+        private int getLastTapPosition() {
+            // No need to create the controller at that point, no last tap position saved
+            if (mSelectionModifierCursorController != null) {
+                int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
+                if (lastTapPosition >= 0) {
+                    // Safety check, should not be possible.
+                    if (lastTapPosition > mText.length()) {
+                        Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
+                                + mText.length() + ")");
+                        lastTapPosition = mText.length();
+                    }
+                    return lastTapPosition;
+                }
+            }
+
+            return -1;
+        }
+
+        void onWindowFocusChanged(boolean hasWindowFocus) {
+            if (hasWindowFocus) {
+                if (mBlink != null) {
+                    mBlink.uncancel();
+                    makeBlink();
+                }
+            } else {
+                if (mBlink != null) {
+                    mBlink.cancel();
+                }
+                if (mInputContentType != null) {
+                    mInputContentType.enterDown = false;
+                }
+                // Order matters! Must be done before onParentLostFocus to rely on isShowingUp
+                hideControllers();
+                if (mSuggestionsPopupWindow != null) {
+                    mSuggestionsPopupWindow.onParentLostFocus();
+                }
+
+                // Don't leave us in the middle of a batch edit.
+                onEndBatchEdit();
+            }
+        }
+
+        void onTouchEvent(MotionEvent event) {
+            if (hasSelectionController()) {
+                getSelectionController().onTouchEvent(event);
+            }
+
+            if (mShowSuggestionRunnable != null) {
+                removeCallbacks(mShowSuggestionRunnable);
+                mShowSuggestionRunnable = null;
+            }
+
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mLastDownPositionX = event.getX();
+                mLastDownPositionY = event.getY();
+
+                // Reset this state; it will be re-set if super.onTouchEvent
+                // causes focus to move to the view.
+                mTouchFocusSelected = false;
+                mIgnoreActionUpEvent = false;
+            }
+        }
+
+        void onDraw(Canvas canvas, Layout layout, int cursorOffsetVertical) {
+            Path highlight = null;
+            Paint highlightPaint = null;
+
+            int selStart = -1, selEnd = -1;
+            boolean drawCursor = false;
+
+            highlightPaint = mHighlightPaint;
+            //  If there is no movement method, then there can be no selection.
+            //  Check that first and attempt to skip everything having to do with
+            //  the cursor.
+            //  XXX This is not strictly true -- a program could set the
+            //  selection manually if it really wanted to.
+            if (mMovement != null && (isFocused() || isPressed())) {
+                selStart = getSelectionStart();
+                selEnd = getSelectionEnd();
+
+                if (selStart >= 0) {
+                    if (mHighlightPath == null) mHighlightPath = new Path();
+
+                    if (selStart == selEnd) {
+                        if (isCursorVisible() &&
+                                (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
+                            if (mHighlightPathBogus) {
+                                mHighlightPath.reset();
+                                mLayout.getCursorPath(selStart, mHighlightPath, mText);
+                                updateCursorsPositions();
+                                mHighlightPathBogus = false;
+                            }
+
+                            // XXX should pass to skin instead of drawing directly
+                            highlightPaint.setColor(mCurTextColor);
+                            if (mCurrentAlpha != 255) {
+                                highlightPaint.setAlpha(
+                                        (mCurrentAlpha * Color.alpha(mCurTextColor)) / 255);
+                            }
+                            highlightPaint.setStyle(Paint.Style.STROKE);
+                            highlight = mHighlightPath;
+                            drawCursor = mCursorCount > 0;
+                        }
+                    } else if (textCanBeSelected()) {
+                        if (mHighlightPathBogus) {
+                            mHighlightPath.reset();
+                            mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
+                            mHighlightPathBogus = false;
+                        }
+
+                        // XXX should pass to skin instead of drawing directly
+                        highlightPaint.setColor(mHighlightColor);
+                        if (mCurrentAlpha != 255) {
+                            highlightPaint.setAlpha(
+                                    (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
+                        }
+                        highlightPaint.setStyle(Paint.Style.FILL);
+
+                        highlight = mHighlightPath;
+                    }
+                }
+            }
+
+            final InputMethodState ims = mInputMethodState;
+            if (ims != null && ims.mBatchEditNesting == 0) {
+                InputMethodManager imm = InputMethodManager.peekInstance();
+                if (imm != null) {
+                    if (imm.isActive(TextView.this)) {
+                        boolean reported = false;
+                        if (ims.mContentChanged || ims.mSelectionModeChanged) {
+                            // We are in extract mode and the content has changed
+                            // in some way... just report complete new text to the
+                            // input method.
+                            reported = reportExtractedText();
+                        }
+                        if (!reported && highlight != null) {
+                            int candStart = -1;
+                            int candEnd = -1;
+                            if (mText instanceof Spannable) {
+                                Spannable sp = (Spannable)mText;
+                                candStart = EditableInputConnection.getComposingSpanStart(sp);
+                                candEnd = EditableInputConnection.getComposingSpanEnd(sp);
+                            }
+                            imm.updateSelection(TextView.this, selStart, selEnd, candStart, candEnd);
+                        }
+                    }
+
+                    if (imm.isWatchingCursor(TextView.this) && highlight != null) {
+                        highlight.computeBounds(ims.mTmpRectF, true);
+                        ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
+
+                        canvas.getMatrix().mapPoints(ims.mTmpOffset);
+                        ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
+
+                        ims.mTmpRectF.offset(0, cursorOffsetVertical);
+
+                        ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
+                                (int)(ims.mTmpRectF.top + 0.5),
+                                (int)(ims.mTmpRectF.right + 0.5),
+                                (int)(ims.mTmpRectF.bottom + 0.5));
+
+                        imm.updateCursor(TextView.this,
+                                ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
+                                ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
+                    }
+                }
+            }
+
+            if (mCorrectionHighlighter != null) {
+                mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
+            }
+
+            if (drawCursor) {
+                drawCursor(canvas, cursorOffsetVertical);
+                // Rely on the drawable entirely, do not draw the cursor line.
+                // Has to be done after the IMM related code above which relies on the highlight.
+                highlight = null;
+            }
+
+            if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
+                final int width = mRight - mLeft;
+                final int height = mBottom - mTop;
+
+                if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
+                        !mTextDisplayListIsValid) {
+                    if (mTextDisplayList == null) {
+                        mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
+                    }
+
+                    final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
+                    try {
+                        hardwareCanvas.setViewport(width, height);
+                        // The dirty rect should always be null for a display list
+                        hardwareCanvas.onPreDraw(null);
+                        hardwareCanvas.translate(-mScrollX, -mScrollY);
+                        layout.draw(hardwareCanvas, highlight, highlightPaint, cursorOffsetVertical);
+                        hardwareCanvas.translate(mScrollX, mScrollY);
+                    } finally {
+                        hardwareCanvas.onPostDraw();
+                        mTextDisplayList.end();
+                        mTextDisplayListIsValid = true;
+                    }
+                }
+                canvas.translate(mScrollX, mScrollY);
+                ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
+                        DisplayList.FLAG_CLIP_CHILDREN);
+                canvas.translate(-mScrollX, -mScrollY);
+            } else {
+                layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
+            }
+
+            if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+                canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
+                layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
+            }
         }
     }
-
-    /**
-     * Deletes the range of text [start, end[.
-     * @hide
-     */
-    protected void deleteText_internal(int start, int end) {
-        ((Editable) mText).delete(start, end);
-    }
-
-    /**
-     * Replaces the range of text [start, end[ by replacement text
-     * @hide
-     */
-    protected void replaceText_internal(int start, int end, CharSequence text) {
-        ((Editable) mText).replace(start, end, text);
-    }
-
-    /**
-     * Sets a span on the specified range of text
-     * @hide
-     */
-    protected void setSpan_internal(Object span, int start, int end, int flags) {
-        ((Editable) mText).setSpan(span, start, end, flags);
-    }
-
-    /**
-     * Moves the cursor to the specified offset position in text
-     * @hide
-     */
-    protected void setCursorPosition_internal(int start, int end) {
-        Selection.setSelection(((Editable) mText), start, end);
-    }
-
-    @ViewDebug.ExportedProperty(category = "text")
-    private CharSequence            mText;
-    private CharSequence            mTransformed;
-    private BufferType              mBufferType = BufferType.NORMAL;
-
-    private int                     mInputType = EditorInfo.TYPE_NULL;
-    private CharSequence            mHint;
-    private Layout                  mHintLayout;
-
-    private KeyListener             mInput;
-
-    private MovementMethod          mMovement;
-    private TransformationMethod    mTransformation;
-    private boolean                 mAllowTransformationLengthChange;
-    private ChangeWatcher           mChangeWatcher;
-
-    private ArrayList<TextWatcher>  mListeners = null;
-
-    // display attributes
-    private final TextPaint         mTextPaint;
-    private boolean                 mUserSetTextScaleX;
-    private final Paint             mHighlightPaint;
-    private int                     mHighlightColor = 0x6633B5E5;
-    private Layout                  mLayout;
-
-    private long                    mShowCursor;
-    private Blink                   mBlink;
-    private boolean                 mCursorVisible = true;
-
-    // Cursor Controllers.
-    private InsertionPointCursorController mInsertionPointCursorController;
-    private SelectionModifierCursorController mSelectionModifierCursorController;
-    private ActionMode              mSelectionActionMode;
-    private boolean                 mInsertionControllerEnabled;
-    private boolean                 mSelectionControllerEnabled;
-    private boolean                 mInBatchEditControllers;
-
-    private boolean                 mSelectAllOnFocus = false;
-
-    private int                     mGravity = Gravity.TOP | Gravity.START;
-    private boolean                 mHorizontallyScrolling;
-
-    private int                     mAutoLinkMask;
-    private boolean                 mLinksClickable = true;
-
-    private float                   mSpacingMult = 1.0f;
-    private float                   mSpacingAdd = 0.0f;
-    private boolean                 mTextIsSelectable = false;
-
-    private static final int        LINES = 1;
-    private static final int        EMS = LINES;
-    private static final int        PIXELS = 2;
-
-    private int                     mMaximum = Integer.MAX_VALUE;
-    private int                     mMaxMode = LINES;
-    private int                     mMinimum = 0;
-    private int                     mMinMode = LINES;
-
-    private int                     mOldMaximum = mMaximum;
-    private int                     mOldMaxMode = mMaxMode;
-
-    private int                     mMaxWidth = Integer.MAX_VALUE;
-    private int                     mMaxWidthMode = PIXELS;
-    private int                     mMinWidth = 0;
-    private int                     mMinWidthMode = PIXELS;
-
-    private boolean                 mSingleLine;
-    private int                     mDesiredHeightAtMeasure = -1;
-    private boolean                 mIncludePad = true;
-
-    // tmp primitives, so we don't alloc them on each draw
-    private Path                    mHighlightPath;
-    private boolean                 mHighlightPathBogus = true;
-    private static final RectF      sTempRect = new RectF();
-    private static final float[]    sTmpPosition = new float[2];
-
-    // XXX should be much larger
-    private static final int        VERY_WIDE = 1024*1024;
-
-    private static final int        BLINK = 500;
-
-    private static final int ANIMATED_SCROLL_GAP = 250;
-    private long mLastScroll;
-    private Scroller mScroller = null;
-
-    private BoringLayout.Metrics mBoring;
-    private BoringLayout.Metrics mHintBoring;
-
-    private BoringLayout mSavedLayout, mSavedHintLayout;
-
-    private TextDirectionHeuristic mTextDir = null;
-
-    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
-    private InputFilter[] mFilters = NO_FILTERS;
-    private static final Spanned EMPTY_SPANNED = new SpannedString("");
-    private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
-    // System wide time for last cut or copy action.
-    private static long sLastCutOrCopyTime;
-    // Used to highlight a word when it is corrected by the IME
-    private CorrectionHighlighter mCorrectionHighlighter;
-    // New state used to change background based on whether this TextView is multiline.
-    private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
 }
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index ddb0438..9a6a293 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -764,10 +764,8 @@
     <string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"به دارنده اجازه می‎دهد تا تاییدکنندگان بسته را درخواست کند. برای برنامه‎های عادی نیاز نیست."</string>
     <string name="permlab_serialPort" msgid="546083327654631076">"دسترسی به درگاه‌های سریال"</string>
     <string name="permdesc_serialPort" msgid="2991639985224598193">"به دارنده اجازه می‌دهد با استفاده از SerialManager API به درگاه‌های سریال دسترسی داشته باشد."</string>
-    <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
-    <skip />
-    <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
-    <skip />
+    <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"دسترسی خارجی به ارائه‌دهندگان محتوا"</string>
+    <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"به دارنده اجازه می‌دهد تا از خارج برنامه به ارائه دهندگان محتوا دسترسی داشته باشد. هرگز برای برنامه‌های معمولی به آن نیازی نیست."</string>
     <string name="save_password_message" msgid="767344687139195790">"می خواهید مرورگر این رمز ورود را به خاطر داشته باشد؟"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"اکنون خیر"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"به خاطر سپردن"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 8d02eff..b3cb9a0 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -764,10 +764,8 @@
     <string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Ivumela umnikazi ukuthi enze izicelo zezinsiza eziqinisekisa iphakheji. Akumele kudingeke ekusetshenzisweni okujwayelekile."</string>
     <string name="permlab_serialPort" msgid="546083327654631076">"finyelela kuma- serial port"</string>
     <string name="permdesc_serialPort" msgid="2991639985224598193">"Ivumela umnikai ukuthi athole inombolo ye-serial ukue angene kwiindawo ze-serial esebenzisa i-SerialManager API."</string>
-    <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
-    <skip />
-    <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
-    <skip />
+    <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"finyelela ngaphandle abahlinzeki bokuqukethwe"</string>
+    <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Ivumela umphathi ukufinyelela abahlinzeki bokuqukethwe kusuka kumasistimu asebenzayo. Akusoze kwadingeka kwizinhlelo zokusebenza ezivamile."</string>
     <string name="save_password_message" msgid="767344687139195790">"Ingabe ufuna ukuba isiphequluli sikhumbule lephasiwedi?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Hha yi manje"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Khumbula"</string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2a6cef3..6a887db 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -962,6 +962,7 @@
   <java-symbol type="drawable" name="tab_bottom_right_v4" />
   <java-symbol type="drawable" name="tab_indicator_v4" />
   <java-symbol type="drawable" name="text_select_handle_left" />
+  <java-symbol type="drawable" name="text_select_handle_middle" />
   <java-symbol type="drawable" name="text_select_handle_right" />
   <java-symbol type="drawable" name="unknown_image" />
   <java-symbol type="drawable" name="unlock_default" />
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index 3963d9c..70799a6 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -22,6 +22,7 @@
 #include <android/native_window.h>
 #include <media/IOMX.h>
 #include <media/stagefright/foundation/AHierarchicalStateMachine.h>
+#include <OMX_Audio.h>
 
 namespace android {
 
@@ -37,6 +38,9 @@
         kWhatFlushCompleted      = 'fcom',
         kWhatOutputFormatChanged = 'outC',
         kWhatError               = 'erro',
+        kWhatComponentAllocated  = 'cAll',
+        kWhatComponentConfigured = 'cCon',
+        kWhatBuffersAllocated    = 'allc',
     };
 
     ACodec();
@@ -47,6 +51,10 @@
     void signalResume();
     void initiateShutdown();
 
+    void initiateAllocateComponent(const sp<AMessage> &msg);
+    void initiateConfigureComponent(const sp<AMessage> &msg);
+    void initiateStart();
+
 protected:
     virtual ~ACodec();
 
@@ -70,6 +78,9 @@
         kWhatFlush                   = 'flus',
         kWhatResume                  = 'resm',
         kWhatDrainDeferredMessages   = 'drai',
+        kWhatAllocateComponent       = 'allo',
+        kWhatConfigureComponent      = 'conf',
+        kWhatStart                   = 'star',
     };
 
     enum {
@@ -118,6 +129,7 @@
     List<sp<AMessage> > mDeferredQueue;
 
     bool mSentFormat;
+    bool mIsEncoder;
 
     status_t allocateBuffersOnPort(OMX_U32 portIndex);
     status_t freeBuffersOnPort(OMX_U32 portIndex);
@@ -132,8 +144,8 @@
             uint32_t portIndex, IOMX::buffer_id bufferID,
             ssize_t *index = NULL);
 
-    void setComponentRole(bool isEncoder, const char *mime);
-    void configureCodec(const char *mime, const sp<AMessage> &msg);
+    status_t setComponentRole(bool isEncoder, const char *mime);
+    status_t configureCodec(const char *mime, const sp<AMessage> &msg);
 
     status_t setVideoPortFormatType(
             OMX_U32 portIndex,
@@ -145,20 +157,37 @@
     status_t setupVideoDecoder(
             const char *mime, int32_t width, int32_t height);
 
+    status_t setupVideoEncoder(
+            const char *mime, const sp<AMessage> &msg);
+
     status_t setVideoFormatOnPort(
             OMX_U32 portIndex,
             int32_t width, int32_t height,
             OMX_VIDEO_CODINGTYPE compressionFormat);
 
-    status_t setupAACDecoder(int32_t numChannels, int32_t sampleRate);
-    status_t setupAMRDecoder(bool isWAMR);
-    status_t setupG711Decoder(int32_t numChannels);
+    status_t setupAACCodec(
+            bool encoder,
+            int32_t numChannels, int32_t sampleRate, int32_t bitRate);
+
+    status_t selectAudioPortFormat(
+            OMX_U32 portIndex, OMX_AUDIO_CODINGTYPE desiredFormat);
+
+    status_t setupAMRCodec(bool encoder, bool isWAMR, int32_t bitRate);
+    status_t setupG711Codec(bool encoder, int32_t numChannels);
 
     status_t setupRawAudioFormat(
             OMX_U32 portIndex, int32_t sampleRate, int32_t numChannels);
 
     status_t setMinBufferSize(OMX_U32 portIndex, size_t size);
 
+    status_t setupMPEG4EncoderParameters(const sp<AMessage> &msg);
+    status_t setupH263EncoderParameters(const sp<AMessage> &msg);
+    status_t setupAVCEncoderParameters(const sp<AMessage> &msg);
+
+    status_t verifySupportForProfileAndLevel(int32_t profile, int32_t level);
+    status_t configureBitrate(int32_t bitrate);
+    status_t setupErrorCorrectionParameters();
+
     status_t initNativeWindow();
 
     // Returns true iff all buffers on the given port have status OWNED_BY_US.
@@ -173,7 +202,9 @@
 
     void sendFormatChange();
 
-    void signalError(OMX_ERRORTYPE error = OMX_ErrorUndefined);
+    void signalError(
+            OMX_ERRORTYPE error = OMX_ErrorUndefined,
+            status_t internalError = UNKNOWN_ERROR);
 
     DISALLOW_EVIL_CONSTRUCTORS(ACodec);
 };
diff --git a/include/media/stagefright/MediaCodec.h b/include/media/stagefright/MediaCodec.h
new file mode 100644
index 0000000..8c11c9c
--- /dev/null
+++ b/include/media/stagefright/MediaCodec.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEDIA_CODEC_H_
+
+#define MEDIA_CODEC_H_
+
+#include <gui/ISurfaceTexture.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct ABuffer;
+struct ACodec;
+struct AMessage;
+struct SoftwareRenderer;
+struct SurfaceTextureClient;
+
+struct MediaCodec : public AHandler {
+    enum ConfigureFlags {
+        CONFIGURE_FLAG_ENCODE   = 1,
+    };
+
+    enum BufferFlags {
+        BUFFER_FLAG_SYNCFRAME   = 1,
+        BUFFER_FLAG_CODECCONFIG = 2,
+        BUFFER_FLAG_EOS         = 4,
+    };
+
+    static sp<MediaCodec> CreateByType(
+            const sp<ALooper> &looper, const char *mime, bool encoder);
+
+    static sp<MediaCodec> CreateByComponentName(
+            const sp<ALooper> &looper, const char *name);
+
+    status_t configure(
+            const sp<AMessage> &format,
+            const sp<SurfaceTextureClient> &nativeWindow,
+            uint32_t flags);
+
+    status_t start();
+    status_t stop();
+
+    status_t flush();
+
+    status_t queueInputBuffer(
+            size_t index,
+            size_t offset,
+            size_t size,
+            int64_t presentationTimeUs,
+            uint32_t flags);
+
+    status_t dequeueInputBuffer(size_t *index, int64_t timeoutUs = 0ll);
+
+    status_t dequeueOutputBuffer(
+            size_t *index,
+            size_t *offset,
+            size_t *size,
+            int64_t *presentationTimeUs,
+            uint32_t *flags,
+            int64_t timeoutUs = 0ll);
+
+    status_t renderOutputBufferAndRelease(size_t index);
+    status_t releaseOutputBuffer(size_t index);
+
+    status_t getOutputFormat(sp<AMessage> *format) const;
+
+    status_t getInputBuffers(Vector<sp<ABuffer> > *buffers) const;
+    status_t getOutputBuffers(Vector<sp<ABuffer> > *buffers) const;
+
+protected:
+    virtual ~MediaCodec();
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    enum State {
+        UNINITIALIZED,
+        INITIALIZING,
+        INITIALIZED,
+        CONFIGURING,
+        CONFIGURED,
+        STARTING,
+        STARTED,
+        FLUSHING,
+        STOPPING,
+    };
+
+    enum {
+        kPortIndexInput         = 0,
+        kPortIndexOutput        = 1,
+    };
+
+    enum {
+        kWhatInit                       = 'init',
+        kWhatConfigure                  = 'conf',
+        kWhatStart                      = 'strt',
+        kWhatStop                       = 'stop',
+        kWhatDequeueInputBuffer         = 'deqI',
+        kWhatQueueInputBuffer           = 'queI',
+        kWhatDequeueOutputBuffer        = 'deqO',
+        kWhatReleaseOutputBuffer        = 'relO',
+        kWhatGetBuffers                 = 'getB',
+        kWhatFlush                      = 'flus',
+        kWhatGetOutputFormat            = 'getO',
+        kWhatDequeueInputTimedOut       = 'dITO',
+        kWhatDequeueOutputTimedOut      = 'dOTO',
+        kWhatCodecNotify                = 'codc',
+    };
+
+    enum {
+        kFlagIsSoftwareCodec            = 1,
+        kFlagOutputFormatChanged        = 2,
+        kFlagOutputBuffersChanged       = 4,
+        kFlagStickyError                = 8,
+        kFlagDequeueInputPending        = 16,
+        kFlagDequeueOutputPending       = 32,
+    };
+
+    struct BufferInfo {
+        void *mBufferID;
+        sp<ABuffer> mData;
+        sp<AMessage> mNotify;
+        bool mOwnedByClient;
+    };
+
+    State mState;
+    sp<ALooper> mLooper;
+    sp<ALooper> mCodecLooper;
+    sp<ACodec> mCodec;
+    uint32_t mReplyID;
+    uint32_t mFlags;
+    sp<SurfaceTextureClient> mNativeWindow;
+    SoftwareRenderer *mSoftRenderer;
+    sp<AMessage> mOutputFormat;
+
+    List<size_t> mAvailPortBuffers[2];
+    Vector<BufferInfo> mPortBuffers[2];
+
+    int32_t mDequeueInputTimeoutGeneration;
+    uint32_t mDequeueInputReplyID;
+
+    int32_t mDequeueOutputTimeoutGeneration;
+    uint32_t mDequeueOutputReplyID;
+
+    MediaCodec(const sp<ALooper> &looper);
+
+    static status_t PostAndAwaitResponse(
+            const sp<AMessage> &msg, sp<AMessage> *response);
+
+    status_t init(const char *name, bool nameIsType, bool encoder);
+
+    void setState(State newState);
+    void returnBuffersToCodec();
+    void returnBuffersToCodecOnPort(int32_t portIndex);
+    size_t updateBuffers(int32_t portIndex, const sp<AMessage> &msg);
+    status_t onQueueInputBuffer(const sp<AMessage> &msg);
+    status_t onReleaseOutputBuffer(const sp<AMessage> &msg);
+    ssize_t dequeuePortBuffer(int32_t portIndex);
+
+    bool handleDequeueInputBuffer(uint32_t replyID, bool newRequest = false);
+    bool handleDequeueOutputBuffer(uint32_t replyID, bool newRequest = false);
+    void cancelPendingDequeueOperations();
+
+    DISALLOW_EVIL_CONSTRUCTORS(MediaCodec);
+};
+
+}  // namespace android
+
+#endif  // MEDIA_CODEC_H_
diff --git a/include/media/stagefright/MediaErrors.h b/include/media/stagefright/MediaErrors.h
index 21d00b8..dd3bf28 100644
--- a/include/media/stagefright/MediaErrors.h
+++ b/include/media/stagefright/MediaErrors.h
@@ -40,6 +40,7 @@
     // Not technically an error.
     INFO_FORMAT_CHANGED    = MEDIA_ERROR_BASE - 12,
     INFO_DISCONTINUITY     = MEDIA_ERROR_BASE - 13,
+    INFO_OUTPUT_BUFFERS_CHANGED = MEDIA_ERROR_BASE - 14,
 
     // The following constant values should be in sync with
     // drm/drm_framework_common.h
diff --git a/include/media/stagefright/NativeWindowWrapper.h b/include/media/stagefright/NativeWindowWrapper.h
index f323cbc..97cc0ce 100644
--- a/include/media/stagefright/NativeWindowWrapper.h
+++ b/include/media/stagefright/NativeWindowWrapper.h
@@ -18,40 +18,28 @@
 
 #define NATIVE_WINDOW_WRAPPER_H_
 
-#include <surfaceflinger/Surface.h>
 #include <gui/SurfaceTextureClient.h>
 
 namespace android {
 
-// Both Surface and SurfaceTextureClient are RefBase that implement the
-// ANativeWindow interface, but at different addresses. ANativeWindow is not
-// a RefBase but acts like one for use with sp<>.  This wrapper converts a
-// Surface or SurfaceTextureClient into a single reference-counted object
-// that holds an sp reference to the underlying Surface or SurfaceTextureClient,
-// It provides a method to get the ANativeWindow.
+// SurfaceTextureClient derives from ANativeWindow which derives from multiple
+// base classes, in order to carry it in AMessages, we'll temporarily wrap it
+// into a NativeWindowWrapper.
 
 struct NativeWindowWrapper : RefBase {
     NativeWindowWrapper(
-            const sp<Surface> &surface) :
-        mSurface(surface) { }
-
-    NativeWindowWrapper(
             const sp<SurfaceTextureClient> &surfaceTextureClient) :
         mSurfaceTextureClient(surfaceTextureClient) { }
 
     sp<ANativeWindow> getNativeWindow() const {
-        if (mSurface != NULL) {
-            return mSurface;
-        } else {
-            return mSurfaceTextureClient;
-        }
+        return mSurfaceTextureClient;
     }
 
-    // If needed later we can provide a method to ask what kind of native window
+    sp<SurfaceTextureClient> getSurfaceTextureClient() const {
+        return mSurfaceTextureClient;
+    }
 
 private:
-    // At most one of mSurface and mSurfaceTextureClient will be non-NULL
-    const sp<Surface> mSurface;
     const sp<SurfaceTextureClient> mSurfaceTextureClient;
 
     DISALLOW_EVIL_CONSTRUCTORS(NativeWindowWrapper);
diff --git a/include/media/stagefright/NuMediaExtractor.h b/include/media/stagefright/NuMediaExtractor.h
new file mode 100644
index 0000000..96efdff
--- /dev/null
+++ b/include/media/stagefright/NuMediaExtractor.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NU_MEDIA_EXTRACTOR_H_
+#define NU_MEDIA_EXTRACTOR_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+struct MediaBuffer;
+struct MediaExtractor;
+struct MediaSource;
+
+struct NuMediaExtractor : public RefBase {
+    NuMediaExtractor();
+
+    status_t setDataSource(const char *path);
+
+    size_t countTracks() const;
+    status_t getTrackFormat(size_t index, sp<AMessage> *format) const;
+
+    status_t selectTrack(size_t index);
+
+    status_t seekTo(int64_t timeUs);
+
+    status_t advance();
+    status_t readSampleData(const sp<ABuffer> &buffer);
+    status_t getSampleTrackIndex(size_t *trackIndex);
+    status_t getSampleTime(int64_t *sampleTimeUs);
+
+protected:
+    virtual ~NuMediaExtractor();
+
+private:
+    enum TrackFlags {
+        kIsVorbis       = 1,
+    };
+
+    struct TrackInfo {
+        sp<MediaSource> mSource;
+        size_t mTrackIndex;
+        status_t mFinalResult;
+        MediaBuffer *mSample;
+        int64_t mSampleTimeUs;
+        uint32_t mFlags;  // bitmask of "TrackFlags"
+    };
+
+    sp<MediaExtractor> mImpl;
+
+    Vector<TrackInfo> mSelectedTracks;
+
+    ssize_t fetchTrackSamples(int64_t seekTimeUs = -1ll);
+    void releaseTrackSamples();
+
+    DISALLOW_EVIL_CONSTRUCTORS(NuMediaExtractor);
+};
+
+}  // namespace android
+
+#endif  // NU_MEDIA_EXTRACTOR_H_
+
diff --git a/include/media/stagefright/foundation/AMessage.h b/include/media/stagefright/foundation/AMessage.h
index 7ec54aa..50455e8 100644
--- a/include/media/stagefright/foundation/AMessage.h
+++ b/include/media/stagefright/foundation/AMessage.h
@@ -90,10 +90,6 @@
 
     AString debugString(int32_t indent = 0) const;
 
-protected:
-    virtual ~AMessage();
-
-private:
     enum Type {
         kTypeInt32,
         kTypeInt64,
@@ -107,6 +103,13 @@
         kTypeRect,
     };
 
+    size_t countEntries() const;
+    const char *getEntryNameAt(size_t index, Type *type) const;
+
+protected:
+    virtual ~AMessage();
+
+private:
     uint32_t mWhat;
     ALooper::handler_id mTarget;
 
@@ -131,7 +134,7 @@
     };
 
     enum {
-        kMaxNumItems = 16
+        kMaxNumItems = 32
     };
     Item mItems[kMaxNumItems];
     size_t mNumItems;
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 16a3d73..55a860e 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -30,7 +30,7 @@
 #define DEBUG_MEMORY_USAGE 0
 
 // Turn on to enable debugging of cache flushes
-#define DEBUG_CACHE_FLUSH 1
+#define DEBUG_CACHE_FLUSH 0
 
 // Turn on to enable layers debugging when rendered as regions
 #define DEBUG_LAYERS_AS_REGIONS 0
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index f0d7b02..c029291 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -31,6 +31,10 @@
 	libEGL \
 	libhardware
 
+ifneq ($(BOARD_FRAMEBUFFER_FORCE_FORMAT),)
+LOCAL_CFLAGS += -DFRAMEBUFFER_FORCE_FORMAT=$(BOARD_FRAMEBUFFER_FORCE_FORMAT)
+endif
+
 LOCAL_MODULE:= libui
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/ui/FramebufferNativeWindow.cpp b/libs/ui/FramebufferNativeWindow.cpp
index d1dca0c..26d4823 100644
--- a/libs/ui/FramebufferNativeWindow.cpp
+++ b/libs/ui/FramebufferNativeWindow.cpp
@@ -100,6 +100,18 @@
         mNumFreeBuffers = NUM_FRAME_BUFFERS;
         mBufferHead = mNumBuffers-1;
 
+        /*
+         * This does not actually change the framebuffer format. It merely
+         * fakes this format to surfaceflinger so that when it creates
+         * framebuffer surfaces it will use this format. It's really a giant
+         * HACK to allow interworking with buggy gralloc+GPU driver
+         * implementations. You should *NEVER* need to set this for shipping
+         * devices.
+         */
+#ifdef FRAMEBUFFER_FORCE_FORMAT
+        *((uint32_t *)&fbDev->format) = FRAMEBUFFER_FORCE_FORMAT;
+#endif
+
         for (i = 0; i < mNumBuffers; i++)
         {
                 buffers[i] = new NativeBuffer(
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
new file mode 100644
index 0000000..7f496ca
--- /dev/null
+++ b/media/java/android/media/MediaCodec.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 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.media;
+
+import android.view.Surface;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+/**
+ * MediaCodec class can be used to access low-level media codec, i.e.
+ * encoder/decoder components.
+ * @hide
+*/
+public class MediaCodec
+{
+    /** Per buffer metadata includes an offset and size specifying
+        the range of valid data in the associated codec buffer.
+    */
+    public final static class BufferInfo {
+        public void set(
+                int offset, int size, long timeUs, int flags) {
+            mOffset = offset;
+            mSize = size;
+            mPresentationTimeUs = timeUs;
+            mFlags = flags;
+        }
+
+        public int mOffset;
+        public int mSize;
+        public long mPresentationTimeUs;
+        public int mFlags;
+    };
+
+    public static int FLAG_SYNCFRAME   = 1;
+    public static int FLAG_CODECCONFIG = 2;
+    public static int FLAG_EOS         = 4;
+
+    /** Instantiate a codec component by mime type. For decoder components
+        this is the mime type of media that this decoder should be able to
+        decoder, for encoder components it's the type of media this encoder
+        should encode _to_.
+    */
+    public static MediaCodec CreateByType(String type, boolean encoder) {
+        return new MediaCodec(type, true /* nameIsType */, encoder);
+    }
+
+    /** If you know the exact name of the component you want to instantiate
+        use this method to instantiate it. Use with caution.
+    */
+    public static MediaCodec CreateByComponentName(String name) {
+        return new MediaCodec(
+                name, false /* nameIsType */, false /* unused */);
+    }
+
+    private MediaCodec(
+            String name, boolean nameIsType, boolean encoder) {
+        native_setup(name, nameIsType, encoder);
+    }
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+
+    // Make sure you call this when you're done to free up any opened
+    // component instance instead of relying on the garbage collector
+    // to do this for you at some point in the future.
+    public native final void release();
+
+    public static int CONFIGURE_FLAG_ENCODE = 1;
+
+    /** Configures a component.
+     *  @param format A map of string/value pairs describing the input format
+     *                (decoder) or the desired output format.
+     *
+     *                Video formats have the following fields:
+     *                  "mime"          - String
+     *                  "width"         - Integer
+     *                  "height"        - Integer
+     *                  optional "max-input-size"       - Integer
+     *                  optional "csd-0", "csd-1" ...   - ByteBuffer
+     *
+     *                Audio formats have the following fields:
+     *                  "mime"          - String
+     *                  "channel-count" - Integer
+     *                  "sample-rate"   - Integer
+     *                  optional "max-input-size"       - Integer
+     *                  optional "csd-0", "csd-1" ...   - ByteBuffer
+     *
+     *                If the format is used to configure an encoder, additional
+     *                fields must be included:
+     *                  "bitrate" - Integer (in bits/sec)
+     *
+     *                for video formats:
+     *                  "color-format"          - Integer
+     *                  "frame-rate"            - Integer or Float
+     *                  "i-frame-interval"      - Integer
+     *                  optional "stride"       - Integer, defaults to "width"
+     *                  optional "slice-height" - Integer, defaults to "height"
+     *
+     *  @param surface Specify a surface on which to render the output of this
+     *                 decoder.
+     *  @param flags   Specify {@see #CONFIGURE_FLAG_ENCODE} to configure the
+     *                 component as an encoder.
+    */
+    public void configure(
+            Map<String, Object> format, Surface surface, int flags) {
+        String[] keys = null;
+        Object[] values = null;
+
+        if (format != null) {
+            keys = new String[format.size()];
+            values = new Object[format.size()];
+
+            int i = 0;
+            for (Map.Entry<String, Object> entry: format.entrySet()) {
+                keys[i] = entry.getKey();
+                values[i] = entry.getValue();
+                ++i;
+            }
+        }
+
+        native_configure(keys, values, surface, flags);
+    }
+
+    private native final void native_configure(
+            String[] keys, Object[] values, Surface surface, int flags);
+
+    /** After successfully configuring the component, call start. On return
+     *  you can query the component for its input/output buffers.
+    */
+    public native final void start();
+
+    public native final void stop();
+
+    /** Flush both input and output ports of the component, all indices
+     *  previously returned in calls to dequeueInputBuffer and
+     *  dequeueOutputBuffer become invalid.
+    */
+    public native final void flush();
+
+    /** After filling a range of the input buffer at the specified index
+     *  submit it to the component.
+    */
+    public native final void queueInputBuffer(
+            int index,
+            int offset, int size, long presentationTimeUs, int flags);
+
+    // Returns the index of an input buffer to be filled with valid data
+    // or -1 if no such buffer is currently available.
+    // This method will return immediately if timeoutUs == 0, wait indefinitely
+    // for the availability of an input buffer if timeoutUs < 0 or wait up
+    // to "timeoutUs" microseconds if timeoutUs > 0.
+    public native final int dequeueInputBuffer(long timeoutUs);
+
+    // Returns the index of an output buffer that has been successfully
+    // decoded or one of the INFO_* constants below.
+    // The provided "info" will be filled with buffer meta data.
+    public static final int INFO_TRY_AGAIN_LATER        = -1;
+    public static final int INFO_OUTPUT_FORMAT_CHANGED  = -2;
+    public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3;
+
+    /** Dequeue an output buffer, block at most "timeoutUs" microseconds. */
+    public native final int dequeueOutputBuffer(
+            BufferInfo info, long timeoutUs);
+
+    // If you are done with a buffer, use this call to return the buffer to
+    // the codec. If you previously specified a surface when configuring this
+    // video decoder you can optionally render the buffer.
+    public native final void releaseOutputBuffer(int index, boolean render);
+
+    /** Call this after dequeueOutputBuffer signals a format change by returning
+     *  {@see #INFO_OUTPUT_FORMAT_CHANGED}
+     */
+    public native final Map<String, Object> getOutputFormat();
+
+    /** Call this after start() returns and whenever dequeueOutputBuffer
+     *  signals an output buffer change by returning
+     *  {@see #INFO_OUTPUT_BUFFERS_CHANGED}
+     */
+    public native final ByteBuffer[] getBuffers(boolean input);
+
+    private static native final void native_init();
+
+    private native final void native_setup(
+            String name, boolean nameIsType, boolean encoder);
+
+    private native final void native_finalize();
+
+    static {
+        System.loadLibrary("media_jni");
+        native_init();
+    }
+
+    private int mNativeContext;
+}
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
new file mode 100644
index 0000000..6a7f2f5
--- /dev/null
+++ b/media/java/android/media/MediaExtractor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 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.media;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+/**
+ * MediaExtractor
+ * @hide
+*/
+public class MediaExtractor
+{
+    public MediaExtractor(String path) {
+        native_setup(path);
+    }
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+
+    // Make sure you call this when you're done to free up any resources
+    // instead of relying on the garbage collector to do this for you at
+    // some point in the future.
+    public native final void release();
+
+    public native int countTracks();
+    public native Map<String, Object> getTrackFormat(int index);
+
+    // Subsequent calls to "readSampleData", "getSampleTrackIndex" and
+    // "getSampleTime" only retrieve information for the subset of tracks
+    // selected by the call below.
+    // Selecting the same track multiple times has no effect, the track
+    // is only selected once.
+    public native void selectTrack(int index);
+
+    // All selected tracks seek near the requested time. The next sample
+    // returned for each selected track will be a sync sample.
+    public native void seekTo(long timeUs);
+
+    public native boolean advance();
+
+    // Retrieve the current encoded sample and store it in the byte buffer
+    // starting at the given offset.
+    public native int readSampleData(ByteBuffer byteBuf, int offset);
+
+    // Returns the track index the current sample originates from.
+    public native int getSampleTrackIndex();
+
+    // Returns the current sample's presentation time in microseconds.
+    public native long getSampleTime();
+
+    private static native final void native_init();
+    private native final void native_setup(String path);
+    private native final void native_finalize();
+
+    static {
+        System.loadLibrary("media_jni");
+        native_init();
+    }
+
+    private int mNativeContext;
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 23cc0e2..070d2d9 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -2,6 +2,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
+    android_media_MediaCodec.cpp \
+    android_media_MediaExtractor.cpp \
     android_media_MediaPlayer.cpp \
     android_media_MediaRecorder.cpp \
     android_media_MediaScanner.cpp \
@@ -25,6 +27,7 @@
     libcutils \
     libgui \
     libstagefright \
+    libstagefright_foundation \
     libcamera_client \
     libmtp \
     libusbhost \
@@ -39,10 +42,12 @@
     external/tremor/Tremor \
     frameworks/base/core/jni \
     frameworks/base/media/libmedia \
+    frameworks/base/media/libstagefright \
     frameworks/base/media/libstagefright/codecs/amrnb/enc/src \
     frameworks/base/media/libstagefright/codecs/amrnb/common \
     frameworks/base/media/libstagefright/codecs/amrnb/common/include \
     frameworks/base/media/mtp \
+    frameworks/base/include/media/stagefright/openmax \
     $(PV_INCLUDES) \
     $(JNI_H_INCLUDE) \
     $(call include-path-for, corecg graphics)
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
new file mode 100644
index 0000000..43ca263
--- /dev/null
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -0,0 +1,550 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaCodec-JNI"
+#include <utils/Log.h>
+
+#include "android_media_MediaCodec.h"
+
+#include "android_media_Utils.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+#include <surfaceflinger/Surface.h>
+
+namespace android {
+
+// Keep these in sync with their equivalents in MediaCodec.java !!!
+enum {
+    DEQUEUE_INFO_TRY_AGAIN_LATER            = -1,
+    DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED      = -2,
+    DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED     = -3,
+};
+
+struct fields_t {
+    jfieldID context;
+};
+
+static fields_t gFields;
+
+////////////////////////////////////////////////////////////////////////////////
+
+JMediaCodec::JMediaCodec(
+        JNIEnv *env, jobject thiz,
+        const char *name, bool nameIsType, bool encoder)
+    : mClass(NULL),
+      mObject(NULL) {
+    jclass clazz = env->GetObjectClass(thiz);
+    CHECK(clazz != NULL);
+
+    mClass = (jclass)env->NewGlobalRef(clazz);
+    mObject = env->NewWeakGlobalRef(thiz);
+
+    mLooper = new ALooper;
+    mLooper->setName("MediaCodec_looper");
+
+    mLooper->start(
+            false,      // runOnCallingThread
+            false,       // canCallJava
+            PRIORITY_DEFAULT);
+
+    if (nameIsType) {
+        mCodec = MediaCodec::CreateByType(mLooper, name, encoder);
+    } else {
+        mCodec = MediaCodec::CreateByComponentName(mLooper, name);
+    }
+}
+
+status_t JMediaCodec::initCheck() const {
+    return mCodec != NULL ? OK : NO_INIT;
+}
+
+JMediaCodec::~JMediaCodec() {
+    mCodec->stop();
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    env->DeleteWeakGlobalRef(mObject);
+    mObject = NULL;
+    env->DeleteGlobalRef(mClass);
+    mClass = NULL;
+}
+
+status_t JMediaCodec::configure(
+        const sp<AMessage> &format,
+        const sp<ISurfaceTexture> &surfaceTexture,
+        int flags) {
+    sp<SurfaceTextureClient> client;
+    if (surfaceTexture != NULL) {
+        client = new SurfaceTextureClient(surfaceTexture);
+    }
+    return mCodec->configure(format, client, flags);
+}
+
+status_t JMediaCodec::start() {
+    return mCodec->start();
+}
+
+status_t JMediaCodec::stop() {
+    return mCodec->stop();
+}
+
+status_t JMediaCodec::flush() {
+    return mCodec->flush();
+}
+
+status_t JMediaCodec::queueInputBuffer(
+        size_t index,
+        size_t offset, size_t size, int64_t timeUs, uint32_t flags) {
+    return mCodec->queueInputBuffer(index, offset, size, timeUs, flags);
+}
+
+status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
+    return mCodec->dequeueInputBuffer(index, timeoutUs);
+}
+
+status_t JMediaCodec::dequeueOutputBuffer(
+        JNIEnv *env, jobject bufferInfo, size_t *index, int64_t timeoutUs) {
+    size_t size, offset;
+    int64_t timeUs;
+    uint32_t flags;
+    status_t err;
+    if ((err = mCodec->dequeueOutputBuffer(
+                    index, &size, &offset, &timeUs, &flags, timeoutUs)) != OK) {
+        return err;
+    }
+
+    jclass clazz = env->FindClass("android/media/MediaCodec$BufferInfo");
+
+    jmethodID method = env->GetMethodID(clazz, "set", "(IIJI)V");
+    env->CallVoidMethod(bufferInfo, method, offset, size, timeUs, flags);
+
+    return OK;
+}
+
+status_t JMediaCodec::releaseOutputBuffer(size_t index, bool render) {
+    return render
+        ? mCodec->renderOutputBufferAndRelease(index)
+        : mCodec->releaseOutputBuffer(index);
+}
+
+status_t JMediaCodec::getOutputFormat(JNIEnv *env, jobject *format) const {
+    sp<AMessage> msg;
+    status_t err;
+    if ((err = mCodec->getOutputFormat(&msg)) != OK) {
+        return err;
+    }
+
+    return ConvertMessageToMap(env, msg, format);
+}
+
+status_t JMediaCodec::getBuffers(
+        JNIEnv *env, bool input, jobjectArray *bufArray) const {
+    Vector<sp<ABuffer> > buffers;
+
+    status_t err =
+        input
+            ? mCodec->getInputBuffers(&buffers)
+            : mCodec->getOutputBuffers(&buffers);
+
+    if (err != OK) {
+        return err;
+    }
+
+    jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer");
+
+    *bufArray = (jobjectArray)env->NewObjectArray(
+            buffers.size(), byteBufferClass, NULL);
+
+    for (size_t i = 0; i < buffers.size(); ++i) {
+        const sp<ABuffer> &buffer = buffers.itemAt(i);
+
+        jobject byteBuffer =
+            env->NewDirectByteBuffer(
+                buffer->base(),
+                buffer->capacity());
+
+        env->SetObjectArrayElement(
+                *bufArray, i, byteBuffer);
+
+        env->DeleteLocalRef(byteBuffer);
+        byteBuffer = NULL;
+    }
+
+    return OK;
+}
+
+}  // namespace android
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace android;
+
+static sp<JMediaCodec> setMediaCodec(
+        JNIEnv *env, jobject thiz, const sp<JMediaCodec> &codec) {
+    sp<JMediaCodec> old = (JMediaCodec *)env->GetIntField(thiz, gFields.context);
+    if (codec != NULL) {
+        codec->incStrong(thiz);
+    }
+    if (old != NULL) {
+        old->decStrong(thiz);
+    }
+    env->SetIntField(thiz, gFields.context, (int)codec.get());
+
+    return old;
+}
+
+static sp<JMediaCodec> getMediaCodec(JNIEnv *env, jobject thiz) {
+    return (JMediaCodec *)env->GetIntField(thiz, gFields.context);
+}
+
+static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) {
+    setMediaCodec(env, thiz, NULL);
+}
+
+static jint throwExceptionAsNecessary(JNIEnv *env, status_t err) {
+    switch (err) {
+        case OK:
+            return 0;
+
+        case -EAGAIN:
+            return DEQUEUE_INFO_TRY_AGAIN_LATER;
+
+        case INFO_FORMAT_CHANGED:
+            return DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED;
+
+        case INFO_OUTPUT_BUFFERS_CHANGED:
+            return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED;
+
+        default:
+        {
+            jniThrowException(env, "java/lang/IllegalStateException", NULL);
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static void android_media_MediaCodec_native_configure(
+        JNIEnv *env,
+        jobject thiz,
+        jobjectArray keys, jobjectArray values,
+        jobject jsurface,
+        jint flags) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    sp<AMessage> format;
+    status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    sp<ISurfaceTexture> surfaceTexture;
+    if (jsurface != NULL) {
+        sp<Surface> surface(Surface_getSurface(env, jsurface));
+        if (surface != NULL) {
+            surfaceTexture = surface->getSurfaceTexture();
+        } else {
+            jniThrowException(
+                    env,
+                    "java/lang/IllegalArgumentException",
+                    "The surface has been released");
+            return;
+        }
+    }
+
+    err = codec->configure(format, surfaceTexture, flags);
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_start");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->start();
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_stop");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->stop();
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_flush");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->flush();
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_queueInputBuffer(
+        JNIEnv *env,
+        jobject thiz,
+        jint index,
+        jint offset,
+        jint size,
+        jlong timestampUs,
+        jint flags) {
+    ALOGV("android_media_MediaCodec_queueInputBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->queueInputBuffer(
+            index, offset, size, timestampUs, flags);
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static jint android_media_MediaCodec_dequeueInputBuffer(
+        JNIEnv *env, jobject thiz, jlong timeoutUs) {
+    ALOGV("android_media_MediaCodec_dequeueInputBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1;
+    }
+
+    size_t index;
+    status_t err = codec->dequeueInputBuffer(&index, timeoutUs);
+
+    if (err == OK) {
+        return index;
+    }
+
+    return throwExceptionAsNecessary(env, err);
+}
+
+static jint android_media_MediaCodec_dequeueOutputBuffer(
+        JNIEnv *env, jobject thiz, jobject bufferInfo, jlong timeoutUs) {
+    ALOGV("android_media_MediaCodec_dequeueOutputBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    size_t index;
+    status_t err = codec->dequeueOutputBuffer(
+            env, bufferInfo, &index, timeoutUs);
+
+    if (err == OK) {
+        return index;
+    }
+
+    return throwExceptionAsNecessary(env, err);
+}
+
+static void android_media_MediaCodec_releaseOutputBuffer(
+        JNIEnv *env, jobject thiz, jint index, jboolean render) {
+    ALOGV("android_media_MediaCodec_renderOutputBufferAndRelease");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->releaseOutputBuffer(index, render);
+
+    throwExceptionAsNecessary(env, err);
+}
+
+static jobject android_media_MediaCodec_getOutputFormat(
+        JNIEnv *env, jobject thiz) {
+    ALOGV("android_media_MediaCodec_getOutputFormat");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject format;
+    status_t err = codec->getOutputFormat(env, &format);
+
+    if (err == OK) {
+        return format;
+    }
+
+    throwExceptionAsNecessary(env, err);
+
+    return NULL;
+}
+
+static jobjectArray android_media_MediaCodec_getBuffers(
+        JNIEnv *env, jobject thiz, jboolean input) {
+    ALOGV("android_media_MediaCodec_getBuffers");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobjectArray buffers;
+    status_t err = codec->getBuffers(env, input, &buffers);
+
+    if (err == OK) {
+        return buffers;
+    }
+
+    throwExceptionAsNecessary(env, err);
+
+    return NULL;
+}
+
+static void android_media_MediaCodec_native_init(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/media/MediaCodec");
+    CHECK(clazz != NULL);
+
+    gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
+    CHECK(gFields.context != NULL);
+}
+
+static void android_media_MediaCodec_native_setup(
+        JNIEnv *env, jobject thiz,
+        jstring name, jboolean nameIsType, jboolean encoder) {
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    const char *tmp = env->GetStringUTFChars(name, NULL);
+
+    if (tmp == NULL) {
+        return;
+    }
+
+    sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
+
+    status_t err = codec->initCheck();
+
+    env->ReleaseStringUTFChars(name, tmp);
+    tmp = NULL;
+
+    if (err != OK) {
+        jniThrowException(
+                env,
+                "java/io/IOException",
+                "Failed to allocate component instance");
+        return;
+    }
+
+    setMediaCodec(env,thiz, codec);
+}
+
+static void android_media_MediaCodec_native_finalize(
+        JNIEnv *env, jobject thiz) {
+    android_media_MediaCodec_release(env, thiz);
+}
+
+static JNINativeMethod gMethods[] = {
+    { "release", "()V", (void *)android_media_MediaCodec_release },
+
+    { "native_configure",
+      "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;I)V",
+      (void *)android_media_MediaCodec_native_configure },
+
+    { "start", "()V", (void *)android_media_MediaCodec_start },
+    { "stop", "()V", (void *)android_media_MediaCodec_stop },
+    { "flush", "()V", (void *)android_media_MediaCodec_flush },
+
+    { "queueInputBuffer", "(IIIJI)V",
+      (void *)android_media_MediaCodec_queueInputBuffer },
+
+    { "dequeueInputBuffer", "(J)I",
+      (void *)android_media_MediaCodec_dequeueInputBuffer },
+
+    { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
+      (void *)android_media_MediaCodec_dequeueOutputBuffer },
+
+    { "releaseOutputBuffer", "(IZ)V",
+      (void *)android_media_MediaCodec_releaseOutputBuffer },
+
+    { "getOutputFormat", "()Ljava/util/Map;",
+      (void *)android_media_MediaCodec_getOutputFormat },
+
+    { "getBuffers", "(Z)[Ljava/nio/ByteBuffer;",
+      (void *)android_media_MediaCodec_getBuffers },
+
+    { "native_init", "()V", (void *)android_media_MediaCodec_native_init },
+
+    { "native_setup", "(Ljava/lang/String;ZZ)V",
+      (void *)android_media_MediaCodec_native_setup },
+
+    { "native_finalize", "()V",
+      (void *)android_media_MediaCodec_native_finalize },
+};
+
+int register_android_media_MediaCodec(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaCodec", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
new file mode 100644
index 0000000..6b1257d
--- /dev/null
+++ b/media/jni/android_media_MediaCodec.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIACODEC_H_
+#define _ANDROID_MEDIA_MEDIACODEC_H_
+
+#include "jni.h"
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ALooper;
+struct AMessage;
+struct ISurfaceTexture;
+struct MediaCodec;
+
+struct JMediaCodec : public RefBase {
+    JMediaCodec(
+            JNIEnv *env, jobject thiz,
+            const char *name, bool nameIsType, bool encoder);
+
+    status_t initCheck() const;
+
+    status_t configure(
+            const sp<AMessage> &format,
+            const sp<ISurfaceTexture> &surfaceTexture,
+            int flags);
+
+    status_t start();
+    status_t stop();
+
+    status_t flush();
+
+    status_t queueInputBuffer(
+            size_t index,
+            size_t offset, size_t size, int64_t timeUs, uint32_t flags);
+
+    status_t dequeueInputBuffer(size_t *index, int64_t timeoutUs);
+
+    status_t dequeueOutputBuffer(
+            JNIEnv *env, jobject bufferInfo, size_t *index, int64_t timeoutUs);
+
+    status_t releaseOutputBuffer(size_t index, bool render);
+
+    status_t getOutputFormat(JNIEnv *env, jobject *format) const;
+
+    status_t getBuffers(
+            JNIEnv *env, bool input, jobjectArray *bufArray) const;
+
+protected:
+    virtual ~JMediaCodec();
+
+private:
+    jclass mClass;
+    jweak mObject;
+
+    sp<ALooper> mLooper;
+    sp<MediaCodec> mCodec;
+
+    DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
+};
+
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_MEDIACODEC_H_
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
new file mode 100644
index 0000000..4757adf
--- /dev/null
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaExtractor-JNI"
+#include <utils/Log.h>
+
+#include "android_media_MediaExtractor.h"
+
+#include "android_media_Utils.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/NuMediaExtractor.h>
+
+namespace android {
+
+struct fields_t {
+    jfieldID context;
+};
+
+static fields_t gFields;
+
+////////////////////////////////////////////////////////////////////////////////
+
+JMediaExtractor::JMediaExtractor(JNIEnv *env, jobject thiz)
+    : mClass(NULL),
+      mObject(NULL) {
+    jclass clazz = env->GetObjectClass(thiz);
+    CHECK(clazz != NULL);
+
+    mClass = (jclass)env->NewGlobalRef(clazz);
+    mObject = env->NewWeakGlobalRef(thiz);
+
+    mImpl = new NuMediaExtractor;
+}
+
+JMediaExtractor::~JMediaExtractor() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    env->DeleteWeakGlobalRef(mObject);
+    mObject = NULL;
+    env->DeleteGlobalRef(mClass);
+    mClass = NULL;
+}
+
+status_t JMediaExtractor::setDataSource(const char *path) {
+    return mImpl->setDataSource(path);
+}
+
+size_t JMediaExtractor::countTracks() const {
+    return mImpl->countTracks();
+}
+
+status_t JMediaExtractor::getTrackFormat(size_t index, jobject *format) const {
+    sp<AMessage> msg;
+    status_t err;
+    if ((err = mImpl->getTrackFormat(index, &msg)) != OK) {
+        return err;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    return ConvertMessageToMap(env, msg, format);
+}
+
+status_t JMediaExtractor::selectTrack(size_t index) {
+    return mImpl->selectTrack(index);
+}
+
+status_t JMediaExtractor::seekTo(int64_t timeUs) {
+    return mImpl->seekTo(timeUs);
+}
+
+status_t JMediaExtractor::advance() {
+    return mImpl->advance();
+}
+
+status_t JMediaExtractor::readSampleData(
+        jobject byteBuf, size_t offset, size_t *sampleSize) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    void *dst = env->GetDirectBufferAddress(byteBuf);
+
+    if (dst == NULL) {
+        // XXX if dst is NULL also fall back to "array()"
+        return INVALID_OPERATION;
+    }
+
+    jlong dstSize = env->GetDirectBufferCapacity(byteBuf);
+
+    if (dstSize < offset) {
+        return -ERANGE;
+    }
+
+    sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset);
+
+    status_t err = mImpl->readSampleData(buffer);
+
+    if (err != OK) {
+        return err;
+    }
+
+    *sampleSize = buffer->size();
+
+    return OK;
+}
+
+status_t JMediaExtractor::getSampleTrackIndex(size_t *trackIndex) {
+    return mImpl->getSampleTrackIndex(trackIndex);
+}
+
+status_t JMediaExtractor::getSampleTime(int64_t *sampleTimeUs) {
+    return mImpl->getSampleTime(sampleTimeUs);
+}
+
+}  // namespace android
+
+////////////////////////////////////////////////////////////////////////////////
+
+using namespace android;
+
+static sp<JMediaExtractor> setMediaExtractor(
+        JNIEnv *env, jobject thiz, const sp<JMediaExtractor> &extractor) {
+    sp<JMediaExtractor> old =
+        (JMediaExtractor *)env->GetIntField(thiz, gFields.context);
+
+    if (extractor != NULL) {
+        extractor->incStrong(thiz);
+    }
+    if (old != NULL) {
+        old->decStrong(thiz);
+    }
+    env->SetIntField(thiz, gFields.context, (int)extractor.get());
+
+    return old;
+}
+
+static sp<JMediaExtractor> getMediaExtractor(JNIEnv *env, jobject thiz) {
+    return (JMediaExtractor *)env->GetIntField(thiz, gFields.context);
+}
+
+static void android_media_MediaExtractor_release(JNIEnv *env, jobject thiz) {
+    setMediaExtractor(env, thiz, NULL);
+}
+
+static jint android_media_MediaExtractor_countTracks(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    return extractor->countTracks();
+}
+
+static jobject android_media_MediaExtractor_getTrackFormat(
+        JNIEnv *env, jobject thiz, jint index) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject format;
+    status_t err = extractor->getTrackFormat(index, &format);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return NULL;
+    }
+
+    return format;
+}
+
+static void android_media_MediaExtractor_selectTrack(
+        JNIEnv *env, jobject thiz, jint index) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = extractor->selectTrack(index);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+}
+
+static void android_media_MediaExtractor_seekTo(
+        JNIEnv *env, jobject thiz, jlong timeUs) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = extractor->seekTo(timeUs);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+}
+
+static jboolean android_media_MediaExtractor_advance(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return false;
+    }
+
+    status_t err = extractor->advance();
+
+    if (err == ERROR_END_OF_STREAM) {
+        return false;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return true;
+}
+
+static jint android_media_MediaExtractor_readSampleData(
+        JNIEnv *env, jobject thiz, jobject byteBuf, jint offset) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1;
+    }
+
+    size_t sampleSize;
+    status_t err = extractor->readSampleData(byteBuf, offset, &sampleSize);
+
+    if (err == ERROR_END_OF_STREAM) {
+        return -1;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return sampleSize;
+}
+
+static jint android_media_MediaExtractor_getSampleTrackIndex(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1;
+    }
+
+    size_t trackIndex;
+    status_t err = extractor->getSampleTrackIndex(&trackIndex);
+
+    if (err == ERROR_END_OF_STREAM) {
+        return -1;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return trackIndex;
+}
+
+static jlong android_media_MediaExtractor_getSampleTime(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1ll;
+    }
+
+    int64_t sampleTimeUs;
+    status_t err = extractor->getSampleTime(&sampleTimeUs);
+
+    if (err == ERROR_END_OF_STREAM) {
+        return -1ll;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return sampleTimeUs;
+}
+
+static void android_media_MediaExtractor_native_init(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/media/MediaExtractor");
+    CHECK(clazz != NULL);
+
+    gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
+    CHECK(gFields.context != NULL);
+
+    DataSource::RegisterDefaultSniffers();
+}
+
+static void android_media_MediaExtractor_native_setup(
+        JNIEnv *env, jobject thiz, jstring path) {
+    sp<JMediaExtractor> extractor = new JMediaExtractor(env, thiz);
+
+    if (path == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    const char *tmp = env->GetStringUTFChars(path, NULL);
+
+    if (tmp == NULL) {
+        return;
+    }
+
+    status_t err = extractor->setDataSource(tmp);
+
+    env->ReleaseStringUTFChars(path, tmp);
+    tmp = NULL;
+
+    if (err != OK) {
+        jniThrowException(
+                env,
+                "java/io/IOException",
+                "Failed to instantiate extractor.");
+        return;
+    }
+
+    setMediaExtractor(env,thiz, extractor);
+}
+
+static void android_media_MediaExtractor_native_finalize(
+        JNIEnv *env, jobject thiz) {
+    android_media_MediaExtractor_release(env, thiz);
+}
+
+static JNINativeMethod gMethods[] = {
+    { "release", "()V", (void *)android_media_MediaExtractor_release },
+
+    { "countTracks", "()I", (void *)android_media_MediaExtractor_countTracks },
+
+    { "getTrackFormat", "(I)Ljava/util/Map;",
+        (void *)android_media_MediaExtractor_getTrackFormat },
+
+    { "selectTrack", "(I)V", (void *)android_media_MediaExtractor_selectTrack },
+
+    { "seekTo", "(J)V", (void *)android_media_MediaExtractor_seekTo },
+
+    { "advance", "()Z", (void *)android_media_MediaExtractor_advance },
+
+    { "readSampleData", "(Ljava/nio/ByteBuffer;I)I",
+        (void *)android_media_MediaExtractor_readSampleData },
+
+    { "getSampleTrackIndex", "()I",
+        (void *)android_media_MediaExtractor_getSampleTrackIndex },
+
+    { "getSampleTime", "()J",
+        (void *)android_media_MediaExtractor_getSampleTime },
+
+    { "native_init", "()V", (void *)android_media_MediaExtractor_native_init },
+
+    { "native_setup", "(Ljava/lang/String;)V",
+      (void *)android_media_MediaExtractor_native_setup },
+
+    { "native_finalize", "()V",
+      (void *)android_media_MediaExtractor_native_finalize },
+};
+
+int register_android_media_MediaExtractor(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaExtractor", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
new file mode 100644
index 0000000..70e58c6
--- /dev/null
+++ b/media/jni/android_media_MediaExtractor.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
+#define _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include "jni.h"
+
+namespace android {
+
+struct NuMediaExtractor;
+
+struct JMediaExtractor : public RefBase {
+    JMediaExtractor(JNIEnv *env, jobject thiz);
+
+    status_t setDataSource(const char *path);
+
+    size_t countTracks() const;
+    status_t getTrackFormat(size_t index, jobject *format) const;
+
+    status_t selectTrack(size_t index);
+
+    status_t seekTo(int64_t timeUs);
+
+    status_t advance();
+    status_t readSampleData(jobject byteBuf, size_t offset, size_t *sampleSize);
+    status_t getSampleTrackIndex(size_t *trackIndex);
+    status_t getSampleTime(int64_t *sampleTimeUs);
+
+protected:
+    virtual ~JMediaExtractor();
+
+private:
+    jclass mClass;
+    jweak mObject;
+    sp<NuMediaExtractor> mImpl;
+
+    DISALLOW_EVIL_CONSTRUCTORS(JMediaExtractor);
+};
+
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 8ff9dd3..199d56e4 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -810,6 +810,8 @@
                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));
 }
 
+extern int register_android_media_MediaCodec(JNIEnv *env);
+extern int register_android_media_MediaExtractor(JNIEnv *env);
 extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
 extern int register_android_media_MediaRecorder(JNIEnv *env);
 extern int register_android_media_MediaScanner(JNIEnv *env);
@@ -881,6 +883,16 @@
         goto bail;
     }
 
+    if (register_android_media_MediaCodec(env) < 0) {
+        ALOGE("ERROR: MediaCodec native registration failed");
+        goto bail;
+    }
+
+    if (register_android_media_MediaExtractor(env) < 0) {
+        ALOGE("ERROR: MediaCodec native registration failed");
+        goto bail;
+    }
+
     /* success -- return valid version number */
     result = JNI_VERSION_1_4;
 
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 47963b1..7dacdcd 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -20,6 +20,10 @@
 #include <utils/Log.h>
 #include "android_media_Utils.h"
 
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+
 namespace android {
 
 bool ConvertKeyValueArraysToKeyedVector(
@@ -71,5 +75,266 @@
     return true;
 }
 
+static jobject makeIntegerObject(JNIEnv *env, int32_t value) {
+    jclass clazz = env->FindClass("java/lang/Integer");
+    CHECK(clazz != NULL);
+
+    jmethodID integerConstructID = env->GetMethodID(clazz, "<init>", "(I)V");
+    CHECK(integerConstructID != NULL);
+
+    return env->NewObject(clazz, integerConstructID, value);
+}
+
+static jobject makeFloatObject(JNIEnv *env, float value) {
+    jclass clazz = env->FindClass("java/lang/Float");
+    CHECK(clazz != NULL);
+
+    jmethodID floatConstructID = env->GetMethodID(clazz, "<init>", "(F)V");
+    CHECK(floatConstructID != NULL);
+
+    return env->NewObject(clazz, floatConstructID, value);
+}
+
+static jobject makeByteBufferObject(
+        JNIEnv *env, const void *data, size_t size) {
+    jbyteArray byteArrayObj = env->NewByteArray(size);
+    env->SetByteArrayRegion(byteArrayObj, 0, size, (const jbyte *)data);
+
+    jclass clazz = env->FindClass("java/nio/ByteBuffer");
+    CHECK(clazz != NULL);
+
+    jmethodID byteBufWrapID =
+        env->GetStaticMethodID(clazz, "wrap", "([B)Ljava/nio/ByteBuffer;");
+    CHECK(byteBufWrapID != NULL);
+
+    jobject byteBufObj = env->CallStaticObjectMethod(
+            clazz, byteBufWrapID, byteArrayObj);
+
+    env->DeleteLocalRef(byteArrayObj); byteArrayObj = NULL;
+
+    return byteBufObj;
+}
+
+status_t ConvertMessageToMap(
+        JNIEnv *env, const sp<AMessage> &msg, jobject *map) {
+    jclass hashMapClazz = env->FindClass("java/util/HashMap");
+
+    if (hashMapClazz == NULL) {
+        return -EINVAL;
+    }
+
+    jmethodID hashMapConstructID =
+        env->GetMethodID(hashMapClazz, "<init>", "()V");
+
+    if (hashMapConstructID == NULL) {
+        return -EINVAL;
+    }
+
+    jmethodID hashMapPutID =
+        env->GetMethodID(
+                hashMapClazz,
+                "put",
+                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+    if (hashMapPutID == NULL) {
+        return -EINVAL;
+    }
+
+    jobject hashMap = env->NewObject(hashMapClazz, hashMapConstructID);
+
+    for (size_t i = 0; i < msg->countEntries(); ++i) {
+        AMessage::Type valueType;
+        const char *key = msg->getEntryNameAt(i, &valueType);
+
+        jobject valueObj = NULL;
+
+        switch (valueType) {
+            case AMessage::kTypeInt32:
+            {
+                int32_t val;
+                CHECK(msg->findInt32(key, &val));
+
+                valueObj = makeIntegerObject(env, val);
+                break;
+            }
+
+            case AMessage::kTypeFloat:
+            {
+                float val;
+                CHECK(msg->findFloat(key, &val));
+
+                valueObj = makeFloatObject(env, val);
+                break;
+            }
+
+            case AMessage::kTypeString:
+            {
+                AString val;
+                CHECK(msg->findString(key, &val));
+
+                valueObj = env->NewStringUTF(val.c_str());
+                break;
+            }
+
+            case AMessage::kTypeObject:
+            {
+                sp<RefBase> obj;
+                CHECK(msg->findObject(key, &obj));
+
+                // XXX dangerous, object is not guaranteed to be a buffer.
+                sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+
+                valueObj = makeByteBufferObject(
+                        env, buffer->data(), buffer->size());
+                break;
+            }
+
+            default:
+                break;
+        }
+
+        if (valueObj != NULL) {
+            jstring keyObj = env->NewStringUTF(key);
+
+            jobject res = env->CallObjectMethod(
+                    hashMap, hashMapPutID, keyObj, valueObj);
+
+            env->DeleteLocalRef(keyObj); keyObj = NULL;
+            env->DeleteLocalRef(valueObj); valueObj = NULL;
+        }
+    }
+
+    *map = hashMap;
+
+    return OK;
+}
+
+status_t ConvertKeyValueArraysToMessage(
+        JNIEnv *env, jobjectArray keys, jobjectArray values,
+        sp<AMessage> *out) {
+    jclass stringClass = env->FindClass("java/lang/String");
+    CHECK(stringClass != NULL);
+
+    jclass integerClass = env->FindClass("java/lang/Integer");
+    CHECK(integerClass != NULL);
+
+    jclass floatClass = env->FindClass("java/lang/Float");
+    CHECK(floatClass != NULL);
+
+    jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
+    CHECK(byteBufClass != NULL);
+
+    sp<AMessage> msg = new AMessage;
+
+    jsize numEntries = 0;
+
+    if (keys != NULL) {
+        if (values == NULL) {
+            return -EINVAL;
+        }
+
+        numEntries = env->GetArrayLength(keys);
+
+        if (numEntries != env->GetArrayLength(values)) {
+            return -EINVAL;
+        }
+    } else if (values != NULL) {
+        return -EINVAL;
+    }
+
+    for (jsize i = 0; i < numEntries; ++i) {
+        jobject keyObj = env->GetObjectArrayElement(keys, i);
+
+        if (!env->IsInstanceOf(keyObj, stringClass)) {
+            return -EINVAL;
+        }
+
+        const char *tmp = env->GetStringUTFChars((jstring)keyObj, NULL);
+
+        if (tmp == NULL) {
+            return -ENOMEM;
+        }
+
+        AString key = tmp;
+
+        env->ReleaseStringUTFChars((jstring)keyObj, tmp);
+        tmp = NULL;
+
+        jobject valueObj = env->GetObjectArrayElement(values, i);
+
+        if (env->IsInstanceOf(valueObj, stringClass)) {
+            const char *value = env->GetStringUTFChars((jstring)valueObj, NULL);
+
+            if (value == NULL) {
+                return -ENOMEM;
+            }
+
+            msg->setString(key.c_str(), value);
+
+            env->ReleaseStringUTFChars((jstring)valueObj, value);
+            value = NULL;
+        } else if (env->IsInstanceOf(valueObj, integerClass)) {
+            jmethodID intValueID =
+                env->GetMethodID(integerClass, "intValue", "()I");
+            CHECK(intValueID != NULL);
+
+            jint value = env->CallIntMethod(valueObj, intValueID);
+
+            msg->setInt32(key.c_str(), value);
+        } else if (env->IsInstanceOf(valueObj, floatClass)) {
+            jmethodID floatValueID =
+                env->GetMethodID(floatClass, "floatValue", "()F");
+            CHECK(floatValueID != NULL);
+
+            jfloat value = env->CallFloatMethod(valueObj, floatValueID);
+
+            msg->setFloat(key.c_str(), value);
+        } else if (env->IsInstanceOf(valueObj, byteBufClass)) {
+            jmethodID positionID =
+                env->GetMethodID(byteBufClass, "position", "()I");
+            CHECK(positionID != NULL);
+
+            jmethodID limitID =
+                env->GetMethodID(byteBufClass, "limit", "()I");
+            CHECK(limitID != NULL);
+
+            jint position = env->CallIntMethod(valueObj, positionID);
+            jint limit = env->CallIntMethod(valueObj, limitID);
+
+            sp<ABuffer> buffer = new ABuffer(limit - position);
+
+            void *data = env->GetDirectBufferAddress(valueObj);
+
+            if (data != NULL) {
+                memcpy(buffer->data(),
+                       (const uint8_t *)data + position,
+                       buffer->size());
+            } else {
+                jmethodID arrayID =
+                    env->GetMethodID(byteBufClass, "array", "()[B");
+                CHECK(arrayID != NULL);
+
+                jbyteArray byteArray =
+                    (jbyteArray)env->CallObjectMethod(valueObj, arrayID);
+                CHECK(byteArray != NULL);
+
+                env->GetByteArrayRegion(
+                        byteArray,
+                        position,
+                        buffer->size(),
+                        (jbyte *)buffer->data());
+
+                env->DeleteLocalRef(byteArray); byteArray = NULL;
+            }
+
+            msg->setObject(key.c_str(), buffer);
+        }
+    }
+
+    *out = msg;
+
+    return OK;
+}
+
 }  // namespace android
 
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index a2c155a..635bceb 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -33,6 +33,14 @@
     JNIEnv *env, jobjectArray keys, jobjectArray values,
     KeyedVector<String8, String8>* vector);
 
+struct AMessage;
+status_t ConvertMessageToMap(
+        JNIEnv *env, const sp<AMessage> &msg, jobject *map);
+
+status_t ConvertKeyValueArraysToMessage(
+        JNIEnv *env, jobjectArray keys, jobjectArray values,
+        sp<AMessage> *msg);
+
 };  // namespace android
 
 #endif //  _ANDROID_MEDIA_UTILS_H_
diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp
index 19b7e32..6808aa2 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -202,7 +202,7 @@
 status_t AudioEffect::setEnabled(bool enabled)
 {
     if (mStatus != NO_ERROR) {
-        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
+        return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus;
     }
 
     status_t status = NO_ERROR;
@@ -263,7 +263,7 @@
 status_t AudioEffect::setParameter(effect_param_t *param)
 {
     if (mStatus != NO_ERROR) {
-        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
+        return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus;
     }
 
     if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -281,7 +281,7 @@
 status_t AudioEffect::setParameterDeferred(effect_param_t *param)
 {
     if (mStatus != NO_ERROR) {
-        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
+        return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus;
     }
 
     if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -307,7 +307,7 @@
 status_t AudioEffect::setParameterCommit()
 {
     if (mStatus != NO_ERROR) {
-        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
+        return (mStatus == ALREADY_EXISTS) ? (status_t) INVALID_OPERATION : mStatus;
     }
 
     Mutex::Autolock _l(mCblk->lock);
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index b731d0f..04415cd 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -387,10 +387,10 @@
                      audio ? "audio" : "video");
 
                 mRenderer->queueEOS(audio, UNKNOWN_ERROR);
-            } else {
-                CHECK_EQ((int)what, (int)ACodec::kWhatDrainThisBuffer);
-
+            } else if (what == ACodec::kWhatDrainThisBuffer) {
                 renderBuffer(audio, codecRequest);
+            } else {
+                ALOGV("Unhandled codec notification %d.", what);
             }
 
             break;
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index ca44ea3..605b497 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -171,6 +171,9 @@
 
 private:
     void onSetup(const sp<AMessage> &msg);
+    void onAllocateComponent(const sp<AMessage> &msg);
+    void onConfigureComponent(const sp<AMessage> &msg);
+    void onStart();
 
     DISALLOW_EVIL_CONSTRUCTORS(UninitializedState);
 };
@@ -265,6 +268,8 @@
 private:
     void changeStateIfWeOwnAllBuffers();
 
+    bool mComponentNowIdle;
+
     DISALLOW_EVIL_CONSTRUCTORS(ExecutingToIdleState);
 };
 
@@ -309,7 +314,8 @@
 
 ACodec::ACodec()
     : mNode(NULL),
-      mSentFormat(false) {
+      mSentFormat(false),
+      mIsEncoder(false) {
     mUninitializedState = new UninitializedState(this);
     mLoadedToIdleState = new LoadedToIdleState(this);
     mIdleToExecutingState = new IdleToExecutingState(this);
@@ -341,6 +347,22 @@
     msg->post();
 }
 
+void ACodec::initiateAllocateComponent(const sp<AMessage> &msg) {
+    msg->setWhat(kWhatAllocateComponent);
+    msg->setTarget(id());
+    msg->post();
+}
+
+void ACodec::initiateConfigureComponent(const sp<AMessage> &msg) {
+    msg->setWhat(kWhatConfigureComponent);
+    msg->setTarget(id());
+    msg->post();
+}
+
+void ACodec::initiateStart() {
+    (new AMessage(kWhatStart, id()))->post();
+}
+
 void ACodec::signalFlush() {
     ALOGV("[%s] signalFlush", mComponentName.c_str());
     (new AMessage(kWhatFlush, id()))->post();
@@ -360,62 +382,75 @@
     CHECK(mDealer[portIndex] == NULL);
     CHECK(mBuffers[portIndex].isEmpty());
 
+    status_t err;
     if (mNativeWindow != NULL && portIndex == kPortIndexOutput) {
-        return allocateOutputBuffersFromNativeWindow();
+        err = allocateOutputBuffersFromNativeWindow();
+    } else {
+        OMX_PARAM_PORTDEFINITIONTYPE def;
+        InitOMXParams(&def);
+        def.nPortIndex = portIndex;
+
+        err = mOMX->getParameter(
+                mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+        if (err == OK) {
+            ALOGV("[%s] Allocating %lu buffers of size %lu on %s port",
+                    mComponentName.c_str(),
+                    def.nBufferCountActual, def.nBufferSize,
+                    portIndex == kPortIndexInput ? "input" : "output");
+
+            size_t totalSize = def.nBufferCountActual * def.nBufferSize;
+            mDealer[portIndex] = new MemoryDealer(totalSize, "ACodec");
+
+            for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
+                sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
+                CHECK(mem.get() != NULL);
+
+                IOMX::buffer_id buffer;
+
+                if (!strncasecmp(
+                            mComponentName.c_str(), "OMX.TI.DUCATI1.VIDEO.", 21)) {
+                    if (portIndex == kPortIndexInput && i == 0) {
+                        // Only log this warning once per allocation round.
+
+                        ALOGW("OMX.TI.DUCATI1.VIDEO.* require the use of "
+                             "OMX_AllocateBuffer instead of the preferred "
+                             "OMX_UseBuffer. Vendor must fix this.");
+                    }
+
+                    err = mOMX->allocateBufferWithBackup(
+                            mNode, portIndex, mem, &buffer);
+                } else {
+                    err = mOMX->useBuffer(mNode, portIndex, mem, &buffer);
+                }
+
+                BufferInfo info;
+                info.mBufferID = buffer;
+                info.mStatus = BufferInfo::OWNED_BY_US;
+                info.mData = new ABuffer(mem->pointer(), def.nBufferSize);
+                mBuffers[portIndex].push(info);
+            }
+        }
     }
 
-    OMX_PARAM_PORTDEFINITIONTYPE def;
-    InitOMXParams(&def);
-    def.nPortIndex = portIndex;
-
-    status_t err = mOMX->getParameter(
-            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
-
     if (err != OK) {
         return err;
     }
 
-    ALOGV("[%s] Allocating %lu buffers of size %lu on %s port",
-            mComponentName.c_str(),
-            def.nBufferCountActual, def.nBufferSize,
-            portIndex == kPortIndexInput ? "input" : "output");
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("what", ACodec::kWhatBuffersAllocated);
 
-    size_t totalSize = def.nBufferCountActual * def.nBufferSize;
-    mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec");
+    notify->setInt32("portIndex", portIndex);
+    for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) {
+        AString name = StringPrintf("buffer-id_%d", i);
+        notify->setPointer(name.c_str(), mBuffers[portIndex][i].mBufferID);
 
-    for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
-        sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
-        CHECK(mem.get() != NULL);
-
-        IOMX::buffer_id buffer;
-
-        if (!strcasecmp(
-                    mComponentName.c_str(), "OMX.TI.DUCATI1.VIDEO.DECODER")) {
-            if (portIndex == kPortIndexInput && i == 0) {
-                // Only log this warning once per allocation round.
-
-                ALOGW("OMX.TI.DUCATI1.VIDEO.DECODER requires the use of "
-                     "OMX_AllocateBuffer instead of the preferred "
-                     "OMX_UseBuffer. Vendor must fix this.");
-            }
-
-            err = mOMX->allocateBufferWithBackup(
-                    mNode, portIndex, mem, &buffer);
-        } else {
-            err = mOMX->useBuffer(mNode, portIndex, mem, &buffer);
-        }
-
-        if (err != OK) {
-            return err;
-        }
-
-        BufferInfo info;
-        info.mBufferID = buffer;
-        info.mStatus = BufferInfo::OWNED_BY_US;
-        info.mData = new ABuffer(mem->pointer(), def.nBufferSize);
-        mBuffers[portIndex].push(info);
+        name = StringPrintf("data_%d", i);
+        notify->setObject(name.c_str(), mBuffers[portIndex][i].mData);
     }
 
+    notify->post();
+
     return OK;
 }
 
@@ -671,7 +706,7 @@
     return NULL;
 }
 
-void ACodec::setComponentRole(
+status_t ACodec::setComponentRole(
         bool isEncoder, const char *mime) {
     struct MimeToRole {
         const char *mime;
@@ -700,6 +735,8 @@
             "video_decoder.mpeg4", "video_encoder.mpeg4" },
         { MEDIA_MIMETYPE_VIDEO_H263,
             "video_decoder.h263", "video_encoder.h263" },
+        { MEDIA_MIMETYPE_VIDEO_VPX,
+            "video_decoder.vpx", "video_encoder.vpx" },
     };
 
     static const size_t kNumMimeToRole =
@@ -713,7 +750,7 @@
     }
 
     if (i == kNumMimeToRole) {
-        return;
+        return ERROR_UNSUPPORTED;
     }
 
     const char *role =
@@ -736,50 +773,83 @@
         if (err != OK) {
             ALOGW("[%s] Failed to set standard component role '%s'.",
                  mComponentName.c_str(), role);
+
+            return err;
         }
     }
+
+    return OK;
 }
 
-void ACodec::configureCodec(
+status_t ACodec::configureCodec(
         const char *mime, const sp<AMessage> &msg) {
-    setComponentRole(false /* isEncoder */, mime);
+    int32_t encoder;
+    if (!msg->findInt32("encoder", &encoder)) {
+        encoder = false;
+    }
+
+    mIsEncoder = encoder;
+
+    status_t err = setComponentRole(encoder /* isEncoder */, mime);
+
+    if (err != OK) {
+        return err;
+    }
+
+    int32_t bitRate = 0;
+    if (encoder && !msg->findInt32("bitrate", &bitRate)) {
+        return INVALID_OPERATION;
+    }
 
     if (!strncasecmp(mime, "video/", 6)) {
-        int32_t width, height;
-        CHECK(msg->findInt32("width", &width));
-        CHECK(msg->findInt32("height", &height));
-
-        CHECK_EQ(setupVideoDecoder(mime, width, height),
-                 (status_t)OK);
+        if (encoder) {
+            err = setupVideoEncoder(mime, msg);
+        } else {
+            int32_t width, height;
+            if (!msg->findInt32("width", &width)
+                    || !msg->findInt32("height", &height)) {
+                err = INVALID_OPERATION;
+            } else {
+                err = setupVideoDecoder(mime, width, height);
+            }
+        }
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
         int32_t numChannels, sampleRate;
-        CHECK(msg->findInt32("channel-count", &numChannels));
-        CHECK(msg->findInt32("sample-rate", &sampleRate));
-
-        CHECK_EQ(setupAACDecoder(numChannels, sampleRate), (status_t)OK);
+        if (!msg->findInt32("channel-count", &numChannels)
+                || !msg->findInt32("sample-rate", &sampleRate)) {
+            err = INVALID_OPERATION;
+        } else {
+            err = setupAACCodec(encoder, numChannels, sampleRate, bitRate);
+        }
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
-        CHECK_EQ(setupAMRDecoder(false /* isWAMR */), (status_t)OK);
+        err = setupAMRCodec(encoder, false /* isWAMR */, bitRate);
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
-        CHECK_EQ(setupAMRDecoder(true /* isWAMR */), (status_t)OK);
+        err = setupAMRCodec(encoder, true /* isWAMR */, bitRate);
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_ALAW)
             || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_MLAW)) {
         // These are PCM-like formats with a fixed sample rate but
         // a variable number of channels.
 
         int32_t numChannels;
-        CHECK(msg->findInt32("channel-count", &numChannels));
+        if (!msg->findInt32("channel-count", &numChannels)) {
+            err = INVALID_OPERATION;
+        } else {
+            err = setupG711Codec(encoder, numChannels);
+        }
+    }
 
-        CHECK_EQ(setupG711Decoder(numChannels), (status_t)OK);
+    if (err != OK) {
+        return err;
     }
 
     int32_t maxInputSize;
     if (msg->findInt32("max-input-size", &maxInputSize)) {
-        CHECK_EQ(setMinBufferSize(kPortIndexInput, (size_t)maxInputSize),
-                 (status_t)OK);
+        err = setMinBufferSize(kPortIndexInput, (size_t)maxInputSize);
     } else if (!strcmp("OMX.Nvidia.aac.decoder", mComponentName.c_str())) {
-        CHECK_EQ(setMinBufferSize(kPortIndexInput, 8192),  // XXX
-                 (status_t)OK);
+        err = setMinBufferSize(kPortIndexInput, 8192);  // XXX
     }
+
+    return err;
 }
 
 status_t ACodec::setMinBufferSize(OMX_U32 portIndex, size_t size) {
@@ -819,12 +889,113 @@
     return OK;
 }
 
-status_t ACodec::setupAACDecoder(int32_t numChannels, int32_t sampleRate) {
+status_t ACodec::selectAudioPortFormat(
+        OMX_U32 portIndex, OMX_AUDIO_CODINGTYPE desiredFormat) {
+    OMX_AUDIO_PARAM_PORTFORMATTYPE format;
+    InitOMXParams(&format);
+
+    format.nPortIndex = portIndex;
+    for (OMX_U32 index = 0;; ++index) {
+        format.nIndex = index;
+
+        status_t err = mOMX->getParameter(
+                mNode, OMX_IndexParamAudioPortFormat,
+                &format, sizeof(format));
+
+        if (err != OK) {
+            return err;
+        }
+
+        if (format.eEncoding == desiredFormat) {
+            break;
+        }
+    }
+
+    return mOMX->setParameter(
+            mNode, OMX_IndexParamAudioPortFormat, &format, sizeof(format));
+}
+
+status_t ACodec::setupAACCodec(
+        bool encoder,
+        int32_t numChannels, int32_t sampleRate, int32_t bitRate) {
+    status_t err = setupRawAudioFormat(
+            encoder ? kPortIndexInput : kPortIndexOutput,
+            sampleRate,
+            numChannels);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (encoder) {
+        err = selectAudioPortFormat(kPortIndexOutput, OMX_AUDIO_CodingAAC);
+
+        if (err != OK) {
+            return err;
+        }
+
+        OMX_PARAM_PORTDEFINITIONTYPE def;
+        InitOMXParams(&def);
+        def.nPortIndex = kPortIndexOutput;
+
+        err = mOMX->getParameter(
+                mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+        if (err != OK) {
+            return err;
+        }
+
+        def.format.audio.bFlagErrorConcealment = OMX_TRUE;
+        def.format.audio.eEncoding = OMX_AUDIO_CodingAAC;
+
+        err = mOMX->setParameter(
+                mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+        if (err != OK) {
+            return err;
+        }
+
+        OMX_AUDIO_PARAM_AACPROFILETYPE profile;
+        InitOMXParams(&profile);
+        profile.nPortIndex = kPortIndexOutput;
+
+        err = mOMX->getParameter(
+                mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
+
+        if (err != OK) {
+            return err;
+        }
+
+        profile.nChannels = numChannels;
+
+        profile.eChannelMode =
+            (numChannels == 1)
+                ? OMX_AUDIO_ChannelModeMono: OMX_AUDIO_ChannelModeStereo;
+
+        profile.nSampleRate = sampleRate;
+        profile.nBitRate = bitRate;
+        profile.nAudioBandWidth = 0;
+        profile.nFrameLength = 0;
+        profile.nAACtools = OMX_AUDIO_AACToolAll;
+        profile.nAACERtools = OMX_AUDIO_AACERNone;
+        profile.eAACProfile = OMX_AUDIO_AACObjectLC;
+        profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF;
+
+        err = mOMX->setParameter(
+                mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
+
+        if (err != OK) {
+            return err;
+        }
+
+        return err;
+    }
+
     OMX_AUDIO_PARAM_AACPROFILETYPE profile;
     InitOMXParams(&profile);
     profile.nPortIndex = kPortIndexInput;
 
-    status_t err = mOMX->getParameter(
+    err = mOMX->getParameter(
             mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
 
     if (err != OK) {
@@ -835,16 +1006,59 @@
     profile.nSampleRate = sampleRate;
     profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
 
-    err = mOMX->setParameter(
+    return mOMX->setParameter(
             mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
-
-    return err;
 }
 
-status_t ACodec::setupAMRDecoder(bool isWAMR) {
+static OMX_AUDIO_AMRBANDMODETYPE pickModeFromBitRate(
+        bool isAMRWB, int32_t bps) {
+    if (isAMRWB) {
+        if (bps <= 6600) {
+            return OMX_AUDIO_AMRBandModeWB0;
+        } else if (bps <= 8850) {
+            return OMX_AUDIO_AMRBandModeWB1;
+        } else if (bps <= 12650) {
+            return OMX_AUDIO_AMRBandModeWB2;
+        } else if (bps <= 14250) {
+            return OMX_AUDIO_AMRBandModeWB3;
+        } else if (bps <= 15850) {
+            return OMX_AUDIO_AMRBandModeWB4;
+        } else if (bps <= 18250) {
+            return OMX_AUDIO_AMRBandModeWB5;
+        } else if (bps <= 19850) {
+            return OMX_AUDIO_AMRBandModeWB6;
+        } else if (bps <= 23050) {
+            return OMX_AUDIO_AMRBandModeWB7;
+        }
+
+        // 23850 bps
+        return OMX_AUDIO_AMRBandModeWB8;
+    } else {  // AMRNB
+        if (bps <= 4750) {
+            return OMX_AUDIO_AMRBandModeNB0;
+        } else if (bps <= 5150) {
+            return OMX_AUDIO_AMRBandModeNB1;
+        } else if (bps <= 5900) {
+            return OMX_AUDIO_AMRBandModeNB2;
+        } else if (bps <= 6700) {
+            return OMX_AUDIO_AMRBandModeNB3;
+        } else if (bps <= 7400) {
+            return OMX_AUDIO_AMRBandModeNB4;
+        } else if (bps <= 7950) {
+            return OMX_AUDIO_AMRBandModeNB5;
+        } else if (bps <= 10200) {
+            return OMX_AUDIO_AMRBandModeNB6;
+        }
+
+        // 12200 bps
+        return OMX_AUDIO_AMRBandModeNB7;
+    }
+}
+
+status_t ACodec::setupAMRCodec(bool encoder, bool isWAMR, int32_t bitrate) {
     OMX_AUDIO_PARAM_AMRTYPE def;
     InitOMXParams(&def);
-    def.nPortIndex = kPortIndexInput;
+    def.nPortIndex = encoder ? kPortIndexOutput : kPortIndexInput;
 
     status_t err =
         mOMX->getParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
@@ -854,14 +1068,24 @@
     }
 
     def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+    def.eAMRBandMode = pickModeFromBitRate(isWAMR, bitrate);
 
-    def.eAMRBandMode =
-        isWAMR ? OMX_AUDIO_AMRBandModeWB0 : OMX_AUDIO_AMRBandModeNB0;
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
 
-    return mOMX->setParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
+    if (err != OK) {
+        return err;
+    }
+
+    return setupRawAudioFormat(
+            encoder ? kPortIndexInput : kPortIndexOutput,
+            isWAMR ? 16000 : 8000 /* sampleRate */,
+            1 /* numChannels */);
 }
 
-status_t ACodec::setupG711Decoder(int32_t numChannels) {
+status_t ACodec::setupG711Codec(bool encoder, int32_t numChannels) {
+    CHECK(!encoder);  // XXX TODO
+
     return setupRawAudioFormat(
             kPortIndexInput, 8000 /* sampleRate */, numChannels);
 }
@@ -1001,22 +1225,36 @@
             &format, sizeof(format));
 }
 
-status_t ACodec::setupVideoDecoder(
-        const char *mime, int32_t width, int32_t height) {
-    OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused;
+static status_t GetVideoCodingTypeFromMime(
+        const char *mime, OMX_VIDEO_CODINGTYPE *codingType) {
     if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
-        compressionFormat = OMX_VIDEO_CodingAVC;
+        *codingType = OMX_VIDEO_CodingAVC;
     } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) {
-        compressionFormat = OMX_VIDEO_CodingMPEG4;
+        *codingType = OMX_VIDEO_CodingMPEG4;
     } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) {
-        compressionFormat = OMX_VIDEO_CodingH263;
+        *codingType = OMX_VIDEO_CodingH263;
     } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG2, mime)) {
-        compressionFormat = OMX_VIDEO_CodingMPEG2;
+        *codingType = OMX_VIDEO_CodingMPEG2;
+    } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_VPX, mime)) {
+        *codingType = OMX_VIDEO_CodingVPX;
     } else {
-        TRESPASS();
+        *codingType = OMX_VIDEO_CodingUnused;
+        return ERROR_UNSUPPORTED;
     }
 
-    status_t err = setVideoPortFormatType(
+    return OK;
+}
+
+status_t ACodec::setupVideoDecoder(
+        const char *mime, int32_t width, int32_t height) {
+    OMX_VIDEO_CODINGTYPE compressionFormat;
+    status_t err = GetVideoCodingTypeFromMime(mime, &compressionFormat);
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = setVideoPortFormatType(
             kPortIndexInput, compressionFormat, OMX_COLOR_FormatUnused);
 
     if (err != OK) {
@@ -1046,6 +1284,489 @@
     return OK;
 }
 
+status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg) {
+    int32_t tmp;
+    if (!msg->findInt32("color-format", &tmp)) {
+        return INVALID_OPERATION;
+    }
+
+    OMX_COLOR_FORMATTYPE colorFormat =
+        static_cast<OMX_COLOR_FORMATTYPE>(tmp);
+
+    status_t err = setVideoPortFormatType(
+            kPortIndexInput, OMX_VIDEO_CodingUnused, colorFormat);
+
+    if (err != OK) {
+        ALOGE("[%s] does not support color format %d",
+              mComponentName.c_str(), colorFormat);
+
+        return err;
+    }
+
+    /* Input port configuration */
+
+    OMX_PARAM_PORTDEFINITIONTYPE def;
+    InitOMXParams(&def);
+
+    OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+    def.nPortIndex = kPortIndexInput;
+
+    err = mOMX->getParameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+    if (err != OK) {
+        return err;
+    }
+
+    int32_t width, height, bitrate;
+    if (!msg->findInt32("width", &width)
+            || !msg->findInt32("height", &height)
+            || !msg->findInt32("bitrate", &bitrate)) {
+        return INVALID_OPERATION;
+    }
+
+    video_def->nFrameWidth = width;
+    video_def->nFrameHeight = height;
+
+    int32_t stride;
+    if (!msg->findInt32("stride", &stride)) {
+        stride = width;
+    }
+
+    video_def->nStride = stride;
+
+    int32_t sliceHeight;
+    if (!msg->findInt32("slice-height", &sliceHeight)) {
+        sliceHeight = height;
+    }
+
+    video_def->nSliceHeight = sliceHeight;
+
+    def.nBufferSize = (video_def->nStride * video_def->nSliceHeight * 3) / 2;
+
+    float frameRate;
+    if (!msg->findFloat("frame-rate", &frameRate)) {
+        int32_t tmp;
+        if (!msg->findInt32("frame-rate", &tmp)) {
+            return INVALID_OPERATION;
+        }
+        frameRate = (float)tmp;
+    }
+
+    video_def->xFramerate = (OMX_U32)(frameRate * 65536.0f);
+    video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
+    video_def->eColorFormat = colorFormat;
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+    if (err != OK) {
+        ALOGE("[%s] failed to set input port definition parameters.",
+              mComponentName.c_str());
+
+        return err;
+    }
+
+    /* Output port configuration */
+
+    OMX_VIDEO_CODINGTYPE compressionFormat;
+    err = GetVideoCodingTypeFromMime(mime, &compressionFormat);
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = setVideoPortFormatType(
+            kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused);
+
+    if (err != OK) {
+        ALOGE("[%s] does not support compression format %d",
+             mComponentName.c_str(), compressionFormat);
+
+        return err;
+    }
+
+    def.nPortIndex = kPortIndexOutput;
+
+    err = mOMX->getParameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+    if (err != OK) {
+        return err;
+    }
+
+    video_def->nFrameWidth = width;
+    video_def->nFrameHeight = height;
+    video_def->xFramerate = 0;
+    video_def->nBitrate = bitrate;
+    video_def->eCompressionFormat = compressionFormat;
+    video_def->eColorFormat = OMX_COLOR_FormatUnused;
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+    if (err != OK) {
+        ALOGE("[%s] failed to set output port definition parameters.",
+              mComponentName.c_str());
+
+        return err;
+    }
+
+    switch (compressionFormat) {
+        case OMX_VIDEO_CodingMPEG4:
+            err = setupMPEG4EncoderParameters(msg);
+            break;
+
+        case OMX_VIDEO_CodingH263:
+            err = setupH263EncoderParameters(msg);
+            break;
+
+        case OMX_VIDEO_CodingAVC:
+            err = setupAVCEncoderParameters(msg);
+            break;
+
+        default:
+            break;
+    }
+
+    ALOGI("setupVideoEncoder succeeded");
+
+    return err;
+}
+
+static OMX_U32 setPFramesSpacing(int32_t iFramesInterval, int32_t frameRate) {
+    if (iFramesInterval < 0) {
+        return 0xFFFFFFFF;
+    } else if (iFramesInterval == 0) {
+        return 0;
+    }
+    OMX_U32 ret = frameRate * iFramesInterval;
+    CHECK(ret > 1);
+    return ret;
+}
+
+status_t ACodec::setupMPEG4EncoderParameters(const sp<AMessage> &msg) {
+    int32_t bitrate, iFrameInterval;
+    if (!msg->findInt32("bitrate", &bitrate)
+            || !msg->findInt32("i-frame-interval", &iFrameInterval)) {
+        return INVALID_OPERATION;
+    }
+
+    float frameRate;
+    if (!msg->findFloat("frame-rate", &frameRate)) {
+        int32_t tmp;
+        if (!msg->findInt32("frame-rate", &tmp)) {
+            return INVALID_OPERATION;
+        }
+        frameRate = (float)tmp;
+    }
+
+    OMX_VIDEO_PARAM_MPEG4TYPE mpeg4type;
+    InitOMXParams(&mpeg4type);
+    mpeg4type.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+
+    if (err != OK) {
+        return err;
+    }
+
+    mpeg4type.nSliceHeaderSpacing = 0;
+    mpeg4type.bSVH = OMX_FALSE;
+    mpeg4type.bGov = OMX_FALSE;
+
+    mpeg4type.nAllowedPictureTypes =
+        OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+    mpeg4type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate);
+    if (mpeg4type.nPFrames == 0) {
+        mpeg4type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+    }
+    mpeg4type.nBFrames = 0;
+    mpeg4type.nIDCVLCThreshold = 0;
+    mpeg4type.bACPred = OMX_TRUE;
+    mpeg4type.nMaxPacketSize = 256;
+    mpeg4type.nTimeIncRes = 1000;
+    mpeg4type.nHeaderExtension = 0;
+    mpeg4type.bReversibleVLC = OMX_FALSE;
+
+    int32_t profile;
+    if (msg->findInt32("profile", &profile)) {
+        int32_t level;
+        if (!msg->findInt32("level", &level)) {
+            return INVALID_OPERATION;
+        }
+
+        err = verifySupportForProfileAndLevel(profile, level);
+
+        if (err != OK) {
+            return err;
+        }
+
+        mpeg4type.eProfile = static_cast<OMX_VIDEO_MPEG4PROFILETYPE>(profile);
+        mpeg4type.eLevel = static_cast<OMX_VIDEO_MPEG4LEVELTYPE>(level);
+    }
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = configureBitrate(bitrate);
+
+    if (err != OK) {
+        return err;
+    }
+
+    return setupErrorCorrectionParameters();
+}
+
+status_t ACodec::setupH263EncoderParameters(const sp<AMessage> &msg) {
+    int32_t bitrate, iFrameInterval;
+    if (!msg->findInt32("bitrate", &bitrate)
+            || !msg->findInt32("i-frame-interval", &iFrameInterval)) {
+        return INVALID_OPERATION;
+    }
+
+    float frameRate;
+    if (!msg->findFloat("frame-rate", &frameRate)) {
+        int32_t tmp;
+        if (!msg->findInt32("frame-rate", &tmp)) {
+            return INVALID_OPERATION;
+        }
+        frameRate = (float)tmp;
+    }
+
+    OMX_VIDEO_PARAM_H263TYPE h263type;
+    InitOMXParams(&h263type);
+    h263type.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type));
+
+    if (err != OK) {
+        return err;
+    }
+
+    h263type.nAllowedPictureTypes =
+        OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+    h263type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate);
+    if (h263type.nPFrames == 0) {
+        h263type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+    }
+    h263type.nBFrames = 0;
+
+    int32_t profile;
+    if (msg->findInt32("profile", &profile)) {
+        int32_t level;
+        if (!msg->findInt32("level", &level)) {
+            return INVALID_OPERATION;
+        }
+
+        err = verifySupportForProfileAndLevel(profile, level);
+
+        if (err != OK) {
+            return err;
+        }
+
+        h263type.eProfile = static_cast<OMX_VIDEO_H263PROFILETYPE>(profile);
+        h263type.eLevel = static_cast<OMX_VIDEO_H263LEVELTYPE>(level);
+    }
+
+    h263type.bPLUSPTYPEAllowed = OMX_FALSE;
+    h263type.bForceRoundingTypeToZero = OMX_FALSE;
+    h263type.nPictureHeaderRepetition = 0;
+    h263type.nGOBHeaderInterval = 0;
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type));
+
+    if (err != OK) {
+        return err;
+    }
+
+    err = configureBitrate(bitrate);
+
+    if (err != OK) {
+        return err;
+    }
+
+    return setupErrorCorrectionParameters();
+}
+
+status_t ACodec::setupAVCEncoderParameters(const sp<AMessage> &msg) {
+    int32_t bitrate, iFrameInterval;
+    if (!msg->findInt32("bitrate", &bitrate)
+            || !msg->findInt32("i-frame-interval", &iFrameInterval)) {
+        return INVALID_OPERATION;
+    }
+
+    float frameRate;
+    if (!msg->findFloat("frame-rate", &frameRate)) {
+        int32_t tmp;
+        if (!msg->findInt32("frame-rate", &tmp)) {
+            return INVALID_OPERATION;
+        }
+        frameRate = (float)tmp;
+    }
+
+    OMX_VIDEO_PARAM_AVCTYPE h264type;
+    InitOMXParams(&h264type);
+    h264type.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type));
+
+    if (err != OK) {
+        return err;
+    }
+
+    h264type.nAllowedPictureTypes =
+        OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+    int32_t profile;
+    if (msg->findInt32("profile", &profile)) {
+        int32_t level;
+        if (!msg->findInt32("level", &level)) {
+            return INVALID_OPERATION;
+        }
+
+        err = verifySupportForProfileAndLevel(profile, level);
+
+        if (err != OK) {
+            return err;
+        }
+
+        h264type.eProfile = static_cast<OMX_VIDEO_AVCPROFILETYPE>(profile);
+        h264type.eLevel = static_cast<OMX_VIDEO_AVCLEVELTYPE>(level);
+    }
+
+    // XXX
+    if (!strncmp(mComponentName.c_str(), "OMX.TI.DUCATI1", 14)) {
+        h264type.eProfile = OMX_VIDEO_AVCProfileBaseline;
+    }
+
+    if (h264type.eProfile == OMX_VIDEO_AVCProfileBaseline) {
+        h264type.nSliceHeaderSpacing = 0;
+        h264type.bUseHadamard = OMX_TRUE;
+        h264type.nRefFrames = 1;
+        h264type.nBFrames = 0;
+        h264type.nPFrames = setPFramesSpacing(iFrameInterval, frameRate);
+        if (h264type.nPFrames == 0) {
+            h264type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI;
+        }
+        h264type.nRefIdx10ActiveMinus1 = 0;
+        h264type.nRefIdx11ActiveMinus1 = 0;
+        h264type.bEntropyCodingCABAC = OMX_FALSE;
+        h264type.bWeightedPPrediction = OMX_FALSE;
+        h264type.bconstIpred = OMX_FALSE;
+        h264type.bDirect8x8Inference = OMX_FALSE;
+        h264type.bDirectSpatialTemporal = OMX_FALSE;
+        h264type.nCabacInitIdc = 0;
+    }
+
+    if (h264type.nBFrames != 0) {
+        h264type.nAllowedPictureTypes |= OMX_VIDEO_PictureTypeB;
+    }
+
+    h264type.bEnableUEP = OMX_FALSE;
+    h264type.bEnableFMO = OMX_FALSE;
+    h264type.bEnableASO = OMX_FALSE;
+    h264type.bEnableRS = OMX_FALSE;
+    h264type.bFrameMBsOnly = OMX_TRUE;
+    h264type.bMBAFF = OMX_FALSE;
+    h264type.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable;
+
+    if (!strcasecmp("OMX.Nvidia.h264.encoder", mComponentName.c_str())) {
+        h264type.eLevel = OMX_VIDEO_AVCLevelMax;
+    }
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type));
+
+    if (err != OK) {
+        return err;
+    }
+
+    return configureBitrate(bitrate);
+}
+
+status_t ACodec::verifySupportForProfileAndLevel(
+        int32_t profile, int32_t level) {
+    OMX_VIDEO_PARAM_PROFILELEVELTYPE params;
+    InitOMXParams(&params);
+    params.nPortIndex = kPortIndexOutput;
+
+    for (params.nProfileIndex = 0;; ++params.nProfileIndex) {
+        status_t err = mOMX->getParameter(
+                mNode,
+                OMX_IndexParamVideoProfileLevelQuerySupported,
+                &params,
+                sizeof(params));
+
+        if (err != OK) {
+            return err;
+        }
+
+        int32_t supportedProfile = static_cast<int32_t>(params.eProfile);
+        int32_t supportedLevel = static_cast<int32_t>(params.eLevel);
+
+        if (profile == supportedProfile && level <= supportedLevel) {
+            return OK;
+        }
+    }
+}
+
+status_t ACodec::configureBitrate(int32_t bitrate) {
+    OMX_VIDEO_PARAM_BITRATETYPE bitrateType;
+    InitOMXParams(&bitrateType);
+    bitrateType.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoBitrate,
+            &bitrateType, sizeof(bitrateType));
+
+    if (err != OK) {
+        return err;
+    }
+
+    bitrateType.eControlRate = OMX_Video_ControlRateVariable;
+    bitrateType.nTargetBitrate = bitrate;
+
+    return mOMX->setParameter(
+            mNode, OMX_IndexParamVideoBitrate,
+            &bitrateType, sizeof(bitrateType));
+}
+
+status_t ACodec::setupErrorCorrectionParameters() {
+    OMX_VIDEO_PARAM_ERRORCORRECTIONTYPE errorCorrectionType;
+    InitOMXParams(&errorCorrectionType);
+    errorCorrectionType.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoErrorCorrection,
+            &errorCorrectionType, sizeof(errorCorrectionType));
+
+    if (err != OK) {
+        return OK;  // Optional feature. Ignore this failure
+    }
+
+    errorCorrectionType.bEnableHEC = OMX_FALSE;
+    errorCorrectionType.bEnableResync = OMX_TRUE;
+    errorCorrectionType.nResynchMarkerSpacing = 256;
+    errorCorrectionType.bEnableDataPartitioning = OMX_FALSE;
+    errorCorrectionType.bEnableRVLC = OMX_FALSE;
+
+    return mOMX->setParameter(
+            mNode, OMX_IndexParamVideoErrorCorrection,
+            &errorCorrectionType, sizeof(errorCorrectionType));
+}
+
 status_t ACodec::setVideoFormatOnPort(
         OMX_U32 portIndex,
         int32_t width, int32_t height, OMX_VIDEO_CODINGTYPE compressionFormat) {
@@ -1166,6 +1887,9 @@
             notify->setString("mime", MEDIA_MIMETYPE_VIDEO_RAW);
             notify->setInt32("width", videoDef->nFrameWidth);
             notify->setInt32("height", videoDef->nFrameHeight);
+            notify->setInt32("stride", videoDef->nStride);
+            notify->setInt32("slice-height", videoDef->nSliceHeight);
+            notify->setInt32("color-format", videoDef->eColorFormat);
 
             OMX_CONFIG_RECTTYPE rect;
             InitOMXParams(&rect);
@@ -1241,10 +1965,11 @@
     mSentFormat = true;
 }
 
-void ACodec::signalError(OMX_ERRORTYPE error) {
+void ACodec::signalError(OMX_ERRORTYPE error, status_t internalError) {
     sp<AMessage> notify = mNotify->dup();
     notify->setInt32("what", ACodec::kWhatError);
     notify->setInt32("omx-error", error);
+    notify->setInt32("err", internalError);
     notify->post();
 }
 
@@ -1435,6 +2160,8 @@
 
     sp<RefBase> obj;
     int32_t err = OK;
+    bool eos = false;
+
     if (!msg->findObject("buffer", &obj)) {
         CHECK(msg->findInt32("err", &err));
 
@@ -1442,10 +2169,18 @@
              mCodec->mComponentName.c_str(), err);
 
         obj.clear();
+
+        eos = true;
     }
 
     sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
 
+    int32_t tmp;
+    if (buffer != NULL && buffer->meta()->findInt32("eos", &tmp) && tmp) {
+        eos = true;
+        err = ERROR_END_OF_STREAM;
+    }
+
     BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID);
     CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_UPSTREAM);
 
@@ -1456,7 +2191,7 @@
     switch (mode) {
         case KEEP_BUFFERS:
         {
-            if (buffer == NULL) {
+            if (eos) {
                 if (!mCodec->mPortEOS[kPortIndexInput]) {
                     mCodec->mPortEOS[kPortIndexInput] = true;
                     mCodec->mInputEOSResult = err;
@@ -1467,9 +2202,7 @@
 
         case RESUBMIT_BUFFERS:
         {
-            if (buffer != NULL) {
-                CHECK(!mCodec->mPortEOS[kPortIndexInput]);
-
+            if (buffer != NULL && !mCodec->mPortEOS[kPortIndexInput]) {
                 int64_t timeUs;
                 CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
 
@@ -1480,6 +2213,10 @@
                     flags |= OMX_BUFFERFLAG_CODECCONFIG;
                 }
 
+                if (eos) {
+                    flags |= OMX_BUFFERFLAG_EOS;
+                }
+
                 if (buffer != info->mData) {
                     if (0 && !(flags & OMX_BUFFERFLAG_CODECCONFIG)) {
                         ALOGV("[%s] Needs to copy input data.",
@@ -1493,6 +2230,9 @@
                 if (flags & OMX_BUFFERFLAG_CODECCONFIG) {
                     ALOGV("[%s] calling emptyBuffer %p w/ codec specific data",
                          mCodec->mComponentName.c_str(), bufferID);
+                } else if (flags & OMX_BUFFERFLAG_EOS) {
+                    ALOGV("[%s] calling emptyBuffer %p w/ EOS",
+                         mCodec->mComponentName.c_str(), bufferID);
                 } else {
                     ALOGV("[%s] calling emptyBuffer %p w/ time %lld us",
                          mCodec->mComponentName.c_str(), bufferID, timeUs);
@@ -1509,7 +2249,15 @@
 
                 info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
 
-                getMoreInputDataIfPossible();
+                if (!eos) {
+                    getMoreInputDataIfPossible();
+                } else {
+                    ALOGV("[%s] Signalled EOS on the input port",
+                         mCodec->mComponentName.c_str());
+
+                    mCodec->mPortEOS[kPortIndexInput] = true;
+                    mCodec->mInputEOSResult = err;
+                }
             } else if (!mCodec->mPortEOS[kPortIndexInput]) {
                 if (err != ERROR_END_OF_STREAM) {
                     ALOGV("[%s] Signalling EOS on the input port "
@@ -1582,8 +2330,8 @@
         int64_t timeUs,
         void *platformPrivate,
         void *dataPtr) {
-    ALOGV("[%s] onOMXFillBufferDone %p time %lld us",
-         mCodec->mComponentName.c_str(), bufferID, timeUs);
+    ALOGV("[%s] onOMXFillBufferDone %p time %lld us, flags = 0x%08lx",
+         mCodec->mComponentName.c_str(), bufferID, timeUs, flags);
 
     ssize_t index;
     BufferInfo *info =
@@ -1601,46 +2349,48 @@
 
         case RESUBMIT_BUFFERS:
         {
-            if (rangeLength == 0) {
-                if (!(flags & OMX_BUFFERFLAG_EOS)) {
-                    ALOGV("[%s] calling fillBuffer %p",
-                         mCodec->mComponentName.c_str(), info->mBufferID);
+            if (rangeLength == 0 && !(flags & OMX_BUFFERFLAG_EOS)) {
+                ALOGV("[%s] calling fillBuffer %p",
+                     mCodec->mComponentName.c_str(), info->mBufferID);
 
-                    CHECK_EQ(mCodec->mOMX->fillBuffer(
-                                mCodec->mNode, info->mBufferID),
-                             (status_t)OK);
+                CHECK_EQ(mCodec->mOMX->fillBuffer(
+                            mCodec->mNode, info->mBufferID),
+                         (status_t)OK);
 
-                    info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
-                }
-            } else {
-                if (!mCodec->mSentFormat) {
-                    mCodec->sendFormatChange();
-                }
-
-                if (mCodec->mNativeWindow == NULL) {
-                    info->mData->setRange(rangeOffset, rangeLength);
-                }
-
-                info->mData->meta()->setInt64("timeUs", timeUs);
-
-                sp<AMessage> notify = mCodec->mNotify->dup();
-                notify->setInt32("what", ACodec::kWhatDrainThisBuffer);
-                notify->setPointer("buffer-id", info->mBufferID);
-                notify->setObject("buffer", info->mData);
-
-                sp<AMessage> reply =
-                    new AMessage(kWhatOutputBufferDrained, mCodec->id());
-
-                reply->setPointer("buffer-id", info->mBufferID);
-
-                notify->setMessage("reply", reply);
-
-                notify->post();
-
-                info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM;
+                info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
+                break;
             }
 
+            if (!mCodec->mIsEncoder && !mCodec->mSentFormat) {
+                mCodec->sendFormatChange();
+            }
+
+            if (mCodec->mNativeWindow == NULL) {
+                info->mData->setRange(rangeOffset, rangeLength);
+            }
+
+            info->mData->meta()->setInt64("timeUs", timeUs);
+
+            sp<AMessage> notify = mCodec->mNotify->dup();
+            notify->setInt32("what", ACodec::kWhatDrainThisBuffer);
+            notify->setPointer("buffer-id", info->mBufferID);
+            notify->setObject("buffer", info->mData);
+            notify->setInt32("flags", flags);
+
+            sp<AMessage> reply =
+                new AMessage(kWhatOutputBufferDrained, mCodec->id());
+
+            reply->setPointer("buffer-id", info->mBufferID);
+
+            notify->setMessage("reply", reply);
+
+            notify->post();
+
+            info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM;
+
             if (flags & OMX_BUFFERFLAG_EOS) {
+                ALOGV("[%s] saw output EOS", mCodec->mComponentName.c_str());
+
                 sp<AMessage> notify = mCodec->mNotify->dup();
                 notify->setInt32("what", ACodec::kWhatEOS);
                 notify->setInt32("err", mCodec->mInputEOSResult);
@@ -1678,12 +2428,13 @@
             && msg->findInt32("render", &render) && render != 0) {
         // The client wants this buffer to be rendered.
 
-        if (mCodec->mNativeWindow->queueBuffer(
+        status_t err;
+        if ((err = mCodec->mNativeWindow->queueBuffer(
                     mCodec->mNativeWindow.get(),
-                    info->mGraphicBuffer.get()) == OK) {
+                    info->mGraphicBuffer.get())) == OK) {
             info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW;
         } else {
-            mCodec->signalError();
+            mCodec->signalError(OMX_ErrorUndefined, err);
             info->mStatus = BufferInfo::OWNED_BY_US;
         }
     } else {
@@ -1758,6 +2509,27 @@
             break;
         }
 
+        case ACodec::kWhatAllocateComponent:
+        {
+            onAllocateComponent(msg);
+            handled = true;
+            break;
+        }
+
+        case ACodec::kWhatConfigureComponent:
+        {
+            onConfigureComponent(msg);
+            handled = true;
+            break;
+        }
+
+        case ACodec::kWhatStart:
+        {
+            onStart();
+            handled = true;
+            break;
+        }
+
         case ACodec::kWhatShutdown:
         {
             sp<AMessage> notify = mCodec->mNotify->dup();
@@ -1787,27 +2559,54 @@
 
 void ACodec::UninitializedState::onSetup(
         const sp<AMessage> &msg) {
+    onAllocateComponent(msg);
+    onConfigureComponent(msg);
+    onStart();
+}
+
+void ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
+    ALOGV("onAllocateComponent");
+
+    if (mCodec->mNode != NULL) {
+        CHECK_EQ(mCodec->mOMX->freeNode(mCodec->mNode), (status_t)OK);
+
+        mCodec->mNativeWindow.clear();
+        mCodec->mNode = NULL;
+        mCodec->mOMX.clear();
+        mCodec->mComponentName.clear();
+    }
+
     OMXClient client;
     CHECK_EQ(client.connect(), (status_t)OK);
 
     sp<IOMX> omx = client.interface();
 
-    AString mime;
-    CHECK(msg->findString("mime", &mime));
-
     Vector<String8> matchingCodecs;
-    OMXCodec::findMatchingCodecs(
-            mime.c_str(),
-            false, // createEncoder
-            NULL,  // matchComponentName
-            0,     // flags
-            &matchingCodecs);
+
+    AString mime;
+
+    AString componentName;
+    if (msg->findString("componentName", &componentName)) {
+        matchingCodecs.push_back(String8(componentName.c_str()));
+    } else {
+        CHECK(msg->findString("mime", &mime));
+
+        int32_t encoder;
+        if (!msg->findInt32("encoder", &encoder)) {
+            encoder = false;
+        }
+
+        OMXCodec::findMatchingCodecs(
+                mime.c_str(),
+                encoder, // createEncoder
+                NULL,  // matchComponentName
+                0,     // flags
+                &matchingCodecs);
+    }
 
     sp<CodecObserver> observer = new CodecObserver;
     IOMX::node_id node = NULL;
 
-    AString componentName;
-
     for (size_t matchIndex = 0; matchIndex < matchingCodecs.size();
             ++matchIndex) {
         componentName = matchingCodecs.itemAt(matchIndex).string();
@@ -1826,7 +2625,12 @@
     }
 
     if (node == NULL) {
-        ALOGE("Unable to instantiate a decoder for type '%s'.", mime.c_str());
+        if (!mime.empty()) {
+            ALOGE("Unable to instantiate a decoder for type '%s'.",
+                 mime.c_str());
+        } else {
+            ALOGE("Unable to instantiate decoder '%s'.", componentName.c_str());
+        }
 
         mCodec->signalError(OMX_ErrorComponentNotFound);
         return;
@@ -1844,20 +2648,52 @@
 
     mCodec->mInputEOSResult = OK;
 
-    mCodec->configureCodec(mime.c_str(), msg);
+    {
+        sp<AMessage> notify = mCodec->mNotify->dup();
+        notify->setInt32("what", ACodec::kWhatComponentAllocated);
+        notify->setString("componentName", mCodec->mComponentName.c_str());
+        notify->post();
+    }
+}
+
+void ACodec::UninitializedState::onConfigureComponent(
+        const sp<AMessage> &msg) {
+    ALOGV("onConfigureComponent");
+
+    CHECK(mCodec->mNode != NULL);
+
+    AString mime;
+    CHECK(msg->findString("mime", &mime));
+
+    status_t err = mCodec->configureCodec(mime.c_str(), msg);
+
+    if (err != OK) {
+        mCodec->signalError(OMX_ErrorUndefined, err);
+        return;
+    }
 
     sp<RefBase> obj;
     if (msg->findObject("native-window", &obj)
-            && strncmp("OMX.google.", componentName.c_str(), 11)) {
+            && strncmp("OMX.google.", mCodec->mComponentName.c_str(), 11)) {
         sp<NativeWindowWrapper> nativeWindow(
                 static_cast<NativeWindowWrapper *>(obj.get()));
         CHECK(nativeWindow != NULL);
         mCodec->mNativeWindow = nativeWindow->getNativeWindow();
     }
-
     CHECK_EQ((status_t)OK, mCodec->initNativeWindow());
 
-    CHECK_EQ(omx->sendCommand(node, OMX_CommandStateSet, OMX_StateIdle),
+    {
+        sp<AMessage> notify = mCodec->mNotify->dup();
+        notify->setInt32("what", ACodec::kWhatComponentConfigured);
+        notify->post();
+    }
+}
+
+void ACodec::UninitializedState::onStart() {
+    ALOGV("onStart");
+
+    CHECK_EQ(mCodec->mOMX->sendCommand(
+                mCodec->mNode, OMX_CommandStateSet, OMX_StateIdle),
              (status_t)OK);
 
     mCodec->changeState(mCodec->mLoadedToIdleState);
@@ -1878,7 +2714,7 @@
              "(error 0x%08x)",
              err);
 
-        mCodec->signalError();
+        mCodec->signalError(OMX_ErrorUndefined, err);
     }
 }
 
@@ -2202,7 +3038,7 @@
                          "port reconfiguration (error 0x%08x)",
                          err);
 
-                    mCodec->signalError();
+                    mCodec->signalError(OMX_ErrorUndefined, err);
 
                     // This is technically not correct, since we were unable
                     // to allocate output buffers and therefore the output port
@@ -2240,7 +3076,8 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 ACodec::ExecutingToIdleState::ExecutingToIdleState(ACodec *codec)
-    : BaseState(codec) {
+    : BaseState(codec),
+      mComponentNowIdle(false) {
 }
 
 bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) {
@@ -2274,6 +3111,7 @@
 void ACodec::ExecutingToIdleState::stateEntered() {
     ALOGV("[%s] Now Executing->Idle", mCodec->mComponentName.c_str());
 
+    mComponentNowIdle = false;
     mCodec->mSentFormat = false;
 }
 
@@ -2285,6 +3123,8 @@
             CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet);
             CHECK_EQ(data2, (OMX_U32)OMX_StateIdle);
 
+            mComponentNowIdle = true;
+
             changeStateIfWeOwnAllBuffers();
 
             return true;
@@ -2303,7 +3143,7 @@
 }
 
 void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() {
-    if (mCodec->allYourBuffersAreBelongToUs()) {
+    if (mComponentNowIdle && mCodec->allYourBuffersAreBelongToUs()) {
         CHECK_EQ(mCodec->mOMX->sendCommand(
                     mCodec->mNode, OMX_CommandStateSet, OMX_StateLoaded),
                  (status_t)OK);
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 3f9ba47..829f657 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -29,12 +29,14 @@
         MPEG4Writer.cpp                   \
         MediaBuffer.cpp                   \
         MediaBufferGroup.cpp              \
+        MediaCodec.cpp                    \
         MediaDefs.cpp                     \
         MediaExtractor.cpp                \
         MediaSource.cpp                   \
         MediaSourceSplitter.cpp           \
         MetaData.cpp                      \
         NuCachedSource2.cpp               \
+        NuMediaExtractor.cpp              \
         OMXClient.cpp                     \
         OMXCodec.cpp                      \
         OggExtractor.cpp                  \
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
new file mode 100644
index 0000000..6702a1c
--- /dev/null
+++ b/media/libstagefright/MediaCodec.cpp
@@ -0,0 +1,1185 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaCodec"
+#include <utils/Log.h>
+
+#include <media/stagefright/MediaCodec.h>
+
+#include "include/SoftwareRenderer.h"
+
+#include <gui/SurfaceTextureClient.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/ACodec.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/NativeWindowWrapper.h>
+
+namespace android {
+
+// static
+sp<MediaCodec> MediaCodec::CreateByType(
+        const sp<ALooper> &looper, const char *mime, bool encoder) {
+    sp<MediaCodec> codec = new MediaCodec(looper);
+    if (codec->init(mime, true /* nameIsType */, encoder) != OK) {
+        return NULL;
+    }
+
+    return codec;
+}
+
+// static
+sp<MediaCodec> MediaCodec::CreateByComponentName(
+        const sp<ALooper> &looper, const char *name) {
+    sp<MediaCodec> codec = new MediaCodec(looper);
+    if (codec->init(name, false /* nameIsType */, false /* encoder */) != OK) {
+        return NULL;
+    }
+
+    return codec;
+}
+
+MediaCodec::MediaCodec(const sp<ALooper> &looper)
+    : mState(UNINITIALIZED),
+      mLooper(looper),
+      mCodec(new ACodec),
+      mFlags(0),
+      mSoftRenderer(NULL),
+      mDequeueInputTimeoutGeneration(0),
+      mDequeueInputReplyID(0),
+      mDequeueOutputTimeoutGeneration(0),
+      mDequeueOutputReplyID(0) {
+}
+
+MediaCodec::~MediaCodec() {
+    CHECK_EQ(mState, UNINITIALIZED);
+}
+
+// static
+status_t MediaCodec::PostAndAwaitResponse(
+        const sp<AMessage> &msg, sp<AMessage> *response) {
+    status_t err = msg->postAndAwaitResponse(response);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (!(*response)->findInt32("err", &err)) {
+        err = OK;
+    }
+
+    return err;
+}
+
+status_t MediaCodec::init(const char *name, bool nameIsType, bool encoder) {
+    // Current video decoders do not return from OMX_FillThisBuffer
+    // quickly, violating the OpenMAX specs, until that is remedied
+    // we need to invest in an extra looper to free the main event
+    // queue.
+    bool needDedicatedLooper = false;
+    if (nameIsType && !strncasecmp(name, "video/", 6)) {
+        needDedicatedLooper = true;
+    } else if (!nameIsType && !strncmp(name, "OMX.TI.DUCATI1.VIDEO.", 21)) {
+        needDedicatedLooper = true;
+    }
+
+    if (needDedicatedLooper) {
+        if (mCodecLooper == NULL) {
+            mCodecLooper = new ALooper;
+            mCodecLooper->setName("CodecLooper");
+            mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
+        }
+
+        mCodecLooper->registerHandler(mCodec);
+    } else {
+        mLooper->registerHandler(mCodec);
+    }
+
+    mLooper->registerHandler(this);
+
+    mCodec->setNotificationMessage(new AMessage(kWhatCodecNotify, id()));
+
+    sp<AMessage> msg = new AMessage(kWhatInit, id());
+    msg->setString("name", name);
+    msg->setInt32("nameIsType", nameIsType);
+
+    if (nameIsType) {
+        msg->setInt32("encoder", encoder);
+    }
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::configure(
+        const sp<AMessage> &format,
+        const sp<SurfaceTextureClient> &nativeWindow,
+        uint32_t flags) {
+    sp<AMessage> msg = new AMessage(kWhatConfigure, id());
+
+    msg->setMessage("format", format);
+    msg->setInt32("flags", flags);
+
+    if (nativeWindow != NULL) {
+        if (!(mFlags & kFlagIsSoftwareCodec)) {
+            msg->setObject(
+                    "native-window",
+                    new NativeWindowWrapper(nativeWindow));
+        } else {
+            mNativeWindow = nativeWindow;
+        }
+    }
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::start() {
+    sp<AMessage> msg = new AMessage(kWhatStart, id());
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::stop() {
+    sp<AMessage> msg = new AMessage(kWhatStop, id());
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::queueInputBuffer(
+        size_t index,
+        size_t offset,
+        size_t size,
+        int64_t presentationTimeUs,
+        uint32_t flags) {
+    sp<AMessage> msg = new AMessage(kWhatQueueInputBuffer, id());
+    msg->setSize("index", index);
+    msg->setSize("offset", offset);
+    msg->setSize("size", size);
+    msg->setInt64("timeUs", presentationTimeUs);
+    msg->setInt32("flags", flags);
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
+    sp<AMessage> msg = new AMessage(kWhatDequeueInputBuffer, id());
+    msg->setInt64("timeoutUs", timeoutUs);
+
+    sp<AMessage> response;
+    status_t err;
+    if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
+        return err;
+    }
+
+    CHECK(response->findSize("index", index));
+
+    return OK;
+}
+
+status_t MediaCodec::dequeueOutputBuffer(
+        size_t *index,
+        size_t *offset,
+        size_t *size,
+        int64_t *presentationTimeUs,
+        uint32_t *flags,
+        int64_t timeoutUs) {
+    sp<AMessage> msg = new AMessage(kWhatDequeueOutputBuffer, id());
+    msg->setInt64("timeoutUs", timeoutUs);
+
+    sp<AMessage> response;
+    status_t err;
+    if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
+        return err;
+    }
+
+    CHECK(response->findSize("index", index));
+    CHECK(response->findSize("offset", offset));
+    CHECK(response->findSize("size", size));
+    CHECK(response->findInt64("timeUs", presentationTimeUs));
+    CHECK(response->findInt32("flags", (int32_t *)flags));
+
+    return OK;
+}
+
+status_t MediaCodec::renderOutputBufferAndRelease(size_t index) {
+    sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, id());
+    msg->setSize("index", index);
+    msg->setInt32("render", true);
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::releaseOutputBuffer(size_t index) {
+    sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, id());
+    msg->setSize("index", index);
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::getOutputFormat(sp<AMessage> *format) const {
+    sp<AMessage> msg = new AMessage(kWhatGetOutputFormat, id());
+
+    sp<AMessage> response;
+    status_t err;
+    if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
+        return err;
+    }
+
+    CHECK(response->findMessage("format", format));
+
+    return OK;
+}
+
+status_t MediaCodec::getInputBuffers(Vector<sp<ABuffer> > *buffers) const {
+    sp<AMessage> msg = new AMessage(kWhatGetBuffers, id());
+    msg->setInt32("portIndex", kPortIndexInput);
+    msg->setPointer("buffers", buffers);
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::getOutputBuffers(Vector<sp<ABuffer> > *buffers) const {
+    sp<AMessage> msg = new AMessage(kWhatGetBuffers, id());
+    msg->setInt32("portIndex", kPortIndexOutput);
+    msg->setPointer("buffers", buffers);
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+status_t MediaCodec::flush() {
+    sp<AMessage> msg = new AMessage(kWhatFlush, id());
+
+    sp<AMessage> response;
+    return PostAndAwaitResponse(msg, &response);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void MediaCodec::cancelPendingDequeueOperations() {
+    if (mFlags & kFlagDequeueInputPending) {
+        sp<AMessage> response = new AMessage;
+        response->setInt32("err", INVALID_OPERATION);
+        response->postReply(mDequeueInputReplyID);
+
+        ++mDequeueInputTimeoutGeneration;
+        mDequeueInputReplyID = 0;
+        mFlags &= ~kFlagDequeueInputPending;
+    }
+
+    if (mFlags & kFlagDequeueOutputPending) {
+        sp<AMessage> response = new AMessage;
+        response->setInt32("err", INVALID_OPERATION);
+        response->postReply(mDequeueOutputReplyID);
+
+        ++mDequeueOutputTimeoutGeneration;
+        mDequeueOutputReplyID = 0;
+        mFlags &= ~kFlagDequeueOutputPending;
+    }
+}
+
+bool MediaCodec::handleDequeueInputBuffer(uint32_t replyID, bool newRequest) {
+    if (mState != STARTED
+            || (mFlags & kFlagStickyError)
+            || (newRequest && (mFlags & kFlagDequeueInputPending))) {
+        sp<AMessage> response = new AMessage;
+        response->setInt32("err", INVALID_OPERATION);
+
+        response->postReply(replyID);
+
+        return true;
+    }
+
+    ssize_t index = dequeuePortBuffer(kPortIndexInput);
+
+    if (index < 0) {
+        CHECK_EQ(index, -EAGAIN);
+        return false;
+    }
+
+    sp<AMessage> response = new AMessage;
+    response->setSize("index", index);
+    response->postReply(replyID);
+
+    return true;
+}
+
+bool MediaCodec::handleDequeueOutputBuffer(uint32_t replyID, bool newRequest) {
+    sp<AMessage> response = new AMessage;
+
+    if (mState != STARTED
+            || (mFlags & kFlagStickyError)
+            || (newRequest && (mFlags & kFlagDequeueOutputPending))) {
+        response->setInt32("err", INVALID_OPERATION);
+    } else if (mFlags & kFlagOutputBuffersChanged) {
+        response->setInt32("err", INFO_OUTPUT_BUFFERS_CHANGED);
+        mFlags &= ~kFlagOutputBuffersChanged;
+    } else if (mFlags & kFlagOutputFormatChanged) {
+        response->setInt32("err", INFO_FORMAT_CHANGED);
+        mFlags &= ~kFlagOutputFormatChanged;
+    } else {
+        ssize_t index = dequeuePortBuffer(kPortIndexOutput);
+
+        if (index < 0) {
+            CHECK_EQ(index, -EAGAIN);
+            return false;
+        }
+
+        const sp<ABuffer> &buffer =
+            mPortBuffers[kPortIndexOutput].itemAt(index).mData;
+
+        response->setSize("index", index);
+        response->setSize("offset", buffer->offset());
+        response->setSize("size", buffer->size());
+
+        int64_t timeUs;
+        CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
+
+        response->setInt64("timeUs", timeUs);
+
+        int32_t omxFlags;
+        CHECK(buffer->meta()->findInt32("omxFlags", &omxFlags));
+
+        uint32_t flags = 0;
+        if (omxFlags & OMX_BUFFERFLAG_SYNCFRAME) {
+            flags |= BUFFER_FLAG_SYNCFRAME;
+        }
+        if (omxFlags & OMX_BUFFERFLAG_CODECCONFIG) {
+            flags |= BUFFER_FLAG_CODECCONFIG;
+        }
+        if (omxFlags & OMX_BUFFERFLAG_EOS) {
+            flags |= BUFFER_FLAG_EOS;
+        }
+
+        response->setInt32("flags", flags);
+    }
+
+    response->postReply(replyID);
+
+    return true;
+}
+
+void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatCodecNotify:
+        {
+            int32_t what;
+            CHECK(msg->findInt32("what", &what));
+
+            switch (what) {
+                case ACodec::kWhatError:
+                {
+                    int32_t omxError, internalError;
+                    CHECK(msg->findInt32("omx-error", &omxError));
+                    CHECK(msg->findInt32("err", &internalError));
+
+                    ALOGE("Codec reported an error. "
+                          "(omx error 0x%08x, internalError %d)",
+                          omxError, internalError);
+
+                    bool sendErrorReponse = true;
+
+                    switch (mState) {
+                        case INITIALIZING:
+                        {
+                            setState(UNINITIALIZED);
+                            break;
+                        }
+
+                        case CONFIGURING:
+                        {
+                            setState(INITIALIZED);
+                            break;
+                        }
+
+                        case STARTING:
+                        {
+                            setState(CONFIGURED);
+                            break;
+                        }
+
+                        case STOPPING:
+                        {
+                            // Ignore the error, assuming we'll still get
+                            // the shutdown complete notification.
+
+                            sendErrorReponse = false;
+                            break;
+                        }
+
+                        case FLUSHING:
+                        {
+                            setState(STARTED);
+                            break;
+                        }
+
+                        case STARTED:
+                        {
+                            sendErrorReponse = false;
+
+                            mFlags |= kFlagStickyError;
+
+                            cancelPendingDequeueOperations();
+                            break;
+                        }
+
+                        default:
+                        {
+                            sendErrorReponse = false;
+
+                            mFlags |= kFlagStickyError;
+                            break;
+                        }
+                    }
+
+                    if (sendErrorReponse) {
+                        sp<AMessage> response = new AMessage;
+                        response->setInt32("err", UNKNOWN_ERROR);
+
+                        response->postReply(mReplyID);
+                    }
+                    break;
+                }
+
+                case ACodec::kWhatComponentAllocated:
+                {
+                    CHECK_EQ(mState, INITIALIZING);
+                    setState(INITIALIZED);
+
+                    AString componentName;
+                    CHECK(msg->findString("componentName", &componentName));
+
+                    if (componentName.startsWith("OMX.google.")) {
+                        mFlags |= kFlagIsSoftwareCodec;
+                    } else {
+                        mFlags &= ~kFlagIsSoftwareCodec;
+                    }
+
+                    (new AMessage)->postReply(mReplyID);
+                    break;
+                }
+
+                case ACodec::kWhatComponentConfigured:
+                {
+                    CHECK_EQ(mState, CONFIGURING);
+                    setState(CONFIGURED);
+
+                    (new AMessage)->postReply(mReplyID);
+                    break;
+                }
+
+                case ACodec::kWhatBuffersAllocated:
+                {
+                    int32_t portIndex;
+                    CHECK(msg->findInt32("portIndex", &portIndex));
+
+                    ALOGV("%s buffers allocated",
+                          portIndex == kPortIndexInput ? "input" : "output");
+
+                    CHECK(portIndex == kPortIndexInput
+                            || portIndex == kPortIndexOutput);
+
+                    mPortBuffers[portIndex].clear();
+
+                    Vector<BufferInfo> *buffers = &mPortBuffers[portIndex];
+                    for (size_t i = 0;; ++i) {
+                        AString name = StringPrintf("buffer-id_%d", i);
+
+                        void *bufferID;
+                        if (!msg->findPointer(name.c_str(), &bufferID)) {
+                            break;
+                        }
+
+                        name = StringPrintf("data_%d", i);
+
+                        sp<RefBase> obj;
+                        CHECK(msg->findObject(name.c_str(), &obj));
+
+                        BufferInfo info;
+                        info.mBufferID = bufferID;
+                        info.mData = static_cast<ABuffer *>(obj.get());
+                        info.mOwnedByClient = false;
+
+                        buffers->push_back(info);
+                    }
+
+                    if (portIndex == kPortIndexOutput) {
+                        if (mState == STARTING) {
+                            // We're always allocating output buffers after
+                            // allocating input buffers, so this is a good
+                            // indication that now all buffers are allocated.
+                            setState(STARTED);
+                            (new AMessage)->postReply(mReplyID);
+                        } else {
+                            mFlags |= kFlagOutputBuffersChanged;
+                        }
+                    }
+                    break;
+                }
+
+                case ACodec::kWhatOutputFormatChanged:
+                {
+                    ALOGV("codec output format changed");
+
+                    if ((mFlags & kFlagIsSoftwareCodec)
+                            && mNativeWindow != NULL) {
+                        AString mime;
+                        CHECK(msg->findString("mime", &mime));
+
+                        if (!strncasecmp("video/", mime.c_str(), 6)) {
+                            delete mSoftRenderer;
+                            mSoftRenderer = NULL;
+
+                            int32_t width, height;
+                            CHECK(msg->findInt32("width", &width));
+                            CHECK(msg->findInt32("height", &height));
+
+                            int32_t colorFormat;
+                            CHECK(msg->findInt32(
+                                        "color-format", &colorFormat));
+
+                            sp<MetaData> meta = new MetaData;
+                            meta->setInt32(kKeyWidth, width);
+                            meta->setInt32(kKeyHeight, height);
+                            meta->setInt32(kKeyColorFormat, colorFormat);
+
+                            mSoftRenderer =
+                                new SoftwareRenderer(mNativeWindow, meta);
+                        }
+                    }
+
+                    mOutputFormat = msg;
+                    mFlags |= kFlagOutputFormatChanged;
+                    break;
+                }
+
+                case ACodec::kWhatFillThisBuffer:
+                {
+                    /* size_t index = */updateBuffers(kPortIndexInput, msg);
+
+                    if (mState == FLUSHING) {
+                        returnBuffersToCodecOnPort(kPortIndexInput);
+                        break;
+                    }
+
+                    if (mFlags & kFlagDequeueInputPending) {
+                        CHECK(handleDequeueInputBuffer(mDequeueInputReplyID));
+
+                        ++mDequeueInputTimeoutGeneration;
+                        mFlags &= ~kFlagDequeueInputPending;
+                        mDequeueInputReplyID = 0;
+                    }
+                    break;
+                }
+
+                case ACodec::kWhatDrainThisBuffer:
+                {
+                    /* size_t index = */updateBuffers(kPortIndexOutput, msg);
+
+                    if (mState == FLUSHING) {
+                        returnBuffersToCodecOnPort(kPortIndexOutput);
+                        break;
+                    }
+
+                    sp<RefBase> obj;
+                    CHECK(msg->findObject("buffer", &obj));
+
+                    sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());
+
+                    int32_t omxFlags;
+                    CHECK(msg->findInt32("flags", &omxFlags));
+
+                    buffer->meta()->setInt32("omxFlags", omxFlags);
+
+                    if (mFlags & kFlagDequeueOutputPending) {
+                        CHECK(handleDequeueOutputBuffer(mDequeueOutputReplyID));
+
+                        ++mDequeueOutputTimeoutGeneration;
+                        mFlags &= ~kFlagDequeueOutputPending;
+                        mDequeueOutputReplyID = 0;
+                    }
+                    break;
+                }
+
+                case ACodec::kWhatEOS:
+                {
+                    // We already notify the client of this by using the
+                    // corresponding flag in "onOutputBufferReady".
+                    break;
+                }
+
+                case ACodec::kWhatShutdownCompleted:
+                {
+                    CHECK_EQ(mState, STOPPING);
+                    setState(UNINITIALIZED);
+
+                    (new AMessage)->postReply(mReplyID);
+                    break;
+                }
+
+                case ACodec::kWhatFlushCompleted:
+                {
+                    CHECK_EQ(mState, FLUSHING);
+                    setState(STARTED);
+
+                    mCodec->signalResume();
+
+                    (new AMessage)->postReply(mReplyID);
+                    break;
+                }
+
+                default:
+                    TRESPASS();
+            }
+            break;
+        }
+
+        case kWhatInit:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != UNINITIALIZED) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            mReplyID = replyID;
+            setState(INITIALIZING);
+
+            AString name;
+            CHECK(msg->findString("name", &name));
+
+            int32_t nameIsType;
+            int32_t encoder = false;
+            if (!msg->findInt32("nameIsType", &nameIsType)) {
+                nameIsType = false;
+            } else {
+                CHECK(msg->findInt32("encoder", &encoder));
+            }
+
+            sp<AMessage> format = new AMessage;
+
+            if (nameIsType) {
+                format->setString("mime", name.c_str());
+                format->setInt32("encoder", encoder);
+            } else {
+                format->setString("componentName", name.c_str());
+            }
+
+            mCodec->initiateAllocateComponent(format);
+            break;
+        }
+
+        case kWhatConfigure:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != INITIALIZED) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            mReplyID = replyID;
+            setState(CONFIGURING);
+
+            sp<RefBase> obj;
+            if (!msg->findObject("native-window", &obj)) {
+                obj.clear();
+            }
+
+            sp<AMessage> format;
+            CHECK(msg->findMessage("format", &format));
+
+            if (obj != NULL) {
+                format->setObject("native-window", obj);
+            }
+
+            uint32_t flags;
+            CHECK(msg->findInt32("flags", (int32_t *)&flags));
+
+            if (flags & CONFIGURE_FLAG_ENCODE) {
+                format->setInt32("encoder", true);
+            }
+
+            mCodec->initiateConfigureComponent(format);
+            break;
+        }
+
+        case kWhatStart:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != CONFIGURED) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            mReplyID = replyID;
+            setState(STARTING);
+
+            mCodec->initiateStart();
+            break;
+        }
+
+        case kWhatStop:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != INITIALIZED
+                    && mState != CONFIGURED && mState != STARTED) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            mReplyID = replyID;
+            setState(STOPPING);
+
+            mCodec->initiateShutdown();
+            returnBuffersToCodec();
+            break;
+        }
+
+        case kWhatDequeueInputBuffer:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (handleDequeueInputBuffer(replyID, true /* new request */)) {
+                break;
+            }
+
+            int64_t timeoutUs;
+            CHECK(msg->findInt64("timeoutUs", &timeoutUs));
+
+            if (timeoutUs == 0ll) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", -EAGAIN);
+                response->postReply(replyID);
+                break;
+            }
+
+            mFlags |= kFlagDequeueInputPending;
+            mDequeueInputReplyID = replyID;
+
+            if (timeoutUs > 0ll) {
+                sp<AMessage> timeoutMsg =
+                    new AMessage(kWhatDequeueInputTimedOut, id());
+                timeoutMsg->setInt32(
+                        "generation", ++mDequeueInputTimeoutGeneration);
+                timeoutMsg->post(timeoutUs);
+            }
+            break;
+        }
+
+        case kWhatDequeueInputTimedOut:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mDequeueInputTimeoutGeneration) {
+                // Obsolete
+                break;
+            }
+
+            CHECK(mFlags & kFlagDequeueInputPending);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", -EAGAIN);
+            response->postReply(mDequeueInputReplyID);
+
+            mFlags &= ~kFlagDequeueInputPending;
+            mDequeueInputReplyID = 0;
+            break;
+        }
+
+        case kWhatQueueInputBuffer:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != STARTED || (mFlags & kFlagStickyError)) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            status_t err = onQueueInputBuffer(msg);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatDequeueOutputBuffer:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (handleDequeueOutputBuffer(replyID, true /* new request */)) {
+                break;
+            }
+
+            int64_t timeoutUs;
+            CHECK(msg->findInt64("timeoutUs", &timeoutUs));
+
+            if (timeoutUs == 0ll) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", -EAGAIN);
+                response->postReply(replyID);
+                break;
+            }
+
+            mFlags |= kFlagDequeueOutputPending;
+            mDequeueOutputReplyID = replyID;
+
+            if (timeoutUs > 0ll) {
+                sp<AMessage> timeoutMsg =
+                    new AMessage(kWhatDequeueOutputTimedOut, id());
+                timeoutMsg->setInt32(
+                        "generation", ++mDequeueOutputTimeoutGeneration);
+                timeoutMsg->post(timeoutUs);
+            }
+            break;
+        }
+
+        case kWhatDequeueOutputTimedOut:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mDequeueOutputTimeoutGeneration) {
+                // Obsolete
+                break;
+            }
+
+            CHECK(mFlags & kFlagDequeueOutputPending);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", -EAGAIN);
+            response->postReply(mDequeueOutputReplyID);
+
+            mFlags &= ~kFlagDequeueOutputPending;
+            mDequeueOutputReplyID = 0;
+            break;
+        }
+
+        case kWhatReleaseOutputBuffer:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != STARTED || (mFlags & kFlagStickyError)) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            status_t err = onReleaseOutputBuffer(msg);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
+        case kWhatGetBuffers:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != STARTED || (mFlags & kFlagStickyError)) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            int32_t portIndex;
+            CHECK(msg->findInt32("portIndex", &portIndex));
+
+            Vector<sp<ABuffer> > *dstBuffers;
+            CHECK(msg->findPointer("buffers", (void **)&dstBuffers));
+
+            dstBuffers->clear();
+            const Vector<BufferInfo> &srcBuffers = mPortBuffers[portIndex];
+
+            for (size_t i = 0; i < srcBuffers.size(); ++i) {
+                const BufferInfo &info = srcBuffers.itemAt(i);
+
+                dstBuffers->push_back(info.mData);
+            }
+
+            (new AMessage)->postReply(replyID);
+            break;
+        }
+
+        case kWhatFlush:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if (mState != STARTED || (mFlags & kFlagStickyError)) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            mReplyID = replyID;
+            setState(FLUSHING);
+
+            mCodec->signalFlush();
+            returnBuffersToCodec();
+            break;
+        }
+
+        case kWhatGetOutputFormat:
+        {
+            uint32_t replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            if ((mState != STARTED && mState != FLUSHING)
+                    || (mFlags & kFlagStickyError)) {
+                sp<AMessage> response = new AMessage;
+                response->setInt32("err", INVALID_OPERATION);
+
+                response->postReply(replyID);
+                break;
+            }
+
+            sp<AMessage> response = new AMessage;
+            response->setMessage("format", mOutputFormat);
+            response->postReply(replyID);
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+void MediaCodec::setState(State newState) {
+    if (newState == UNINITIALIZED) {
+        delete mSoftRenderer;
+        mSoftRenderer = NULL;
+
+        mNativeWindow.clear();
+
+        mOutputFormat.clear();
+        mFlags &= ~kFlagOutputFormatChanged;
+        mFlags &= ~kFlagOutputBuffersChanged;
+        mFlags &= ~kFlagStickyError;
+    }
+
+    mState = newState;
+
+    cancelPendingDequeueOperations();
+}
+
+void MediaCodec::returnBuffersToCodec() {
+    returnBuffersToCodecOnPort(kPortIndexInput);
+    returnBuffersToCodecOnPort(kPortIndexOutput);
+}
+
+void MediaCodec::returnBuffersToCodecOnPort(int32_t portIndex) {
+    CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
+
+    Vector<BufferInfo> *buffers = &mPortBuffers[portIndex];
+
+    for (size_t i = 0; i < buffers->size(); ++i) {
+        BufferInfo *info = &buffers->editItemAt(i);
+
+        if (info->mNotify != NULL) {
+            sp<AMessage> msg = info->mNotify;
+            info->mNotify = NULL;
+            info->mOwnedByClient = false;
+
+            if (portIndex == kPortIndexInput) {
+                msg->setInt32("err", ERROR_END_OF_STREAM);
+            }
+            msg->post();
+        }
+    }
+
+    mAvailPortBuffers[portIndex].clear();
+}
+
+size_t MediaCodec::updateBuffers(
+        int32_t portIndex, const sp<AMessage> &msg) {
+    CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
+
+    void *bufferID;
+    CHECK(msg->findPointer("buffer-id", &bufferID));
+
+    Vector<BufferInfo> *buffers = &mPortBuffers[portIndex];
+
+    for (size_t i = 0; i < buffers->size(); ++i) {
+        BufferInfo *info = &buffers->editItemAt(i);
+
+        if (info->mBufferID == bufferID) {
+            CHECK(info->mNotify == NULL);
+            CHECK(msg->findMessage("reply", &info->mNotify));
+
+            mAvailPortBuffers[portIndex].push_back(i);
+
+            return i;
+        }
+    }
+
+    TRESPASS();
+
+    return 0;
+}
+
+status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {
+    size_t index;
+    size_t offset;
+    size_t size;
+    int64_t timeUs;
+    uint32_t flags;
+    CHECK(msg->findSize("index", &index));
+    CHECK(msg->findSize("offset", &offset));
+    CHECK(msg->findSize("size", &size));
+    CHECK(msg->findInt64("timeUs", &timeUs));
+    CHECK(msg->findInt32("flags", (int32_t *)&flags));
+
+    if (index >= mPortBuffers[kPortIndexInput].size()) {
+        return -ERANGE;
+    }
+
+    BufferInfo *info = &mPortBuffers[kPortIndexInput].editItemAt(index);
+
+    if (info->mNotify == NULL || !info->mOwnedByClient) {
+        return -EACCES;
+    }
+
+    if (offset + size > info->mData->capacity()) {
+        return -EINVAL;
+    }
+
+    sp<AMessage> reply = info->mNotify;
+    info->mNotify = NULL;
+    info->mOwnedByClient = false;
+
+    info->mData->setRange(offset, size);
+    info->mData->meta()->setInt64("timeUs", timeUs);
+
+    if (flags & BUFFER_FLAG_EOS) {
+        info->mData->meta()->setInt32("eos", true);
+    }
+
+    if (flags & BUFFER_FLAG_CODECCONFIG) {
+        info->mData->meta()->setInt32("csd", true);
+    }
+
+    reply->setObject("buffer", info->mData);
+    reply->post();
+
+    return OK;
+}
+
+status_t MediaCodec::onReleaseOutputBuffer(const sp<AMessage> &msg) {
+    size_t index;
+    CHECK(msg->findSize("index", &index));
+
+    int32_t render;
+    if (!msg->findInt32("render", &render)) {
+        render = 0;
+    }
+
+    if (mState != STARTED) {
+        return -EINVAL;
+    }
+
+    if (index >= mPortBuffers[kPortIndexOutput].size()) {
+        return -ERANGE;
+    }
+
+    BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);
+
+    if (info->mNotify == NULL || !info->mOwnedByClient) {
+        return -EACCES;
+    }
+
+    if (render) {
+        info->mNotify->setInt32("render", true);
+
+        if (mSoftRenderer != NULL) {
+            mSoftRenderer->render(
+                    info->mData->data(), info->mData->size(), NULL);
+        }
+    }
+
+    info->mNotify->post();
+    info->mNotify = NULL;
+    info->mOwnedByClient = false;
+
+    return OK;
+}
+
+ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {
+    CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
+
+    List<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
+
+    if (availBuffers->empty()) {
+        return -EAGAIN;
+    }
+
+    size_t index = *availBuffers->begin();
+    availBuffers->erase(availBuffers->begin());
+
+    BufferInfo *info = &mPortBuffers[portIndex].editItemAt(index);
+    CHECK(!info->mOwnedByClient);
+    info->mOwnedByClient = true;
+
+    return index;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
new file mode 100644
index 0000000..f2f8436
--- /dev/null
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NuMediaExtractor"
+#include <utils/Log.h>
+
+#include <media/stagefright/NuMediaExtractor.h>
+
+#include "include/ESDS.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+NuMediaExtractor::NuMediaExtractor() {
+}
+
+NuMediaExtractor::~NuMediaExtractor() {
+    releaseTrackSamples();
+
+    for (size_t i = 0; i < mSelectedTracks.size(); ++i) {
+        TrackInfo *info = &mSelectedTracks.editItemAt(i);
+
+        CHECK_EQ((status_t)OK, info->mSource->stop());
+    }
+
+    mSelectedTracks.clear();
+}
+
+status_t NuMediaExtractor::setDataSource(const char *path) {
+    sp<DataSource> dataSource = DataSource::CreateFromURI(path);
+
+    if (dataSource == NULL) {
+        return -ENOENT;
+    }
+
+    mImpl = MediaExtractor::Create(dataSource);
+
+    if (mImpl == NULL) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    return OK;
+}
+
+size_t NuMediaExtractor::countTracks() const {
+    return mImpl == NULL ? 0 : mImpl->countTracks();
+}
+
+status_t NuMediaExtractor::getTrackFormat(
+        size_t index, sp<AMessage> *format) const {
+    *format = NULL;
+
+    if (mImpl == NULL) {
+        return -EINVAL;
+    }
+
+    if (index >= mImpl->countTracks()) {
+        return -ERANGE;
+    }
+
+    sp<MetaData> meta = mImpl->getTrackMetaData(index);
+
+    const char *mime;
+    CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+    sp<AMessage> msg = new AMessage;
+    msg->setString("mime", mime);
+
+    if (!strncasecmp("video/", mime, 6)) {
+        int32_t width, height;
+        CHECK(meta->findInt32(kKeyWidth, &width));
+        CHECK(meta->findInt32(kKeyHeight, &height));
+
+        msg->setInt32("width", width);
+        msg->setInt32("height", height);
+    } else {
+        CHECK(!strncasecmp("audio/", mime, 6));
+
+        int32_t numChannels, sampleRate;
+        CHECK(meta->findInt32(kKeyChannelCount, &numChannels));
+        CHECK(meta->findInt32(kKeySampleRate, &sampleRate));
+
+        msg->setInt32("channel-count", numChannels);
+        msg->setInt32("sample-rate", sampleRate);
+    }
+
+    int32_t maxInputSize;
+    if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) {
+        msg->setInt32("max-input-size", maxInputSize);
+    }
+
+    uint32_t type;
+    const void *data;
+    size_t size;
+    if (meta->findData(kKeyAVCC, &type, &data, &size)) {
+        // Parse the AVCDecoderConfigurationRecord
+
+        const uint8_t *ptr = (const uint8_t *)data;
+
+        CHECK(size >= 7);
+        CHECK_EQ((unsigned)ptr[0], 1u);  // configurationVersion == 1
+        uint8_t profile = ptr[1];
+        uint8_t level = ptr[3];
+
+        // There is decodable content out there that fails the following
+        // assertion, let's be lenient for now...
+        // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
+
+        size_t lengthSize = 1 + (ptr[4] & 3);
+
+        // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
+        // violates it...
+        // CHECK((ptr[5] >> 5) == 7);  // reserved
+
+        size_t numSeqParameterSets = ptr[5] & 31;
+
+        ptr += 6;
+        size -= 6;
+
+        sp<ABuffer> buffer = new ABuffer(1024);
+        buffer->setRange(0, 0);
+
+        for (size_t i = 0; i < numSeqParameterSets; ++i) {
+            CHECK(size >= 2);
+            size_t length = U16_AT(ptr);
+
+            ptr += 2;
+            size -= 2;
+
+            CHECK(size >= length);
+
+            memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4);
+            memcpy(buffer->data() + buffer->size() + 4, ptr, length);
+            buffer->setRange(0, buffer->size() + 4 + length);
+
+            ptr += length;
+            size -= length;
+        }
+
+        buffer->meta()->setInt32("csd", true);
+        buffer->meta()->setInt64("timeUs", 0);
+
+        msg->setObject("csd-0", buffer);
+
+        buffer = new ABuffer(1024);
+        buffer->setRange(0, 0);
+
+        CHECK(size >= 1);
+        size_t numPictureParameterSets = *ptr;
+        ++ptr;
+        --size;
+
+        for (size_t i = 0; i < numPictureParameterSets; ++i) {
+            CHECK(size >= 2);
+            size_t length = U16_AT(ptr);
+
+            ptr += 2;
+            size -= 2;
+
+            CHECK(size >= length);
+
+            memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4);
+            memcpy(buffer->data() + buffer->size() + 4, ptr, length);
+            buffer->setRange(0, buffer->size() + 4 + length);
+
+            ptr += length;
+            size -= length;
+        }
+
+        buffer->meta()->setInt32("csd", true);
+        buffer->meta()->setInt64("timeUs", 0);
+        msg->setObject("csd-1", buffer);
+    } else if (meta->findData(kKeyESDS, &type, &data, &size)) {
+        ESDS esds((const char *)data, size);
+        CHECK_EQ(esds.InitCheck(), (status_t)OK);
+
+        const void *codec_specific_data;
+        size_t codec_specific_data_size;
+        esds.getCodecSpecificInfo(
+                &codec_specific_data, &codec_specific_data_size);
+
+        sp<ABuffer> buffer = new ABuffer(codec_specific_data_size);
+
+        memcpy(buffer->data(), codec_specific_data,
+               codec_specific_data_size);
+
+        buffer->meta()->setInt32("csd", true);
+        buffer->meta()->setInt64("timeUs", 0);
+        msg->setObject("csd-0", buffer);
+    } else if (meta->findData(kKeyVorbisInfo, &type, &data, &size)) {
+        sp<ABuffer> buffer = new ABuffer(size);
+        memcpy(buffer->data(), data, size);
+
+        buffer->meta()->setInt32("csd", true);
+        buffer->meta()->setInt64("timeUs", 0);
+        msg->setObject("csd-0", buffer);
+
+        if (!meta->findData(kKeyVorbisBooks, &type, &data, &size)) {
+            return -EINVAL;
+        }
+
+        buffer = new ABuffer(size);
+        memcpy(buffer->data(), data, size);
+
+        buffer->meta()->setInt32("csd", true);
+        buffer->meta()->setInt64("timeUs", 0);
+        msg->setObject("csd-1", buffer);
+    }
+
+    *format = msg;
+
+    return OK;
+}
+
+status_t NuMediaExtractor::selectTrack(size_t index) {
+    if (mImpl == NULL) {
+        return -EINVAL;
+    }
+
+    if (index >= mImpl->countTracks()) {
+        return -ERANGE;
+    }
+
+    for (size_t i = 0; i < mSelectedTracks.size(); ++i) {
+        TrackInfo *info = &mSelectedTracks.editItemAt(i);
+
+        if (info->mTrackIndex == index) {
+            // This track has already been selected.
+            return OK;
+        }
+    }
+
+    sp<MediaSource> source = mImpl->getTrack(index);
+
+    CHECK_EQ((status_t)OK, source->start());
+
+    mSelectedTracks.push();
+    TrackInfo *info = &mSelectedTracks.editItemAt(mSelectedTracks.size() - 1);
+
+    info->mSource = source;
+    info->mTrackIndex = index;
+    info->mFinalResult = OK;
+    info->mSample = NULL;
+    info->mSampleTimeUs = -1ll;
+    info->mFlags = 0;
+
+    const char *mime;
+    CHECK(source->getFormat()->findCString(kKeyMIMEType, &mime));
+
+    if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
+        info->mFlags |= kIsVorbis;
+    }
+
+    return OK;
+}
+
+void NuMediaExtractor::releaseTrackSamples() {
+    for (size_t i = 0; i < mSelectedTracks.size(); ++i) {
+        TrackInfo *info = &mSelectedTracks.editItemAt(i);
+
+        if (info->mSample != NULL) {
+            info->mSample->release();
+            info->mSample = NULL;
+
+            info->mSampleTimeUs = -1ll;
+        }
+    }
+}
+
+ssize_t NuMediaExtractor::fetchTrackSamples(int64_t seekTimeUs) {
+    TrackInfo *minInfo = NULL;
+    ssize_t minIndex = -1;
+
+    for (size_t i = 0; i < mSelectedTracks.size(); ++i) {
+        TrackInfo *info = &mSelectedTracks.editItemAt(i);
+
+        if (seekTimeUs >= 0ll) {
+            info->mFinalResult = OK;
+
+            if (info->mSample != NULL) {
+                info->mSample->release();
+                info->mSample = NULL;
+                info->mSampleTimeUs = -1ll;
+            }
+        } else if (info->mFinalResult != OK) {
+            continue;
+        }
+
+        if (info->mSample == NULL) {
+            MediaSource::ReadOptions options;
+            if (seekTimeUs >= 0ll) {
+                options.setSeekTo(seekTimeUs);
+            }
+            status_t err = info->mSource->read(&info->mSample, &options);
+
+            if (err != OK) {
+                CHECK(info->mSample == NULL);
+
+                info->mFinalResult = err;
+                info->mSampleTimeUs = -1ll;
+                continue;
+            } else {
+                CHECK(info->mSample != NULL);
+                CHECK(info->mSample->meta_data()->findInt64(
+                            kKeyTime, &info->mSampleTimeUs));
+            }
+        }
+
+        if (minInfo == NULL  || info->mSampleTimeUs < minInfo->mSampleTimeUs) {
+            minInfo = info;
+            minIndex = i;
+        }
+    }
+
+    return minIndex;
+}
+
+status_t NuMediaExtractor::seekTo(int64_t timeUs) {
+    return fetchTrackSamples(timeUs);
+}
+
+status_t NuMediaExtractor::advance() {
+    ssize_t minIndex = fetchTrackSamples();
+
+    if (minIndex < 0) {
+        return ERROR_END_OF_STREAM;
+    }
+
+    TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
+
+    info->mSample->release();
+    info->mSample = NULL;
+    info->mSampleTimeUs = -1ll;
+
+    return OK;
+}
+
+status_t NuMediaExtractor::readSampleData(const sp<ABuffer> &buffer) {
+    ssize_t minIndex = fetchTrackSamples();
+
+    if (minIndex < 0) {
+        return ERROR_END_OF_STREAM;
+    }
+
+    TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
+
+    size_t sampleSize = info->mSample->range_length();
+
+    if (info->mFlags & kIsVorbis) {
+        // Each sample's data is suffixed by the number of page samples
+        // or -1 if not available.
+        sampleSize += sizeof(int32_t);
+    }
+
+    if (buffer->capacity() < sampleSize) {
+        return -ENOMEM;
+    }
+
+    const uint8_t *src =
+        (const uint8_t *)info->mSample->data()
+            + info->mSample->range_offset();
+
+    memcpy((uint8_t *)buffer->data(), src, info->mSample->range_length());
+
+    if (info->mFlags & kIsVorbis) {
+        int32_t numPageSamples;
+        if (!info->mSample->meta_data()->findInt32(
+                    kKeyValidSamples, &numPageSamples)) {
+            numPageSamples = -1;
+        }
+
+        memcpy((uint8_t *)buffer->data() + info->mSample->range_length(),
+               &numPageSamples,
+               sizeof(numPageSamples));
+    }
+
+    buffer->setRange(0, sampleSize);
+
+    return OK;
+}
+
+status_t NuMediaExtractor::getSampleTrackIndex(size_t *trackIndex) {
+    ssize_t minIndex = fetchTrackSamples();
+
+    if (minIndex < 0) {
+        return ERROR_END_OF_STREAM;
+    }
+
+    TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
+    *trackIndex = info->mTrackIndex;
+
+    return OK;
+}
+
+status_t NuMediaExtractor::getSampleTime(int64_t *sampleTimeUs) {
+    ssize_t minIndex = fetchTrackSamples();
+
+    if (minIndex < 0) {
+        return ERROR_END_OF_STREAM;
+    }
+
+    TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
+    *sampleTimeUs = info->mSampleTimeUs;
+
+    return OK;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 470f750..1325462 100755
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1541,6 +1541,8 @@
             "video_decoder.mpeg4", "video_encoder.mpeg4" },
         { MEDIA_MIMETYPE_VIDEO_H263,
             "video_decoder.h263", "video_encoder.h263" },
+        { MEDIA_MIMETYPE_VIDEO_VPX,
+            "video_decoder.vpx", "video_encoder.vpx" },
     };
 
     static const size_t kNumMimeToRole =
@@ -3556,6 +3558,7 @@
         //////////////// output port ////////////////////
         // format
         OMX_AUDIO_PARAM_PORTFORMATTYPE format;
+        InitOMXParams(&format);
         format.nPortIndex = kPortIndexOutput;
         format.nIndex = 0;
         status_t err = OMX_ErrorNone;
diff --git a/media/libstagefright/foundation/AMessage.cpp b/media/libstagefright/foundation/AMessage.cpp
index 0a6776e..0d6e07b 100644
--- a/media/libstagefright/foundation/AMessage.cpp
+++ b/media/libstagefright/foundation/AMessage.cpp
@@ -542,4 +542,20 @@
     }
 }
 
+size_t AMessage::countEntries() const {
+    return mNumItems;
+}
+
+const char *AMessage::getEntryNameAt(size_t index, Type *type) const {
+    if (index >= mNumItems) {
+        *type = kTypeInt32;
+
+        return NULL;
+    }
+
+    *type = mItems[index].mType;
+
+    return mItems[index].mName;
+}
+
 }  // namespace android
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
index 6f1959c..d15a535 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/VideoEditorPerformance.java
@@ -196,7 +196,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_001
     @LargeTest
     public void testPerformanceAddRemoveVideoItem() throws Exception {
         final String videoItemFileName = INPUT_FILE_PATH +
@@ -241,7 +240,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_002
     @LargeTest
     public void testPerformanceAddRemoveImageItem() throws Exception {
         final String imageItemFileName = INPUT_FILE_PATH + "IMG_1600x1200.jpg";
@@ -280,7 +278,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_003
     @LargeTest
     public void testPerformanceAddRemoveTransition() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -360,7 +357,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_004
     @LargeTest
     public void testPerformanceExport() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -541,7 +537,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_005
     @LargeTest
     public void testPerformanceThumbnailVideoItem() throws Exception {
         final String videoItemFileName = INPUT_FILE_PATH
@@ -574,7 +569,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_006
     @LargeTest
     public void testPerformanceOverlayVideoItem() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -629,7 +623,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_007
     @LargeTest
     public void testPerformanceVideoItemProperties() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -688,7 +681,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_008
     @LargeTest
     public void testPerformanceGeneratePreviewWithTransitions()
         throws Exception {
@@ -740,7 +732,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_009
     @LargeTest
     public void testPerformanceWithKenBurn() throws Exception {
         final String videoItemFileName = INPUT_FILE_PATH +
@@ -795,7 +786,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_010
     @LargeTest
     public void testPerformanceEffectOverlappingTransition() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -864,7 +854,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_011
     @LargeTest
     public void testPerformanceTransitionWithEffectOverlapping() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -994,7 +983,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_014
     @LargeTest
     public void testPerformanceWithAudioTrack() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -1049,7 +1037,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove PRF_015
     @LargeTest
     public void testPerformanceAddRemoveImageItem640x480() throws Exception {
         final String imageItemFileName = INPUT_FILE_PATH + "IMG_640x480.jpg";
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java
index 4d30784..7784c7b 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/VideoEditorStressTest.java
@@ -167,7 +167,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_001
     @LargeTest
     public void testStressAddRemoveVideoItem() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -241,7 +240,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_002
     @LargeTest
     public void testStressAddRemoveImageItem() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -310,7 +308,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_003
     @LargeTest
     public void testStressAddRemoveTransition() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -428,7 +425,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_004
     @LargeTest
     public void testStressAddRemoveOverlay() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -493,7 +489,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_005
     @LargeTest
     public void testStressAddRemoveEffects() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -590,7 +585,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_006
     @LargeTest
     public void testStressThumbnailVideoItem() throws Exception {
         final String videoItemFileName = INPUT_FILE_PATH
@@ -651,7 +645,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_007
     @LargeTest
     public void testStressMediaProperties() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -747,7 +740,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_008
     @LargeTest
     public void testStressInsertMovieItems() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -759,7 +751,7 @@
             "MPEG4_SP_640x480_15fps_1200kbps_AACLC_48khz_64kbps_m_1_17.3gp";
         final String[] loggingInfo = new String[1];
         int i = 0;
-        writeTestCaseHeader("testStressInsertMoveItems");
+        writeTestCaseHeader("testStressInsertMovieItems");
 
         final MediaVideoItem mediaItem1 = new MediaVideoItem(mVideoEditor,
             "m1", VideoItemFileName1, renderingMode);
@@ -801,7 +793,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_009
     @LargeTest
     public void testStressLoadAndSave() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -916,7 +907,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_010
     @LargeTest
     public void testStressMultipleExport() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -1007,7 +997,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_011
     @LargeTest
     public void testStressOverlayTransKenBurn() throws Exception {
         final int renderingMode = MediaItem.RENDERING_MODE_BLACK_BORDER;
@@ -1094,7 +1083,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_012
     @LargeTest
     public void testStressAudioTrackVideo() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -1147,7 +1135,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_013
     @LargeTest
     public void testStressStoryBoard() throws Exception {
         final String videoItemFileName1 = INPUT_FILE_PATH +
@@ -1237,7 +1224,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_014
     @LargeTest
     public void testStressAudioTrackOnly() throws Exception {
 
@@ -1267,7 +1253,6 @@
      *
      * @throws Exception
      */
-    // TODO : remove TC_STR_016  -- New Test Case
     @LargeTest
     public void testStressThumbnailImageItem() throws Exception {
         final String imageItemFileName = INPUT_FILE_PATH + "IMG_640x480.jpg";
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 2e2834c..6256951 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -3231,7 +3231,7 @@
 {
     // FIXME explain this formula
     int frameCount = (3 * mFrameCount * mSampleRate) / thread->sampleRate();
-    OutputTrack *outputTrack = new OutputTrack((ThreadBase *)thread,
+    OutputTrack *outputTrack = new OutputTrack(thread,
                                             this,
                                             mSampleRate,
                                             mFormat,
@@ -3300,7 +3300,7 @@
 
 // TrackBase constructor must be called with AudioFlinger::mLock held
 AudioFlinger::ThreadBase::TrackBase::TrackBase(
-            const wp<ThreadBase>& thread,
+            ThreadBase *thread,
             const sp<Client>& client,
             uint32_t sampleRate,
             audio_format_t format,
@@ -3456,7 +3456,7 @@
 
 // Track constructor must be called with AudioFlinger::mLock and ThreadBase::mLock held
 AudioFlinger::PlaybackThread::Track::Track(
-            const wp<ThreadBase>& thread,
+            PlaybackThread *thread,
             const sp<Client>& client,
             audio_stream_type_t streamType,
             uint32_t sampleRate,
@@ -3470,11 +3470,9 @@
     mAuxEffectId(0), mHasVolumeController(false)
 {
     if (mCblk != NULL) {
-        sp<ThreadBase> baseThread = thread.promote();
-        if (baseThread != 0) {
-            PlaybackThread *playbackThread = (PlaybackThread *)baseThread.get();
-            mName = playbackThread->getTrackName_l();
-            mMainBuffer = playbackThread->mixBuffer();
+        if (thread != NULL) {
+            mName = thread->getTrackName_l();
+            mMainBuffer = thread->mixBuffer();
         }
         ALOGV("Track constructor name %d, calling pid %d", mName, IPCThreadState::self()->getCallingPid());
         if (mName < 0) {
@@ -3760,7 +3758,7 @@
 
 sp<AudioFlinger::PlaybackThread::TimedTrack>
 AudioFlinger::PlaybackThread::TimedTrack::create(
-            const wp<ThreadBase>& thread,
+            PlaybackThread *thread,
             const sp<Client>& client,
             audio_stream_type_t streamType,
             uint32_t sampleRate,
@@ -3785,7 +3783,7 @@
 }
 
 AudioFlinger::PlaybackThread::TimedTrack::TimedTrack(
-            const wp<ThreadBase>& thread,
+            PlaybackThread *thread,
             const sp<Client>& client,
             audio_stream_type_t streamType,
             uint32_t sampleRate,
@@ -3926,15 +3924,6 @@
         return INVALID_OPERATION;
     }
 
-    // get ahold of the output stream that these samples will be written to
-    sp<ThreadBase> thread = mThread.promote();
-    if (thread == NULL) {
-        buffer->raw = 0;
-        buffer->frameCount = 0;
-        return INVALID_OPERATION;
-    }
-    PlaybackThread* playbackThread = static_cast<PlaybackThread*>(thread.get());
-
     Mutex::Autolock _l(mTimedBufferQueueLock);
 
     while (true) {
@@ -4122,7 +4111,7 @@
         TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
 
         void* start = head.buffer()->pointer();
-        void* end   = head.buffer()->pointer() + head.buffer()->size();
+        void* end   = (char *) head.buffer()->pointer() + head.buffer()->size();
 
         if ((buffer->raw >= start) && (buffer->raw <= end)) {
             head.setPosition(head.position() +
@@ -4160,7 +4149,7 @@
 
 // RecordTrack constructor must be called with AudioFlinger::mLock held
 AudioFlinger::RecordThread::RecordTrack::RecordTrack(
-            const wp<ThreadBase>& thread,
+            RecordThread *thread,
             const sp<Client>& client,
             uint32_t sampleRate,
             audio_format_t format,
@@ -4273,17 +4262,16 @@
 // ----------------------------------------------------------------------------
 
 AudioFlinger::PlaybackThread::OutputTrack::OutputTrack(
-            const wp<ThreadBase>& thread,
+            PlaybackThread *playbackThread,
             DuplicatingThread *sourceThread,
             uint32_t sampleRate,
             audio_format_t format,
             uint32_t channelMask,
             int frameCount)
-    :   Track(thread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelMask, frameCount, NULL, 0),
+    :   Track(playbackThread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelMask, frameCount, NULL, 0),
     mActive(false), mSourceThread(sourceThread)
 {
 
-    PlaybackThread *playbackThread = (PlaybackThread *)thread.unsafe_get();
     if (mCblk != NULL) {
         mCblk->flags |= CBLK_DIRECTION_OUT;
         mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t);
@@ -6595,18 +6583,17 @@
 #undef LOG_TAG
 #define LOG_TAG "AudioFlinger::EffectModule"
 
-AudioFlinger::EffectModule::EffectModule(const wp<ThreadBase>& wThread,
+AudioFlinger::EffectModule::EffectModule(ThreadBase *thread,
                                         const wp<AudioFlinger::EffectChain>& chain,
                                         effect_descriptor_t *desc,
                                         int id,
                                         int sessionId)
-    : mThread(wThread), mChain(chain), mId(id), mSessionId(sessionId), mEffectInterface(NULL),
+    : mThread(thread), mChain(chain), mId(id), mSessionId(sessionId), mEffectInterface(NULL),
       mStatus(NO_INIT), mState(IDLE), mSuspended(false)
 {
     ALOGV("Constructor %p", this);
     int lStatus;
-    sp<ThreadBase> thread = mThread.promote();
-    if (thread == 0) {
+    if (thread == NULL) {
         return;
     }
 
@@ -7576,15 +7563,14 @@
 #undef LOG_TAG
 #define LOG_TAG "AudioFlinger::EffectChain"
 
-AudioFlinger::EffectChain::EffectChain(const wp<ThreadBase>& wThread,
+AudioFlinger::EffectChain::EffectChain(ThreadBase *thread,
                                         int sessionId)
-    : mThread(wThread), mSessionId(sessionId), mActiveTrackCnt(0), mTrackCnt(0), mTailBufferCount(0),
+    : mThread(thread), mSessionId(sessionId), mActiveTrackCnt(0), mTrackCnt(0), mTailBufferCount(0),
       mOwnInBuffer(false), mVolumeCtrlIdx(-1), mLeftVolume(UINT_MAX), mRightVolume(UINT_MAX),
       mNewLeftVolume(UINT_MAX), mNewRightVolume(UINT_MAX)
 {
     mStrategy = AudioSystem::getStrategyForStream(AUDIO_STREAM_MUSIC);
-    sp<ThreadBase> thread = mThread.promote();
-    if (thread == 0) {
+    if (thread == NULL) {
         return;
     }
     mMaxTailBuffers = ((kProcessTailDurationMs * thread->sampleRate()) / 1000) /
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 50712cf..1a52de5 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -318,7 +318,7 @@
                 // The upper 16 bits are used for track-specific flags.
             };
 
-                                TrackBase(const wp<ThreadBase>& thread,
+                                TrackBase(ThreadBase *thread,
                                         const sp<Client>& client,
                                         uint32_t sampleRate,
                                         audio_format_t format,
@@ -591,7 +591,7 @@
         // playback track
         class Track : public TrackBase {
         public:
-                                Track(  const wp<ThreadBase>& thread,
+                                Track(  PlaybackThread *thread,
                                         const sp<Client>& client,
                                         audio_stream_type_t streamType,
                                         uint32_t sampleRate,
@@ -674,7 +674,7 @@
 
         class TimedTrack : public Track {
           public:
-            static sp<TimedTrack> create(const wp<ThreadBase>& thread,
+            static sp<TimedTrack> create(PlaybackThread *thread,
                                          const sp<Client>& client,
                                          audio_stream_type_t streamType,
                                          uint32_t sampleRate,
@@ -719,7 +719,7 @@
             void        trimTimedBufferQueue_l();
 
           private:
-            TimedTrack(const wp<ThreadBase>& thread,
+            TimedTrack(PlaybackThread *thread,
                        const sp<Client>& client,
                        audio_stream_type_t streamType,
                        uint32_t sampleRate,
@@ -755,7 +755,7 @@
                 int16_t *mBuffer;
             };
 
-                                OutputTrack(  const wp<ThreadBase>& thread,
+                                OutputTrack(PlaybackThread *thread,
                                         DuplicatingThread *sourceThread,
                                         uint32_t sampleRate,
                                         audio_format_t format,
@@ -1042,7 +1042,7 @@
         // record track
         class RecordTrack : public TrackBase {
         public:
-                                RecordTrack(const wp<ThreadBase>& thread,
+                                RecordTrack(RecordThread *thread,
                                         const sp<Client>& client,
                                         uint32_t sampleRate,
                                         audio_format_t format,
@@ -1168,7 +1168,7 @@
     // the attached track(s) to accumulate their auxiliary channel.
     class EffectModule: public RefBase {
     public:
-        EffectModule(const wp<ThreadBase>& wThread,
+        EffectModule(ThreadBase *thread,
                         const wp<AudioFlinger::EffectChain>& chain,
                         effect_descriptor_t *desc,
                         int id,
@@ -1353,6 +1353,7 @@
     class EffectChain: public RefBase {
     public:
         EffectChain(const wp<ThreadBase>& wThread, int sessionId);
+        EffectChain(ThreadBase *thread, int sessionId);
         virtual ~EffectChain();
 
         // special key used for an entry in mSuspendedEffects keyed vector
diff --git a/tests/RenderScriptTests/SceneGraph/res/drawable-nodpi/icon.png b/tests/RenderScriptTests/SceneGraph/res/drawable-nodpi/icon.png
new file mode 100644
index 0000000..ff34a7f
--- /dev/null
+++ b/tests/RenderScriptTests/SceneGraph/res/drawable-nodpi/icon.png
Binary files differ
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/blur_h.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/blur_h.glsl
index fa468cc..c34adc9 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/blur_h.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/blur_h.glsl
@@ -3,13 +3,13 @@
 void main() {
    vec2 blurCoord = varTex0;
    blurCoord.x = varTex0.x + UNI_blurOffset0;
-   vec3 col = texture2D(UNI_Tex0, blurCoord).rgb;
+   vec3 col = texture2D(UNI_color, blurCoord).rgb;
    blurCoord.x = varTex0.x + UNI_blurOffset1;
-   col += texture2D(UNI_Tex0, blurCoord).rgb;
+   col += texture2D(UNI_color, blurCoord).rgb;
    blurCoord.x = varTex0.x + UNI_blurOffset2;
-   col += texture2D(UNI_Tex0, blurCoord).rgb;
+   col += texture2D(UNI_color, blurCoord).rgb;
    blurCoord.x = varTex0.x + UNI_blurOffset3;
-   col += texture2D(UNI_Tex0, blurCoord).rgb;
+   col += texture2D(UNI_color, blurCoord).rgb;
 
-   gl_FragColor = vec4(col * 0.25, 0.0); //texture2D(UNI_Tex0, varTex0);
+   gl_FragColor = vec4(col * 0.25, 0.0);
 }
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/blur_v.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/blur_v.glsl
index a644a3e..ade05a2 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/blur_v.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/blur_v.glsl
@@ -3,15 +3,15 @@
 void main() {
    vec2 blurCoord = varTex0;
    blurCoord.y = varTex0.y + UNI_blurOffset0;
-   vec3 col = texture2D(UNI_Tex0, blurCoord).rgb;
+   vec3 col = texture2D(UNI_color, blurCoord).rgb;
    blurCoord.y = varTex0.y + UNI_blurOffset1;
-   col += texture2D(UNI_Tex0, blurCoord).rgb;
+   col += texture2D(UNI_color, blurCoord).rgb;
    blurCoord.y = varTex0.y + UNI_blurOffset2;
-   col += texture2D(UNI_Tex0, blurCoord).rgb;
+   col += texture2D(UNI_color, blurCoord).rgb;
    blurCoord.y = varTex0.y + UNI_blurOffset3;
-   col += texture2D(UNI_Tex0, blurCoord).rgb;
+   col += texture2D(UNI_color, blurCoord).rgb;
 
    col = col * 0.25;
 
-   gl_FragColor = vec4(col, 0.0); //texture2D(UNI_Tex0, varTex0);
+   gl_FragColor = vec4(col, 0.0);
 }
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/diffuse.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/diffuse.glsl
index 5d8938b..2eb1028 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/diffuse.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/diffuse.glsl
@@ -12,8 +12,8 @@
    float light0_Diffuse = dot(worldNorm, light0Vec);
 
    vec2 t0 = varTex0.xy;
-   lowp vec4 col = texture2D(UNI_Tex0, t0).rgba;
+   lowp vec4 col = texture2D(UNI_diffuse, t0).rgba;
    col.xyz = col.xyz * light0_Diffuse * 1.2;
-   gl_FragColor = col; //vec4(0.0, 1.0, 0.0, 0.0);
+   gl_FragColor = col;
 }
 
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/metal.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/metal.glsl
index 51f0612..b90a7b2 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/metal.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/metal.glsl
@@ -14,8 +14,8 @@
    float light0_Specular = pow(light0Spec, 15.0) * 0.5;
 
    vec2 t0 = varTex0.xy;
-   lowp vec4 col = texture2D(UNI_Tex0, t0).rgba;
-   col.xyz = col.xyz * (textureCube(UNI_Tex1, worldNorm).rgb * 0.5 + vec3(light0_Diffuse));
+   lowp vec4 col = texture2D(UNI_diffuse, t0).rgba;
+   col.xyz = col.xyz * (textureCube(UNI_reflection, worldNorm).rgb * 0.5 + vec3(light0_Diffuse));
    col.xyz += light0_Specular * vec3(0.8, 0.8, 1.0);
 
    gl_FragColor = col;
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/paintf.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/paintf.glsl
index 893d553..f3b89ed 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/paintf.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/paintf.glsl
@@ -14,12 +14,12 @@
    float light0_Specular = pow(light0Spec, 150.0) * 0.5;
 
    vec2 t0 = varTex0.xy;
-   lowp vec4 col = texture2D(UNI_Tex0, t0).rgba;
+   lowp vec4 col = texture2D(UNI_diffuse, t0).rgba;
    col.xyz = col.xyz * light0_Diffuse * 1.1;
    col.xyz += light0_Specular * vec3(0.8, 0.8, 1.0);
 
    float fresnel = mix(pow(1.0 - light0_Diffuse, 15.0), 1.0, 0.1);
-   col.xyz = mix(col.xyz, textureCube(UNI_Tex1, -light0R).rgb * 2.4, fresnel);
+   col.xyz = mix(col.xyz, textureCube(UNI_reflection, -light0R).rgb * 2.4, fresnel);
    col.w = 0.8;
    gl_FragColor = col;
 }
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/plastic.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/plastic.glsl
index ceb53bd..56f7151f 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/plastic.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/plastic.glsl
@@ -14,7 +14,7 @@
    float light0_Specular = pow(light0Spec, 10.0) * 0.5;
 
    vec2 t0 = varTex0.xy;
-   lowp vec4 col = texture2D(UNI_Tex0, t0).rgba;
+   lowp vec4 col = texture2D(UNI_diffuse, t0).rgba;
    col.xyz = col.xyz * light0_Diffuse * 1.2;
    col.xyz += light0_Specular * vec3(0.8, 0.8, 1.0);
    gl_FragColor = col;
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/select_color.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/select_color.glsl
index 42b231a..1a927ca 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/select_color.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/select_color.glsl
@@ -1,7 +1,7 @@
 varying vec2 varTex0;
 
 void main() {
-   vec3 col = texture2D(UNI_Tex0, varTex0).rgb;
+   vec3 col = texture2D(UNI_color, varTex0).rgb;
 
    vec3 desat = vec3(0.299, 0.587, 0.114);
    float lum = dot(desat, col);
diff --git a/tests/RenderScriptTests/SceneGraph/res/raw/texture.glsl b/tests/RenderScriptTests/SceneGraph/res/raw/texture.glsl
index dd709cf..662ecd8 100644
--- a/tests/RenderScriptTests/SceneGraph/res/raw/texture.glsl
+++ b/tests/RenderScriptTests/SceneGraph/res/raw/texture.glsl
@@ -1,7 +1,7 @@
 varying vec2 varTex0;
 
 void main() {
-   lowp vec4 col = texture2D(UNI_Tex0, varTex0).rgba;
+   lowp vec4 col = texture2D(UNI_color, varTex0).rgba;
    gl_FragColor = col;
 }
 
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java
index c8e25feb..8a468db 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java
@@ -83,10 +83,12 @@
                 mBuilder.addConstant(mShader.mPerObjConstants);
             }
             for (int i = 0; i < mShader.mTextureTypes.size(); i ++) {
-                mBuilder.addTexture(mShader.mTextureTypes.get(i));
+                mBuilder.addTexture(mShader.mTextureTypes.get(i),
+                                    mShader.mTextureNames.get(i));
             }
             for (int i = 0; i < mShader.mShaderTextureTypes.size(); i ++) {
-                mBuilder.addTexture(mShader.mShaderTextureTypes.get(i));
+                mBuilder.addTexture(mShader.mShaderTextureTypes.get(i),
+                                    mShader.mShaderTextureNames.get(i));
             }
 
             mShader.mProgram = mBuilder.create();
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Renderable.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Renderable.java
index 3476e35..9266f30 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Renderable.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Renderable.java
@@ -22,6 +22,7 @@
 import java.util.Iterator;
 
 import com.android.scenegraph.Float4Param;
+import com.android.scenegraph.MatrixTransform;
 import com.android.scenegraph.SceneManager;
 import com.android.scenegraph.ShaderParam;
 import com.android.scenegraph.TransformParam;
@@ -89,6 +90,10 @@
         mMaterialName = name;
     }
 
+    public Transform getTransform() {
+        return mTransform;
+    }
+
     public void setTransform(Transform t) {
         mTransform = t;
         if (mField != null) {
@@ -199,12 +204,14 @@
         if (mRenderState == null) {
             mRenderState = SceneManager.getDefaultState();
         }
+        if (mTransform == null) {
+            mTransform = SceneManager.getDefaultTransform();
+        }
         updateVertexConstants(rs);
         updateFragmentConstants(rs);
 
-        if (mTransform != null) {
-            mData.transformMatrix = mTransform.getRSData().getAllocation();
-        }
+        mData.transformMatrix = mTransform.getRSData().getAllocation();
+
         mData.name = getNameAlloc(rs);
         mData.render_state = mRenderState.getRSData().getAllocation();
         mData.bVolInitialized = 0;
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Scene.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Scene.java
index 9bd3bf9..27336ab 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Scene.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Scene.java
@@ -22,6 +22,10 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import com.android.scenegraph.Camera;
+import com.android.scenegraph.CompoundTransform;
+import com.android.scenegraph.RenderPass;
+import com.android.scenegraph.Renderable;
 import com.android.scenegraph.SceneManager;
 import com.android.scenegraph.TextureBase;
 
@@ -81,6 +85,18 @@
         mRootTransforms.appendChild(t);
     }
 
+    public CompoundTransform appendNewCompoundTransform() {
+        CompoundTransform t = new CompoundTransform();
+        appendTransform(t);
+        return t;
+    }
+
+    public MatrixTransform appendNewMatrixTransform() {
+        MatrixTransform t = new MatrixTransform();
+        appendTransform(t);
+        return t;
+    }
+
     // temporary
     public void addToTransformMap(Transform t) {
         mTransformMap.put(t.getName(), t);
@@ -97,6 +113,12 @@
         mRenderPasses.add(p);
     }
 
+    public RenderPass appendNewRenderPass() {
+        RenderPass p = new RenderPass();
+        appendRenderPass(p);
+        return p;
+    }
+
     public void clearRenderPasses() {
         mRenderPasses.clear();
     }
@@ -115,6 +137,12 @@
         mCameras.add(c);
     }
 
+    public Camera appendNewCamera() {
+        Camera c = new Camera();
+        appendCamera(c);
+        return c;
+    }
+
     public void appendShader(FragmentShader f) {
         if (f == null) {
             throw new RuntimeException("Adding null object");
@@ -142,7 +170,15 @@
             throw new RuntimeException("Adding null object");
         }
         mRenderables.add(d);
-        mRenderableMap.put(d.getName(), d);
+        if (d.getName() != null) {
+            mRenderableMap.put(d.getName(), d);
+        }
+    }
+
+    public Renderable appendNewRenderable() {
+        Renderable r = new Renderable();
+        appendRenderable(r);
+        return r;
     }
 
     public ArrayList<RenderableBase> getRenderables() {
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java
index 3d1019e..4ff2c8b 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java
@@ -80,6 +80,7 @@
     private VertexShader mDefaultVertex;
 
     private RenderState mDefaultState;
+    private Transform mDefaultTransform;
 
     private static Allocation getDefault(boolean isCube) {
         final int dimension = 4;
@@ -161,24 +162,32 @@
         return b;
     }
 
-    public static Allocation loadCubemap(String name, RenderScriptGL rs, Resources res) {
-        Bitmap b = loadBitmap(name, res);
+    static Allocation createFromBitmap(Bitmap b, RenderScriptGL rs, boolean isCube) {
         if (b == null) {
             return null;
         }
-        return Allocation.createCubemapFromBitmap(rs, b,
-                                                  MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE,
-                                                  Allocation.USAGE_GRAPHICS_TEXTURE);
+        MipmapControl mip = MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE;
+        int usage = Allocation.USAGE_GRAPHICS_TEXTURE;
+        if (isCube) {
+            return Allocation.createCubemapFromBitmap(rs, b, mip, usage);
+        }
+        return Allocation.createFromBitmap(rs, b, mip, usage);
+    }
+
+    public static Allocation loadCubemap(String name, RenderScriptGL rs, Resources res) {
+        return createFromBitmap(loadBitmap(name, res), rs, true);
+    }
+
+    public static Allocation loadCubemap(int id, RenderScriptGL rs, Resources res) {
+        return createFromBitmap(BitmapFactory.decodeResource(res, id), rs, true);
     }
 
     public static Allocation loadTexture2D(String name, RenderScriptGL rs, Resources res) {
-        Bitmap b = loadBitmap(name, res);
-        if (b == null) {
-            return null;
-        }
-        return Allocation.createFromBitmap(rs, b,
-                                           Allocation.MipmapControl.MIPMAP_ON_SYNC_TO_TEXTURE,
-                                           Allocation.USAGE_GRAPHICS_TEXTURE);
+        return createFromBitmap(loadBitmap(name, res), rs, false);
+    }
+
+    public static Allocation loadTexture2D(int id, RenderScriptGL rs, Resources res) {
+        return createFromBitmap(BitmapFactory.decodeResource(res, id), rs, false);
     }
 
     public static ProgramStore BLEND_ADD_DEPTH_NONE(RenderScript rs) {
@@ -261,6 +270,7 @@
         }
 
         mActiveScene.appendShader(getDefaultVS());
+        mActiveScene.appendTransform(getDefaultTransform());
     }
 
     static RenderScriptGL getRS() {
@@ -364,14 +374,15 @@
             final String code = "\n" +
                 "varying vec2 varTex0;\n" +
                 "void main() {\n" +
-                "   lowp vec4 col = texture2D(UNI_Tex0, varTex0).rgba;\n" +
+                "   lowp vec4 col = texture2D(UNI_color, varTex0).rgba;\n" +
                 "   gl_FragColor = col;\n" +
                 "}\n";
 
             FragmentShader.Builder fb = new FragmentShader.Builder(rs);
             fb.setShader(code);
-            fb.addTexture(Program.TextureType.TEXTURE_2D, "Tex0");
+            fb.addTexture(Program.TextureType.TEXTURE_2D, "color");
             sSceneManager.mTexture = fb.create();
+            sSceneManager.mTexture.mProgram.bindSampler(Sampler.CLAMP_LINEAR_MIP_LINEAR(rs), 0);
         }
 
         return sSceneManager.mTexture;
@@ -383,10 +394,22 @@
         }
         if (sSceneManager.mDefaultState == null) {
             sSceneManager.mDefaultState = new RenderState(getDefaultVS(), getColorFS(), null, null);
+            sSceneManager.mDefaultState.setName("__DefaultState");
         }
         return sSceneManager.mDefaultState;
     }
 
+    static Transform getDefaultTransform() {
+        if (sSceneManager == null) {
+            return null;
+        }
+        if (sSceneManager.mDefaultTransform == null) {
+            sSceneManager.mDefaultTransform = new MatrixTransform();
+            sSceneManager.mDefaultTransform.setName("__DefaultTransform");
+        }
+        return sSceneManager.mDefaultTransform;
+    }
+
     public static SceneManager getInstance() {
         if (sSceneManager == null) {
             sSceneManager = new SceneManager();
@@ -444,6 +467,7 @@
         mColor = null;
         mTexture = null;
         mDefaultState = null;
+        mDefaultTransform = null;
 
         mExportScript = new ScriptC_export(rs, res, R.raw.export);
 
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Texture2D.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Texture2D.java
index 8fae9d9..b53ab88 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Texture2D.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/Texture2D.java
@@ -30,6 +30,7 @@
 public class Texture2D extends TextureBase {
     String mFileName;
     String mFileDir;
+    int mResourceID;
 
     public Texture2D() {
         super(ScriptC_export.const_TextureType_TEXTURE_2D);
@@ -40,6 +41,17 @@
         setTexture(tex);
     }
 
+    public Texture2D(String dir, String file) {
+        super(ScriptC_export.const_TextureType_TEXTURE_CUBE);
+        setFileDir(dir);
+        setFileName(file);
+    }
+
+    public Texture2D(int resourceID) {
+        super(ScriptC_export.const_TextureType_TEXTURE_2D);
+        mResourceID = resourceID;
+    }
+
     public void setFileDir(String dir) {
         mFileDir = dir;
     }
@@ -62,8 +74,12 @@
     void load() {
         RenderScriptGL rs = SceneManager.getRS();
         Resources res = SceneManager.getRes();
-        String shortName = mFileName.substring(mFileName.lastIndexOf('/') + 1);
-        setTexture(SceneManager.loadTexture2D(mFileDir + shortName, rs, res));
+        if (mFileName != null && mFileName.length() > 0) {
+            String shortName = mFileName.substring(mFileName.lastIndexOf('/') + 1);
+            setTexture(SceneManager.loadTexture2D(mFileDir + shortName, rs, res));
+        } else if (mResourceID != 0) {
+            setTexture(SceneManager.loadTexture2D(mResourceID, rs, res));
+        }
     }
 
     ScriptField_Texture_s getRsData(boolean loadNow) {
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/TextureCube.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/TextureCube.java
index 12c81c2..1269e3c 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/TextureCube.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/TextureCube.java
@@ -31,6 +31,7 @@
 public class TextureCube extends TextureBase {
     String mFileName;
     String mFileDir;
+    int mResourceID;
 
     public TextureCube() {
         super(ScriptC_export.const_TextureType_TEXTURE_CUBE);
@@ -47,6 +48,11 @@
         setFileName(file);
     }
 
+    public TextureCube(int resourceID) {
+        super(ScriptC_export.const_TextureType_TEXTURE_2D);
+        mResourceID = resourceID;
+    }
+
     public void setFileDir(String dir) {
         mFileDir = dir;
     }
@@ -69,8 +75,12 @@
     void load() {
         RenderScriptGL rs = SceneManager.getRS();
         Resources res = SceneManager.getRes();
-        String shortName = mFileName.substring(mFileName.lastIndexOf('/') + 1);
-        setTexture(SceneManager.loadCubemap(mFileDir + shortName, rs, res));
+        if (mFileName != null && mFileName.length() > 0) {
+            String shortName = mFileName.substring(mFileName.lastIndexOf('/') + 1);
+            setTexture(SceneManager.loadCubemap(mFileDir + shortName, rs, res));
+        } else if (mResourceID != 0) {
+            setTexture(SceneManager.loadCubemap(mResourceID , rs, res));
+        }
     }
 
     ScriptField_Texture_s getRsData(boolean loadNow) {
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
index 9388838..621bfa36 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
@@ -38,12 +38,13 @@
     private static String TAG = "SimpleAppRS";
 
     SceneManager mSceneManager;
-    ArrayList<Renderable> geometry = new ArrayList<Renderable>();
 
-    Scene mScene;
     RenderScriptGL mRS;
     Resources mRes;
 
+    Scene mScene;
+    Mesh mSimpleMesh;
+
     public void init(RenderScriptGL rs, Resources res, int width, int height) {
         mRS = rs;
         mRes = res;
@@ -53,9 +54,9 @@
         mScene = new Scene();
 
         setupGeometry();
+        setupRenderables();
         setupCamera();
         setupRenderPass();
-        setupShaders();
 
         mSceneManager.setActiveScene(mScene);
 
@@ -63,16 +64,9 @@
         mRS.bindRootScript(mSceneManager.getRenderLoop());
     }
 
-    private void setupShaders() {
-        // Built-in shader that provides position, texcoord and normal
-        VertexShader genericV = SceneManager.getDefaultVS();
-        // Built-in shader that displays a color
-        FragmentShader colorF = SceneManager.getColorFS();
-        mScene.assignRenderState(new RenderState(genericV, colorF, null, null));
-    }
     private void setupGeometry() {
-        Mesh.TriangleMeshBuilder tmb = new Mesh.TriangleMeshBuilder(mRS,
-                                           3, Mesh.TriangleMeshBuilder.TEXTURE_0);
+        Mesh.TriangleMeshBuilder tmb = new Mesh.TriangleMeshBuilder(mRS, 3,
+                                                         Mesh.TriangleMeshBuilder.TEXTURE_0);
 
         tmb.setTexture(0.0f, 1.0f).addVertex(-1.0f, 1.0f, 0.0f);
         tmb.setTexture(0.0f, 0.0f).addVertex(-1.0f, -1.0f, 0.0f);
@@ -81,40 +75,57 @@
 
         tmb.addTriangle(0, 1, 2);
         tmb.addTriangle(2, 3, 0);
+        mSimpleMesh = tmb.create(true);
+    }
 
-        Mesh mesh  = tmb.create(true);
+    private void setupRenderables() {
+        // Built-in shader that provides position, texcoord and normal
+        VertexShader genericV = SceneManager.getDefaultVS();
+        // Built-in shader that displays a color
+        FragmentShader colorF = SceneManager.getColorFS();
+        // Built-in shader that displays a texture
+        FragmentShader textureF = SceneManager.getTextureFS();
+        RenderState colorRS = new RenderState(genericV, colorF, null, null);
+        ProgramStore alphaBlend = ProgramStore.BLEND_ALPHA_DEPTH_TEST(mRS);
+        RenderState texRS = new RenderState(genericV, textureF, alphaBlend, null);
 
-        Renderable quad = new Renderable();
-        quad.setMesh(mesh);
-        quad.setTransform(new CompoundTransform());
+        // Draw a simple colored quad
+        Renderable quad = mScene.appendNewRenderable();
+        quad.setMesh(mSimpleMesh);
         quad.appendSourceParams(new Float4Param("color", 0.2f, 0.3f, 0.4f));
+        quad.setRenderState(colorRS);
 
-        mScene.appendRenderable(quad);
-        geometry.add(quad);
+        // Draw a textured quad
+        quad = mScene.appendNewRenderable();
+        quad.setMesh(mSimpleMesh);
+        // Make a transform to position the quad
+        CompoundTransform t = mScene.appendNewCompoundTransform();
+        t.addTranslate("position", new Float3(2, 2, 0));
+        quad.setTransform(t);
+        quad.appendSourceParams(new TextureParam("color", new Texture2D(R.drawable.icon)));
+        quad.setRenderState(texRS);
     }
 
     private void setupCamera() {
-        Camera camera = new Camera();
+        Camera camera = mScene.appendNewCamera();
         camera.setFar(200);
         camera.setNear(0.1f);
         camera.setFOV(60);
-        CompoundTransform cameraTransform = new CompoundTransform();
+        CompoundTransform cameraTransform = mScene.appendNewCompoundTransform();
         cameraTransform.addTranslate("camera", new Float3(0, 0, 10));
-        mScene.appendTransform(cameraTransform);
         camera.setTransform(cameraTransform);
-        mScene.appendCamera(camera);
     }
 
     private void setupRenderPass() {
-        RenderPass mainPass = new RenderPass();
+        RenderPass mainPass = mScene.appendNewRenderPass();
         mainPass.setClearColor(new Float4(1.0f, 1.0f, 1.0f, 1.0f));
         mainPass.setShouldClearColor(true);
         mainPass.setClearDepth(1.0f);
         mainPass.setShouldClearDepth(true);
         mainPass.setCamera(mScene.getCameras().get(0));
-        for (Renderable renderable : geometry) {
-            mainPass.appendRenderable(renderable);
+        ArrayList<RenderableBase> allRender = mScene.getRenderables();
+        for (RenderableBase renderable : allRender) {
+            mainPass.appendRenderable((Renderable)renderable);
         }
-        mScene.appendRenderPass(mainPass);
     }
 }
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java
index c182c29..e272cc5 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java
@@ -48,7 +48,7 @@
         mPosValue = new Float3(0, 4, 0);
 
         // Make a camera transform we can manipulate
-        mCameraRig = new CompoundTransform();
+        mCameraRig = scene.appendNewCompoundTransform();
         mCameraRig.setName("CameraRig");
 
         mPosition = mCameraRig.addTranslate("Position", mPosValue);
@@ -56,10 +56,8 @@
         mRotateX  = mCameraRig.addRotate("RotateX", new Float3(1, 0, 0), mRotateXValue);
         mDist     = mCameraRig.addTranslate("Distance", mDistValue);
 
-        scene.appendTransform(mCameraRig);
-        mCamera = new Camera();
+        mCamera = scene.appendNewCamera();
         mCamera.setTransform(mCameraRig);
-        scene.appendCamera(mCamera);
     }
 
     public Camera getCamera() {