Merge "libui: add ability to force a framebuffer format for EGL's use"
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/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index bee5880..90dfe76 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -49,7 +49,6 @@
{
switch (f) {
case PIXEL_FORMAT_A_8:
- case PIXEL_FORMAT_L_8:
return SkBitmap::kA8_Config;
case PIXEL_FORMAT_RGB_565:
return SkBitmap::kRGB_565_Config;
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/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index c9468eb..882dd6b 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -30,14 +30,14 @@
void setServiceInfo(in AccessibilityServiceInfo info);
/**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id.
+ * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by accessibility id.
*
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id from
* where to start the search. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
@@ -49,17 +49,16 @@
IAccessibilityInteractionConnectionCallback callback, long threadId);
/**
- * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
- * insensitive containment. The search is performed in the window whose
- * id is specified and starts from the node whose accessibility id is
- * specified.
+ * Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text.
+ * The match is case insensitive containment. The search is performed in the window
+ * whose id is specified and starts from the node whose accessibility id is specified.
*
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id from
* where to start the search. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
* @param text The searched text.
* @param interactionId The id of the interaction for matching with the callback result.
@@ -72,16 +71,16 @@
long threadId);
/**
- * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
- * the window whose id is specified and starts from the node whose accessibility
- * id is specified.
+ * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by View id. The search
+ * is performed in the window whose id is specified and starts from the node whose
+ * accessibility id is specified.
*
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id from
* where to start the search. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
* @param id The id of the node.
* @param interactionId The id of the interaction for matching with the callback result.
@@ -94,14 +93,15 @@
long threadId);
/**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
+ * Performs an accessibility action on an
+ * {@link android.view.accessibility.AccessibilityNodeInfo}.
*
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id from
* where to start the search. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
* @param action The action to perform.
* @param interactionId The id of the interaction for matching with the callback result.
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
index 616b796..334981a 100644
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
@@ -49,12 +49,14 @@
private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
- public static final int ACTIVE_WINDOW_ID = -1;
-
- public static final long ROOT_NODE_ID = -1;
-
private static final int TIMEOUT_REGISTER_SERVICE = 5000;
+ public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID;
+
+ public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID;
+
+ public static final int UNDEFINED = -1;
+
private final Object mLock = new Object();
private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
@@ -63,8 +65,6 @@
private AccessibilityEvent mLastEvent;
- private AccessibilityEvent mLastWindowStateChangeEvent;
-
private volatile boolean mWaitingForEventDelivery;
private volatile boolean mUnprocessedEventAvailable;
@@ -141,17 +141,8 @@
synchronized (mLock) {
while (true) {
mLastEvent = AccessibilityEvent.obtain(event);
-
- final int eventType = event.getEventType();
- if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- || eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
- if (mLastWindowStateChangeEvent != null) {
- mLastWindowStateChangeEvent.recycle();
- }
- mLastWindowStateChangeEvent = mLastEvent;
- }
-
if (!mWaitingForEventDelivery) {
+ mLock.notifyAll();
break;
}
if (!mUnprocessedEventAvailable) {
@@ -295,6 +286,43 @@
}
/**
+ * Waits for the accessibility event stream to become idle, which is not to
+ * have received a new accessibility event within <code>idleTimeout</code>,
+ * and do so within a maximal global timeout as specified by
+ * <code>globalTimeout</code>.
+ *
+ * @param idleTimeout The timeout between two event to consider the device idle.
+ * @param globalTimeout The maximal global timeout in which to wait for idle.
+ */
+ public void waitForIdle(long idleTimeout, long globalTimeout) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ long lastEventTime = (mLastEvent != null)
+ ? mLastEvent.getEventTime() : SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ while (true) {
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+ final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime;
+ if (sinceLastEventTimeMillis > idleTimeout) {
+ return;
+ }
+ if (mLastEvent != null) {
+ lastEventTime = mLastEvent.getEventTime();
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = globalTimeout - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ return;
+ }
+ try {
+ mLock.wait(idleTimeout);
+ } catch (InterruptedException e) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
* Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
* window. The search is performed from the root node.
*
@@ -310,8 +338,8 @@
/**
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
*
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
- * to query the currently active window.
+ * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query
+ * the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id for
* which to search.
* @return The current window scale, where zero means a failure.
@@ -341,10 +369,10 @@
* the window whose id is specified and starts from the node whose accessibility
* id is specified.
*
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
- * to query the currently active window.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
+ * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
* @return The current window scale, where zero means a failure.
*/
public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
@@ -374,8 +402,8 @@
* id is specified and starts from the node whose accessibility id is
* specified.
*
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
- * to query the currently active window.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
* @param accessibilityNodeId A unique view id or virtual descendant id from
* where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
* @param text The searched text.
@@ -406,8 +434,8 @@
/**
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
*
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID}
- * to query the currently active window.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
* @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
* @param action The action to perform.
* @return Whether the action was performed.
@@ -427,16 +455,16 @@
* @return The root info.
*/
public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() {
- synchronized (mLock) {
- if (mLastWindowStateChangeEvent != null) {
- return mLastWindowStateChangeEvent.getSource();
- }
- }
- return null;
+ // Cache the id to avoid locking
+ final int connectionId = mConnectionId;
+ ensureValidConnection(connectionId);
+ return AccessibilityInteractionClient.getInstance()
+ .findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
+ ROOT_NODE_ID);
}
private void ensureValidConnection(int connectionId) {
- if (connectionId == AccessibilityInteractionClient.NO_ID) {
+ if (connectionId == UNDEFINED) {
throw new IllegalStateException("UiAutomationService not connected."
+ " Did you call #register()?");
}
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/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java
index 2b8a458..cac449d 100644
--- a/core/java/android/service/textservice/SpellCheckerService.java
+++ b/core/java/android/service/textservice/SpellCheckerService.java
@@ -27,6 +27,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
+import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
@@ -140,19 +141,21 @@
/**
* @hide
- * The default implementation returns an array of SuggestionsInfo by simply calling
+ * The default implementation returns an array of SentenceSuggestionsInfo by simply calling
* onGetSuggestions().
* When you override this method, make sure that suggestionsLimit is applied to suggestions
* that share the same start position and length.
*/
- public SuggestionsInfo[] onGetSuggestionsMultipleForSentence(TextInfo[] textInfos,
+ public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit) {
final int length = textInfos.length;
- final SuggestionsInfo[] retval = new SuggestionsInfo[length];
+ final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[length];
for (int i = 0; i < length; ++i) {
- retval[i] = onGetSuggestions(textInfos[i], suggestionsLimit);
- retval[i].setCookieAndSequence(
- textInfos[i].getCookie(), textInfos[i].getSequence());
+ final SuggestionsInfo si = onGetSuggestions(textInfos[i], suggestionsLimit);
+ si.setCookieAndSequence(textInfos[i].getCookie(), textInfos[i].getSequence());
+ final int N = textInfos[i].getText().length();
+ retval[i] = new SentenceSuggestionsInfo(
+ new SuggestionsInfo[] {si}, new int[]{0}, new int[]{N});
}
return retval;
}
@@ -220,11 +223,10 @@
}
@Override
- public void onGetSuggestionsMultipleForSentence(
- TextInfo[] textInfos, int suggestionsLimit) {
+ public void onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
try {
- mListener.onGetSuggestionsForSentence(
- mSession.onGetSuggestionsMultipleForSentence(textInfos, suggestionsLimit));
+ mListener.onGetSentenceSuggestions(
+ mSession.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit));
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/view/AccessibilityNodeInfoCache.java b/core/java/android/view/AccessibilityNodeInfoCache.java
index 244a491..84b510d 100644
--- a/core/java/android/view/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/AccessibilityNodeInfoCache.java
@@ -16,6 +16,8 @@
package android.view;
+import android.os.Process;
+import android.util.Log;
import android.util.LongSparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -30,7 +32,11 @@
*/
public class AccessibilityNodeInfoCache {
- private final boolean ENABLED = true;
+ private static final String LOG_TAG = AccessibilityNodeInfoCache.class.getSimpleName();
+
+ private static final boolean ENABLED = true;
+
+ private static final boolean DEBUG = false;
/**
* @return A new <strong>not synchronized</strong> AccessibilityNodeInfoCache.
@@ -95,6 +101,7 @@
public void onAccessibilityEvent(AccessibilityEvent event) {
final int eventType = event.getEventType();
switch (eventType) {
+ case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
case AccessibilityEvent.TYPE_VIEW_SCROLLED:
clear();
@@ -117,7 +124,14 @@
*/
public AccessibilityNodeInfo get(long accessibilityNodeId) {
if (ENABLED) {
- return mCacheImpl.get(accessibilityNodeId);
+ if (DEBUG) {
+ AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId);
+ Log.i(LOG_TAG, "Process: " + Process.myPid() +
+ " get(" + accessibilityNodeId + ") = " + info);
+ return info;
+ } else {
+ return mCacheImpl.get(accessibilityNodeId);
+ }
} else {
return null;
}
@@ -131,6 +145,10 @@
*/
public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
if (ENABLED) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Process: " + Process.myPid()
+ + " put(" + accessibilityNodeId + ", " + info + ")");
+ }
mCacheImpl.put(accessibilityNodeId, info);
}
}
@@ -156,6 +174,10 @@
*/
public void remove(long accessibilityNodeId) {
if (ENABLED) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Process: " + Process.myPid()
+ + " remove(" + accessibilityNodeId + ")");
+ }
mCacheImpl.remove(accessibilityNodeId);
}
}
@@ -165,6 +187,9 @@
*/
public void clear() {
if (ENABLED) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Process: " + Process.myPid() + "clear()");
+ }
mCacheImpl.clear();
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c16eb31..384e39a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3631,7 +3631,9 @@
* @see ActionMode
*/
public ActionMode startActionMode(ActionMode.Callback callback) {
- return getParent().startActionModeForChild(this, callback);
+ ViewParent parent = getParent();
+ if (parent == null) return null;
+ return parent.startActionModeForChild(this, callback);
}
/**
@@ -5046,6 +5048,10 @@
* the View's internal state from a previously set "pressed" state.
*/
public void setPressed(boolean pressed) {
+ if (pressed == ((mPrivateFlags & PRESSED) == PRESSED)) {
+ return;
+ }
+
if (pressed) {
mPrivateFlags |= PRESSED;
} else {
@@ -6548,8 +6554,7 @@
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
+ setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
@@ -6581,8 +6586,7 @@
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
+ setPressed(true);
}
if (!mHasPerformedLongPress) {
@@ -6638,15 +6642,13 @@
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
+ setPressed(true);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
+ setPressed(false);
removeTapCallback();
break;
@@ -6662,9 +6664,7 @@
// Remove any future long press/tap checks
removeLongPressCallback();
- // Need to switch from pressed to not pressed
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
+ setPressed(false);
}
}
break;
@@ -14505,8 +14505,7 @@
private final class CheckForTap implements Runnable {
public void run() {
mPrivateFlags &= ~PREPRESSED;
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
+ setPressed(true);
checkForLongClick(ViewConfiguration.getTapTimeout());
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 84ce2a4..28737fc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2647,8 +2647,8 @@
mHasHadWindowFocus = true;
}
- if (hasWindowFocus && mView != null) {
- sendAccessibilityEvents();
+ if (hasWindowFocus && mView != null && mAccessibilityManager.isEnabled()) {
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
}
} break;
@@ -4063,21 +4063,6 @@
}
/**
- * The window is getting focus so if there is anything focused/selected
- * send an {@link AccessibilityEvent} to announce that.
- */
- private void sendAccessibilityEvents() {
- if (!mAccessibilityManager.isEnabled()) {
- return;
- }
- mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- View focusedView = mView.findFocus();
- if (focusedView != null && focusedView != mView) {
- focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
- }
-
- /**
* Post a callback to send a
* {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
* This event is send at most once every
@@ -4646,24 +4631,35 @@
public void onAccessibilityStateChanged(boolean enabled) {
if (enabled) {
ensureConnection();
+ if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ View focusedView = mView.findFocus();
+ if (focusedView != null && focusedView != mView) {
+ focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ }
} else {
ensureNoConnection();
}
}
public void ensureConnection() {
- final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
- if (!registered) {
- mAttachInfo.mAccessibilityWindowId =
- mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
- new AccessibilityInteractionConnection(ViewRootImpl.this));
+ if (mAttachInfo != null) {
+ final boolean registered =
+ mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED;
+ if (!registered) {
+ mAttachInfo.mAccessibilityWindowId =
+ mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
+ new AccessibilityInteractionConnection(ViewRootImpl.this));
+ }
}
}
public void ensureNoConnection() {
- final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID;
+ final boolean registered =
+ mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED;
if (registered) {
- mAttachInfo.mAccessibilityWindowId = View.NO_ID;
+ mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED;
mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
}
}
@@ -4860,14 +4856,21 @@
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
infos.clear();
try {
- View target = findViewByAccessibilityId(accessibilityViewId);
- if (target != null && target.getVisibility() == View.VISIBLE) {
- AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
- if (provider != null) {
- infos.add(provider.createAccessibilityNodeInfo(virtualDescendantId));
- } else if (virtualDescendantId == View.NO_ID) {
- getAccessibilityPrefetchStrategy().prefetchAccessibilityNodeInfos(
- interrogatingPid, target, infos);
+ if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
+ View target = ViewRootImpl.this.mView;
+ if (target != null && target.getVisibility() == View.VISIBLE) {
+ infos.add(target.createAccessibilityNodeInfo());
+ }
+ } else {
+ View target = findViewByAccessibilityId(accessibilityViewId);
+ if (target != null && target.getVisibility() == View.VISIBLE) {
+ AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+ if (provider != null) {
+ infos.add(provider.createAccessibilityNodeInfo(virtualDescendantId));
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
+ getAccessibilityPrefetchStrategy().prefetchAccessibilityNodeInfos(
+ interrogatingPid, target, infos);
+ }
}
}
} finally {
@@ -4915,7 +4918,7 @@
AccessibilityNodeInfo info = null;
try {
View root = null;
- if (accessibilityViewId != View.NO_ID) {
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
} else {
root = ViewRootImpl.this.mView;
@@ -4973,7 +4976,7 @@
List<AccessibilityNodeInfo> infos = null;
try {
View target;
- if (accessibilityViewId != View.NO_ID) {
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
target = findViewByAccessibilityId(accessibilityViewId);
} else {
target = ViewRootImpl.this.mView;
@@ -4983,7 +4986,7 @@
if (provider != null) {
infos = provider.findAccessibilityNodeInfosByText(text,
virtualDescendantId);
- } else if (virtualDescendantId == View.NO_ID) {
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
foundViews.clear();
target.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
@@ -5063,7 +5066,7 @@
if (provider != null) {
succeeded = provider.performAccessibilityAction(action,
virtualDescendantId);
- } else if (virtualDescendantId == View.NO_ID) {
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
switch (action) {
case AccessibilityNodeInfo.ACTION_FOCUS: {
if (!target.hasFocus()) {
@@ -5171,7 +5174,7 @@
private void addAndCacheNotCachedNodeInfo(long interrogatingPid,
View view, List<AccessibilityNodeInfo> outInfos) {
final long accessibilityNodeId = AccessibilityNodeInfo.makeNodeId(
- view.getAccessibilityViewId(), View.NO_ID);
+ view.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
AccessibilityNodeInfoCache cache = getCacheForInterrogatingPid(interrogatingPid);
if (!cache.containsKey(accessibilityNodeId)) {
// Account for the ids of the fetched infos. The infos will be
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 072fdd8..105c010 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -153,10 +153,12 @@
*
* @param connectionId The id of a connection for interacting with the system.
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
- * @param accessibilityNodeId A unique node accessibility id
- * (accessibility view and virtual descendant id).
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
* @return An {@link AccessibilityNodeInfo} if found, null otherwise.
*/
public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
@@ -164,7 +166,8 @@
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
- AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get(accessibilityNodeId);
+ AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get(
+ accessibilityNodeId);
if (cachedInfo != null) {
return cachedInfo;
}
@@ -176,7 +179,7 @@
if (windowScale > 0) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
if (infos != null && !infos.isEmpty()) {
return infos.get(0);
}
@@ -202,10 +205,11 @@
*
* @param connectionId The id of a connection for interacting with the system.
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
- * @param accessibilityNodeId A unique view id from where to start the search. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
* @param viewId The id of the view.
* @return An {@link AccessibilityNodeInfo} if found, null otherwise.
@@ -224,7 +228,7 @@
if (windowScale > 0) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
return info;
}
} else {
@@ -249,10 +253,12 @@
*
* @param connectionId The id of a connection for interacting with the system.
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
- * @param accessibilityNodeId A unique view id from where to start the search. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ROOT_NODE_ID}
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
* @param text The searched text.
* @return A list of found {@link AccessibilityNodeInfo}s.
*/
@@ -269,7 +275,7 @@
if (windowScale > 0) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
return infos;
}
} else {
@@ -291,9 +297,12 @@
*
* @param connectionId The id of a connection for interacting with the system.
* @param accessibilityWindowId A unique window id. Use
- * {@link com.android.server.accessibility.AccessibilityManagerService#ACTIVE_WINDOW_ID}
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
* to query the currently active window.
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
* @param action The action to perform.
* @return Whether the action was performed.
*/
@@ -323,16 +332,10 @@
}
public void clearCache() {
- if (DEBUG) {
- Log.w(LOG_TAG, "clearCache()");
- }
sAccessibilityNodeInfoCache.clear();
}
public void removeCachedNode(long accessibilityNodeId) {
- if (DEBUG) {
- Log.w(LOG_TAG, "removeCachedNode(" + accessibilityNodeId +")");
- }
sAccessibilityNodeInfoCache.remove(accessibilityNodeId);
}
@@ -364,9 +367,6 @@
if (interactionId > mInteractionId) {
mFindAccessibilityNodeInfoResult = info;
mInteractionId = interactionId;
- if (info != null) {
- sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
- }
}
mInstanceLock.notifyAll();
}
@@ -404,11 +404,6 @@
mFindAccessibilityNodeInfosResult = infos;
}
mInteractionId = interactionId;
- final int infoCount = infos.size();
- for (int i = 0; i < infoCount; i ++) {
- AccessibilityNodeInfo info = infos.get(i);
- sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
- }
}
mInstanceLock.notifyAll();
}
@@ -513,12 +508,13 @@
* @param connectionId The id of the connection to the system.
* @param windowScale The source window compatibility scale.
*/
- private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
+ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
float windowScale) {
if (info != null) {
applyCompatibilityScaleIfNeeded(info, windowScale);
info.setConnectionId(connectionId);
info.setSealed(true);
+ sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
}
}
@@ -529,13 +525,13 @@
* @param connectionId The id of the connection to the system.
* @param windowScale The source window compatibility scale.
*/
- private void finalizeAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
+ private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
int connectionId, float windowScale) {
if (infos != null) {
final int infosCount = infos.size();
for (int i = 0; i < infosCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
- finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index d7d6792..84ad268 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -52,7 +52,14 @@
private static final boolean DEBUG = false;
- private static final int UNDEFINED = -1;
+ /** @hide */
+ public static final int UNDEFINED = -1;
+
+ /** @hide */
+ public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED, UNDEFINED);
+
+ /** @hide */
+ public static final int ACTIVE_WINDOW_ID = UNDEFINED;
// Actions.
@@ -162,8 +169,8 @@
// Data.
private int mWindowId = UNDEFINED;
- private long mSourceNodeId = makeNodeId(UNDEFINED, UNDEFINED);
- private long mParentNodeId = makeNodeId(UNDEFINED, UNDEFINED);
+ private long mSourceNodeId = ROOT_NODE_ID;
+ private long mParentNodeId = ROOT_NODE_ID;
private int mBooleanProperties;
private final Rect mBoundsInParent = new Rect();
@@ -1160,8 +1167,8 @@
*/
private void clear() {
mSealed = false;
- mSourceNodeId = makeNodeId(UNDEFINED, UNDEFINED);
- mParentNodeId = makeNodeId(UNDEFINED, UNDEFINED);
+ mSourceNodeId = ROOT_NODE_ID;
+ mParentNodeId = ROOT_NODE_ID;
mWindowId = UNDEFINED;
mConnectionId = UNDEFINED;
mChildIds.clear();
diff --git a/core/java/android/view/textservice/SentenceSuggestionsInfo.aidl b/core/java/android/view/textservice/SentenceSuggestionsInfo.aidl
new file mode 100644
index 0000000..d0b6ba6
--- /dev/null
+++ b/core/java/android/view/textservice/SentenceSuggestionsInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.view.textservice;
+
+parcelable SentenceSuggestionsInfo;
diff --git a/core/java/android/view/textservice/SentenceSuggestionsInfo.java b/core/java/android/view/textservice/SentenceSuggestionsInfo.java
new file mode 100644
index 0000000..8d7c6cf
--- /dev/null
+++ b/core/java/android/view/textservice/SentenceSuggestionsInfo.java
@@ -0,0 +1,129 @@
+/*
+ * 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.view.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ * This class contains a metadata of sentence level suggestions from the text service
+ */
+public final class SentenceSuggestionsInfo implements Parcelable {
+
+ private final SuggestionsInfo[] mSuggestionsInfos;
+ private final int[] mOffsets;
+ private final int[] mLengths;
+
+ /**
+ * Constructor.
+ * @param suggestionsInfos from the text service
+ * @param offsets the array of offsets of suggestions
+ * @param lengths the array of lengths of suggestions
+ */
+ public SentenceSuggestionsInfo(
+ SuggestionsInfo[] suggestionsInfos, int[] offsets, int[] lengths) {
+ if (suggestionsInfos == null || offsets == null || lengths == null) {
+ throw new NullPointerException();
+ }
+ if (suggestionsInfos.length != offsets.length || offsets.length != lengths.length) {
+ throw new IllegalArgumentException();
+ }
+ final int infoSize = suggestionsInfos.length;
+ mSuggestionsInfos = Arrays.copyOf(suggestionsInfos, infoSize);
+ mOffsets = Arrays.copyOf(offsets, infoSize);
+ mLengths = Arrays.copyOf(lengths, infoSize);
+ }
+
+ public SentenceSuggestionsInfo(Parcel source) {
+ final int infoSize = source.readInt();
+ mSuggestionsInfos = new SuggestionsInfo[infoSize];
+ source.readTypedArray(mSuggestionsInfos, SuggestionsInfo.CREATOR);
+ mOffsets = new int[mSuggestionsInfos.length];
+ source.readIntArray(mOffsets);
+ mLengths = new int[mSuggestionsInfos.length];
+ source.readIntArray(mLengths);
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int infoSize = mSuggestionsInfos.length;
+ dest.writeInt(infoSize);
+ dest.writeTypedArray(mSuggestionsInfos, 0);
+ dest.writeIntArray(mOffsets);
+ dest.writeIntArray(mLengths);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public SuggestionsInfo getSuggestionsInfoAt(int i) {
+ if (i >= 0 && i < mSuggestionsInfos.length) {
+ return mSuggestionsInfos[i];
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public int getOffsetAt(int i) {
+ if (i >= 0 && i < mOffsets.length) {
+ return mOffsets[i];
+ }
+ return -1;
+ }
+
+ /**
+ * @hide
+ */
+ public int getLengthAt(int i) {
+ if (i >= 0 && i < mLengths.length) {
+ return mLengths[i];
+ }
+ return -1;
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<SentenceSuggestionsInfo> CREATOR
+ = new Parcelable.Creator<SentenceSuggestionsInfo>() {
+ @Override
+ public SentenceSuggestionsInfo createFromParcel(Parcel source) {
+ return new SentenceSuggestionsInfo(source);
+ }
+
+ @Override
+ public SentenceSuggestionsInfo[] newArray(int size) {
+ return new SentenceSuggestionsInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index f6418ce..3491a537 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -115,7 +115,7 @@
handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
break;
case MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE:
- handleOnGetSuggestionsMultipleForSentence((SuggestionsInfo[]) msg.obj);
+ handleOnGetSentenceSuggestionsMultiple((SentenceSuggestionsInfo[]) msg.obj);
break;
}
}
@@ -180,8 +180,8 @@
/**
* @hide
*/
- public void getSuggestionsForSentence(TextInfo textInfo, int suggestionsLimit) {
- mSpellCheckerSessionListenerImpl.getSuggestionsMultipleForSentence(
+ public void getSentenceSuggestions(TextInfo textInfo, int suggestionsLimit) {
+ mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple(
new TextInfo[] {textInfo}, suggestionsLimit);
}
@@ -214,8 +214,8 @@
mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
}
- private void handleOnGetSuggestionsMultipleForSentence(SuggestionsInfo[] suggestionInfos) {
- mSpellCheckerSessionListener.onGetSuggestionsForSentence(suggestionInfos);
+ private void handleOnGetSentenceSuggestionsMultiple(SentenceSuggestionsInfo[] suggestionInfos) {
+ mSpellCheckerSessionListener.onGetSentenceSuggestions(suggestionInfos);
}
private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
@@ -285,7 +285,7 @@
throw new IllegalArgumentException();
}
try {
- session.onGetSuggestionsMultipleForSentence(
+ session.onGetSentenceSuggestionsMultiple(
scp.mTextInfos, scp.mSuggestionsLimit);
} catch (RemoteException e) {
Log.e(TAG, "Failed to get suggestions " + e);
@@ -366,9 +366,9 @@
suggestionsLimit, sequentialWords));
}
- public void getSuggestionsMultipleForSentence(TextInfo[] textInfos, int suggestionsLimit) {
+ public void getSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
if (DBG) {
- Log.w(TAG, "getSuggestionsMultipleForSentence");
+ Log.w(TAG, "getSentenceSuggestionsMultiple");
}
processOrEnqueueTask(
new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE,
@@ -399,8 +399,8 @@
while (!mPendingTasks.isEmpty()) {
final SpellCheckerParams tmp = mPendingTasks.poll();
if (tmp.mWhat == TASK_CLOSE) {
- // Only one close task should be processed, while we need to remove all
- // close tasks from the queue
+ // Only one close task should be processed, while we need to remove
+ // all close tasks from the queue
closeTask = tmp;
}
}
@@ -426,7 +426,7 @@
}
@Override
- public void onGetSuggestionsForSentence(SuggestionsInfo[] results) {
+ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
mHandler.sendMessage(
Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
}
@@ -444,7 +444,7 @@
/**
* @hide
*/
- public void onGetSuggestionsForSentence(SuggestionsInfo[] results);
+ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results);
}
private static class InternalListener extends ITextServicesSessionListener.Stub {
diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java
index 9b99770..78bc1a9 100644
--- a/core/java/android/view/textservice/SuggestionsInfo.java
+++ b/core/java/android/view/textservice/SuggestionsInfo.java
@@ -21,14 +21,11 @@
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Arrays;
-
/**
* This class contains a metadata of suggestions from the text service
*/
public final class SuggestionsInfo implements Parcelable {
private static final String[] EMPTY = ArrayUtils.emptyArray(String.class);
- private static final int NOT_A_LENGTH = -1;
/**
* Flag of the attributes of the suggestions that can be obtained by
@@ -50,8 +47,6 @@
public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 0x0004;
private final int mSuggestionsAttributes;
private final String[] mSuggestions;
- private final int[] mStartPosArray;
- private final int[] mLengthArray;
private final boolean mSuggestionsAvailable;
private int mCookie;
private int mSequence;
@@ -74,46 +69,12 @@
*/
public SuggestionsInfo(
int suggestionsAttributes, String[] suggestions, int cookie, int sequence) {
- this(suggestionsAttributes, suggestions, cookie, sequence, null, null);
- }
-
- /**
- * @hide
- * Constructor.
- * @param suggestionsAttributes from the text service
- * @param suggestions from the text service
- * @param cookie the cookie of the input TextInfo
- * @param sequence the cookie of the input TextInfo
- * @param startPosArray the array of start positions of suggestions
- * @param lengthArray the array of length of suggestions
- */
- public SuggestionsInfo(
- int suggestionsAttributes, String[] suggestions, int cookie, int sequence,
- int[] startPosArray, int[] lengthArray) {
- final int suggestsLen;
if (suggestions == null) {
mSuggestions = EMPTY;
mSuggestionsAvailable = false;
- suggestsLen = 0;
- mStartPosArray = new int[0];
- mLengthArray = new int[0];
} else {
mSuggestions = suggestions;
mSuggestionsAvailable = true;
- suggestsLen = suggestions.length;
- if (startPosArray == null || lengthArray == null) {
- mStartPosArray = new int[suggestsLen];
- mLengthArray = new int[suggestsLen];
- for (int i = 0; i < suggestsLen; ++i) {
- mStartPosArray[i] = 0;
- mLengthArray[i] = NOT_A_LENGTH;
- }
- } else if (suggestsLen != startPosArray.length || suggestsLen != lengthArray.length) {
- throw new IllegalArgumentException();
- } else {
- mStartPosArray = Arrays.copyOf(startPosArray, suggestsLen);
- mLengthArray = Arrays.copyOf(lengthArray, suggestsLen);
- }
}
mSuggestionsAttributes = suggestionsAttributes;
mCookie = cookie;
@@ -126,10 +87,6 @@
mCookie = source.readInt();
mSequence = source.readInt();
mSuggestionsAvailable = source.readInt() == 1;
- mStartPosArray = new int[mSuggestions.length];
- mLengthArray = new int[mSuggestions.length];
- source.readIntArray(mStartPosArray);
- source.readIntArray(mLengthArray);
}
/**
@@ -145,8 +102,6 @@
dest.writeInt(mCookie);
dest.writeInt(mSequence);
dest.writeInt(mSuggestionsAvailable ? 1 : 0);
- dest.writeIntArray(mStartPosArray);
- dest.writeIntArray(mLengthArray);
}
/**
@@ -227,24 +182,4 @@
public int describeContents() {
return 0;
}
-
- /**
- * @hide
- */
- public int getSuggestionStartPosAt(int i) {
- if (i >= 0 && i < mStartPosArray.length) {
- return mStartPosArray[i];
- }
- return -1;
- }
-
- /**
- * @hide
- */
- public int getSuggestionLengthAt(int i) {
- if (i >= 0 && i < mLengthArray.length) {
- return mLengthArray[i];
- }
- return -1;
- }
}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 6fddb1a..3613ec9 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -1351,15 +1351,6 @@
private native void nativeAddJavascriptInterface(int nativeFramePointer,
Object obj, String interfaceName);
- /**
- * Enable or disable the native cache.
- */
- /* FIXME: The native cache is always on for now until we have a better
- * solution for our 2 caches. */
- private native void setCacheDisabled(boolean disabled);
-
- public native boolean cacheDisabled();
-
public native void clearCache();
/**
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index e2342e9..36b4de1 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -68,8 +68,6 @@
// Limit the maximum cache file size to half of the normal capacity
static long CACHE_MAX_SIZE = (CACHE_THRESHOLD - CACHE_TRIM_AMOUNT) / 2;
- private static boolean mDisabled;
-
// Reference count the enable/disable transaction
private static int mRefCount;
@@ -322,29 +320,13 @@
}
/**
- * Sets whether the HTTP cache should be disabled.
- * @param disabled Whether the cache should be disabled
- */
- static void setCacheDisabled(boolean disabled) {
- assert !JniUtil.useChromiumHttpStack();
-
- if (disabled == mDisabled) {
- return;
- }
- mDisabled = disabled;
- if (mDisabled) {
- removeAllCacheFiles();
- }
- }
-
- /**
* Gets whether the HTTP cache is disabled.
* @return True if the HTTP cache is disabled
* @deprecated Access to the HTTP cache will be removed in a future release.
*/
@Deprecated
public static boolean cacheDisabled() {
- return mDisabled;
+ return false;
}
// only called from WebViewWorkerThread
@@ -489,10 +471,6 @@
static CacheResult getCacheFile(String url, long postIdentifier,
Map<String, String> headers) {
- if (mDisabled) {
- return null;
- }
-
CacheResult result = JniUtil.useChromiumHttpStack() ?
getCacheFileChromiumHttpStack(url) :
getCacheFileAndroidHttpStack(url, postIdentifier);
@@ -552,10 +530,6 @@
boolean forceCache) {
assert !JniUtil.useChromiumHttpStack();
- if (!forceCache && mDisabled) {
- return null;
- }
-
String databaseKey = getDatabaseKey(url, postIdentifier);
// according to the rfc 2616, the 303 response MUST NOT be cached.
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 2b59b80..510c168 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -160,14 +160,14 @@
private MyResultReceiver mReceiver;
// Types used with setType. Keep in sync with CachedInput.h
- private static final int NORMAL_TEXT_FIELD = 0;
- private static final int TEXT_AREA = 1;
- private static final int PASSWORD = 2;
- private static final int SEARCH = 3;
- private static final int EMAIL = 4;
- private static final int NUMBER = 5;
- private static final int TELEPHONE = 6;
- private static final int URL = 7;
+ static final int NORMAL_TEXT_FIELD = 0;
+ static final int TEXT_AREA = 1;
+ static final int PASSWORD = 2;
+ static final int SEARCH = 3;
+ static final int EMAIL = 4;
+ static final int NUMBER = 5;
+ static final int TELEPHONE = 6;
+ static final int URL = 7;
private static final int AUTOFILL_FORM = 100;
private Handler mHandler;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 2304c25..71d1627 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.animation.ObjectAnimator;
import android.annotation.Widget;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -36,6 +37,7 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorFilter;
import android.graphics.DrawFilter;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
@@ -96,6 +98,7 @@
import android.webkit.WebTextView.AutoCompleteAdapter;
import android.webkit.WebViewCore.DrawData;
import android.webkit.WebViewCore.EventHub;
+import android.webkit.WebViewCore.TextFieldInitData;
import android.webkit.WebViewCore.TouchEventData;
import android.webkit.WebViewCore.TouchHighlightData;
import android.webkit.WebViewCore.WebKitHitTest;
@@ -369,6 +372,9 @@
// Used for mapping characters to keys typed.
private KeyCharacterMap mKeyCharacterMap;
private boolean mIsKeySentByMe;
+ private int mInputType;
+ private int mImeOptions;
+ private String mHint;
public WebViewInputConnection() {
super(WebView.this, true);
@@ -450,6 +456,77 @@
return super.deleteSurroundingText(leftLength, rightLength);
}
+ public void initEditorInfo(WebViewCore.TextFieldInitData initData) {
+ int type = initData.mType;
+ int inputType = InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
+ int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
+ | EditorInfo.IME_FLAG_NO_FULLSCREEN;
+ if (!initData.mIsSpellCheckEnabled) {
+ inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
+ }
+ if (WebTextView.TEXT_AREA != type
+ && initData.mIsTextFieldNext) {
+ imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
+ }
+ switch (type) {
+ case WebTextView.NORMAL_TEXT_FIELD:
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ case WebTextView.TEXT_AREA:
+ inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE
+ | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
+ | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+ imeOptions |= EditorInfo.IME_ACTION_NONE;
+ break;
+ case WebTextView.PASSWORD:
+ inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ case WebTextView.SEARCH:
+ imeOptions |= EditorInfo.IME_ACTION_SEARCH;
+ break;
+ case WebTextView.EMAIL:
+ // inputType needs to be overwritten because of the different text variation.
+ inputType = InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ case WebTextView.NUMBER:
+ // inputType needs to be overwritten because of the different class.
+ inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL
+ | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ // Number and telephone do not have both a Tab key and an
+ // action, so set the action to NEXT
+ imeOptions |= EditorInfo.IME_ACTION_NEXT;
+ break;
+ case WebTextView.TELEPHONE:
+ // inputType needs to be overwritten because of the different class.
+ inputType = InputType.TYPE_CLASS_PHONE;
+ imeOptions |= EditorInfo.IME_ACTION_NEXT;
+ break;
+ case WebTextView.URL:
+ // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
+ // exclude it for now.
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ inputType |= InputType.TYPE_TEXT_VARIATION_URI;
+ break;
+ default:
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ }
+ mHint = initData.mLabel;
+ mInputType = inputType;
+ mImeOptions = imeOptions;
+ }
+
+ public void setupEditorInfo(EditorInfo outAttrs) {
+ outAttrs.inputType = mInputType;
+ outAttrs.imeOptions = mImeOptions;
+ outAttrs.hintText = mHint;
+ outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
+ }
+
/**
* Sends a text change to webkit indirectly. If it is a single-
* character add or delete, it sends it as a key stroke. If it cannot
@@ -831,14 +908,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;
@@ -935,6 +1020,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;
@@ -1341,7 +1427,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();
@@ -4621,15 +4707,7 @@
if (mTitleBar != null) {
canvas.translate(0, getTitleHeight());
}
- boolean drawJavaRings = !mTouchHighlightRegion.isEmpty()
- && (mTouchMode == TOUCH_INIT_MODE
- || mTouchMode == TOUCH_SHORTPRESS_START_MODE
- || mTouchMode == TOUCH_SHORTPRESS_MODE
- || mTouchMode == TOUCH_DONE_MODE);
- boolean drawNativeRings = !drawJavaRings;
- if (sDisableNavcache) {
- drawNativeRings = !drawJavaRings && !isInTouchMode();
- }
+ boolean drawNativeRings = !sDisableNavcache;
drawContent(canvas, drawNativeRings);
canvas.restoreToCount(saveCount);
@@ -4642,18 +4720,13 @@
invalidate();
}
- // paint the highlight in the end
- if (drawJavaRings) {
- long delay = System.currentTimeMillis() - mTouchHighlightRequested;
- if (delay < ViewConfiguration.getTapTimeout()) {
- Rect r = mTouchHighlightRegion.getBounds();
- postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
- } else {
- RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
- Rect r = new Rect();
- while (iter.next(r)) {
- canvas.drawRect(r, mTouchHightlightPaint);
- }
+ if (mFocusTransition != null) {
+ mFocusTransition.draw(canvas);
+ } else if (shouldDrawHighlightRect()) {
+ RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
+ Rect r = new Rect();
+ while (iter.next(r)) {
+ canvas.drawRect(r, mTouchHightlightPaint);
}
}
if (DEBUG_TOUCH_HIGHLIGHT) {
@@ -5044,31 +5117,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);
}
/**
@@ -5170,18 +5257,10 @@
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- outAttrs.inputType = EditorInfo.IME_FLAG_NO_FULLSCREEN
- | EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
- | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
- | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT
- | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
- outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
-
if (mInputConnection == null) {
mInputConnection = new WebViewInputConnection();
}
- outAttrs.initialCapsMode = mInputConnection.getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
+ mInputConnection.setupEditorInfo(outAttrs);
return mInputConnection;
}
@@ -5498,6 +5577,9 @@
+ "keyCode=" + keyCode
+ ", " + event + ", unicode=" + event.getUnicodeChar());
}
+ if (mIsCaretSelection) {
+ selectionDone();
+ }
if (mBlockWebkitViewMessages) {
return false;
}
@@ -5810,6 +5892,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
@@ -5830,9 +5913,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;
@@ -5841,6 +5928,9 @@
private void updateWebkitSelection() {
int[] handles = null;
+ if (mIsCaretSelection) {
+ mSelectCursorExtent.set(mSelectCursorBase);
+ }
if (mSelectingText) {
handles = new int[4];
handles[0] = mSelectCursorBase.centerX();
@@ -5854,6 +5944,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.
@@ -5881,9 +5979,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;
@@ -6497,18 +6600,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(
@@ -7159,6 +7270,9 @@
if (mSelectingText) {
mSelectionStarted = false;
+ if (mIsCaretSelection) {
+ resetCaretTimer();
+ }
syncSelectionCursors();
invalidate();
}
@@ -9092,9 +9206,11 @@
case INIT_EDIT_FIELD:
if (mInputConnection != null) {
+ TextFieldInitData initData = (TextFieldInitData) msg.obj;
mTextGeneration = 0;
- mFieldPointer = msg.arg1;
- mInputConnection.setTextAndKeepSelection((String) msg.obj);
+ mFieldPointer = initData.mFieldPointer;
+ mInputConnection.initEditorInfo(initData);
+ mInputConnection.setTextAndKeepSelection(initData.mText);
}
break;
@@ -9115,6 +9231,9 @@
}
break;
}
+ case CLEAR_CARET_HANDLE:
+ selectionDone();
+ break;
default:
super.handleMessage(msg);
@@ -9123,10 +9242,122 @@
}
}
+ private boolean shouldDrawHighlightRect() {
+ if (mFocusedNode == null || mInitialHitTestResult == null) {
+ return false;
+ }
+ if (mTouchHighlightRegion.isEmpty()) {
+ return false;
+ }
+ if (mFocusedNode.mHasFocus && !isInTouchMode()) {
+ return !mFocusedNode.mEditable;
+ }
+ if (mInitialHitTestResult.mType == HitTestResult.UNKNOWN_TYPE) {
+ return false;
+ }
+ long delay = System.currentTimeMillis() - mTouchHighlightRequested;
+ if (delay < ViewConfiguration.getTapTimeout()) {
+ Rect r = mTouchHighlightRegion.getBounds();
+ postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
+ return false;
+ }
+ return true;
+ }
+
+
+ private FocusTransitionDrawable mFocusTransition = null;
+ static class FocusTransitionDrawable extends Drawable {
+ Region mPreviousRegion;
+ Region mNewRegion;
+ float mProgress = 0;
+ WebView mWebView;
+ Paint mPaint;
+ int mMaxAlpha;
+ Point mTranslate;
+
+ public FocusTransitionDrawable(WebView view) {
+ mWebView = view;
+ mPaint = new Paint(mWebView.mTouchHightlightPaint);
+ mMaxAlpha = mPaint.getAlpha();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ public void setProgress(float p) {
+ mProgress = p;
+ if (mWebView.mFocusTransition == this) {
+ if (mProgress == 1f)
+ mWebView.mFocusTransition = null;
+ mWebView.invalidate();
+ }
+ }
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mTranslate == null) {
+ Rect bounds = mPreviousRegion.getBounds();
+ Point from = new Point(bounds.centerX(), bounds.centerY());
+ mNewRegion.getBounds(bounds);
+ Point to = new Point(bounds.centerX(), bounds.centerY());
+ mTranslate = new Point(from.x - to.x, from.y - to.y);
+ }
+ int alpha = (int) (mProgress * mMaxAlpha);
+ RegionIterator iter = new RegionIterator(mPreviousRegion);
+ Rect r = new Rect();
+ mPaint.setAlpha(mMaxAlpha - alpha);
+ float tx = mTranslate.x * mProgress;
+ float ty = mTranslate.y * mProgress;
+ int save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(-tx, -ty);
+ while (iter.next(r)) {
+ canvas.drawRect(r, mPaint);
+ }
+ canvas.restoreToCount(save);
+ iter = new RegionIterator(mNewRegion);
+ r = new Rect();
+ mPaint.setAlpha(alpha);
+ save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ tx = mTranslate.x - tx;
+ ty = mTranslate.y - ty;
+ canvas.translate(tx, ty);
+ while (iter.next(r)) {
+ canvas.drawRect(r, mPaint);
+ }
+ canvas.restoreToCount(save);
+ }
+ };
+
+ private boolean shouldAnimateTo(WebKitHitTest hit) {
+ // TODO: Don't be annoying or throw out the animation entirely
+ return false;
+ }
+
private void setTouchHighlightRects(WebKitHitTest hit) {
+ FocusTransitionDrawable transition = null;
+ if (shouldAnimateTo(hit)) {
+ transition = new FocusTransitionDrawable(this);
+ }
Rect[] rects = hit != null ? hit.mTouchRects : null;
if (!mTouchHighlightRegion.isEmpty()) {
invalidate(mTouchHighlightRegion.getBounds());
+ if (transition != null) {
+ transition.mPreviousRegion = new Region(mTouchHighlightRegion);
+ }
mTouchHighlightRegion.setEmpty();
}
if (rects != null) {
@@ -9146,6 +9377,13 @@
}
}
invalidate(mTouchHighlightRegion.getBounds());
+ if (transition != null && transition.mPreviousRegion != null) {
+ transition.mNewRegion = new Region(mTouchHighlightRegion);
+ mFocusTransition = transition;
+ ObjectAnimator animator = ObjectAnimator.ofFloat(
+ mFocusTransition, "progress", 1f);
+ animator.start();
+ }
}
}
@@ -9246,14 +9484,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 4b1ae37..e7d3238 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -887,6 +887,7 @@
boolean mEditable;
int mTapHighlightColor = WebView.HIGHLIGHT_COLOR;
Rect[] mEnclosingParentRects;
+ boolean mHasFocus;
// These are the input values that produced this hit test
int mHitTestX;
@@ -918,6 +919,25 @@
private String mPreview;
}
+ static class TextFieldInitData {
+ public TextFieldInitData(int fieldPointer,
+ String text, int type, boolean isSpellCheckEnabled,
+ boolean isTextFieldNext, String label) {
+ mFieldPointer = fieldPointer;
+ mText = text;
+ mType = type;
+ mIsSpellCheckEnabled = isSpellCheckEnabled;
+ mIsTextFieldNext = isTextFieldNext;
+ mLabel = label;
+ }
+ int mFieldPointer;
+ String mText;
+ int mType;
+ boolean mIsSpellCheckEnabled;
+ boolean mIsTextFieldNext;
+ String mLabel;
+ }
+
// mAction of TouchEventData can be MotionEvent.getAction() which uses the
// last two bytes or one of the following values
static final int ACTION_LONGPRESS = 0x100;
@@ -2812,15 +2832,19 @@
}
// called by JNI
- private void initEditField(int pointer, String text, int start, int end) {
+ private void initEditField(int pointer, String text, int inputType,
+ boolean isSpellCheckEnabled, boolean nextFieldIsText,
+ String label, int start, int end, int selectionPtr) {
if (mWebView == null) {
return;
}
+ TextFieldInitData initData = new TextFieldInitData(pointer,
+ text, inputType, isSpellCheckEnabled, nextFieldIsText, label);
Message.obtain(mWebView.mPrivateHandler,
- WebView.INIT_EDIT_FIELD, pointer, 0, text).sendToTarget();
+ 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/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 909f383..a1cf205 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -24,6 +24,7 @@
import android.text.method.WordIterator;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
+import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
import android.view.textservice.SuggestionsInfo;
@@ -277,9 +278,9 @@
}
@Override
- public void onGetSuggestionsForSentence(SuggestionsInfo[] results) {
+ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
// TODO: Handle the position and length for each suggestion
- onGetSuggestions(results);
+ // do nothing for now
}
@Override
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/java/com/android/internal/textservice/ISpellCheckerSession.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
index ba0aa1a..4553f9f 100644
--- a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
+++ b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
@@ -24,7 +24,7 @@
oneway interface ISpellCheckerSession {
void onGetSuggestionsMultiple(
in TextInfo[] textInfos, int suggestionsLimit, boolean multipleWords);
- void onGetSuggestionsMultipleForSentence(in TextInfo[] textInfos, int suggestionsLimit);
+ void onGetSentenceSuggestionsMultiple(in TextInfo[] textInfos, int suggestionsLimit);
void onCancel();
void onClose();
}
diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl
index b44dbc8..641ed8c 100644
--- a/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl
+++ b/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl
@@ -16,6 +16,7 @@
package com.android.internal.textservice;
+import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
/**
@@ -23,5 +24,5 @@
*/
oneway interface ISpellCheckerSessionListener {
void onGetSuggestions(in SuggestionsInfo[] results);
- void onGetSuggestionsForSentence(in SuggestionsInfo[] results);
+ void onGetSentenceSuggestions(in SentenceSuggestionsInfo[] result);
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 517ce4e..2f325bf 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -606,6 +606,9 @@
((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
mHomeLayout.setIcon(icon);
}
+ if (mExpandedActionView != null) {
+ mExpandedHomeLayout.setIcon(mIcon.getConstantState().newDrawable(getResources()));
+ }
}
public void setIcon(int resId) {
diff --git a/core/res/assets/webkit/youtube.html b/core/res/assets/webkit/youtube.html
index d808bcf..8e103c1 100644
--- a/core/res/assets/webkit/youtube.html
+++ b/core/res/assets/webkit/youtube.html
@@ -13,94 +13,59 @@
height: 100%;
padding: 0%;
z-index: 10;
+ background-size: 100%;
+ background: no-repeat;
+ background-position: center;
+ }
+ #play {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ }
+ #logo {
+ position: absolute;
+ bottom: 0;
+ right: 0;
}
</style>
</head>
<body id="body">
<script type="text/javascript">
- // Nominal original size. If the embed is smaller than this, the play and logo
- // images get scaled appropriately. These are actually 3/4 of the sizes suggested
- // by youtube, so the images don't get too tiny.
- defHeight = 258;
- defWidth = 318;
-
function setup() {
var width = document.body.clientWidth;
var height = document.body.clientHeight;
- var canvas = document.getElementById("canvas");
- // Resize the canvas to the right size
- canvas.width = width;
- canvas.height = height;
- var ctx = canvas.getContext('2d');
+ var mainElement = document.getElementById("main");
+ var playElement = document.getElementById("play");
var loadcount = 0;
+ var POSTER = "http://img.youtube.com/vi/VIDEO_ID/0.jpg";
+
function doload() {
- if (++loadcount == 3) {
- // All images are loaded, so display them.
- // (Note that the images are loaded from javascript, so might load
- // after document.onload fires)
-
- playWidth = play.width;
- playHeight = play.height;
- logoWidth = logo.width;
- logoHeight = logo.height;
- var ratio = 1;
- // If the page is smaller than it 'should' be in either dimension
- // we scale the background, play button and logo according to the
- // dimension that has been shrunk the most.
- if (width / height > defWidth / defHeight && height < defHeight) {
- ratio = height / defHeight;
- // Stretch the background in this dimension only.
- backgroundHeight = background.height / ratio;
- ctx.drawImage(background, 0, 0, background.width, background.height,
- 0, (height - backgroundHeight) / 2, width, backgroundHeight);
- } else if (width / height < defWidth / defHeight && width < defWidth) {
- ratio = width / defWidth;
- backgroundWidth = background.width / ratio;
- ctx.drawImage(background, 0, 0, background.width, background.height,
- (width - backgroundWidth) / 2, 0, backgroundWidth, height);
- } else {
- // In this case stretch the background in both dimensions to fill the space.
- ctx.drawImage(background, 0, 0, width, height);
- }
- playWidth *= ratio;
- playHeight *= ratio;
- logoWidth *= ratio;
- logoHeight *= ratio;
- playLeft = (width - playWidth) / 2;
- playTop = (height - playHeight) / 2;
- ctx.drawImage(play, playLeft, playTop, playWidth, playHeight);
- ctx.globalAlpha = 0.7
- ctx.drawImage(logo, width - logoWidth, height - logoHeight, logoWidth, logoHeight);
- // To make it slightly easier to hit, the click target is twice the width/height of the unscaled play button
- targetLeft = width / 2 - play.width;
- targetRight = width / 2 + play.width;
- targetTop = height / 2 - play.height;
- targetBottom = height / 2 + play.height;
-
- canvas.addEventListener("click", function(e) {
- var posx = e.clientX-canvas.offsetLeft;
- var posy = e.clientY-canvas.offsetTop;
- if (posx >= targetLeft && posx <= targetRight &&
- posy >= targetTop && posy <= targetBottom) {
- top.location.href = "vnd.youtube:VIDEO_ID";
- }
- }, false);
+ if (++loadcount == 2) {
+ // Resize the element to the right size
+ mainElement.width = width;
+ mainElement.height = height;
+ mainElement.style.backgroundImage = "url('" + POSTER + "')";
+ // Center the play button
+ playElement.style.marginTop = "-" + play.height/2 + "px";
+ playElement.style.marginLeft = "-" + play.width/2 + "px";
+ playElement.addEventListener("click", function(e) {
+ top.location.href = "vnd.youtube:VIDEO_ID";
+ }, false);
}
}
var background = new Image();
background.onload = doload;
- background.src = "http://img.youtube.com/vi/VIDEO_ID/0.jpg";
+ background.src = POSTER;
play = new Image();
play.onload = doload;
play.src = "play.png";
- logo = new Image();
- logo.onload = doload;
- logo.src = "youtube.png";
}
+
window.onload = setup;
</script>
<div id="main">
- <canvas id="canvas"></canvas>
+ <img src="play.png" id="play"></img>
+ <img src="youtube.png" id="logo"></img>
</div>
</body>
</html>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 6f861fb..abf6676 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -766,10 +766,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Laat die houer toe om versoeke aan pakketverifieerders te rig. Dit moet nooit vir normale programme nodig wees nie."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"kry toegang tot reekspoorte"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Laat die houer toe om toegang te verkry tot reekspoorte wat die SerialManager API gebruik."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"verkry toegang tot inhoud ekstern"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Stel die houer in staat om toegang te verkry tot inhoudverskaffers vanuit die dop. Behoort nooit nodig te wees vir gewone programme nie."</string>
<string name="save_password_message" msgid="767344687139195790">"Wil jy hê die blaaier moet hierdie wagwoord onthou?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nie nou nie"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Onthou"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index bb6246a..18fb405 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/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">"Allows the holder to access serial ports using the SerialManager API. የተከታታይ አደራጅ 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-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 580f9bc..a98a7d4 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/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">"يسمح لحامله بالدخول إلى المنافذ التسلسلية باستخدام واجهة برمجة التطبيقات."</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-be/strings.xml b/core/res/res/values-be/strings.xml
index 8de1cf5..233d5a9 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/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">"Дазваляе ўладальніку атрымліваць доступ да паслядоўных партоў з дапамогай API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"знешнi доступ да кантэнт-правайдэраў"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Дае ўладальніку доступ да кантэнт-правайдэраў з абалонкi. Не патрабуецца для звычайных прыкладанняў."</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-bg/strings.xml b/core/res/res/values-bg/strings.xml
index ec2e0bf..66516b9 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/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">"Разрешава на притежателя достъп до серийни портове посредством приложния програмен интерфейс (API) SerialManager."</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-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 173f1fc..b848c2c 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Permet que el titular sol·liciti verificadors de paquets. No s\'hauria de necessitar mai per a les aplicacions normals."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"accedeix a ports sèrie"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permet que el titular accedeixi a ports sèrie amb l\'API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"accedeix als proveïdors de contingut externament"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permet que el titular accedeixi als proveïdors de contingut des de l\'intèrpret d\'ordres. No és necessari per a les aplicacions normals."</string>
<string name="save_password_message" msgid="767344687139195790">"Voleu que el navegador recordi aquesta contrasenya?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ara no"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recorda-ho"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index d3b5276..544395f 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Umožňuje držiteli podávat žádosti o ověření balíčků. Běžné aplikace by toto oprávnění neměly nikdy požadovat."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"přístup k sériovým portům"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Umožňuje držiteli přístup k sériovým portům pomocí rozhraní 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">"externí přístup k poskytovatelům obsahu"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Umožňuje držiteli získat z příkazového řádku přístup k poskytovatelům obsahu. Běžné aplikace by toto oprávnění neměly nikdy požadovat."</string>
<string name="save_password_message" msgid="767344687139195790">"Chcete, aby si prohlížeč zapamatoval toto heslo?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nyní ne"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamatovat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 78aff33..ceefe1d 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Tillader, at indehaveren kan sende anmodninger om bekræftelser af pakker. Dette bør aldrig være nødvendigt for almindelige apps."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"adgang til serielle porte"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Tillader, at indehaveren kan få adgang til serielle porte ved hjælp af 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">"adgang til indholdsleverandører eksternt"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Giver indehaveren adgang til indholdsleverandører fra startsiden. Bør aldrig være nødvendigt for normale apps."</string>
<string name="save_password_message" msgid="767344687139195790">"Ønsker du, at browseren skal huske denne adgangskode?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ikke nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Husk"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 78845c4..3b3d08d 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Ermöglicht dem Halter, Anfragen für die Paketprüfung zu senden. Sollte nie für normale Apps benötigt werden."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"Zugriff auf serielle Schnittstellen"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Ermöglicht dem Inhaber den Zugriff auf serielle Schnittstellen über das 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">"Extern auf Content-Anbieter zugreifen"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Berechtigt den Inhaber, extern auf Content-Anbieter zuzugreifen. Bei normalen Apps nicht notwendig"</string>
<string name="save_password_message" msgid="767344687139195790">"Möchten Sie, dass der Browser dieses Passwort speichert?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nicht jetzt"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Speichern"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 9244dab..e2bc244 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/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-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index dbd2218..bed4de2 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Allows the holder to make requests of package verifiers. Should never be needed for normal apps."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"access serial ports"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Allows the holder to access serial ports using the 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">"access content providers externally"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Allows the holder to access content providers from the shell. Should never be needed for normal apps."</string>
<string name="save_password_message" msgid="767344687139195790">"Do you want the browser to remember this password?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Not now"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Remember"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 42cc24a..89f51c8 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Permite que el titular solicite verificadores de paquetes. Las aplicaciones normales no deberían necesitar este permiso."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"Acceder a los puertos serie"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permite acceder a puertos serie a través de la API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"acceder a proveedores de contenido externamente"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite a su titular a acceder a los proveedores de contenido desde el shell. Las aplicaciones normales nunca deberían necesitarlo."</string>
<string name="save_password_message" msgid="767344687139195790">"¿Quieres recordar esta contraseña en el navegador?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ahora no."</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recuerda"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index a09a6bf..9499214 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Permite que se envíen solicitudes de detectores de paquetes. Las aplicaciones normales no deberían necesitar este permiso."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"acceder a puertos serie"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permite acceder a puertos serie a través de 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">"acceder a proveedores de contenido externamente"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite acceder a los proveedores de contenido desde el shell. Las aplicaciones normales nunca deberían necesitar este permiso."</string>
<string name="save_password_message" msgid="767344687139195790">"¿Quieres que el navegador recuerde esta contraseña?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ahora no"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recordar"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 07e732a..da06538 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Lubab omanikul teha taotlusi paketi kinnitajate kohta. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"juurdepääs jadaportidele"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Võimaldab omanikul SerialManageri API-liidese abil jadaportidele juurde pääseda."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"väline juurdepääs sisupakkujatele"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Võimaldab valdajal hankida juurdepääsu sisupakkujatele kesta kaudu. Pole kunagi vajalik tavaliste rakenduste puhul."</string>
<string name="save_password_message" msgid="767344687139195790">"Kas soovite, et brauser jätaks selle parooli meelde?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Mitte praegu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Pidage meeles"</string>
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-fi/strings.xml b/core/res/res/values-fi/strings.xml
index b837c5a..2fe9b4e 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Antaa sovelluksen tehdä pakettien vahvistuspyyntöjä. Ei tavallisten sovellusten käyttöön."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"käytä sarjaportteja"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Luvan haltija voi käyttää sarjaportteja SerialManager-sovellusliittymän avulla."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"käytä ulkoisia sisällöntarjoajia"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Antaa luvan haltijan käyttää liittymän sisällöntarjoajia. Ei normaalien sovelluksien käyttöön."</string>
<string name="save_password_message" msgid="767344687139195790">"Haluatko selaimen muistavan tämän salasanan?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ei nyt"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Muista"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index aebe460..d6e8f30 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Permet à l\'application autorisée d\'effectuer des requêtes de vérificateurs de package. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"accéder aux ports série"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permet à l\'application autorisée d\'accéder aux ports série avec l\'API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"accès externe fournisseurs de contenu"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permettre à l\'application titulaire d\'accéder à des fournisseurs de contenu depuis l\'interface. Les applications standards ne devraient jamais avoir recours à cette autorisation."</string>
<string name="save_password_message" msgid="767344687139195790">"Voulez-vous que le navigateur se souvienne de ce mot de passe ?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Pas maintenant"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Mémoriser"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 11bad07..f5a3cb7 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/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-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 462cb52..01378666 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Nositelju omogućuje da traži paketnu provjeru. Ne bi smjelo biti potrebno za normalne aplikacije."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"pristup serijskim priključcima"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Rukovatelju omogućuje pristup serijskim priključcima pomoću značajke 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">"pristup pružateljima sadržaja izvana"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Omogućuje vlasniku pristup pružateljima sadržaja iz programske ovojnice. Ne bi trebalo biti potrebno za normalne aplikacije."</string>
<string name="save_password_message" msgid="767344687139195790">"Želite li da preglednik zapamti ovu zaporku?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne sada"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamti"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index c74ef36..54e8b06 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Lehetővé teszi, hogy a tulajdonos kérelmeket nyújtson be a csomag hitelesítőivel kapcsolatban. A normál alkalmazásoknak erre soha nincs szüksége."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"soros portok elérése"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Lehetővé teszi a tulajdonos számára a soros portok elérését a SerialManager API segítségével."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"tartalomszolgáltatók külső elérése"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Lehetővé teszi, hogy a tulajdonos hozzáférjen a tartalomszolgáltatókhoz a shellből. Normál alkalmazásoknál nem szükséges."</string>
<string name="save_password_message" msgid="767344687139195790">"Szeretné, hogy a böngésző megjegyezze a jelszót?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Most nem"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Megjegyzés"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 068356e..ac5d463 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Mengizinkan pemegang mengajukan permintaan pemverifikasian paket. Tidak pernah dibutuhkan oleh apl normal."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"akses port serial"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Memungkinkan pemegangnya mengakses port serial menggunakan API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"mengakses penyedia konten dari luar"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Memungkinkan pemegang mengakses penyedia konten dari cangkang. Tidak pernah diperlukan untuk apl normal."</string>
<string name="save_password_message" msgid="767344687139195790">"Apakah Anda ingin peramban menyimpan sandi ini?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Tidak sekarang"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Ingat"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 221fff0..77733b3 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Consente al proprietario di effettuare richieste relative alle verifiche dei pacchetti. Non dovrebbe mai essere necessario per le normali applicazioni."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"accesso alle porte seriali"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permette al proprietario di accedere alle porte seriali utilizzando l\'API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"accesso a fornitori di contenuti esterni"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Consente al proprietario di accedere ai fornitori di contenuti dalla shell. Non dovrebbe mai essere necessario per le normali applicazioni."</string>
<string name="save_password_message" msgid="767344687139195790">"Memorizzare la password nel browser?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Non ora"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Memorizza"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index c1d7d82..a232c69 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/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">"מאפשר לבעלים לגשת ליציאות טוריות באמצעות ממשק ה- API של SerialManager."</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-ja/strings.xml b/core/res/res/values-ja/strings.xml
index b3476db..66e847a 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/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-ko/strings.xml b/core/res/res/values-ko/strings.xml
index aeca1ed..754f791 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/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-large/themes.xml b/core/res/res/values-large/themes.xml
index 871a131..448e7c8 100644
--- a/core/res/res/values-large/themes.xml
+++ b/core/res/res/values-large/themes.xml
@@ -33,17 +33,17 @@
-->
<resources>
<style name="Theme.Holo.DialogWhenLarge"
- parent="@android:style/Theme.Holo.Dialog.MinWidth">
+ parent="@android:style/Theme.Holo.Dialog.FixedSize">
<item name="preferencePanelStyle">@style/PreferencePanel.Dialog</item>
</style>
<style name="Theme.Holo.DialogWhenLarge.NoActionBar"
- parent="@android:style/Theme.Holo.Dialog.NoActionBar.MinWidth">
+ parent="@android:style/Theme.Holo.Dialog.NoActionBar.FixedSize">
<item name="preferencePanelStyle">@style/PreferencePanel.Dialog</item>
</style>
<style name="Theme.Holo.Light.DialogWhenLarge"
- parent="@android:style/Theme.Holo.Light.Dialog.MinWidth">
+ parent="@android:style/Theme.Holo.Light.Dialog.FixedSize">
</style>
<style name="Theme.Holo.Light.DialogWhenLarge.NoActionBar"
- parent="@android:style/Theme.Holo.Light.Dialog.NoActionBar.MinWidth">
+ parent="@android:style/Theme.Holo.Light.Dialog.NoActionBar.FixedSize">
</style>
</resources>
diff --git a/core/res/res/values-large/themes_device_defaults.xml b/core/res/res/values-large/themes_device_defaults.xml
index 52fff5c..d57e827 100644
--- a/core/res/res/values-large/themes_device_defaults.xml
+++ b/core/res/res/values-large/themes_device_defaults.xml
@@ -32,17 +32,17 @@
-->
<resources>
<style name="Theme.DeviceDefault.DialogWhenLarge"
- parent="@android:style/Theme.DeviceDefault.Dialog.MinWidth">
+ parent="@android:style/Theme.DeviceDefault.Dialog.FixedSize">
<item name="preferencePanelStyle">@style/PreferencePanel.Dialog</item>
</style>
<style name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar"
- parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar.MinWidth">
+ parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar.FixedSize">
<item name="preferencePanelStyle">@style/PreferencePanel.Dialog</item>
</style>
<style name="Theme.DeviceDefault.Light.DialogWhenLarge"
- parent="@android:style/Theme.DeviceDefault.Light.Dialog.MinWidth">
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.FixedSize">
</style>
<style name="Theme.DeviceDefault.Light.DialogWhenLarge.NoActionBar"
- parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar.MinWidth">
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar.FixedSize">
</style>
</resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 739dca8..43aa1d3 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Savininkui leidžiama teikti užklausas patikrinti paketą. Įprastoms programoms to neturėtų prireikti."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"pasiekti nuosekliuosius prievadus"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Leidžiama savininkui pasiekti nuosekliuosius prievadus naudojant „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">"pasiekti turinio teikėjus iš išorės"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Leidžiama savininkui pasiekti turinio teikėjus naudojant apvalkalą. To niekada neturėtų prireikti naudojant įprastas programas."</string>
<string name="save_password_message" msgid="767344687139195790">"Ar norite, kad naršyklė atsimintų šį slaptažodį?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne dabar"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Atsiminti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index c36434c..5f84a48 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Ļauj īpašniekam sūtīt pakotņu verificētāju pieprasījumus. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"piekļuve seriālajiem portiem"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Ļauj īpašniekam piekļūt seriālajiem portiem, izmantojot 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">"ārēji piekļūt satura nodrošinātājiem"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Ļauj īpašniekam no čaulas piekļūt satura nodrošinātājiem. Nekad nav nepieciešama parastām lietotnēm."</string>
<string name="save_password_message" msgid="767344687139195790">"Vai vēlaties, lai pārlūkprogrammā tiktu saglabāta šī parole?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne tagad"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Atcerēties"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 6d0f5fd..3f240ab 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Membenarkan pemegang membuat permintaan pengesah pakej. Tidak sekali-kali diperlukan untuk apl normal."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"akses port bersiri"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Membenarkan pemegang mengakses port bersiri menggunakan API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"akses pembekal kandungan secara luaran"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Membolehkan pemegang mengakses pembekal kandungan dari luar. Tidak akan sekali-kali diperlukan untuk apl biasa."</string>
<string name="save_password_message" msgid="767344687139195790">"Adakah anda mahu penyemak imbas mengingati kata laluan ini?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Bukan sekarang"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Ingat"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 41ff046..63df5f4 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Lar innehaveren sende forespørsler om pakkeverifikatorer. Skal aldri være nødvendig for normale apper."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"tilgang til serielle porter"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Gir innehaveren tilgang til serielle porter ved hjelp av 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">"gå til innholdsleverandører eksternt"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Lar innehaveren gå til innholdsleverandører fra kommandovinduet. Skal aldri være nødvendig for vanlige apper."</string>
<string name="save_password_message" msgid="767344687139195790">"Ønsker du at nettleseren skal huske dette passordet?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ikke nå"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Husk"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 694ed75..c17da8f 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Pozwala na wysyłanie żądań weryfikacji pakietu. To uprawnienie nie powinno być potrzebne zwykłym aplikacjom."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"dostęp do portów szeregowych"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Umożliwia posiadaczowi dostęp do portów szeregowych przy użyciu interfejsu API narzędzia SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"Dostęp do dostawców treści z zewnątrz"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Pozwala na dostęp do dostawców treści z powłoki. To uprawnienie nie powinno być potrzebne zwykłym aplikacjom."</string>
<string name="save_password_message" msgid="767344687139195790">"Czy chcesz, aby zapamiętać to hasło w przeglądarce?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nie teraz"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamiętaj"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 3fa89d3..74870ff 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Permite ao titular solicitar verificadores de pacotes. Nunca deverá ser necessário para aplicações normais."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"aceder a portas série"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permite ao titular aceder a portas de série através da API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"aceder a fornecedores de conteúdos externamente"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite ao titular aceder a fornecedores de conteúdos a partir da shell. Nunca deverá ser necessário para aplicações normais."</string>
<string name="save_password_message" msgid="767344687139195790">"Quer que o browser memorize esta palavra-passe?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index d99bc60..efe6833 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Permite que o titular solicite verificadores de pacote. Nunca deve ser necessário para aplicativos normais."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"acessar portas seriais"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permite que o detentor tenha acesso a portas seriais usando a API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"acessar fornec. de conteúdo externamente"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite que o proprietário tenha acesso a fornecedores de conteúdo a partir da camada. Nunca deve ser necessário para aplicativos normais."</string>
<string name="save_password_message" msgid="767344687139195790">"Deseja que o navegador lembre desta senha?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index d870d58..8ffa16e 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Permite proprietarului să efectueze solicitări pentru verificatori de pachete. Nu ar trebui să fie niciodată necesară pentru aplicaţiile obişnuite."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"acces la porturi seriale"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Permite posesorului accesul la porturile serial utilizând API-ul SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"accesaţi furniz. de conţin. din exterior"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite deţinătorului să acceseze furnizorii de conţinut din interfaţă. Nu ar trebui să fie necesară pentru aplicaţiile normale."</string>
<string name="save_password_message" msgid="767344687139195790">"Doriţi ca browserul să reţină această parolă?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nu acum"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Reţineţi"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 0b63de1..a6644e5 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/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-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 88d02ab..81e1e6f 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Imetniku omogoča zahtevanje preverjanja paketov. Tega nikoli ni treba uporabiti za navadne programe."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"dostop do serijskih vrat"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Imetniku omogoča, da z API-jem za SerialManager dostopa do serijskih vrat."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"zunanji dostop do ponudnikov vsebine"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Omogoča imetniku, da dostopa do ponudnikov vsebine iz lupine. Naj ne bi bilo nikoli potrebno za običajne programe"</string>
<string name="save_password_message" msgid="767344687139195790">"Ali želite, da si brskalnik zapomni to geslo?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ne zdaj"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapomni si"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 7d0b9f5..199d318 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/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-sv/strings.xml b/core/res/res/values-sv/strings.xml
index ee6295f..be397b0 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Tillåter att innehavaren skickar förfrågningar till paketverifierare. Det ska inte behövas för vanliga appar."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"åtkomst till serieportar"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Innebär att innehavaren får åtkomst till serieportar med programmeringsgränssnittet för SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"komma åt innehållsleverantörer externt"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Innehavaren kan få åtkomst till innehållsleverantörer från skalet. Ska inte behövas för vanliga appar."</string>
<string name="save_password_message" msgid="767344687139195790">"Vill du att webbläsaren ska komma ihåg lösenordet?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Inte nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Kom ihåg"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 70b7c2e..9005a9b 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Inaruhusu mmiliki kutuma maombi ya vibainishi furushi. Kamwe hazitahitajika kwa programu za kawaida."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"kituo tambulishi cha ufikivu"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Inaruhusu mmiliki kufikia vituo tambulishi kwa kutumia KisimamiziTambulishi cha API."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"fikia watoa huduma nje"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Inaruhusu mmiliki kufikia watoa huduma kutoka kwa onyesho. Haifai kuhitajika kwa programu za kawaida."</string>
<string name="save_password_message" msgid="767344687139195790">"Unataka kuvinjari ili ukumbuke nenosiri hili?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Si Sasa"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Kumbuka"</string>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 431a502..13acdd6 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -68,5 +68,19 @@
<!-- Minimum width for an action button in the menu area of an action bar -->
<dimen name="action_button_min_width">64dip</dimen>
+
+ <!-- The platform's desired fixed width for a dialog along the major axis
+ (the screen is in landscape). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_width_major">50%</item>
+ <!-- The platform's desired fixed width for a dialog along the minor axis
+ (the screen is in portrait). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_width_minor">70%</item>
+ <!-- The platform's desired fixed height for a dialog along the major axis
+ (the screen is in portrait). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_height_major">60%</item>
+ <!-- The platform's desired fixed height for a dialog along the minor axis
+ (the screen is in landscape). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_height_minor">90%</item>
+
</resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 0acd104..41c56f4 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/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-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 3bde5c8..9548fe9 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Pinapayagan ang may-ari na gumawa ng mga kahilingan ng mga taga-verify ng package. Hindi kailanman dapat na kailanganin para sa normal na apps."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"mag-access sa mga serial port"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Binibigyang-daan ang may-ari na mag-access ng mga serial port gamit ang 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">"panlabas na mag-access ng mga provider ng nilalaman"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Binibigyang-daan ang may-ari na ma-access ang mga provider ng nilalaman mula sa shell. Hindi kailanman dapat kailanganin para sa karaniwang apps."</string>
<string name="save_password_message" msgid="767344687139195790">"Gusto mo bang tandaan ng browser ang password na ito?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Hindi ngayon"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Tandaan"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 1b27dbd..6da7fcd 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Cihazın sahibine, paket doğrulayıcıları için istek yapma izni verir. Normal uygulamalar için gerekli olmaz."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"seri bağlantı noktalarına eriş"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"İzin sahibinin, SerialManager API\'sını kullanarak seri bağlantı noktalarına erişmesine olanak sağlar."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"içerik sağlayıcılara harici olarak eriş"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"İzin verilen uygulamaya, Uygulama İş Parçacığının dışından içerik sağlayıcılara erişebilme olanağı verir."</string>
<string name="save_password_message" msgid="767344687139195790">"Tarayıcının bu şifreyi anımsamasını istiyor musunuz?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Şimdi değil"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Anımsa"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index eb22ec8..3fd6264 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/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">"Дозволяє власнику отримувати доступ до послідовних портів за допомогою API SerialManager."</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-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 583d216..8503dc5 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -764,10 +764,8 @@
<string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"Cho phép chủ sở hữu yêu cầu trình xác minh gói. Không cần thiết cho các ứng dụng thông thường."</string>
<string name="permlab_serialPort" msgid="546083327654631076">"truy cập cổng nối tiếp"</string>
<string name="permdesc_serialPort" msgid="2991639985224598193">"Cho phép chủ sở hữu truy cập cổng nối tiếp sử dụng API SerialManager."</string>
- <!-- no translation found for permlab_accessContentProvidersExternally (5077774297943409285) -->
- <skip />
- <!-- no translation found for permdesc_accessContentProvidersExternally (4544346486697853685) -->
- <skip />
+ <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"truy cập vào nhà cung cấp nội dung từ bên ngoài"</string>
+ <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Cho phép chủ sở hữu truy cập vào nhà cung cấp nội dung từ bên ngoài. Không bao giờ cần cho ứng dụng thông thường."</string>
<string name="save_password_message" msgid="767344687139195790">"Bạn có muốn trình duyệt nhớ mật khẩu này không?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Không phải bây giờ"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Nhớ"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index ca3c65c..bc74518 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -216,7 +216,7 @@
<string name="permlab_removeTasks" msgid="6821513401870377403">"停止正在运行的应用程序"</string>
<string name="permdesc_removeTasks" msgid="1394714352062635493">"允许该应用程序删除任务并终止这些任务的应用程序。恶意应用程序可以籍此影响其他应用程序的行为。"</string>
<string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"设置屏幕兼容性"</string>
- <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"允许应用程序控制其他应用程序的屏幕兼容模式。恶意应用程序可以籍此影响其他应用程序的行为。"</string>
+ <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"允许该应用程序控制其他应用程序的屏幕兼容模式。恶意应用程序可以籍此影响其他应用程序的行为。"</string>
<string name="permlab_setDebugApp" msgid="3022107198686584052">"启用应用程序调试"</string>
<string name="permdesc_setDebugApp" msgid="4474512416299013256">"允许该应用程序对其他应用程序启用调试。恶意应用程序可以籍此终止其他的应用程序。"</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"更改用户界面设置"</string>
@@ -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-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9b11c43..5c89004 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/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/attrs.xml b/core/res/res/values/attrs.xml
index 16b7ff3..c152b73 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1556,6 +1556,24 @@
an absolute dimension or a fraction of the screen size in that
dimension. -->
<attr name="windowMinWidthMinor" format="dimension|fraction" />
+
+ <!-- A fixed width for the window along the major axis of the screen,
+ that is, when in landscape. Can be either an absolute dimension
+ or a fraction of the screen size in that dimension. -->
+ <attr name="windowFixedWidthMajor" format="dimension|fraction" />
+ <!-- A fixed height for the window along the minor axis of the screen,
+ that is, when in landscape. Can be either an absolute dimension
+ or a fraction of the screen size in that dimension. -->
+ <attr name="windowFixedHeightMinor" format="dimension|fraction" />
+
+ <!-- A fixed width for the window along the minor axis of the screen,
+ that is, when in portrait. Can be either an absolute dimension
+ or a fraction of the screen size in that dimension. -->
+ <attr name="windowFixedWidthMinor" format="dimension|fraction" />
+ <!-- A fixed height for the window along the major axis of the screen,
+ that is, when in portrait. Can be either an absolute dimension
+ or a fraction of the screen size in that dimension. -->
+ <attr name="windowFixedHeightMajor" format="dimension|fraction" />
</declare-styleable>
<!-- The set of attributes that describe a AlertDialog's theme. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 82ef68a..6d6b86b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -108,6 +108,20 @@
is along the major axis (that is the screen is landscape). This may
be either a fraction or a dimension. -->
<item type="dimen" name="dialog_min_width_major">65%</item>
+
+ <!-- The platform's desired fixed width for a dialog along the major axis
+ (the screen is in landscape). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_width_major">320dp</item>
+ <!-- The platform's desired fixed width for a dialog along the minor axis
+ (the screen is in portrait). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_width_minor">320dp</item>
+ <!-- The platform's desired fixed height for a dialog along the major axis
+ (the screen is in portrait). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_height_major">80%</item>
+ <!-- The platform's desired fixed height for a dialog along the minor axis
+ (the screen is in landscape). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="dialog_fixed_height_minor">100%</item>
+
<!-- Preference activity, vertical padding for the header list -->
<dimen name="preference_screen_header_vertical_padding">0dp</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f347a4e..6a887db 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -212,6 +212,10 @@
<java-symbol type="attr" name="textAppearanceMisspelledSuggestion" />
<java-symbol type="attr" name="textColorSearchUrl" />
<java-symbol type="attr" name="timePickerStyle" />
+ <java-symbol type="attr" name="windowFixedWidthMajor" />
+ <java-symbol type="attr" name="windowFixedWidthMinor" />
+ <java-symbol type="attr" name="windowFixedHeightMajor" />
+ <java-symbol type="attr" name="windowFixedHeightMinor" />
<java-symbol type="bool" name="action_bar_embed_tabs" />
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
@@ -958,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/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 7046fc5..55438b2 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -1580,6 +1580,22 @@
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
</style>
+ <!-- Variant of Theme.Holo.Dialog that has a fixed size. -->
+ <style name="Theme.Holo.Dialog.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
+ <!-- Variant of Theme.Holo.Dialog.NoActionBar that has a fixed size. -->
+ <style name="Theme.Holo.Dialog.NoActionBar.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
<!-- Variant of Theme.Holo.Dialog that does not include a frame (or background).
The view hierarchy of the dialog is responsible for drawing all of
its pixels. -->
@@ -1672,6 +1688,22 @@
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
</style>
+ <!-- Variant of Theme.Holo.Light.Dialog that has a fixed size. -->
+ <style name="Theme.Holo.Light.Dialog.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
+ <!-- Variant of Theme.Holo.Light.Dialog.NoActionBar that has a fixed size. -->
+ <style name="Theme.Holo.Light.Dialog.NoActionBar.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
<!-- Theme for a window that will be displayed either full-screen on
smaller screens (small, normal) or as a dialog on larger screens
(large, xlarge). -->
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index abe4aad..7fd981c 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -379,6 +379,23 @@
<style name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" parent="Theme.Holo.Dialog.NoActionBar.MinWidth" >
</style>
+
+ <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
+ <style name="Theme.DeviceDefault.Dialog.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
+ <!-- Variant of Theme.DeviceDefault.Dialog.NoActionBar that has a fixed size. -->
+ <style name="Theme.DeviceDefault.Dialog.NoActionBar.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
<!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be
floating (not fill the entire screen), and puts a frame around its contents. You can set this
theme on an activity if you would like to make an activity that looks like a Dialog.-->
@@ -406,6 +423,23 @@
<style name="Theme.DeviceDefault.Light.Dialog.NoActionBar.MinWidth" parent="Theme.Holo.Light.Dialog.NoActionBar.MinWidth" >
</style>
+
+ <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
+ <style name="Theme.DeviceDefault.Light.Dialog.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
+ <!-- Variant of Theme.DeviceDefault.Dialog.NoActionBar that has a fixed size. -->
+ <style name="Theme.DeviceDefault.Light.Dialog.NoActionBar.FixedSize">
+ <item name="windowFixedWidthMajor">@android:dimen/dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@android:dimen/dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@android:dimen/dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@android:dimen/dialog_fixed_height_minor</item>
+ </style>
+
<!-- DeviceDefault theme for a window that will be displayed either full-screen on smaller
screens (small, normal) or as a dialog on larger screens (large, xlarge). -->
<style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Holo.DialogWhenLarge" >
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
index 2fb4237..3dc140b 100644
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
@@ -50,6 +50,12 @@
// Timeout for the accessibility state of an Activity to be fully initialized.
private static final int TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS = 5000;
+ // Timeout for which non getting accessibility events considers the app idle.
+ private static final long IDLE_EVENT_TIME_DELTA_MILLIS = 200;
+
+ // Timeout in which to wait for idle device.
+ private static final long GLOBAL_IDLE_DETECTION_TIMEOUT_MILLIS = 1000;
+
// Handle to a connection to the AccessibilityManagerService
private UiTestAutomationBridge mUiTestAutomationBridge;
@@ -62,6 +68,8 @@
super.setUp();
mUiTestAutomationBridge = new UiTestAutomationBridge();
mUiTestAutomationBridge.connect();
+ mUiTestAutomationBridge.waitForIdle(IDLE_EVENT_TIME_DELTA_MILLIS,
+ GLOBAL_IDLE_DETECTION_TIMEOUT_MILLIS);
mUiTestAutomationBridge.executeCommandAndWaitForAccessibilityEvent(new Runnable() {
// wait for the first accessibility event
@Override
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/include/ui/PixelFormat.h b/include/ui/PixelFormat.h
index 848c5a1..fc260c4 100644
--- a/include/ui/PixelFormat.h
+++ b/include/ui/PixelFormat.h
@@ -21,7 +21,6 @@
// skia or SurfaceFlinger are not required to support all of these formats
// (either as source or destination)
-// XXX: we should consolidate these formats and skia's
#ifndef UI_PIXELFORMAT_H
#define UI_PIXELFORMAT_H
@@ -29,7 +28,6 @@
#include <stdint.h>
#include <sys/types.h>
#include <utils/Errors.h>
-#include <pixelflinger/format.h>
#include <hardware/hardware.h>
namespace android {
@@ -65,10 +63,7 @@
PIXEL_FORMAT_BGRA_8888 = HAL_PIXEL_FORMAT_BGRA_8888, // 4x8-bit BGRA
PIXEL_FORMAT_RGBA_5551 = HAL_PIXEL_FORMAT_RGBA_5551, // 16-bit ARGB
PIXEL_FORMAT_RGBA_4444 = HAL_PIXEL_FORMAT_RGBA_4444, // 16-bit ARGB
- PIXEL_FORMAT_A_8 = GGL_PIXEL_FORMAT_A_8, // 8-bit A
- PIXEL_FORMAT_L_8 = GGL_PIXEL_FORMAT_L_8, // 8-bit L (R=G=B=L)
- PIXEL_FORMAT_LA_88 = GGL_PIXEL_FORMAT_LA_88, // 16-bit LA
- PIXEL_FORMAT_RGB_332 = GGL_PIXEL_FORMAT_RGB_332, // 8-bit RGB
+ PIXEL_FORMAT_A_8 = 8, // 8-bit A
// New formats can be added if they're also defined in
// pixelflinger/format.h
@@ -76,8 +71,7 @@
typedef int32_t PixelFormat;
-struct PixelFormatInfo
-{
+struct PixelFormatInfo {
enum {
INDEX_ALPHA = 0,
INDEX_RED = 1,
@@ -89,8 +83,6 @@
ALPHA = 1,
RGB = 2,
RGBA = 3,
- LUMINANCE = 4,
- LUMINANCE_ALPHA = 5,
OTHER = 0xFF
};
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index 5dc77f3..c5f8a87 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -52,6 +52,9 @@
LOCAL_MODULE_TAGS := optional
+LOCAL_C_INCLUDES := \
+ external/zlib
+
include $(BUILD_HOST_STATIC_LIBRARY)
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/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 6dd47be..90a7145 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -462,7 +462,8 @@
SkPath* pathCopy = mPathMap.valueFor(path);
if (pathCopy == NULL || pathCopy->getGenerationID() != path->getGenerationID()) {
pathCopy = new SkPath(*path);
- mPathMap.add(path, pathCopy);
+ // replaceValueFor() performs an add if the entry doesn't exist
+ mPathMap.replaceValueFor(path, pathCopy);
mPaths.add(pathCopy);
}
@@ -478,7 +479,8 @@
SkPaint* paintCopy = mPaintMap.valueFor(paint);
if (paintCopy == NULL || paintCopy->getGenerationID() != paint->getGenerationID()) {
paintCopy = new SkPaint(*paint);
- mPaintMap.add(paint, paintCopy);
+ // replaceValueFor() performs an add if the entry doesn't exist
+ mPaintMap.replaceValueFor(paint, paintCopy);
mPaints.add(paintCopy);
}
@@ -520,7 +522,8 @@
// TODO: We also need to handle generation ID changes in compose shaders
if (shaderCopy == NULL || shaderCopy->getGenerationId() != shader->getGenerationId()) {
shaderCopy = shader->copy();
- mShaderMap.add(shader, shaderCopy);
+ // replaceValueFor() performs an add if the entry doesn't exist
+ mShaderMap.replaceValueFor(shader, shaderCopy);
mShaders.add(shaderCopy);
Caches::getInstance().resourceCache.incrementRefcount(shaderCopy);
}
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index 2885d78..c029291 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -29,7 +29,6 @@
libcutils \
libutils \
libEGL \
- libpixelflinger \
libhardware
ifneq ($(BOARD_FRAMEBUFFER_FORCE_FORMAT),)
diff --git a/libs/ui/PixelFormat.cpp b/libs/ui/PixelFormat.cpp
index ee186c8..6993dac 100644
--- a/libs/ui/PixelFormat.cpp
+++ b/libs/ui/PixelFormat.cpp
@@ -15,13 +15,51 @@
*/
#include <ui/PixelFormat.h>
-#include <pixelflinger/format.h>
#include <hardware/hardware.h>
+// ----------------------------------------------------------------------------
namespace android {
+// ----------------------------------------------------------------------------
static const int COMPONENT_YUV = 0xFF;
+struct Info {
+ size_t size;
+ size_t bitsPerPixel;
+ struct {
+ uint8_t ah;
+ uint8_t al;
+ uint8_t rh;
+ uint8_t rl;
+ uint8_t gh;
+ uint8_t gl;
+ uint8_t bh;
+ uint8_t bl;
+ };
+ uint8_t components;
+};
+
+static Info const sPixelFormatInfos[] = {
+ { 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0 },
+ { 4, 32, {32,24, 8, 0, 16, 8, 24,16 }, PixelFormatInfo::RGBA },
+ { 4, 24, { 0, 0, 8, 0, 16, 8, 24,16 }, PixelFormatInfo::RGB },
+ { 3, 24, { 0, 0, 8, 0, 16, 8, 24,16 }, PixelFormatInfo::RGB },
+ { 2, 16, { 0, 0, 16,11, 11, 5, 5, 0 }, PixelFormatInfo::RGB },
+ { 4, 32, {32,24, 24,16, 16, 8, 8, 0 }, PixelFormatInfo::RGBA },
+ { 2, 16, { 1, 0, 16,11, 11, 6, 6, 1 }, PixelFormatInfo::RGBA },
+ { 2, 16, { 4, 0, 16,12, 12, 8, 8, 4 }, PixelFormatInfo::RGBA },
+ { 1, 8, { 8, 0, 0, 0, 0, 0, 0, 0 }, PixelFormatInfo::ALPHA}
+};
+
+static const Info* gGetPixelFormatTable(size_t* numEntries) {
+ if (numEntries) {
+ *numEntries = sizeof(sPixelFormatInfos)/sizeof(Info);
+ }
+ return sPixelFormatInfos;
+}
+
+// ----------------------------------------------------------------------------
+
size_t PixelFormatInfo::getScanlineSize(unsigned int width) const
{
size_t size;
@@ -77,27 +115,12 @@
}
size_t numEntries;
- const GGLFormat *i = gglGetPixelFormatTable(&numEntries) + format;
+ const Info *i = gGetPixelFormatTable(&numEntries) + format;
bool valid = uint32_t(format) < numEntries;
if (!valid) {
return BAD_INDEX;
}
- #define COMPONENT(name) \
- case GGL_##name: info->components = PixelFormatInfo::name; break;
-
- switch (i->components) {
- COMPONENT(ALPHA)
- COMPONENT(RGB)
- COMPONENT(RGBA)
- COMPONENT(LUMINANCE)
- COMPONENT(LUMINANCE_ALPHA)
- default:
- return BAD_INDEX;
- }
-
- #undef COMPONENT
-
info->format = format;
info->bytesPerPixel = i->size;
info->bitsPerPixel = i->bitsPerPixel;
@@ -109,9 +132,12 @@
info->l_green = i->gl;
info->h_blue = i->bh;
info->l_blue = i->bl;
+ info->components = i->components;
return NO_ERROR;
}
+// ----------------------------------------------------------------------------
}; // namespace android
+// ----------------------------------------------------------------------------
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/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 85d99c1..6319630 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -303,6 +303,8 @@
/**
* Uses the settings from a CamcorderProfile object for recording. This method should
* be called after the video AND audio sources are set, and before setOutputFile().
+ * If a time lapse CamcorderProfile is used, audio related source or recording
+ * parameters are ignored.
*
* @param profile the CamcorderProfile to use
* @see android.media.CamcorderProfile
@@ -315,8 +317,8 @@
setVideoEncoder(profile.videoCodec);
if (profile.quality >= CamcorderProfile.QUALITY_TIME_LAPSE_LOW &&
profile.quality <= CamcorderProfile.QUALITY_TIME_LAPSE_QVGA) {
- // Enable time lapse. Also don't set audio for time lapse.
- setParameter(String.format("time-lapse-enable=1"));
+ // Nothing needs to be done. Call to setCaptureRate() enables
+ // time lapse video recording.
} else {
setAudioEncodingBitRate(profile.audioBitRate);
setAudioChannels(profile.audioChannels);
@@ -327,7 +329,10 @@
/**
* Set video frame capture rate. This can be used to set a different video frame capture
- * rate than the recorded video's playback rate. Currently this works only for time lapse mode.
+ * rate than the recorded video's playback rate. This method also sets the recording mode
+ * to time lapse. In time lapse video recording, only video is recorded. Audio related
+ * parameters are ignored when a time lapse recording session starts, if an application
+ * sets them.
*
* @param fps Rate at which frames should be captured in frames per second.
* The fps can go as low as desired. However the fastest fps will be limited by the hardware.
@@ -339,6 +344,9 @@
* possible.
*/
public void setCaptureRate(double fps) {
+ // Make sure that time lapse is enabled when this method is called.
+ setParameter(String.format("time-lapse-enable=1"));
+
double timeBetweenFrameCapture = 1 / fps;
int timeBetweenFrameCaptureMs = (int) (1000 * timeBetweenFrameCapture);
setParameter(String.format("time-between-time-lapse-frame-capture=%d",
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 52d31c7..a08d6c3 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -62,6 +62,9 @@
import java.util.LinkedHashMap;
import java.util.Locale;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+
/**
* Internal service helper that no-one should use directly.
*
@@ -348,20 +351,18 @@
private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
- private static class FileCacheEntry {
+ private static class FileEntry {
long mRowId;
String mPath;
long mLastModified;
int mFormat;
- boolean mSeenInFileSystem;
boolean mLastModifiedChanged;
- FileCacheEntry(long rowId, String path, long lastModified, int format) {
+ FileEntry(long rowId, String path, long lastModified, int format) {
mRowId = rowId;
mPath = path;
mLastModified = lastModified;
mFormat = format;
- mSeenInFileSystem = false;
mLastModifiedChanged = false;
}
@@ -373,11 +374,7 @@
private MediaInserter mMediaInserter;
- // hashes file path to FileCacheEntry.
- // path should be lower case if mCaseInsensitivePaths is true
- private LinkedHashMap<String, FileCacheEntry> mFileCache;
-
- private ArrayList<FileCacheEntry> mPlayLists;
+ private ArrayList<FileEntry> mPlayLists;
private DrmManagerClient mDrmManagerClient = null;
@@ -432,7 +429,7 @@
private int mWidth;
private int mHeight;
- public FileCacheEntry beginFile(String path, String mimeType, long lastModified,
+ public FileEntry beginFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean noMedia) {
mMimeType = mimeType;
mFileType = 0;
@@ -465,11 +462,7 @@
}
}
- String key = path;
- if (mCaseInsensitivePaths) {
- key = path.toLowerCase();
- }
- FileCacheEntry entry = mFileCache.get(key);
+ FileEntry entry = makeEntryFor(path);
// add some slack to avoid a rounding error
long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
boolean wasModified = delta > 1 || delta < -1;
@@ -477,13 +470,11 @@
if (wasModified) {
entry.mLastModified = lastModified;
} else {
- entry = new FileCacheEntry(0, path, lastModified,
+ entry = new FileEntry(0, path, lastModified,
(isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
- mFileCache.put(key, entry);
}
entry.mLastModifiedChanged = true;
}
- entry.mSeenInFileSystem = true;
if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
mPlayLists.add(entry);
@@ -525,7 +516,7 @@
Uri result = null;
// long t1 = System.currentTimeMillis();
try {
- FileCacheEntry entry = beginFile(path, mimeType, lastModified,
+ FileEntry entry = beginFile(path, mimeType, lastModified,
fileSize, isDirectory, noMedia);
// rescan for metadata if file was modified since last scan
if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
@@ -778,7 +769,7 @@
return map;
}
- private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,
+ private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,
boolean alarms, boolean music, boolean podcasts)
throws RemoteException {
// update database
@@ -1028,55 +1019,94 @@
String where = null;
String[] selectionArgs = null;
- if (mFileCache == null) {
- mFileCache = new LinkedHashMap<String, FileCacheEntry>();
- } else {
- mFileCache.clear();
- }
if (mPlayLists == null) {
- mPlayLists = new ArrayList<FileCacheEntry>();
+ mPlayLists = new ArrayList<FileEntry>();
} else {
mPlayLists.clear();
}
if (filePath != null) {
// query for only one file
- where = Files.FileColumns.DATA + "=?";
- selectionArgs = new String[] { filePath };
+ where = MediaStore.Files.FileColumns._ID + ">?" +
+ " AND " + Files.FileColumns.DATA + "=?";
+ selectionArgs = new String[] { "", filePath };
+ } else {
+ where = MediaStore.Files.FileColumns._ID + ">?";
+ selectionArgs = new String[] { "" };
}
+ // Tell the provider to not delete the file.
+ // If the file is truly gone the delete is unnecessary, and we want to avoid
+ // accidentally deleting files that are really there (this may happen if the
+ // filesystem is mounted and unmounted while the scanner is running).
+ Uri.Builder builder = mFilesUri.buildUpon();
+ builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
+ MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
+
// Build the list of files from the content provider
try {
if (prescanFiles) {
- // First read existing files from the files table
+ // First read existing files from the files table.
+ // Because we'll be deleting entries for missing files as we go,
+ // we need to query the database in small batches, to avoid problems
+ // with CursorWindow positioning.
+ long lastId = Long.MIN_VALUE;
+ Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
+ mWasEmptyPriorToScan = true;
- c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
- where, selectionArgs, null, null);
+ while (true) {
+ selectionArgs[0] = "" + lastId;
+ if (c != null) {
+ c.close();
+ c = null;
+ }
+ c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
+ where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
+ if (c == null) {
+ break;
+ }
- if (c != null) {
- mWasEmptyPriorToScan = c.getCount() == 0;
+ int num = c.getCount();
+
+ if (num == 0) {
+ break;
+ }
+ mWasEmptyPriorToScan = false;
while (c.moveToNext()) {
long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
+ lastId = rowId;
// Only consider entries with absolute path names.
// This allows storing URIs in the database without the
// media scanner removing them.
if (path != null && path.startsWith("/")) {
- String key = path;
- if (mCaseInsensitivePaths) {
- key = path.toLowerCase();
+ boolean exists = false;
+ try {
+ exists = Libcore.os.access(path, libcore.io.OsConstants.F_OK);
+ } catch (ErrnoException e1) {
}
+ if (!exists && !MtpConstants.isAbstractObject(format)) {
+ // do not delete missing playlists, since they may have been
+ // modified by the user.
+ // The user can delete them in the media player instead.
+ // instead, clear the path and lastModified fields in the row
+ MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
+ int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
- FileCacheEntry entry = new FileCacheEntry(rowId, path,
- lastModified, format);
- mFileCache.put(key, entry);
+ if (!MediaFile.isPlayListFileType(fileType)) {
+ deleter.delete(rowId);
+ if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
+ deleter.flush();
+ String parent = new File(path).getParent();
+ mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null);
+ }
+ }
+ }
}
}
- c.close();
- c = null;
}
}
}
@@ -1084,6 +1114,7 @@
if (c != null) {
c.close();
}
+ deleter.flush();
}
// compute original size of images
@@ -1186,57 +1217,6 @@
}
private void postscan(String[] directories) throws RemoteException {
- Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
-
- // Tell the provider to not delete the file.
- // If the file is truly gone the delete is unnecessary, and we want to avoid
- // accidentally deleting files that are really there (this may happen if the
- // filesystem is mounted and unmounted while the scanner is running).
- Uri.Builder builder = mFilesUri.buildUpon();
- builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
- MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
-
- while (iterator.hasNext()) {
- FileCacheEntry entry = iterator.next();
- String path = entry.mPath;
-
- // remove database entries for files that no longer exist.
- boolean fileMissing = false;
-
- if (!entry.mSeenInFileSystem && !MtpConstants.isAbstractObject(entry.mFormat)) {
- if (inScanDirectory(path, directories)) {
- // we didn't see this file in the scan directory.
- fileMissing = true;
- } else {
- // the file actually a directory or other abstract object
- // or is outside of our scan directory,
- // so we need to check for file existence here.
- File testFile = new File(path);
- if (!testFile.exists()) {
- fileMissing = true;
- }
- }
- }
-
- if (fileMissing) {
- // do not delete missing playlists, since they may have been modified by the user.
- // the user can delete them in the media player instead.
- // instead, clear the path and lastModified fields in the row
- MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
- int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
-
- if (!MediaFile.isPlayListFileType(fileType)) {
- deleter.delete(entry.mRowId);
- iterator.remove();
- if (entry.mPath.toLowerCase(Locale.US).endsWith("/.nomedia")) {
- deleter.flush();
- File f = new File(path);
- mMediaProvider.call(MediaStore.UNHIDE_CALL, f.getParent(), null);
- }
- }
- }
- }
- deleter.flush();
// handle playlists last, after we know what media files are on the storage.
if (mProcessPlaylists) {
@@ -1248,7 +1228,6 @@
// allow GC to clean up
mPlayLists = null;
- mFileCache = null;
mMediaProvider = null;
}
@@ -1422,11 +1401,7 @@
// build file cache so we can look up tracks in the playlist
prescan(null, true);
- String key = path;
- if (mCaseInsensitivePaths) {
- key = path.toLowerCase();
- }
- FileCacheEntry entry = mFileCache.get(key);
+ FileEntry entry = makeEntryFor(path);
if (entry != null) {
processPlayList(entry);
}
@@ -1445,6 +1420,37 @@
}
}
+ FileEntry makeEntryFor(String path) {
+ String key = path;
+ String where;
+ String[] selectionArgs;
+ if (mCaseInsensitivePaths) {
+ where = Files.FileColumns.DATA + " LIKE ?";
+ selectionArgs = new String[] { path };
+ } else {
+ where = Files.FileColumns.DATA + "=?";
+ selectionArgs = new String[] { path };
+ }
+
+ Cursor c = null;
+ try {
+ c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
+ where, selectionArgs, null, null);
+ if (c.moveToNext()) {
+ long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
+ int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
+ long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
+ return new FileEntry(rowId, path, lastModified, format);
+ }
+ } catch (RemoteException e) {
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return null;
+ }
+
// returns the number of matching file/directory names, starting from the right
private int matchPaths(String path1, String path2) {
int result = 0;
@@ -1495,26 +1501,37 @@
//FIXME - should we look for "../" within the path?
// best matching MediaFile for the play list entry
- FileCacheEntry bestMatch = null;
+ FileEntry bestMatch = null;
// number of rightmost file/directory names for bestMatch
int bestMatchLength = 0;
- Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
- while (iterator.hasNext()) {
- FileCacheEntry cacheEntry = iterator.next();
- String path = cacheEntry.mPath;
+ Cursor c = null;
+ try {
+ c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
+ null, null, null, null);
+ } catch (RemoteException e1) {
+ }
- if (path.equalsIgnoreCase(entry)) {
- bestMatch = cacheEntry;
- break; // don't bother continuing search
- }
+ if (c != null) {
+ while (c.moveToNext()) {
+ long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
+ String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
+ int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
+ long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
- int matchLength = matchPaths(path, entry);
- if (matchLength > bestMatchLength) {
- bestMatch = cacheEntry;
- bestMatchLength = matchLength;
+ if (path.equalsIgnoreCase(entry)) {
+ bestMatch = new FileEntry(rowId, path, lastModified, format);
+ break; // don't bother continuing search
+ }
+
+ int matchLength = matchPaths(path, entry);
+ if (matchLength > bestMatchLength) {
+ bestMatch = new FileEntry(rowId, path, lastModified, format);
+ bestMatchLength = matchLength;
+ }
}
+ c.close();
}
if (bestMatch == null) {
@@ -1524,7 +1541,7 @@
try {
// check rowid is set. Rowid may be missing if it is inserted by bulkInsert().
if (bestMatch.mRowId == 0) {
- Cursor c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
+ c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
MediaStore.Files.FileColumns.DATA + "=?",
new String[] { bestMatch.mPath }, null, null);
if (c != null) {
@@ -1677,7 +1694,7 @@
}
}
- private void processPlayList(FileCacheEntry entry) throws RemoteException {
+ private void processPlayList(FileEntry entry) throws RemoteException {
String path = entry.mPath;
ContentValues values = new ContentValues();
int lastSlash = path.lastIndexOf('/');
@@ -1728,9 +1745,9 @@
}
private void processPlayLists() throws RemoteException {
- Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
+ Iterator<FileEntry> iterator = mPlayLists.iterator();
while (iterator.hasNext()) {
- FileCacheEntry entry = iterator.next();
+ FileEntry entry = iterator.next();
// only process playlist files if they are new or have been modified since the last scan
if (entry.mLastModifiedChanged) {
processPlayList(entry);
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/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index fe519b0..c5f4f86 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1291,6 +1291,12 @@
videoSize.width = mVideoWidth;
videoSize.height = mVideoHeight;
if (mCaptureTimeLapse) {
+ if (mTimeBetweenTimeLapseFrameCaptureUs < 0) {
+ ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld",
+ mTimeBetweenTimeLapseFrameCaptureUs);
+ return BAD_VALUE;
+ }
+
mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera(
mCamera, mCameraProxy, mCameraId,
videoSize, mFrameRate, mPreviewSurface,
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(¶ms);
+ params.nPortIndex = kPortIndexOutput;
+
+ for (params.nProfileIndex = 0;; ++params.nProfileIndex) {
+ status_t err = mOMX->getParameter(
+ mNode,
+ OMX_IndexParamVideoProfileLevelQuerySupported,
+ ¶ms,
+ 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/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index b87b8c3..301dbf5 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -115,6 +115,10 @@
final TypedValue mMinWidthMajor = new TypedValue();
final TypedValue mMinWidthMinor = new TypedValue();
+ TypedValue mFixedWidthMajor;
+ TypedValue mFixedWidthMinor;
+ TypedValue mFixedHeightMajor;
+ TypedValue mFixedHeightMinor;
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@@ -2088,6 +2092,44 @@
final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
final int widthMode = getMode(widthMeasureSpec);
+ final int heightMode = getMode(heightMeasureSpec);
+
+ boolean fixedWidth = false;
+ if (widthMode == AT_MOST) {
+ final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
+ if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
+ fixedWidth = true;
+ final int w;
+ if (tvw.type == TypedValue.TYPE_DIMENSION) {
+ w = (int) tvw.getDimension(metrics);
+ } else if (tvw.type == TypedValue.TYPE_FRACTION) {
+ w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ w = 0;
+ }
+
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(w, widthSize), EXACTLY);
+ }
+ }
+
+ if (heightMode == AT_MOST) {
+ final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
+ if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
+ final int h;
+ if (tvh.type == TypedValue.TYPE_DIMENSION) {
+ h = (int) tvh.getDimension(metrics);
+ } else if (tvh.type == TypedValue.TYPE_FRACTION) {
+ h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
+ } else {
+ h = 0;
+ }
+
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ heightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(Math.min(h, heightSize), EXACTLY);
+ }
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -2096,21 +2138,22 @@
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
- final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
+ if (!fixedWidth && widthMode == AT_MOST) {
+ final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
+ if (tv.type != TypedValue.TYPE_NULL) {
+ final int min;
+ if (tv.type == TypedValue.TYPE_DIMENSION) {
+ min = (int)tv.getDimension(metrics);
+ } else if (tv.type == TypedValue.TYPE_FRACTION) {
+ min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ min = 0;
+ }
- if (widthMode == AT_MOST && tv.type != TypedValue.TYPE_NULL) {
- final int min;
- if (tv.type == TypedValue.TYPE_DIMENSION) {
- min = (int)tv.getDimension(metrics);
- } else if (tv.type == TypedValue.TYPE_FRACTION) {
- min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
- } else {
- min = 0;
- }
-
- if (width < min) {
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
- measure = true;
+ if (width < min) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
+ measure = true;
+ }
}
}
@@ -2571,6 +2614,26 @@
a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
+ if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor)) {
+ if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
+ a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor,
+ mFixedWidthMajor);
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor)) {
+ if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
+ a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor,
+ mFixedWidthMinor);
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor)) {
+ if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
+ a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor,
+ mFixedHeightMajor);
+ }
+ if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor)) {
+ if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
+ a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor,
+ mFixedHeightMinor);
+ }
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
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/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 586a67e..455325a 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -86,8 +86,8 @@
private static final String LOG_TAG = "AccessibilityManagerService";
- private static final String FUNCTION_REGISTER_EVENT_LISTENER =
- "registerEventListener";
+ private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
+ "registerUiTestAutomationService";
private static int sIdCounter = 0;
@@ -95,10 +95,6 @@
private static final int DO_SET_SERVICE_INFO = 10;
- public static final int ACTIVE_WINDOW_ID = -1;
-
- public static final long ROOT_NODE_ID = -1;
-
private static int sNextWindowId;
final HandlerCaller mCaller;
@@ -241,19 +237,9 @@
if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {
synchronized (mLock) {
populateAccessibilityServiceListLocked();
- // get accessibility enabled setting on boot
- mIsAccessibilityEnabled = Settings.Secure.getInt(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
-
- manageServicesLocked();
-
- // get touch exploration enabled setting on boot
- mIsTouchExplorationEnabled = Settings.Secure.getInt(
- mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
+ handleAccessibilityEnabledSettingChangedLocked();
+ handleTouchExplorationEnabledSettingChangedLocked();
updateInputFilterLocked();
-
sendStateToClientsLocked();
}
@@ -301,9 +287,10 @@
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
-
synchronized (mLock) {
handleAccessibilityEnabledSettingChangedLocked();
+ updateInputFilterLocked();
+ sendStateToClientsLocked();
}
}
});
@@ -315,11 +302,8 @@
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
-
synchronized (mLock) {
- mIsTouchExplorationEnabled = Settings.Secure.getInt(
- mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
+ handleTouchExplorationEnabledSettingChangedLocked();
updateInputFilterLocked();
sendStateToClientsLocked();
}
@@ -333,7 +317,6 @@
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
-
synchronized (mLock) {
manageServicesLocked();
}
@@ -475,7 +458,7 @@
public void registerUiTestAutomationService(IEventListener listener,
AccessibilityServiceInfo accessibilityServiceInfo) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
- FUNCTION_REGISTER_EVENT_LISTENER);
+ FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
ComponentName componentName = new ComponentName("foo.bar",
"AutomationAccessibilityService");
synchronized (mLock) {
@@ -905,8 +888,15 @@
} else {
unbindAllServicesLocked();
}
- updateInputFilterLocked();
- sendStateToClientsLocked();
+ }
+
+ /**
+ * Updates the state based on the touch exploration enabled setting.
+ */
+ private void handleTouchExplorationEnabledSettingChangedLocked() {
+ mIsTouchExplorationEnabled = Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
}
private class AccessibilityConnectionWrapper implements DeathRecipient {
@@ -1229,13 +1219,16 @@
public void binderDied() {
synchronized (mLock) {
- unlinkToOwnDeath();
+ // The death recipient is unregistered in tryRemoveServiceLocked
tryRemoveServiceLocked(this);
// We no longer have an automation service, so restore
// the state based on values in the settings database.
if (mIsAutomation) {
mUiAutomationService = null;
handleAccessibilityEnabledSettingChangedLocked();
+ handleTouchExplorationEnabledSettingChangedLocked();
+ updateInputFilterLocked();
+ sendStateToClientsLocked();
}
}
}
@@ -1256,7 +1249,7 @@
}
private int resolveAccessibilityWindowId(int accessibilityWindowId) {
- if (accessibilityWindowId == ACTIVE_WINDOW_ID) {
+ if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) {
return mSecurityPolicy.mRetrievalAlowingWindowId;
}
return accessibilityWindowId;
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 80ef0e6..dafc613 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -268,6 +268,7 @@
final TokenWatcher mKeyguardTokenWatcher = new TokenWatcher(
new Handler(), "WindowManagerService.mKeyguardTokenWatcher") {
+ @Override
public void acquired() {
if (shouldAllowDisableKeyguard()) {
mPolicy.enableKeyguard(false);
@@ -276,6 +277,7 @@
Log.v(TAG, "Not disabling keyguard since device policy is enforced");
}
}
+ @Override
public void released() {
mPolicy.enableKeyguard(true);
synchronized (mKeyguardTokenWatcher) {
@@ -599,6 +601,7 @@
private boolean mSyswin = false;
private float mScreenBrightness = -1;
private float mButtonBrightness = -1;
+ private boolean mUpdateRotation = false;
}
private LayoutAndSurfaceFields mInnerFields = new LayoutAndSurfaceFields();
@@ -7620,53 +7623,53 @@
* @param innerDh Height of app window.
* @return true if rotation has stopped, false otherwise
*/
- private boolean updateAppsAndRotationAnimationsLocked(long currentTime,
+ private void updateWindowsAppsAndRotationAnimationsLocked(long currentTime,
int innerDw, int innerDh) {
int i;
+ for (i = mWindows.size() - 1; i >= 0; i--) {
+ mInnerFields.mAnimating |= mWindows.get(i).stepAnimationLocked(currentTime);
+ }
+
final int NAT = mAppTokens.size();
for (i=0; i<NAT; i++) {
- if (mAppTokens.get(i).stepAnimationLocked(currentTime,
- innerDw, innerDh)) {
- mInnerFields.mAnimating = true;
- }
+ mInnerFields.mAnimating |=
+ mAppTokens.get(i).stepAnimationLocked(currentTime, innerDw, innerDh);
}
final int NEAT = mExitingAppTokens.size();
for (i=0; i<NEAT; i++) {
- if (mExitingAppTokens.get(i).stepAnimationLocked(currentTime,
- innerDw, innerDh)) {
- mInnerFields.mAnimating = true;
- }
+ mInnerFields.mAnimating |=
+ mExitingAppTokens.get(i).stepAnimationLocked(currentTime, innerDw, innerDh);
}
- boolean updateRotation = false;
if (mScreenRotationAnimation != null) {
if (mScreenRotationAnimation.isAnimating()) {
if (mScreenRotationAnimation.stepAnimation(currentTime)) {
+ mInnerFields.mUpdateRotation = false;
mInnerFields.mAnimating = true;
} else {
- updateRotation = true;
+ mInnerFields.mUpdateRotation = true;
mScreenRotationAnimation.kill();
mScreenRotationAnimation = null;
}
}
}
-
- return updateRotation;
}
/**
* Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
*
* @param currentTime The time which animations use for calculating transitions.
+ * @param dw Width of app window.
+ * @param dh Height of app window.
* @param innerDw Width of app window.
* @param innerDh Height of app window.
*/
- private void updateWindowsAndWallpaperLocked(final long currentTime,
- final int innerDw, final int innerDh) {
- int i;
- final int N = mWindows.size();
+ private int updateWindowsAndWallpaperLocked(final long currentTime, final int dw, final int dh,
+ final int innerDw, final int innerDh) {
- for (i=N-1; i>=0; i--) {
+ mPolicy.beginAnimationLw(dw, dh);
+
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
WindowState w = mWindows.get(i);
final WindowManager.LayoutParams attrs = w.mAttrs;
@@ -7684,9 +7687,6 @@
final boolean wasAnimating = w.mAnimating;
- int animDw = innerDw;
- int animDh = innerDh;
-
// If the window has moved due to its containing
// content frame changing, then we'd like to animate
// it. The checks here are ordered by what is least
@@ -7698,13 +7698,15 @@
Animation a = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.window_move_from_decor);
w.setAnimation(a);
- animDw = w.mLastFrame.left - w.mFrame.left;
- animDh = w.mLastFrame.top - w.mFrame.top;
+ w.mAnimDw = w.mLastFrame.left - w.mFrame.left;
+ w.mAnimDh = w.mLastFrame.top - w.mFrame.top;
+ } else {
+ w.mAnimDw = innerDw;
+ w.mAnimDh = innerDh;
}
// Execute animation.
- final boolean nowAnimating = w.stepAnimationLocked(currentTime,
- animDw, animDh);
+ final boolean nowAnimating = w.isAnimating();
// If this window is animating, make a note that we have
// an animating window and take care of a request to run
@@ -7846,6 +7848,8 @@
w.performShowLocked();
}
} // end forall windows
+
+ return mPolicy.finishAnimationLw();
}
/**
@@ -8116,7 +8120,7 @@
*
* @return bitmap indicating if another pass through layout must be made.
*/
- private int handleAnimatingAndTransitionLocked() {
+ private int handleAnimatingStoppedAndTransitionLocked() {
int changes = 0;
mAppTransitionRunning = false;
@@ -8652,7 +8656,6 @@
boolean focusDisplayed = false;
mInnerFields.mAnimating = false;
boolean createWatermark = false;
- boolean updateRotation = false;
if (mFxSession == null) {
mFxSession = new SurfaceSession();
@@ -8706,22 +8709,13 @@
// FIRST LOOP: Perform a layout, if needed.
if (repeats < 4) {
- performLayoutLockedInner(repeats == 0, false /*updateInputWindows*/);
+ performLayoutLockedInner(repeats == 1, false /*updateInputWindows*/);
} else {
Slog.w(TAG, "Layout repeat skipped after too many iterations");
}
- changes = 0;
++mTransactionSequence;
- // Update animations of all applications, including those
- // associated with exiting/removed apps
- mInnerFields.mAnimating = false;
-
- // SECOND LOOP: Execute animations and update visibility of windows.
- updateRotation |=
- updateAppsAndRotationAnimationsLocked(currentTime, innerDw, innerDh);
-
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq="
+ mTransactionSequence + " mAnimating="
+ mInnerFields.mAnimating);
@@ -8733,11 +8727,7 @@
mInnerFields.mWindowAnimationBackground = null;
mInnerFields.mWindowAnimationBackgroundColor = 0;
- mPolicy.beginAnimationLw(dw, dh);
-
- updateWindowsAndWallpaperLocked(currentTime, innerDw, innerDh);
-
- changes |= mPolicy.finishAnimationLw();
+ changes = updateWindowsAndWallpaperLocked(currentTime, dw, dh, innerDw, innerDh);
if (mInnerFields.mTokenMayBeDrawn) {
changes |= testTokenMayBeDrawnLocked();
@@ -8759,7 +8749,7 @@
// reflects the correct Z-order, but the window list may now
// be out of sync with it. So here we will just rebuild the
// entire app window list. Fun!
- changes |= handleAnimatingAndTransitionLocked();
+ changes |= handleAnimatingStoppedAndTransitionLocked();
}
if (mInnerFields.mWallpaperForceHidingChanged && changes == 0 && !mAppTransitionReady) {
@@ -8782,6 +8772,12 @@
+ Integer.toHexString(changes));
} while (changes != 0);
+ // Update animations of all applications, including those
+ // associated with exiting/removed apps
+ mInnerFields.mAnimating = false;
+
+ updateWindowsAppsAndRotationAnimationsLocked(currentTime, innerDw, innerDh);
+
// THIRD LOOP: Update the surfaces of all windows.
final boolean someoneLosingFocus = mLosingFocus.size() != 0;
@@ -9023,16 +9019,17 @@
mTurnOnScreen = false;
}
- if (updateRotation) {
+ if (mInnerFields.mUpdateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
if (updateRotationUncheckedLocked(false)) {
mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
} else {
- updateRotation = false;
+ mInnerFields.mUpdateRotation = false;
}
}
- if (mInnerFields.mOrientationChangeComplete && !needRelayout && !updateRotation) {
+ if (mInnerFields.mOrientationChangeComplete && !needRelayout &&
+ !mInnerFields.mUpdateRotation) {
checkDrawnWindowsLocked();
}
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index b013d27..d7a7cb0 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -297,6 +297,11 @@
CharSequence mLastTitle;
boolean mWasPaused;
+ // Used to save animation distances between the time they are calculated and when they are
+ // used.
+ int mAnimDw;
+ int mAnimDh;
+
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int seq, WindowManager.LayoutParams a,
int viewVisibility) {
@@ -973,7 +978,7 @@
// This must be called while inside a transaction. Returns true if
// there is more animation to run.
- boolean stepAnimationLocked(long currentTime, int dw, int dh) {
+ boolean stepAnimationLocked(long currentTime) {
if (!mService.mDisplayFrozen && mService.mPolicy.isScreenOnFully()) {
// We will run animations as long as the display isn't frozen.
@@ -985,8 +990,9 @@
WindowManagerService.TAG, "Starting animation in " + this +
" @ " + currentTime + ": ww=" + mFrame.width() +
" wh=" + mFrame.height() +
- " dw=" + dw + " dh=" + dh + " scale=" + mService.mWindowAnimationScale);
- mAnimation.initialize(mFrame.width(), mFrame.height(), dw, dh);
+ " dw=" + mAnimDw + " dh=" + mAnimDh +
+ " scale=" + mService.mWindowAnimationScale);
+ mAnimation.initialize(mFrame.width(), mFrame.height(), mAnimDw, mAnimDh);
mAnimation.setStartTime(currentTime);
mLocalAnimating = true;
mAnimating = true;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 3904c21..ed78daa3 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -568,6 +568,15 @@
</activity>
<activity
+ android:name="PathsCacheActivity"
+ android:label="_PathsCache">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="PointsActivity"
android:label="_Points">
<intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
new file mode 100644
index 0000000..b8ad823
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class PathsCacheActivity extends Activity {
+ private Path mPath;
+
+ private final Random mRandom = new Random();
+ private final ArrayList<Path> mPathList = new ArrayList<Path>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mPath = makePath();
+
+ final PathsView view = new PathsView(this);
+ setContentView(view);
+ }
+
+ private Path makePath() {
+ Path path = new Path();
+ buildPath(path);
+ return path;
+ }
+
+ private void buildPath(Path path) {
+ path.moveTo(0.0f, 0.0f);
+ path.cubicTo(0.0f, 0.0f, 100.0f, 150.0f, 100.0f, 200.0f);
+ path.cubicTo(100.0f, 200.0f, 50.0f, 300.0f, -80.0f, 200.0f);
+ path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
+ }
+
+ public class PathsView extends View {
+ private final Paint mMediumPaint;
+
+ public PathsView(Context c) {
+ super(c);
+
+ mMediumPaint = new Paint();
+ mMediumPaint.setAntiAlias(true);
+ mMediumPaint.setColor(0xe00000ff);
+ mMediumPaint.setStrokeWidth(10.0f);
+ mMediumPaint.setStyle(Paint.Style.STROKE);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ Log.d("OpenGLRenderer", "Start frame");
+
+ canvas.drawARGB(255, 255, 255, 255);
+
+ canvas.save();
+ canvas.translate(550.0f, 60.0f);
+ canvas.drawPath(mPath, mMediumPaint);
+
+ mPath.reset();
+ buildPath(mPath);
+
+ canvas.translate(30.0f, 30.0f);
+ canvas.drawPath(mPath, mMediumPaint);
+ canvas.drawPath(mPath, mMediumPaint);
+
+ canvas.restore();
+
+// Path path = makePath();
+// int r = mRandom.nextInt(10);
+// if (r == 5 || r == 3) {
+// mPathList.add(path);
+// } else if (r == 9) {
+// mPathList.clear();
+// }
+//
+// canvas.save();
+// canvas.translate(550.0f + mRandom.nextInt(50), 60.0f + mRandom.nextInt(50));
+// canvas.drawPath(path, mMediumPaint);
+// canvas.restore();
+//
+ invalidate();
+ }
+ }
+}
diff --git a/tests/RenderScriptTests/SceneGraph/AndroidManifest.xml b/tests/RenderScriptTests/SceneGraph/AndroidManifest.xml
index e8d1e8e..67af0fa 100644
--- a/tests/RenderScriptTests/SceneGraph/AndroidManifest.xml
+++ b/tests/RenderScriptTests/SceneGraph/AndroidManifest.xml
@@ -11,6 +11,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name="SimpleApp"
+ android:label="SimpleSceneGraph">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
<activity android:name="FileSelector"
android:label="FileSelector"
android:hardwareAccelerated="true">
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/ColladaParser.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/ColladaParser.java
index d954313..b4b6fb9 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/ColladaParser.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/ColladaParser.java
@@ -248,17 +248,17 @@
String description = field.getAttribute("sid");
if (fieldName.equals("translate")) {
Float3 value = getFloat3(field);
- current.addComponent(new TranslateComponent(description, value));
+ current.addTranslate(description, value);
//Log.v(TAG, indent + " translate " + description + toString(value));
} else if (fieldName.equals("rotate")) {
Float4 value = getFloat4(field);
//Log.v(TAG, indent + " rotate " + description + toString(value));
Float3 axis = new Float3(value.x, value.y, value.z);
- current.addComponent(new RotateComponent(description, axis, value.w));
+ current.addRotate(description, axis, value.w);
} else if (fieldName.equals("scale")) {
Float3 value = getFloat3(field);
//Log.v(TAG, indent + " scale " + description + toString(value));
- current.addComponent(new ScaleComponent(description, value));
+ current.addScale(description, value);
} else if (fieldName.equals("instance_geometry")) {
getRenderable(field, current);
} else if (fieldName.equals("instance_light")) {
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/CompoundTransform.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/CompoundTransform.java
index d995dd0..9274b17 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/CompoundTransform.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/CompoundTransform.java
@@ -134,6 +134,24 @@
mTransformComponents = new ArrayList<Component>();
}
+ public TranslateComponent addTranslate(String name, Float3 translate) {
+ TranslateComponent c = new TranslateComponent(name, translate);
+ addComponent(c);
+ return c;
+ }
+
+ public RotateComponent addRotate(String name, Float3 axis, float angle) {
+ RotateComponent c = new RotateComponent(name, axis, angle);
+ addComponent(c);
+ return c;
+ }
+
+ public ScaleComponent addScale(String name, Float3 scale) {
+ ScaleComponent c = new ScaleComponent(name, scale);
+ addComponent(c);
+ return c;
+ }
+
public void addComponent(Component c) {
if (c.mParent != null) {
throw new IllegalArgumentException("Transform components may not be shared");
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java
index c8cc3ac..8a468db 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/FragmentShader.java
@@ -48,6 +48,11 @@
return this;
}
+ public Builder setShader(String code) {
+ mBuilder.setShader(code);
+ return this;
+ }
+
public Builder setObjectConst(Type type) {
mShader.mPerObjConstants = type;
return this;
@@ -78,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 9f7ab41..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) {
@@ -196,12 +201,17 @@
}
void updateFieldItem(RenderScriptGL rs) {
+ 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 8c09860..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;
@@ -75,9 +79,24 @@
}
public void appendTransform(Transform t) {
+ if (t == null) {
+ throw new RuntimeException("Adding null object");
+ }
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);
@@ -88,26 +107,53 @@
}
public void appendRenderPass(RenderPass p) {
+ if (p == null) {
+ throw new RuntimeException("Adding null object");
+ }
mRenderPasses.add(p);
}
+ public RenderPass appendNewRenderPass() {
+ RenderPass p = new RenderPass();
+ appendRenderPass(p);
+ return p;
+ }
+
public void clearRenderPasses() {
mRenderPasses.clear();
}
public void appendLight(LightBase l) {
+ if (l == null) {
+ throw new RuntimeException("Adding null object");
+ }
mLights.add(l);
}
public void appendCamera(Camera c) {
+ if (c == null) {
+ throw new RuntimeException("Adding null object");
+ }
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");
+ }
mFragmentShaders.add(f);
}
public void appendShader(VertexShader v) {
+ if (v == null) {
+ throw new RuntimeException("Adding null object");
+ }
mVertexShaders.add(v);
}
@@ -120,8 +166,19 @@
}
public void appendRenderable(RenderableBase d) {
+ if (d == null) {
+ 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() {
@@ -133,6 +190,9 @@
}
public void appendTextures(Texture2D tex) {
+ if (tex == null) {
+ throw new RuntimeException("Adding null object");
+ }
mTextures.add(tex);
}
@@ -224,24 +284,29 @@
}
private void addShaders(RenderScriptGL rs, Resources res, SceneManager sceneManager) {
- Allocation shaderData = Allocation.createSized(rs, Element.ALLOCATION(rs),
- mVertexShaders.size());
- Allocation[] shaderAllocs = new Allocation[mVertexShaders.size()];
- for (int i = 0; i < mVertexShaders.size(); i ++) {
- VertexShader sI = mVertexShaders.get(i);
- shaderAllocs[i] = sI.getRSData().getAllocation();
+ if (mVertexShaders.size() > 0) {
+ Allocation shaderData = Allocation.createSized(rs, Element.ALLOCATION(rs),
+ mVertexShaders.size());
+ Allocation[] shaderAllocs = new Allocation[mVertexShaders.size()];
+ for (int i = 0; i < mVertexShaders.size(); i ++) {
+ VertexShader sI = mVertexShaders.get(i);
+ shaderAllocs[i] = sI.getRSData().getAllocation();
+ }
+ shaderData.copyFrom(shaderAllocs);
+ sceneManager.mRenderLoop.set_gVertexShaders(shaderData);
}
- shaderData.copyFrom(shaderAllocs);
- sceneManager.mRenderLoop.set_gVertexShaders(shaderData);
- shaderData = Allocation.createSized(rs, Element.ALLOCATION(rs), mFragmentShaders.size());
- shaderAllocs = new Allocation[mFragmentShaders.size()];
- for (int i = 0; i < mFragmentShaders.size(); i ++) {
- FragmentShader sI = mFragmentShaders.get(i);
- shaderAllocs[i] = sI.getRSData().getAllocation();
+ if (mFragmentShaders.size() > 0) {
+ Allocation shaderData = Allocation.createSized(rs, Element.ALLOCATION(rs),
+ mFragmentShaders.size());
+ Allocation[] shaderAllocs = new Allocation[mFragmentShaders.size()];
+ for (int i = 0; i < mFragmentShaders.size(); i ++) {
+ FragmentShader sI = mFragmentShaders.get(i);
+ shaderAllocs[i] = sI.getRSData().getAllocation();
+ }
+ shaderData.copyFrom(shaderAllocs);
+ sceneManager.mRenderLoop.set_gFragmentShaders(shaderData);
}
- shaderData.copyFrom(shaderAllocs);
- sceneManager.mRenderLoop.set_gFragmentShaders(shaderData);
}
public void initRS() {
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java
index 535905a..4ff2c8b 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/SceneManager.java
@@ -29,8 +29,10 @@
import java.util.regex.Pattern;
import com.android.scenegraph.Camera;
+import com.android.scenegraph.FragmentShader;
import com.android.scenegraph.MatrixTransform;
import com.android.scenegraph.Scene;
+import com.android.scenegraph.VertexShader;
import com.android.testapp.R;
import android.content.res.Resources;
@@ -41,7 +43,6 @@
import android.renderscript.Allocation.MipmapControl;
import android.renderscript.Mesh;
import android.renderscript.RenderScriptGL;
-import android.renderscript.Type.Builder;
import android.util.Log;
import android.view.SurfaceHolder;
@@ -74,6 +75,13 @@
private Allocation mDefault2D;
private Allocation mDefaultCube;
+ private FragmentShader mColor;
+ private FragmentShader mTexture;
+ private VertexShader mDefaultVertex;
+
+ private RenderState mDefaultState;
+ private Transform mDefaultTransform;
+
private static Allocation getDefault(boolean isCube) {
final int dimension = 4;
final int bytesPerPixel = 4;
@@ -101,6 +109,9 @@
if (sSceneManager == null) {
return null;
}
+ if (sSceneManager.mDefault2D == null) {
+ sSceneManager.mDefault2D = getDefault(false);
+ }
return sSceneManager.mDefault2D;
}
@@ -108,6 +119,9 @@
if (sSceneManager == null) {
return null;
}
+ if (sSceneManager.mDefaultCube == null) {
+ sSceneManager.mDefaultCube = getDefault(true);
+ }
return sSceneManager.mDefaultCube;
}
@@ -148,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) {
@@ -229,6 +251,10 @@
public void setActiveScene(Scene s) {
mActiveScene = s;
+ if (mActiveScene == null) {
+ return;
+ }
+
// Do some sanity checking
if (mActiveScene.getCameras().size() == 0) {
Matrix4f camPos = new Matrix4f();
@@ -242,6 +268,9 @@
cam.setTransform(cameraTransform);
mActiveScene.appendCamera(cam);
}
+
+ mActiveScene.appendShader(getDefaultVS());
+ mActiveScene.appendTransform(getDefaultTransform());
}
static RenderScriptGL getRS() {
@@ -258,6 +287,129 @@
return sSceneManager.mRes;
}
+ // Provides the folowing inputs to fragment shader
+ // Assigned by default if nothing is present
+ // vec3 varWorldPos;
+ // vec3 varWorldNormal;
+ // vec2 varTex0;
+ public static VertexShader getDefaultVS() {
+ if (sSceneManager == null) {
+ return null;
+ }
+
+ if (sSceneManager.mDefaultVertex == null) {
+ RenderScriptGL rs = getRS();
+ Element.Builder b = new Element.Builder(rs);
+ b.add(Element.MATRIX_4X4(rs), "model");
+ Type.Builder objConstBuilder = new Type.Builder(rs, b.create());
+
+ b = new Element.Builder(rs);
+ b.add(Element.MATRIX_4X4(rs), "viewProj");
+ Type.Builder shaderConstBuilder = new Type.Builder(rs, b.create());
+
+ b = new Element.Builder(rs);
+ b.add(Element.F32_4(rs), "position");
+ b.add(Element.F32_2(rs), "texture0");
+ b.add(Element.F32_3(rs), "normal");
+ Element defaultIn = b.create();
+
+ final String code = "\n" +
+ "varying vec3 varWorldPos;\n" +
+ "varying vec3 varWorldNormal;\n" +
+ "varying vec2 varTex0;\n" +
+ "void main() {" +
+ " vec4 objPos = ATTRIB_position;\n" +
+ " vec4 worldPos = UNI_model * objPos;\n" +
+ " gl_Position = UNI_viewProj * worldPos;\n" +
+ " mat3 model3 = mat3(UNI_model[0].xyz, UNI_model[1].xyz, UNI_model[2].xyz);\n" +
+ " vec3 worldNorm = model3 * ATTRIB_normal;\n" +
+ " varWorldPos = worldPos.xyz;\n" +
+ " varWorldNormal = worldNorm;\n" +
+ " varTex0 = ATTRIB_texture0;\n" +
+ "}\n";
+
+ VertexShader.Builder sb = new VertexShader.Builder(rs);
+ sb.addInput(defaultIn);
+ sb.setObjectConst(objConstBuilder.setX(1).create());
+ sb.setShaderConst(shaderConstBuilder.setX(1).create());
+ sb.setShader(code);
+ sSceneManager.mDefaultVertex = sb.create();
+ }
+
+ return sSceneManager.mDefaultVertex;
+ }
+
+ public static FragmentShader getColorFS() {
+ if (sSceneManager == null) {
+ return null;
+ }
+ if (sSceneManager.mColor == null) {
+ RenderScriptGL rs = getRS();
+ Element.Builder b = new Element.Builder(rs);
+ b.add(Element.F32_4(rs), "color");
+ Type.Builder objConstBuilder = new Type.Builder(rs, b.create());
+
+ final String code = "\n" +
+ "varying vec2 varTex0;\n" +
+ "void main() {\n" +
+ " lowp vec4 col = UNI_color;\n" +
+ " gl_FragColor = col;\n" +
+ "}\n";
+ FragmentShader.Builder fb = new FragmentShader.Builder(rs);
+ fb.setShader(code);
+ fb.setObjectConst(objConstBuilder.create());
+ sSceneManager.mColor = fb.create();
+ }
+
+ return sSceneManager.mColor;
+ }
+
+ public static FragmentShader getTextureFS() {
+ if (sSceneManager == null) {
+ return null;
+ }
+ if (sSceneManager.mTexture == null) {
+ RenderScriptGL rs = getRS();
+
+ final String code = "\n" +
+ "varying vec2 varTex0;\n" +
+ "void main() {\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, "color");
+ sSceneManager.mTexture = fb.create();
+ sSceneManager.mTexture.mProgram.bindSampler(Sampler.CLAMP_LINEAR_MIP_LINEAR(rs), 0);
+ }
+
+ return sSceneManager.mTexture;
+ }
+
+ static RenderState getDefaultState() {
+ if (sSceneManager == null) {
+ return null;
+ }
+ 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();
@@ -281,17 +433,10 @@
Mesh.TriangleMeshBuilder tmb = new Mesh.TriangleMeshBuilder(mRS,
3, Mesh.TriangleMeshBuilder.TEXTURE_0);
- tmb.setTexture(0.0f, 1.0f);
- tmb.addVertex(-1.0f, 1.0f, 1.0f);
-
- tmb.setTexture(0.0f, 0.0f);
- tmb.addVertex(-1.0f, -1.0f, 1.0f);
-
- tmb.setTexture(1.0f, 0.0f);
- tmb.addVertex(1.0f, -1.0f, 1.0f);
-
- tmb.setTexture(1.0f, 1.0f);
- tmb.addVertex(1.0f, 1.0f, 1.0f);
+ tmb.setTexture(0.0f, 1.0f).addVertex(-1.0f, 1.0f, 1.0f);
+ tmb.setTexture(0.0f, 0.0f).addVertex(-1.0f, -1.0f, 1.0f);
+ tmb.setTexture(1.0f, 0.0f).addVertex(1.0f, -1.0f, 1.0f);
+ tmb.setTexture(1.0f, 1.0f).addVertex(1.0f, 1.0f, 1.0f);
tmb.addTriangle(0, 1, 2);
tmb.addTriangle(2, 3, 0);
@@ -316,8 +461,13 @@
mAllocationMap = new HashMap<String, Allocation>();
mQuad = null;
- mDefault2D = getDefault(false);
- mDefaultCube = getDefault(true);
+ mDefault2D = null;
+ mDefaultCube = null;
+ mDefaultVertex = null;
+ 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/ShaderParam.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/ShaderParam.java
index 8dea535..3dd41ca 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/ShaderParam.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/ShaderParam.java
@@ -75,7 +75,7 @@
// Make one if it's not there
if (matchingParam == null) {
if (subElem.getDataType() == Element.DataType.FLOAT_32) {
- matchingParam = new Float4Param(inputName);
+ matchingParam = new Float4Param(inputName, 0.5f, 0.5f, 0.5f, 0.5f);
} else if (subElem.getDataType() == Element.DataType.MATRIX_4X4) {
TransformParam trParam = new TransformParam(inputName);
trParam.setTransform(transform);
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/scenegraph/VertexShader.java b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/VertexShader.java
index f7d0e6d..4efaff7 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/VertexShader.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/VertexShader.java
@@ -44,6 +44,11 @@
return this;
}
+ public Builder setShader(String code) {
+ mBuilder.setShader(code);
+ return this;
+ }
+
public Builder setObjectConst(Type type) {
mShader.mPerObjConstants = type;
return this;
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/render.rs b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/render.rs
index d8d48b3..8a73dbd 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/render.rs
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/scenegraph/render.rs
@@ -142,10 +142,14 @@
return;
}
- rsForEach(gVertexParamsScript, nullAlloc, gVertexShaders,
- gActiveCamera, sizeof(gActiveCamera));
- rsForEach(gFragmentParamsScript, nullAlloc, gFragmentShaders,
- gActiveCamera, sizeof(gActiveCamera));
+ if (rsIsObject(gVertexShaders)) {
+ rsForEach(gVertexParamsScript, nullAlloc, gVertexShaders,
+ gActiveCamera, sizeof(gActiveCamera));
+ }
+ if (rsIsObject(gFragmentShaders)) {
+ rsForEach(gFragmentParamsScript, nullAlloc, gFragmentShaders,
+ gActiveCamera, sizeof(gActiveCamera));
+ }
// Run the params and cull script
rsForEach(gCullScript, nullAlloc, allObj, gActiveCamera, sizeof(gActiveCamera));
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleApp.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleApp.java
new file mode 100644
index 0000000..314db80
--- /dev/null
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleApp.java
@@ -0,0 +1,46 @@
+/*
+ * 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 com.android.testapp;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.view.Window;
+import android.net.Uri;
+
+import java.lang.Runtime;
+
+public class SimpleApp extends Activity {
+
+ private SimpleAppView mView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Create our Preview view and set it as the content of our
+ // Activity
+ mView = new SimpleAppView(this);
+ setContentView(mView);
+ }
+}
+
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
new file mode 100644
index 0000000..621bfa3
--- /dev/null
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppRS.java
@@ -0,0 +1,131 @@
+/*
+ * 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 com.android.testapp;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import com.android.scenegraph.*;
+import com.android.scenegraph.SceneManager.SceneLoadedCallback;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.renderscript.*;
+import android.renderscript.Program.TextureType;
+import android.util.Log;
+
+// This is where the scenegraph and the rendered objects are initialized and used
+public class SimpleAppRS {
+
+ private static String TAG = "SimpleAppRS";
+
+ SceneManager mSceneManager;
+
+ RenderScriptGL mRS;
+ Resources mRes;
+
+ Scene mScene;
+ Mesh mSimpleMesh;
+
+ public void init(RenderScriptGL rs, Resources res, int width, int height) {
+ mRS = rs;
+ mRes = res;
+ mSceneManager = SceneManager.getInstance();
+ mSceneManager.initRS(mRS, mRes, width, height);
+
+ mScene = new Scene();
+
+ setupGeometry();
+ setupRenderables();
+ setupCamera();
+ setupRenderPass();
+
+ mSceneManager.setActiveScene(mScene);
+
+ mScene.initRS();
+ mRS.bindRootScript(mSceneManager.getRenderLoop());
+ }
+
+ private void setupGeometry() {
+ 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);
+ tmb.setTexture(1.0f, 0.0f).addVertex(1.0f, -1.0f, 0.0f);
+ tmb.setTexture(1.0f, 1.0f).addVertex(1.0f, 1.0f, 0.0f);
+
+ tmb.addTriangle(0, 1, 2);
+ tmb.addTriangle(2, 3, 0);
+ mSimpleMesh = 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);
+
+ // 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);
+
+ // 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 = mScene.appendNewCamera();
+ camera.setFar(200);
+ camera.setNear(0.1f);
+ camera.setFOV(60);
+ CompoundTransform cameraTransform = mScene.appendNewCompoundTransform();
+ cameraTransform.addTranslate("camera", new Float3(0, 0, 10));
+ camera.setTransform(cameraTransform);
+ }
+
+ private void setupRenderPass() {
+ 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));
+ ArrayList<RenderableBase> allRender = mScene.getRenderables();
+ for (RenderableBase renderable : allRender) {
+ mainPass.appendRenderable((Renderable)renderable);
+ }
+ }
+}
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppView.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppView.java
new file mode 100644
index 0000000..053e545
--- /dev/null
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/SimpleAppView.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.android.testapp;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptGL;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+public class SimpleAppView extends RSSurfaceView {
+
+ public SimpleAppView(Context context) {
+ super(context);
+ }
+
+ private RenderScriptGL mRS;
+ SimpleAppRS mRender;
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ super.surfaceChanged(holder, format, w, h);
+ if (mRS == null) {
+ RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
+ sc.setDepth(16, 24);
+ mRS = createRenderScriptGL(sc);
+ mRS.setSurface(holder, w, h);
+ mRender = new SimpleAppRS();
+ mRender.init(mRS, getResources(), w, h);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (mRS != null) {
+ mRender = null;
+ mRS = null;
+ destroyRenderScriptGL();
+ }
+ }
+}
+
+
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TestAppRS.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TestAppRS.java
index 7bf7812..f159e85 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TestAppRS.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TestAppRS.java
@@ -149,34 +149,27 @@
}
private void initPaintShaders() {
- ScriptField_ModelParams objConst = new ScriptField_ModelParams(mRS, 1);
- ScriptField_ViewProjParams shaderConst = new ScriptField_ViewProjParams(mRS, 1);
+ mGenericV = SceneManager.getDefaultVS();
- VertexShader.Builder vb = new VertexShader.Builder(mRS);
- vb.addInput(ScriptField_VertexShaderInputs.createElement(mRS));
- vb.setShader(mRes, R.raw.shader2v);
- vb.setObjectConst(objConst.getAllocation().getType());
- vb.setShaderConst(shaderConst.getAllocation().getType());
- mGenericV = vb.create();
+ ScriptField_CameraParams camParams = new ScriptField_CameraParams(mRS, 1);
+ Type camParamType = camParams.getAllocation().getType();
+ ScriptField_LightParams lightParams = new ScriptField_LightParams(mRS, 1);
- ScriptField_CameraParams fsConst = new ScriptField_CameraParams(mRS, 1);
- ScriptField_LightParams fsConst2 = new ScriptField_LightParams(mRS, 1);
-
- mPaintF = createFromResource(R.raw.paintf, true, fsConst.getAllocation().getType());
+ mPaintF = createFromResource(R.raw.paintf, true, camParamType);
// Assign a reflection map
TextureCube envCube = new TextureCube("sdcard/scenegraph/", "cube_env.png");
mPaintF.appendSourceParams(new TextureParam("reflection", envCube));
- mAluminumF = createFromResource(R.raw.metal, true, fsConst.getAllocation().getType());
+ mAluminumF = createFromResource(R.raw.metal, true, camParamType);
TextureCube diffCube = new TextureCube("sdcard/scenegraph/", "cube_spec.png");
mAluminumF.appendSourceParams(new TextureParam("reflection", diffCube));
- mPlasticF = createFromResource(R.raw.plastic, false, fsConst.getAllocation().getType());
- mDiffuseF = createFromResource(R.raw.diffuse, false, fsConst.getAllocation().getType());
- mTextureF = createFromResource(R.raw.texture, false, fsConst.getAllocation().getType());
+ mPlasticF = createFromResource(R.raw.plastic, false, camParamType);
+ mDiffuseF = createFromResource(R.raw.diffuse, false, camParamType);
+ mTextureF = SceneManager.getTextureFS();
FragmentShader.Builder fb = new FragmentShader.Builder(mRS);
- fb.setObjectConst(fsConst2.getAllocation().getType());
+ fb.setObjectConst(lightParams.getAllocation().getType());
fb.setShader(mRes, R.raw.plastic_lights);
mLightsF = fb.create();
@@ -214,7 +207,6 @@
mActiveScene.appendShader(mPlasticF);
mActiveScene.appendShader(mDiffuseF);
mActiveScene.appendShader(mTextureF);
- mActiveScene.appendShader(mGenericV);
}
public void prepareToRender(Scene s) {
diff --git a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java
index d8e48e8..e272cc5 100644
--- a/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java
+++ b/tests/RenderScriptTests/SceneGraph/src/com/android/testapp/TouchHandler.java
@@ -47,22 +47,17 @@
mDistValue = new Float3(0, 0, 45);
mPosValue = new Float3(0, 4, 0);
- mRotateX = new RotateComponent("RotateX", new Float3(1, 0, 0), mRotateXValue);
- mRotateY = new RotateComponent("RotateY", new Float3(0, 1, 0), mRotateYValue);
- mDist = new TranslateComponent("Distance", mDistValue);
- mPosition = new TranslateComponent("Distance", mPosValue);
-
// Make a camera transform we can manipulate
- mCameraRig = new CompoundTransform();
+ mCameraRig = scene.appendNewCompoundTransform();
mCameraRig.setName("CameraRig");
- mCameraRig.addComponent(mPosition);
- mCameraRig.addComponent(mRotateY);
- mCameraRig.addComponent(mRotateX);
- mCameraRig.addComponent(mDist);
- scene.appendTransform(mCameraRig);
- mCamera = new Camera();
+
+ mPosition = mCameraRig.addTranslate("Position", mPosValue);
+ mRotateY = mCameraRig.addRotate("RotateY", new Float3(0, 1, 0), mRotateYValue);
+ mRotateX = mCameraRig.addRotate("RotateX", new Float3(1, 0, 0), mRotateXValue);
+ mDist = mCameraRig.addTranslate("Distance", mDistValue);
+
+ mCamera = scene.appendNewCamera();
mCamera.setTransform(mCameraRig);
- scene.appendCamera(mCamera);
}
public Camera getCamera() {